1 (function (angular
, $, _
) {
3 angular
.module('crmMailing', [
4 'crmUtil', 'crmAttachment', 'crmAutosave', 'ngRoute', 'ui.utils', 'crmUi', 'dialogService'
5 ]); // TODO ngSanitize, unsavedChanges
7 // Time to wait before triggering AJAX update to recipients list
8 var RECIPIENTS_DEBOUNCE_MS
= 100;
9 var RECIPIENTS_PREVIEW_LIMIT
= 10000;
11 angular
.module('crmMailing').config([
13 function ($routeProvider
) {
14 $routeProvider
.when('/mailing', {
15 template
: '<div></div>',
16 controller
: 'ListMailingsCtrl'
20 '': '~/crmMailing/edit.html',
21 '/unified': '~/crmMailing/edit-unified.html',
22 '/unified2': '~/crmMailing/edit-unified2.html',
23 '/wizard': '~/crmMailing/edit-wizard.html'
25 angular
.forEach(editorPaths
, function(editTemplate
, pathSuffix
) {
26 $routeProvider
.when('/mailing/new' + pathSuffix
, {
27 template
: '<p>' + ts('Initializing...') + '</p>',
28 controller
: 'CreateMailingCtrl',
30 selectedMail: function(crmMailingMgr
) {
31 var m
= crmMailingMgr
.create();
32 return crmMailingMgr
.save(m
);
36 $routeProvider
.when('/mailing/:id' + pathSuffix
, {
37 templateUrl
: editTemplate
,
38 controller
: 'EditMailingCtrl',
40 selectedMail: function($route
, crmMailingMgr
) {
41 return crmMailingMgr
.get($route
.current
.params
.id
);
43 attachments: function($route
, CrmAttachments
) {
44 var attachments
= new CrmAttachments(function () {
45 return {entity_table
: 'civicrm_mailing', entity_id
: $route
.current
.params
.id
};
47 return attachments
.load();
55 angular
.module('crmMailing').controller('ListMailingsCtrl', ['crmLegacy', 'crmNavigator', function ListMailingsCtrl(crmLegacy
, crmNavigator
) {
56 // We haven't implemented this in Angular, but some users may get clever
57 // about typing URLs, so we'll provide a redirect.
58 var new_url
= crmLegacy
.url('civicrm/mailing/browse/unscheduled', {reset
: 1, scheduled
: 'false'});
59 crmNavigator
.redirect(new_url
);
62 angular
.module('crmMailing').controller('CreateMailingCtrl', function EditMailingCtrl($scope
, selectedMail
, $location
) {
63 // Transition URL "/mailing/new/foo" => "/mailing/123/foo"
64 var parts
= $location
.path().split('/'); // e.g. "/mailing/new" or "/mailing/123/wizard"
65 parts
[2] = selectedMail
.id
;
66 $location
.path(parts
.join('/'));
70 angular
.module('crmMailing').controller('EditMailingCtrl', function EditMailingCtrl($scope
, selectedMail
, $location
, crmMailingMgr
, crmStatus
, attachments
, crmMailingPreviewMgr
, crmBlocker
, CrmAutosaveCtrl
, $timeout
) {
71 $scope
.mailing
= selectedMail
;
72 $scope
.attachments
= attachments
;
73 $scope
.crmMailingConst
= CRM
.crmMailing
;
75 var ts
= $scope
.ts
= CRM
.ts(null);
76 var block
= $scope
.block
= crmBlocker();
78 $scope
.isSubmitted
= function isSubmitted() {
79 return _
.size($scope
.mailing
.jobs
) > 0;
83 $scope
.previewMailing
= function previewMailing(mailing
, mode
) {
84 return crmMailingPreviewMgr
.preview(mailing
, mode
);
88 $scope
.sendTest
= function sendTest(mailing
, attachments
, recipient
) {
89 var savePromise
= crmMailingMgr
.save(mailing
)
91 return attachments
.save();
93 return block(crmStatus({start
: ts('Saving...'), success
: ''}, savePromise
)
95 crmMailingPreviewMgr
.sendTest(mailing
, recipient
);
100 $scope
.submit
= function submit() {
101 if (block
.check() || $scope
.crmMailing
.$invalid
) {
105 var promise
= crmMailingMgr
.save($scope
.mailing
)
107 // pre-condition: the mailing exists *before* saving attachments to it
108 return $scope
.attachments
.save();
111 return crmMailingMgr
.submit($scope
.mailing
);
114 $scope
.leave('scheduled');
117 return block(crmStatus({start
: ts('Submitting...'), success
: ts('Submitted')}, promise
));
121 $scope
.save
= function save() {
122 return block(crmStatus(null,
124 .save($scope
.mailing
)
126 // pre-condition: the mailing exists *before* saving attachments to it
127 return $scope
.attachments
.save();
133 $scope
.delete = function cancel() {
134 return block(crmStatus({start
: ts('Deleting...'), success
: ts('Deleted')},
135 crmMailingMgr
.delete($scope
.mailing
)
137 $scope
.leave('unscheduled');
142 // @param string listingScreen 'archive', 'scheduled', 'unscheduled'
143 $scope
.leave
= function leave(listingScreen
) {
144 switch (listingScreen
) {
146 window
.location
= CRM
.url('civicrm/mailing/browse/archived', {
151 window
.location
= CRM
.url('civicrm/mailing/browse/scheduled', {
159 window
.location
= CRM
.url('civicrm/mailing/browse/unscheduled', {
166 var myAutosave
= new CrmAutosaveCtrl({
172 return [$scope
.mailing
, $scope
.attachments
.getAutosaveSignature()];
175 return $scope
.crmMailing
;
178 $timeout(myAutosave
.start
);
179 $scope
.$on('$destroy', myAutosave
.stop
);
182 // Controller for the edit-recipients fields (
183 // WISHLIST: Move most of this to a (cache-enabled) service
185 // - [input] mailing: object
186 // - [output] recipients: array of recipient records
187 angular
.module('crmMailing').controller('EditRecipCtrl', function EditRecipCtrl($scope
, dialogService
, crmApi
, crmMailingMgr
, $q
, crmMetadata
) {
188 var ts
= $scope
.ts
= CRM
.ts(null);
189 $scope
.recipients
= null;
190 $scope
.getRecipientsEstimate = function () {
192 if ($scope
.recipients
=== null) {
193 return ts('(Estimating)');
195 if ($scope
.recipients
.length
=== 0) {
196 return ts('No recipients');
198 if ($scope
.recipients
.length
=== 1) {
199 return ts('~1 recipient');
201 if (RECIPIENTS_PREVIEW_LIMIT
> 0 && $scope
.recipients
.length
>= RECIPIENTS_PREVIEW_LIMIT
) {
202 return ts('>%1 recipients', {1: RECIPIENTS_PREVIEW_LIMIT
});
204 return ts('~%1 recipients', {1: $scope
.recipients
.length
});
206 $scope
.getIncludesAsString = function () {
209 _
.each($scope
.mailing
.recipients
.groups
.include
, function (id
) {
211 names
= names
+ ', ';
213 var group
= _
.where(CRM
.crmMailing
.groupNames
, {id
: '' + id
});
214 names
= names
+ group
[0].title
;
217 _
.each($scope
.mailing
.recipients
.mailings
.include
, function (id
) {
219 names
= names
+ ', ';
221 var oldMailing
= _
.where(CRM
.crmMailing
.civiMails
, {id
: '' + id
});
222 names
= names
+ oldMailing
[0].name
;
227 $scope
.getExcludesAsString = function () {
230 _
.each($scope
.mailing
.recipients
.groups
.exclude
, function (id
) {
232 names
= names
+ ', ';
234 var group
= _
.where(CRM
.crmMailing
.groupNames
, {id
: '' + id
});
235 names
= names
+ group
[0].title
;
238 _
.each($scope
.mailing
.recipients
.mailings
.exclude
, function (id
) {
240 names
= names
+ ', ';
242 var oldMailing
= _
.where(CRM
.crmMailing
.civiMails
, {id
: '' + id
});
243 names
= names
+ oldMailing
[0].name
;
249 // We monitor four fields -- use debounce so that changes across the
250 // four fields can settle-down before AJAX.
251 var refreshRecipients
= _
.debounce(function () {
252 $scope
.$apply(function () {
253 $scope
.recipients
= null;
254 crmMailingMgr
.previewRecipients($scope
.mailing
, RECIPIENTS_PREVIEW_LIMIT
).then(function (recipients
) {
255 $scope
.recipients
= recipients
;
258 }, RECIPIENTS_DEBOUNCE_MS
);
259 $scope
.$watchCollection("mailing.recipients.groups.include", refreshRecipients
);
260 $scope
.$watchCollection("mailing.recipients.groups.exclude", refreshRecipients
);
261 $scope
.$watchCollection("mailing.recipients.mailings.include", refreshRecipients
);
262 $scope
.$watchCollection("mailing.recipients.mailings.exclude", refreshRecipients
);
264 $scope
.previewRecipients
= function previewRecipients() {
266 recipients
: $scope
.recipients
268 var options
= CRM
.utils
.adjustDialogDefaults({
270 title
: ts('Preview (%1)', {
271 1: $scope
.getRecipientsEstimate()
274 dialogService
.open('recipDialog', '~/crmMailing/dialog/recipients.html', model
, options
);
277 // Open a dialog for editing the advanced recipient options.
278 $scope
.editOptions
= function editOptions(mailing
) {
279 var options
= CRM
.utils
.adjustDialogDefaults({
281 title
: ts('Edit Options')
283 $q
.when(crmMetadata
.getFields('Mailing')).then(function(fields
) {
288 dialogService
.open('previewComponentDialog', '~/crmMailing/dialog/recipientOptions.html', model
, options
);
293 // Controller for the "Preview Recipients" dialog
294 // Note: Expects $scope.model to be an object with properties:
295 // - recipients: array of contacts
296 angular
.module('crmMailing').controller('PreviewRecipCtrl', function ($scope
) {
297 $scope
.ts
= CRM
.ts(null);
300 // Controller for the "Preview Mailing" dialog
301 // Note: Expects $scope.model to be an object with properties:
305 angular
.module('crmMailing').controller('PreviewMailingDialogCtrl', function PreviewMailingDialogCtrl($scope
) {
306 $scope
.ts
= CRM
.ts(null);
309 // Controller for the "Recipients: Edit Options" dialog
310 // Note: Expects $scope.model to be an object with properties:
311 // - "mailing" (APIv3 mailing object)
312 // - "fields" (list of fields)
313 angular
.module('crmMailing').controller('EditRecipOptionsDialogCtrl', function EditRecipOptionsDialogCtrl($scope
) {
314 $scope
.ts
= CRM
.ts(null);
317 // Controller for the "Preview Mailing Component" segment
318 // which displays header/footer/auto-responder
319 angular
.module('crmMailing').controller('PreviewComponentCtrl', function PreviewComponentCtrl($scope
, dialogService
) {
320 var ts
= $scope
.ts
= CRM
.ts(null);
322 $scope
.previewComponent
= function previewComponent(title
, componentId
) {
323 var component
= _
.where(CRM
.crmMailing
.headerfooterList
, {id
: "" + componentId
});
324 if (!component
|| !component
[0]) {
325 CRM
.alert(ts('Invalid component ID (%1)', {
330 var options
= CRM
.utils
.adjustDialogDefaults({
332 title
: title
// component[0].name
334 dialogService
.open('previewComponentDialog', '~/crmMailing/dialog/previewComponent.html', component
[0], options
);
338 // Controller for the "Preview Mailing Component" dialog
339 // Note: Expects $scope.model to be an object with properties:
344 angular
.module('crmMailing').controller('PreviewComponentDialogCtrl', function PreviewComponentDialogCtrl($scope
) {
345 $scope
.ts
= CRM
.ts(null);
348 // Controller for the in-place msg-template management
349 angular
.module('crmMailing').controller('MsgTemplateCtrl', function MsgTemplateCtrl($scope
, crmMsgTemplates
, dialogService
) {
350 var ts
= $scope
.ts
= CRM
.ts(null);
351 $scope
.crmMsgTemplates
= crmMsgTemplates
;
353 // @return Promise MessageTemplate (per APIv3)
354 $scope
.saveTemplate
= function saveTemplate(mailing
) {
356 selected_id
: mailing
.msg_template_id
,
359 msg_subject
: mailing
.subject
,
360 msg_text
: mailing
.body_text
,
361 msg_html
: mailing
.body_html
364 var options
= CRM
.utils
.adjustDialogDefaults({
366 title
: ts('Save Template')
368 return dialogService
.open('saveTemplateDialog', '~/crmMailing/dialog/saveTemplate.html', model
, options
)
369 .then(function (item
) {
370 mailing
.msg_template_id
= item
.id
;
377 $scope
.loadTemplate
= function loadTemplate(mailing
, id
) {
378 return crmMsgTemplates
.get(id
).then(function (tpl
) {
379 mailing
.subject
= tpl
.msg_subject
;
380 mailing
.body_text
= tpl
.msg_text
;
381 mailing
.body_html
= tpl
.msg_html
;
386 // Controller for the "Save Message Template" dialog
388 // - [input] "model": Object
389 // - "selected_id": int
391 // - "msg_subject": string
392 // - "msg_text": string
393 // - "msg_html": string
394 angular
.module('crmMailing').controller('SaveMsgTemplateDialogCtrl', function SaveMsgTemplateDialogCtrl($scope
, crmMsgTemplates
, dialogService
) {
395 var ts
= $scope
.ts
= CRM
.ts(null);
396 $scope
.saveOpt
= {mode
: '', newTitle
: ''};
397 $scope
.selected
= null;
399 $scope
.save
= function save() {
400 var tpl
= _
.extend({}, $scope
.model
.tpl
);
401 switch ($scope
.saveOpt
.mode
) {
403 tpl
.msg_title
= $scope
.saveOpt
.newTitle
;
406 tpl
.id
= $scope
.selected
.id
;
407 tpl
.msg_title
= $scope
.selected
.msg_title
;
410 throw 'SaveMsgTemplateDialogCtrl: Unrecognized mode: ' + $scope
.saveOpt
.mode
;
412 return crmMsgTemplates
.save(tpl
)
413 .then(function (item
) {
414 CRM
.status(ts('Saved'));
419 function scopeApply(f
) {
421 var args
= arguments
;
422 $scope
.$apply(function () {
429 crmMsgTemplates
.get($scope
.model
.selected_id
).then(
431 $scope
.saveOpt
.mode
= 'update';
432 $scope
.selected
= tpl
;
435 $scope
.saveOpt
.mode
= 'add';
436 $scope
.selected
= null;
439 // When using dialogService with a button bar, the major button actions
440 // need to be registered with the dialog widget (and not embedded in
441 // the body of the dialog).
445 icons
: {primary
: 'ui-icon-check'},
447 $scope
.save().then(function (item
) {
448 dialogService
.close('saveTemplateDialog', item
);
454 icons
: {primary
: 'ui-icon-close'},
456 dialogService
.cancel('saveTemplateDialog');
460 dialogService
.setButtons('saveTemplateDialog', buttons
);
463 setTimeout(scopeApply(init
), 0);
466 angular
.module('crmMailing').controller('EmailAddrCtrl', function EmailAddrCtrl($scope
, crmFromAddresses
, crmUiAlert
) {
467 var ts
= CRM
.ts(null);
468 function changeAlert(winnerField
, loserField
) {
470 title
: ts('Conflict'),
471 text
: ts('The "%1" option conflicts with the "%2" option. The "%2" option has been disabled.', {
478 $scope
.crmFromAddresses
= crmFromAddresses
;
479 $scope
.checkReplyToChange
= function checkReplyToChange(mailing
) {
480 if (!_
.isEmpty(mailing
.replyto_email
) && mailing
.override_verp
== '0') {
481 mailing
.override_verp
= '1';
482 changeAlert(ts('Reply-To'), ts('Track Replies'));
485 $scope
.checkVerpChange
= function checkVerpChange(mailing
) {
486 if (!_
.isEmpty(mailing
.replyto_email
) && mailing
.override_verp
== '0') {
487 mailing
.replyto_email
= '';
488 changeAlert(ts('Track Replies'), ts('Reply-To'));
493 var lastEmailTokenAlert
= null;
494 angular
.module('crmMailing').controller('EmailBodyCtrl', function EmailBodyCtrl($scope
, crmMailingMgr
, crmUiAlert
, $timeout
) {
495 var ts
= CRM
.ts(null);
497 // ex: if (!hasAllTokens(myMailing, 'body_text)) alert('Oh noes!');
498 $scope
.hasAllTokens
= function hasAllTokens(mailing
, field
) {
499 return _
.isEmpty(crmMailingMgr
.findMissingTokens(mailing
, field
));
502 // ex: checkTokens(myMailing, 'body_text', 'insert:body_text')
503 // ex: checkTokens(myMailing, '*')
504 $scope
.checkTokens
= function checkTokens(mailing
, field
, insertEvent
) {
505 if (lastEmailTokenAlert
) {
506 lastEmailTokenAlert
.close();
508 var missing
, insertable
;
511 missing
= angular
.extend({},
512 crmMailingMgr
.findMissingTokens(mailing
, 'body_html'),
513 crmMailingMgr
.findMissingTokens(mailing
, 'body_text')
516 insertable
= !_
.isEmpty(insertEvent
);
517 missing
= crmMailingMgr
.findMissingTokens(mailing
, field
);
519 if (!_
.isEmpty(missing
)) {
520 lastEmailTokenAlert
= crmUiAlert({
522 title
: ts('Required tokens'),
523 templateUrl
: '~/crmMailing/dialog/tokenAlert.html',
524 scope
: angular
.extend($scope
.$new(), {
525 insertable
: insertable
,
526 insertToken: function(token
) {
528 $scope
.$broadcast(insertEvent
, '{' + token
+ '}');
530 checkTokens(mailing
, field
, insertEvent
);
540 })(angular
, CRM
.$, CRM
._
);