1 (function (angular
, $, _
) {
2 var partialUrl
= function partialUrl(relPath
) {
3 return CRM
.resourceUrls
['civicrm'] + '/partials/crmMailing2/' + relPath
;
6 var crmMailing2
= angular
.module('crmMailing2', ['crmUtil', 'ngRoute', 'ui.utils', 'crmUi', 'dialogService']); // TODO ngSanitize, unsavedChanges
8 // Time to wait before triggering AJAX update to recipients list
9 var RECIPIENTS_DEBOUNCE_MS
= 100;
10 var RECIPIENTS_PREVIEW_LIMIT
= 10000;
12 crmMailing2
.config(['$routeProvider',
13 function ($routeProvider
) {
14 $routeProvider
.when('/mailing2', {
15 template
: '<div></div>',
16 controller
: 'ListMailingsCtrl'
18 $routeProvider
.when('/mailing2/:id', {
19 templateUrl
: partialUrl('edit.html'),
20 controller
: 'EditMailingCtrl',
22 selectedMail
: function selectedMail($route
, crmMailingMgr
) { return crmMailingMgr
.getOrCreate($route
.current
.params
.id
); }
25 $routeProvider
.when('/mailing2/:id/unified', {
26 templateUrl
: partialUrl('edit-unified.html'),
27 controller
: 'EditMailingCtrl',
29 selectedMail
: function selectedMail($route
, crmMailingMgr
) { return crmMailingMgr
.getOrCreate($route
.current
.params
.id
); }
32 $routeProvider
.when('/mailing2/:id/unified2', {
33 templateUrl
: partialUrl('edit-unified2.html'),
34 controller
: 'EditMailingCtrl',
36 selectedMail
: function selectedMail($route
, crmMailingMgr
) { return crmMailingMgr
.getOrCreate($route
.current
.params
.id
); }
39 $routeProvider
.when('/mailing2/:id/wizard', {
40 templateUrl
: partialUrl('edit-wizard.html'),
41 controller
: 'EditMailingCtrl',
43 selectedMail
: function selectedMail($route
, crmMailingMgr
) { return crmMailingMgr
.getOrCreate($route
.current
.params
.id
); }
49 crmMailing2
.controller('ListMailingsCtrl', function ListMailingsCtrl() {
50 // We haven't implemented this in Angular, but some users may get clever
51 // about typing URLs, so we'll provide a redirect.
52 window
.location
= CRM
.url('civicrm/mailing/browse/unscheduled', {
58 crmMailing2
.controller('EditMailingCtrl', function EditMailingCtrl($scope
, selectedMail
, $location
, crmMailingMgr
, crmFromAddresses
, crmStatus
) {
59 $scope
.mailing
= selectedMail
;
60 $scope
.crmMailingConst
= CRM
.crmMailing
;
61 $scope
.crmFromAddresses
= crmFromAddresses
;
63 $scope
.partialUrl
= partialUrl
;
64 var ts
= $scope
.ts
= CRM
.ts('CiviMail');
67 $scope
.submit
= function submit() {
68 return crmStatus({start
: ts('Submitting...'), success
: ts('Submitted')},
69 crmMailingMgr
.submit($scope
.mailing
)
73 $scope
.save
= function save() {
74 return crmStatus(null,
75 crmMailingMgr
.save($scope
.mailing
)
79 $scope
.delete = function cancel() {
80 return crmStatus({start
: ts('Deleting...'), success
: ts('Deleted')},
81 crmMailingMgr
.delete($scope
.mailing
)
84 $scope
.leave
= function leave() {
85 window
.location
= CRM
.url('civicrm/mailing/browse/unscheduled', {
91 // Transition URL "/mailing2/new" => "/mailing2/123" as soon as ID is known
92 $scope
.$watch('mailing.id', function(newValue
, oldValue
) {
93 if (newValue
&& newValue
!= oldValue
) {
94 var parts
= $location
.path().split('/'); // e.g. "/mailing2/new" or "/mailing2/123/wizard"
96 $location
.path(parts
.join('/'));
98 // FIXME: Angular unnecessarily refreshes UI
102 $scope
.fromPlaceholder
= {
103 label
: crmFromAddresses
.getByAuthorEmail($scope
.mailing
.from_name
, $scope
.mailing
.from_email
, true).label
105 $scope
.$watch('fromPlaceholder.label', function(newValue
){
106 var addr
= crmFromAddresses
.getByLabel(newValue
);
107 $scope
.mailing
.from_name
= addr
.author
;
108 $scope
.mailing
.from_email
= addr
.email
;
112 // Controller for the edit-recipients fields (
113 // WISHLIST: Move most of this to a (cache-enabled) service
115 // - [input] mailing: object
116 // - [output] recipients: array of recipient records
117 crmMailing2
.controller('EditRecipCtrl', function EditRecipCtrl($scope
, dialogService
, crmApi
, crmMailingMgr
) {
118 var ts
= $scope
.ts
= CRM
.ts('CiviMail');
119 $scope
.recipients
= null;
120 $scope
.getRecipientsEstimate = function () {
122 if ($scope
.recipients
== null)
123 return ts('(Estimating)');
124 if ($scope
.recipients
.length
== 0)
125 return ts('No recipients');
126 if ($scope
.recipients
.length
== 1)
127 return ts('~1 recipient');
128 if (RECIPIENTS_PREVIEW_LIMIT
> 0 && $scope
.recipients
.length
>= RECIPIENTS_PREVIEW_LIMIT
)
129 return ts('>%1 recipients', {1: RECIPIENTS_PREVIEW_LIMIT
});
130 return ts('~%1 recipients', {1: $scope
.recipients
.length
});
132 $scope
.getIncludesAsString = function() {
135 _
.each($scope
.mailing
.groups
.include
, function(id
){
136 if (!first
) names
= names
+ ', ';
137 var group
= _
.where(CRM
.crmMailing
.groupNames
, {id
: ''+id
});
138 names
= names
+ group
[0].title
;
141 _
.each($scope
.mailing
.mailings
.include
, function(id
){
142 if (!first
) names
= names
+ ', ';
143 var oldMailing
= _
.where(CRM
.crmMailing
.civiMails
, {id
: ''+id
});
144 names
= names
+ oldMailing
[0].name
;
149 $scope
.getExcludesAsString = function() {
152 _
.each($scope
.mailing
.groups
.exclude
, function(id
){
153 if (!first
) names
= names
+ ', ';
154 var group
= _
.where(CRM
.crmMailing
.groupNames
, {id
: ''+id
});
155 names
= names
+ group
[0].title
;
158 _
.each($scope
.mailing
.mailings
.exclude
, function(id
){
159 if (!first
) names
= names
+ ', ';
160 var oldMailing
= _
.where(CRM
.crmMailing
.civiMails
, {id
: ''+id
});
161 names
= names
+ oldMailing
[0].name
;
167 // We monitor four fields -- use debounce so that changes across the
168 // four fields can settle-down before AJAX.
169 var refreshRecipients
= _
.debounce(function () {
170 $scope
.$apply(function () {
171 $scope
.recipients
= null;
172 crmMailingMgr
.previewRecipients($scope
.mailing
, RECIPIENTS_PREVIEW_LIMIT
).then(function (recipients
) {
173 $scope
.recipients
= recipients
;
176 }, RECIPIENTS_DEBOUNCE_MS
);
177 $scope
.$watchCollection("mailing.groups.include", refreshRecipients
);
178 $scope
.$watchCollection("mailing.groups.exclude", refreshRecipients
);
179 $scope
.$watchCollection("mailing.mailings.include", refreshRecipients
);
180 $scope
.$watchCollection("mailing.mailings.exclude", refreshRecipients
);
182 $scope
.previewRecipients
= function previewRecipients() {
184 recipients
: $scope
.recipients
189 title
: ts('Preview (%1)', {
190 1: $scope
.getRecipientsEstimate()
193 dialogService
.open('recipDialog', partialUrl('dialog/recipients.html'), model
, options
);
197 // Controller for the "Preview Recipients" dialog
198 // Note: Expects $scope.model to be an object with properties:
199 // - recipients: array of contacts
200 crmMailing2
.controller('PreviewRecipCtrl', function ($scope
) {
201 $scope
.ts
= CRM
.ts('CiviMail');
204 // Controller for the "Preview Mailing" segment
205 // Note: Expects $scope.model to be an object with properties:
207 crmMailing2
.controller('PreviewMailingCtrl', function ($scope
, dialogService
, crmMailingMgr
) {
208 var ts
= $scope
.ts
= CRM
.ts('CiviMail');
210 $scope
.testContact
= {email
: CRM
.crmMailing
.defaultTestEmail
};
211 $scope
.testGroup
= {gid
: null};
213 $scope
.previewHtml
= function previewHtml() {
214 $scope
.previewDialog(partialUrl('dialog/previewHtml.html'));
216 $scope
.previewText
= function previewText() {
217 $scope
.previewDialog(partialUrl('dialog/previewText.html'));
219 $scope
.previewFull
= function previewFull() {
220 $scope
.previewDialog(partialUrl('dialog/previewFull.html'));
222 // Open a dialog with a preview of the current mailing
223 // @param template string URL of the template to use in the preview dialog
224 $scope
.previewDialog
= function previewDialog(template
) {
225 var p
= crmMailingMgr
226 .preview($scope
.mailing
)
227 .then(function(content
){
231 title
: ts('Subject: %1', {
235 dialogService
.open('previewDialog', template
, content
, options
);
237 CRM
.status({start
: ts('Previewing'), success
: ''}, CRM
.toJqPromise(p
));
239 $scope
.sendTestToContact
= function sendTestToContact() {
240 $scope
.sendTest($scope
.mailing
, $scope
.testContact
.email
, null);
242 $scope
.sendTestToGroup
= function sendTestToGroup() {
243 $scope
.sendTest($scope
.mailing
, null, $scope
.testGroup
.gid
);
245 $scope
.sendTest
= function sendTest(mailing
, testEmail
, testGroup
) {
246 var promise
= crmMailingMgr
.sendTest(mailing
, testEmail
, testGroup
).then(function(deliveryInfos
){
247 var count
= Object
.keys(deliveryInfos
).length
;
249 CRM
.alert(ts('Could not identify any recipients. Perhaps the group is empty?'));
253 start
: ts('Sending...'),
255 }, CRM
.toJqPromise(promise
));
259 // Controller for the "Preview Mailing" dialog
260 // Note: Expects $scope.model to be an object with properties:
264 crmMailing2
.controller('PreviewMailingDialogCtrl', function PreviewMailingDialogCtrl($scope
, crmMailingMgr
) {
265 $scope
.ts
= CRM
.ts('CiviMail');
268 // Controller for the "Preview Mailing Component" segment
269 // which displays header/footer/auto-responder
270 crmMailing2
.controller('PreviewComponentCtrl', function PreviewMailingDialogCtrl($scope
, dialogService
) {
271 var ts
= $scope
.ts
= CRM
.ts('CiviMail');
273 $scope
.previewComponent
= function previewComponent(title
, componentId
) {
274 var component
= _
.where(CRM
.crmMailing
.headerfooterList
, {id
: ""+componentId
});
275 if (!component
|| !component
[0]) {
276 CRM
.alert(ts('Invalid component ID (%1)', {
284 title
: title
// component[0].name
286 dialogService
.open('previewComponentDialog', partialUrl('dialog/previewComponent.html'), component
[0], options
);
290 // Controller for the "Preview Mailing" dialog
291 // Note: Expects $scope.model to be an object with properties:
296 crmMailing2
.controller('PreviewComponentDialogCtrl', function PreviewMailingDialogCtrl($scope
) {
297 $scope
.ts
= CRM
.ts('CiviMail');
300 // Controller for the in-place msg-template management
302 // - [input] mailing: object
303 crmMailing2
.controller('MsgTemplateCtrl', function MsgTemplateCtrl($scope
, crmMsgTemplates
, dialogService
, $parse
) {
304 var ts
= $scope
.ts
= CRM
.ts('CiviMail');
305 $scope
.crmMsgTemplates
= crmMsgTemplates
;
307 // @return Promise MessageTemplate (per APIv3)
308 $scope
.saveTemplate
= function saveTemplate() {
310 selected_id
: $scope
.mailing
.msg_template_id
,
313 msg_subject
: $scope
.mailing
.subject
,
314 msg_text
: $scope
.mailing
.body_text
,
315 msg_html
: $scope
.mailing
.body_html
321 title
: ts('Save Template')
323 return dialogService
.open('saveTemplateDialog', partialUrl('dialog/saveTemplate.html'), model
, options
)
324 .then(function(item
){
325 $parse('mailing.msg_template_id').assign($scope
, item
.id
);
332 $scope
.loadTemplate
= function loadTemplate(id
) {
333 return crmMsgTemplates
.get(id
).then(function (tpl
) {
334 $scope
.mailing
.subject
= tpl
.msg_subject
;
335 $scope
.mailing
.body_text
= tpl
.msg_text
;
336 $scope
.mailing
.body_html
= tpl
.msg_html
;
341 // Controller for the "Save Message Template" dialog
343 // - [input] "model": Object
344 // - "selected_id": int
346 // - "msg_subject": string
347 // - "msg_text": string
348 // - "msg_html": string
349 crmMailing2
.controller('SaveMsgTemplateDialogCtrl', function SaveMsgTemplateDialogCtrl($scope
, crmMsgTemplates
, dialogService
) {
350 var ts
= $scope
.ts
= CRM
.ts('CiviMail');
351 $scope
.saveOpt
= {mode
: '', newTitle
: ''};
352 $scope
.selected
= null;
354 $scope
.save
= function save() {
355 var tpl
= _
.extend({}, $scope
.model
.tpl
);
356 switch ($scope
.saveOpt
.mode
) {
358 tpl
.msg_title
= $scope
.saveOpt
.newTitle
;
361 tpl
.id
= $scope
.selected
.id
;
362 tpl
.msg_title
= $scope
.selected
.msg_title
;
365 throw 'SaveMsgTemplateDialogCtrl: Unrecognized mode: ' + $scope
.saveOpt
.mode
;
367 return crmMsgTemplates
.save(tpl
)
368 .then(function (item
) {
369 CRM
.status(ts('Saved'));
374 function scopeApply(f
) {
376 var args
= arguments
;
377 $scope
.$apply(function () {
384 crmMsgTemplates
.get($scope
.model
.selected_id
).then(
386 $scope
.saveOpt
.mode
= 'update';
387 $scope
.selected
= tpl
;
390 $scope
.saveOpt
.mode
= 'add';
391 $scope
.selected
= null;
394 // When using dialogService with a button bar, the major button actions
395 // need to be registered with the dialog widget (and not embedded in
396 // the body of the dialog).
398 buttons
[ts('Save')] = function () {
399 $scope
.save().then(function (item
) {
400 dialogService
.close('saveTemplateDialog', item
);
403 buttons
[ts('Cancel')] = function () {
404 dialogService
.cancel('saveTemplateDialog');
406 dialogService
.setButtons('saveTemplateDialog', buttons
);
408 setTimeout(scopeApply(init
), 0);
411 // Controller for schedule-editing widget.
413 // - [input] mailing: object
414 // - scheduled_date: null|string(YYYY-MM-DD hh:mm)
415 crmMailing2
.controller('EditScheduleCtrl', function EditScheduleCtrl($scope
, $parse
) {
416 var schedModelExpr
= 'mailing.scheduled_date';
417 var schedModel
= $parse(schedModelExpr
);
423 var updateChildren
= (function () {
424 var sched
= schedModel($scope
);
426 $scope
.schedule
.mode
= 'at';
427 $scope
.schedule
.datetime
= sched
;
430 $scope
.schedule
.mode
= 'now';
433 var updateParent
= (function () {
434 switch ($scope
.schedule
.mode
) {
436 schedModel
.assign($scope
, null);
439 schedModel
.assign($scope
, $scope
.schedule
.datetime
);
442 throw 'Unrecognized schedule mode: ' + $scope
.schedule
.mode
;
446 $scope
.$watch(schedModelExpr
, updateChildren
);
447 $scope
.$watch('schedule.mode', updateParent
);
448 $scope
.$watch('schedule.datetime', function (newValue
, oldValue
) {
449 // automatically switch mode based on datetime entry
450 if (oldValue
!= newValue
) {
451 if (!newValue
|| newValue
== " ") {
452 $scope
.schedule
.mode
= 'now';
455 $scope
.schedule
.mode
= 'at';
462 })(angular
, CRM
.$, CRM
._
);