1 (function (angular
, $, _
) {
2 var partialUrl
= function partialUrl(relPath
) {
3 return CRM
.resourceUrls
['civicrm'] + '/partials/crmMailing2/' + relPath
;
6 var crmMailing2
= angular
.module('crmMailing2', [
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;
16 function ($routeProvider
) {
17 $routeProvider
.when('/mailing2', {
18 template
: '<div></div>',
19 controller
: 'ListMailingsCtrl'
21 $routeProvider
.when('/mailing2/: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('/mailing2/: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('/mailing2/: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('/mailing2/: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 crmMailing2
.controller('ListMailingsCtrl', function ListMailingsCtrl() {
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 window
.location
= CRM
.url('civicrm/mailing/browse/unscheduled', {
69 crmMailing2
.controller('EditMailingCtrl', function EditMailingCtrl($scope
, selectedMail
, $location
, crmMailingMgr
, crmFromAddresses
, crmStatus
, CrmAttachments
) {
70 $scope
.mailing
= selectedMail
;
71 $scope
.attachments
= new CrmAttachments(function () {
72 return {entity_table
: 'civicrm_mailing', entity_id
: $scope
.mailing
.id
};
74 $scope
.attachments
.load();
75 $scope
.crmMailingConst
= CRM
.crmMailing
;
76 $scope
.crmFromAddresses
= crmFromAddresses
;
78 $scope
.partialUrl
= partialUrl
;
79 var ts
= $scope
.ts
= CRM
.ts('CiviMail');
82 $scope
.submit
= function submit() {
83 var promise
= crmMailingMgr
.save($scope
.mailing
)
85 // pre-condition: the mailing exists *before* saving attachments to it
86 return $scope
.attachments
.save();
89 return crmMailingMgr
.submit($scope
.mailing
);
91 return crmStatus({start
: ts('Submitting...'), success
: ts('Submitted')}, promise
);
94 $scope
.save
= function save() {
95 return crmStatus(null,
99 // pre-condition: the mailing exists *before* saving attachments to it
100 return $scope
.attachments
.save();
105 $scope
.delete = function cancel() {
106 return crmStatus({start
: ts('Deleting...'), success
: ts('Deleted')},
107 crmMailingMgr
.delete($scope
.mailing
)
110 $scope
.leave
= function leave() {
111 window
.location
= CRM
.url('civicrm/mailing/browse/unscheduled', {
117 // Transition URL "/mailing2/new" => "/mailing2/123" as soon as ID is known
118 $scope
.$watch('mailing.id', function (newValue
, oldValue
) {
119 if (newValue
&& newValue
!= oldValue
) {
120 var parts
= $location
.path().split('/'); // e.g. "/mailing2/new" or "/mailing2/123/wizard"
122 $location
.path(parts
.join('/'));
124 // FIXME: Angular unnecessarily refreshes UI
128 $scope
.fromPlaceholder
= {
129 label
: crmFromAddresses
.getByAuthorEmail($scope
.mailing
.from_name
, $scope
.mailing
.from_email
, true).label
131 $scope
.$watch('fromPlaceholder.label', function (newValue
) {
132 var addr
= crmFromAddresses
.getByLabel(newValue
);
133 $scope
.mailing
.from_name
= addr
.author
;
134 $scope
.mailing
.from_email
= addr
.email
;
138 // Controller for the edit-recipients fields (
139 // WISHLIST: Move most of this to a (cache-enabled) service
141 // - [input] mailing: object
142 // - [output] recipients: array of recipient records
143 crmMailing2
.controller('EditRecipCtrl', function EditRecipCtrl($scope
, dialogService
, crmApi
, crmMailingMgr
) {
144 var ts
= $scope
.ts
= CRM
.ts('CiviMail');
145 $scope
.recipients
= null;
146 $scope
.getRecipientsEstimate = function () {
148 if ($scope
.recipients
== null) {
149 return ts('(Estimating)');
151 if ($scope
.recipients
.length
== 0) {
152 return ts('No recipients');
154 if ($scope
.recipients
.length
== 1) {
155 return ts('~1 recipient');
157 if (RECIPIENTS_PREVIEW_LIMIT
> 0 && $scope
.recipients
.length
>= RECIPIENTS_PREVIEW_LIMIT
) {
158 return ts('>%1 recipients', {1: RECIPIENTS_PREVIEW_LIMIT
});
160 return ts('~%1 recipients', {1: $scope
.recipients
.length
});
162 $scope
.getIncludesAsString = function () {
165 _
.each($scope
.mailing
.groups
.include
, function (id
) {
167 names
= names
+ ', ';
169 var group
= _
.where(CRM
.crmMailing
.groupNames
, {id
: '' + id
});
170 names
= names
+ group
[0].title
;
173 _
.each($scope
.mailing
.mailings
.include
, function (id
) {
175 names
= names
+ ', ';
177 var oldMailing
= _
.where(CRM
.crmMailing
.civiMails
, {id
: '' + id
});
178 names
= names
+ oldMailing
[0].name
;
183 $scope
.getExcludesAsString = function () {
186 _
.each($scope
.mailing
.groups
.exclude
, function (id
) {
188 names
= names
+ ', ';
190 var group
= _
.where(CRM
.crmMailing
.groupNames
, {id
: '' + id
});
191 names
= names
+ group
[0].title
;
194 _
.each($scope
.mailing
.mailings
.exclude
, function (id
) {
196 names
= names
+ ', ';
198 var oldMailing
= _
.where(CRM
.crmMailing
.civiMails
, {id
: '' + id
});
199 names
= names
+ oldMailing
[0].name
;
205 // We monitor four fields -- use debounce so that changes across the
206 // four fields can settle-down before AJAX.
207 var refreshRecipients
= _
.debounce(function () {
208 $scope
.$apply(function () {
209 $scope
.recipients
= null;
210 crmMailingMgr
.previewRecipients($scope
.mailing
, RECIPIENTS_PREVIEW_LIMIT
).then(function (recipients
) {
211 $scope
.recipients
= recipients
;
214 }, RECIPIENTS_DEBOUNCE_MS
);
215 $scope
.$watchCollection("mailing.groups.include", refreshRecipients
);
216 $scope
.$watchCollection("mailing.groups.exclude", refreshRecipients
);
217 $scope
.$watchCollection("mailing.mailings.include", refreshRecipients
);
218 $scope
.$watchCollection("mailing.mailings.exclude", refreshRecipients
);
220 $scope
.previewRecipients
= function previewRecipients() {
222 recipients
: $scope
.recipients
227 title
: ts('Preview (%1)', {
228 1: $scope
.getRecipientsEstimate()
231 dialogService
.open('recipDialog', partialUrl('dialog/recipients.html'), model
, options
);
235 // Controller for the "Preview Recipients" dialog
236 // Note: Expects $scope.model to be an object with properties:
237 // - recipients: array of contacts
238 crmMailing2
.controller('PreviewRecipCtrl', function ($scope
) {
239 $scope
.ts
= CRM
.ts('CiviMail');
242 // Controller for the "Preview Mailing" segment
243 // Note: Expects $scope.model to be an object with properties:
245 // - attachments: object
246 crmMailing2
.controller('PreviewMailingCtrl', function ($scope
, dialogService
, crmMailingMgr
, crmStatus
) {
247 var ts
= $scope
.ts
= CRM
.ts('CiviMail');
249 $scope
.testContact
= {email
: CRM
.crmMailing
.defaultTestEmail
};
250 $scope
.testGroup
= {gid
: null};
252 $scope
.previewHtml
= function previewHtml() {
253 $scope
.previewDialog(partialUrl('dialog/previewHtml.html'));
255 $scope
.previewText
= function previewText() {
256 $scope
.previewDialog(partialUrl('dialog/previewText.html'));
258 $scope
.previewFull
= function previewFull() {
259 $scope
.previewDialog(partialUrl('dialog/previewFull.html'));
261 // Open a dialog with a preview of the current mailing
262 // @param template string URL of the template to use in the preview dialog
263 $scope
.previewDialog
= function previewDialog(template
) {
264 var p
= crmMailingMgr
265 .preview($scope
.mailing
)
266 .then(function (content
) {
270 title
: ts('Subject: %1', {
274 dialogService
.open('previewDialog', template
, content
, options
);
276 CRM
.status({start
: ts('Previewing'), success
: ''}, CRM
.toJqPromise(p
));
278 $scope
.sendTestToContact
= function sendTestToContact() {
279 $scope
.sendTest($scope
.mailing
, $scope
.attachments
, $scope
.testContact
.email
, null);
281 $scope
.sendTestToGroup
= function sendTestToGroup() {
282 $scope
.sendTest($scope
.mailing
, $scope
.attachments
, null, $scope
.testGroup
.gid
);
284 $scope
.sendTest
= function sendTest(mailing
, attachments
, testEmail
, testGroup
) {
285 var promise
= crmMailingMgr
.save(mailing
)
287 return attachments
.save();
290 return crmMailingMgr
.sendTest(mailing
, testEmail
, testGroup
);
292 .then(function (deliveryInfos
) {
293 var count
= Object
.keys(deliveryInfos
).length
;
295 CRM
.alert(ts('Could not identify any recipients. Perhaps the group is empty?'));
299 return crmStatus({start
: ts('Sending...'), success
: ts('Sent')}, promise
);
303 // Controller for the "Preview Mailing" dialog
304 // Note: Expects $scope.model to be an object with properties:
308 crmMailing2
.controller('PreviewMailingDialogCtrl', function PreviewMailingDialogCtrl($scope
, crmMailingMgr
) {
309 $scope
.ts
= CRM
.ts('CiviMail');
312 // Controller for the "Preview Mailing Component" segment
313 // which displays header/footer/auto-responder
314 crmMailing2
.controller('PreviewComponentCtrl', function PreviewMailingDialogCtrl($scope
, dialogService
) {
315 var ts
= $scope
.ts
= CRM
.ts('CiviMail');
317 $scope
.previewComponent
= function previewComponent(title
, componentId
) {
318 var component
= _
.where(CRM
.crmMailing
.headerfooterList
, {id
: "" + componentId
});
319 if (!component
|| !component
[0]) {
320 CRM
.alert(ts('Invalid component ID (%1)', {
328 title
: title
// component[0].name
330 dialogService
.open('previewComponentDialog', partialUrl('dialog/previewComponent.html'), component
[0], options
);
334 // Controller for the "Preview Mailing" dialog
335 // Note: Expects $scope.model to be an object with properties:
340 crmMailing2
.controller('PreviewComponentDialogCtrl', function PreviewMailingDialogCtrl($scope
) {
341 $scope
.ts
= CRM
.ts('CiviMail');
344 // Controller for the in-place msg-template management
345 crmMailing2
.controller('MsgTemplateCtrl', function MsgTemplateCtrl($scope
, crmMsgTemplates
, dialogService
, $parse
) {
346 var ts
= $scope
.ts
= CRM
.ts('CiviMail');
347 $scope
.crmMsgTemplates
= crmMsgTemplates
;
349 // @return Promise MessageTemplate (per APIv3)
350 $scope
.saveTemplate
= function saveTemplate(mailing
) {
352 selected_id
: mailing
.msg_template_id
,
355 msg_subject
: mailing
.subject
,
356 msg_text
: mailing
.body_text
,
357 msg_html
: mailing
.body_html
363 title
: ts('Save Template')
365 return dialogService
.open('saveTemplateDialog', partialUrl('dialog/saveTemplate.html'), model
, options
)
366 .then(function (item
) {
367 mailing
.msg_template_id
= item
.id
;
374 $scope
.loadTemplate
= function loadTemplate(mailing
, id
) {
375 return crmMsgTemplates
.get(id
).then(function (tpl
) {
376 mailing
.subject
= tpl
.msg_subject
;
377 mailing
.body_text
= tpl
.msg_text
;
378 mailing
.body_html
= tpl
.msg_html
;
383 // Controller for the "Save Message Template" dialog
385 // - [input] "model": Object
386 // - "selected_id": int
388 // - "msg_subject": string
389 // - "msg_text": string
390 // - "msg_html": string
391 crmMailing2
.controller('SaveMsgTemplateDialogCtrl', function SaveMsgTemplateDialogCtrl($scope
, crmMsgTemplates
, dialogService
) {
392 var ts
= $scope
.ts
= CRM
.ts('CiviMail');
393 $scope
.saveOpt
= {mode
: '', newTitle
: ''};
394 $scope
.selected
= null;
396 $scope
.save
= function save() {
397 var tpl
= _
.extend({}, $scope
.model
.tpl
);
398 switch ($scope
.saveOpt
.mode
) {
400 tpl
.msg_title
= $scope
.saveOpt
.newTitle
;
403 tpl
.id
= $scope
.selected
.id
;
404 tpl
.msg_title
= $scope
.selected
.msg_title
;
407 throw 'SaveMsgTemplateDialogCtrl: Unrecognized mode: ' + $scope
.saveOpt
.mode
;
409 return crmMsgTemplates
.save(tpl
)
410 .then(function (item
) {
411 CRM
.status(ts('Saved'));
416 function scopeApply(f
) {
418 var args
= arguments
;
419 $scope
.$apply(function () {
426 crmMsgTemplates
.get($scope
.model
.selected_id
).then(
428 $scope
.saveOpt
.mode
= 'update';
429 $scope
.selected
= tpl
;
432 $scope
.saveOpt
.mode
= 'add';
433 $scope
.selected
= null;
436 // When using dialogService with a button bar, the major button actions
437 // need to be registered with the dialog widget (and not embedded in
438 // the body of the dialog).
440 buttons
[ts('Save')] = function () {
441 $scope
.save().then(function (item
) {
442 dialogService
.close('saveTemplateDialog', item
);
445 buttons
[ts('Cancel')] = function () {
446 dialogService
.cancel('saveTemplateDialog');
448 dialogService
.setButtons('saveTemplateDialog', buttons
);
451 setTimeout(scopeApply(init
), 0);
454 // Controller for schedule-editing widget.
456 // - [input] mailing: object
457 // - scheduled_date: null|string(YYYY-MM-DD hh:mm)
458 crmMailing2
.controller('EditScheduleCtrl', function EditScheduleCtrl($scope
, $parse
) {
459 var schedModelExpr
= 'mailing.scheduled_date';
460 var schedModel
= $parse(schedModelExpr
);
466 var updateChildren
= (function () {
467 var sched
= schedModel($scope
);
469 $scope
.schedule
.mode
= 'at';
470 $scope
.schedule
.datetime
= sched
;
473 $scope
.schedule
.mode
= 'now';
476 var updateParent
= (function () {
477 switch ($scope
.schedule
.mode
) {
479 schedModel
.assign($scope
, null);
482 schedModel
.assign($scope
, $scope
.schedule
.datetime
);
485 throw 'Unrecognized schedule mode: ' + $scope
.schedule
.mode
;
489 $scope
.$watch(schedModelExpr
, updateChildren
);
490 $scope
.$watch('schedule.mode', updateParent
);
491 $scope
.$watch('schedule.datetime', function (newValue
, oldValue
) {
492 // automatically switch mode based on datetime entry
493 if (oldValue
!= newValue
) {
494 if (!newValue
|| newValue
== " ") {
495 $scope
.schedule
.mode
= 'now';
498 $scope
.schedule
.mode
= 'at';
505 })(angular
, CRM
.$, CRM
._
);