1 (function (angular
, $, _
) {
2 var partialUrl
= function partialUrl(relPath
) {
3 return '~/crmMailing/' + relPath
;
6 angular
.module('crmMailing', [
7 'crmUtil', 'crmAttachment', 'ngRoute', 'ui.utils', 'crmUi', 'dialogService'
8 ]); // TODO ngSanitize, unsavedChanges
10 // Time to wait before triggering AJAX update to recipients list
11 var RECIPIENTS_DEBOUNCE_MS
= 100;
12 var RECIPIENTS_PREVIEW_LIMIT
= 10000;
14 angular
.module('crmMailing').config([
16 function ($routeProvider
) {
17 $routeProvider
.when('/mailing', {
18 template
: '<div></div>',
19 controller
: 'ListMailingsCtrl'
21 $routeProvider
.when('/mailing/:id', {
22 templateUrl
: partialUrl('edit.html'),
23 controller
: 'EditMailingCtrl',
25 selectedMail
: function selectedMail($route
, crmMailingMgr
) {
26 return crmMailingMgr
.getOrCreate($route
.current
.params
.id
);
30 $routeProvider
.when('/mailing/:id/unified', {
31 templateUrl
: partialUrl('edit-unified.html'),
32 controller
: 'EditMailingCtrl',
34 selectedMail
: function selectedMail($route
, crmMailingMgr
) {
35 return crmMailingMgr
.getOrCreate($route
.current
.params
.id
);
39 $routeProvider
.when('/mailing/:id/unified2', {
40 templateUrl
: partialUrl('edit-unified2.html'),
41 controller
: 'EditMailingCtrl',
43 selectedMail
: function selectedMail($route
, crmMailingMgr
) {
44 return crmMailingMgr
.getOrCreate($route
.current
.params
.id
);
48 $routeProvider
.when('/mailing/:id/wizard', {
49 templateUrl
: partialUrl('edit-wizard.html'),
50 controller
: 'EditMailingCtrl',
52 selectedMail
: function selectedMail($route
, crmMailingMgr
) {
53 return crmMailingMgr
.getOrCreate($route
.current
.params
.id
);
60 angular
.module('crmMailing').controller('ListMailingsCtrl', ['crmLegacy', 'crmNavigator', function ListMailingsCtrl(crmLegacy
, crmNavigator
) {
61 // We haven't implemented this in Angular, but some users may get clever
62 // about typing URLs, so we'll provide a redirect.
63 var new_url
= crmLegacy
.url('civicrm/mailing/browse/unscheduled', {reset
: 1, scheduled
: 'false'});
64 crmNavigator
.redirect(new_url
);
67 angular
.module('crmMailing').controller('EditMailingCtrl', function EditMailingCtrl($scope
, selectedMail
, $location
, crmMailingMgr
, crmStatus
, CrmAttachments
, crmMailingPreviewMgr
) {
68 $scope
.mailing
= selectedMail
;
69 $scope
.attachments
= new CrmAttachments(function () {
70 return {entity_table
: 'civicrm_mailing', entity_id
: $scope
.mailing
.id
};
72 $scope
.attachments
.load();
73 $scope
.crmMailingConst
= CRM
.crmMailing
;
75 $scope
.partialUrl
= partialUrl
;
76 var ts
= $scope
.ts
= CRM
.ts(null);
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();
94 return crmStatus({start
: ts('Saving...'), success
: ''}, savePromise
)
96 crmMailingPreviewMgr
.sendTest(mailing
, recipient
);
101 $scope
.submit
= function submit() {
102 var promise
= crmMailingMgr
.save($scope
.mailing
)
104 // pre-condition: the mailing exists *before* saving attachments to it
105 return $scope
.attachments
.save();
108 return crmMailingMgr
.submit($scope
.mailing
);
114 return crmStatus({start
: ts('Submitting...'), success
: ts('Submitted')}, promise
);
118 $scope
.save
= function save() {
119 return crmStatus(null,
121 .save($scope
.mailing
)
123 // pre-condition: the mailing exists *before* saving attachments to it
124 return $scope
.attachments
.save();
131 $scope
.delete = function cancel() {
132 return crmStatus({start
: ts('Deleting...'), success
: ts('Deleted')},
133 crmMailingMgr
.delete($scope
.mailing
)
135 leave('unscheduled');
140 // @param string listingScreen 'archive', 'scheduled', 'unscheduled'
141 function leave(listingScreen
) {
142 switch (listingScreen
) {
144 window
.location
= CRM
.url('civicrm/mailing/browse/archived', {
149 window
.location
= CRM
.url('civicrm/mailing/browse/scheduled', {
157 window
.location
= CRM
.url('civicrm/mailing/browse/unscheduled', {
164 // Transition URL "/mailing/new" => "/mailing/123"
165 function updateUrl() {
166 var parts
= $location
.path().split('/'); // e.g. "/mailing/new" or "/mailing/123/wizard"
167 if (parts
[2] != $scope
.mailing
.id
) {
168 parts
[2] = $scope
.mailing
.id
;
169 $location
.path(parts
.join('/'));
171 // FIXME: Angular unnecessarily refreshes UI
172 // WARNING: Changing the URL triggers a full reload. Any pending AJAX operations
173 // could be inconsistently applied. Run updateUrl() after other changes complete.
178 // Controller for the edit-recipients fields (
179 // WISHLIST: Move most of this to a (cache-enabled) service
181 // - [input] mailing: object
182 // - [output] recipients: array of recipient records
183 angular
.module('crmMailing').controller('EditRecipCtrl', function EditRecipCtrl($scope
, dialogService
, crmApi
, crmMailingMgr
) {
184 var ts
= $scope
.ts
= CRM
.ts(null);
185 $scope
.recipients
= null;
186 $scope
.getRecipientsEstimate = function () {
188 if ($scope
.recipients
=== null) {
189 return ts('(Estimating)');
191 if ($scope
.recipients
.length
=== 0) {
192 return ts('No recipients');
194 if ($scope
.recipients
.length
=== 1) {
195 return ts('~1 recipient');
197 if (RECIPIENTS_PREVIEW_LIMIT
> 0 && $scope
.recipients
.length
>= RECIPIENTS_PREVIEW_LIMIT
) {
198 return ts('>%1 recipients', {1: RECIPIENTS_PREVIEW_LIMIT
});
200 return ts('~%1 recipients', {1: $scope
.recipients
.length
});
202 $scope
.getIncludesAsString = function () {
205 _
.each($scope
.mailing
.groups
.include
, function (id
) {
207 names
= names
+ ', ';
209 var group
= _
.where(CRM
.crmMailing
.groupNames
, {id
: '' + id
});
210 names
= names
+ group
[0].title
;
213 _
.each($scope
.mailing
.mailings
.include
, function (id
) {
215 names
= names
+ ', ';
217 var oldMailing
= _
.where(CRM
.crmMailing
.civiMails
, {id
: '' + id
});
218 names
= names
+ oldMailing
[0].name
;
223 $scope
.getExcludesAsString = function () {
226 _
.each($scope
.mailing
.groups
.exclude
, function (id
) {
228 names
= names
+ ', ';
230 var group
= _
.where(CRM
.crmMailing
.groupNames
, {id
: '' + id
});
231 names
= names
+ group
[0].title
;
234 _
.each($scope
.mailing
.mailings
.exclude
, function (id
) {
236 names
= names
+ ', ';
238 var oldMailing
= _
.where(CRM
.crmMailing
.civiMails
, {id
: '' + id
});
239 names
= names
+ oldMailing
[0].name
;
245 // We monitor four fields -- use debounce so that changes across the
246 // four fields can settle-down before AJAX.
247 var refreshRecipients
= _
.debounce(function () {
248 $scope
.$apply(function () {
249 $scope
.recipients
= null;
250 crmMailingMgr
.previewRecipients($scope
.mailing
, RECIPIENTS_PREVIEW_LIMIT
).then(function (recipients
) {
251 $scope
.recipients
= recipients
;
254 }, RECIPIENTS_DEBOUNCE_MS
);
255 $scope
.$watchCollection("mailing.groups.include", refreshRecipients
);
256 $scope
.$watchCollection("mailing.groups.exclude", refreshRecipients
);
257 $scope
.$watchCollection("mailing.mailings.include", refreshRecipients
);
258 $scope
.$watchCollection("mailing.mailings.exclude", refreshRecipients
);
260 $scope
.previewRecipients
= function previewRecipients() {
262 recipients
: $scope
.recipients
267 title
: ts('Preview (%1)', {
268 1: $scope
.getRecipientsEstimate()
271 dialogService
.open('recipDialog', partialUrl('dialog/recipients.html'), model
, options
);
275 // Controller for the "Preview Recipients" dialog
276 // Note: Expects $scope.model to be an object with properties:
277 // - recipients: array of contacts
278 angular
.module('crmMailing').controller('PreviewRecipCtrl', function ($scope
) {
279 $scope
.ts
= CRM
.ts(null);
282 // Controller for the "Preview Mailing" dialog
283 // Note: Expects $scope.model to be an object with properties:
287 angular
.module('crmMailing').controller('PreviewMailingDialogCtrl', function PreviewMailingDialogCtrl($scope
) {
288 $scope
.ts
= CRM
.ts(null);
291 // Controller for the "Preview Mailing Component" segment
292 // which displays header/footer/auto-responder
293 angular
.module('crmMailing').controller('PreviewComponentCtrl', function PreviewMailingDialogCtrl($scope
, dialogService
) {
294 var ts
= $scope
.ts
= CRM
.ts(null);
296 $scope
.previewComponent
= function previewComponent(title
, componentId
) {
297 var component
= _
.where(CRM
.crmMailing
.headerfooterList
, {id
: "" + componentId
});
298 if (!component
|| !component
[0]) {
299 CRM
.alert(ts('Invalid component ID (%1)', {
307 title
: title
// component[0].name
309 dialogService
.open('previewComponentDialog', partialUrl('dialog/previewComponent.html'), component
[0], options
);
313 // Controller for the "Preview Mailing" dialog
314 // Note: Expects $scope.model to be an object with properties:
319 angular
.module('crmMailing').controller('PreviewComponentDialogCtrl', function PreviewMailingDialogCtrl($scope
) {
320 $scope
.ts
= CRM
.ts(null);
323 // Controller for the in-place msg-template management
324 angular
.module('crmMailing').controller('MsgTemplateCtrl', function MsgTemplateCtrl($scope
, crmMsgTemplates
, dialogService
) {
325 var ts
= $scope
.ts
= CRM
.ts(null);
326 $scope
.crmMsgTemplates
= crmMsgTemplates
;
328 // @return Promise MessageTemplate (per APIv3)
329 $scope
.saveTemplate
= function saveTemplate(mailing
) {
331 selected_id
: mailing
.msg_template_id
,
334 msg_subject
: mailing
.subject
,
335 msg_text
: mailing
.body_text
,
336 msg_html
: mailing
.body_html
342 title
: ts('Save Template')
344 return dialogService
.open('saveTemplateDialog', partialUrl('dialog/saveTemplate.html'), model
, options
)
345 .then(function (item
) {
346 mailing
.msg_template_id
= item
.id
;
353 $scope
.loadTemplate
= function loadTemplate(mailing
, id
) {
354 return crmMsgTemplates
.get(id
).then(function (tpl
) {
355 mailing
.subject
= tpl
.msg_subject
;
356 mailing
.body_text
= tpl
.msg_text
;
357 mailing
.body_html
= tpl
.msg_html
;
362 // Controller for the "Save Message Template" dialog
364 // - [input] "model": Object
365 // - "selected_id": int
367 // - "msg_subject": string
368 // - "msg_text": string
369 // - "msg_html": string
370 angular
.module('crmMailing').controller('SaveMsgTemplateDialogCtrl', function SaveMsgTemplateDialogCtrl($scope
, crmMsgTemplates
, dialogService
) {
371 var ts
= $scope
.ts
= CRM
.ts(null);
372 $scope
.saveOpt
= {mode
: '', newTitle
: ''};
373 $scope
.selected
= null;
375 $scope
.save
= function save() {
376 var tpl
= _
.extend({}, $scope
.model
.tpl
);
377 switch ($scope
.saveOpt
.mode
) {
379 tpl
.msg_title
= $scope
.saveOpt
.newTitle
;
382 tpl
.id
= $scope
.selected
.id
;
383 tpl
.msg_title
= $scope
.selected
.msg_title
;
386 throw 'SaveMsgTemplateDialogCtrl: Unrecognized mode: ' + $scope
.saveOpt
.mode
;
388 return crmMsgTemplates
.save(tpl
)
389 .then(function (item
) {
390 CRM
.status(ts('Saved'));
395 function scopeApply(f
) {
397 var args
= arguments
;
398 $scope
.$apply(function () {
405 crmMsgTemplates
.get($scope
.model
.selected_id
).then(
407 $scope
.saveOpt
.mode
= 'update';
408 $scope
.selected
= tpl
;
411 $scope
.saveOpt
.mode
= 'add';
412 $scope
.selected
= null;
415 // When using dialogService with a button bar, the major button actions
416 // need to be registered with the dialog widget (and not embedded in
417 // the body of the dialog).
419 buttons
[ts('Save')] = function () {
420 $scope
.save().then(function (item
) {
421 dialogService
.close('saveTemplateDialog', item
);
424 buttons
[ts('Cancel')] = function () {
425 dialogService
.cancel('saveTemplateDialog');
427 dialogService
.setButtons('saveTemplateDialog', buttons
);
430 setTimeout(scopeApply(init
), 0);
433 angular
.module('crmMailing').controller('EmailAddrCtrl', function EmailAddrCtrl($scope
, crmFromAddresses
) {
434 $scope
.crmFromAddresses
= crmFromAddresses
;
436 })(angular
, CRM
.$, CRM
._
);