crmApp, crmUtil - Tighten scope of crmApp. Move services to crmUtil. Avoid cyclic...
[civicrm-core.git] / js / angular-crmMailing.js
CommitLineData
030dce01 1(function (angular, $, _) {
4dd19229 2 var partialUrl = function partialUrl(relPath) {
dcc7d5c9 3 return CRM.resourceUrls['civicrm'] + '/partials/crmMailing/' + relPath;
030dce01
TO
4 };
5
88e9e883 6 angular.module('crmMailing', [
a8e65974 7 'crmUtil', 'crmAttachment', 'ngRoute', 'ui.utils', 'crmUi', 'dialogService'
f4f103fa 8 ]); // TODO ngSanitize, unsavedChanges
030dce01 9
7801d9b5
TO
10 // Time to wait before triggering AJAX update to recipients list
11 var RECIPIENTS_DEBOUNCE_MS = 100;
b73e0c53 12 var RECIPIENTS_PREVIEW_LIMIT = 10000;
7801d9b5 13
88e9e883 14 angular.module('crmMailing').config([
f4f103fa 15 '$routeProvider',
030dce01 16 function ($routeProvider) {
96ac27bd 17 $routeProvider.when('/mailing', {
030dce01
TO
18 template: '<div></div>',
19 controller: 'ListMailingsCtrl'
20 });
96ac27bd 21 $routeProvider.when('/mailing/:id', {
030dce01
TO
22 templateUrl: partialUrl('edit.html'),
23 controller: 'EditMailingCtrl',
24 resolve: {
f4f103fa
TO
25 selectedMail: function selectedMail($route, crmMailingMgr) {
26 return crmMailingMgr.getOrCreate($route.current.params.id);
27 }
d4182dda
TO
28 }
29 });
96ac27bd 30 $routeProvider.when('/mailing/:id/unified', {
d4182dda
TO
31 templateUrl: partialUrl('edit-unified.html'),
32 controller: 'EditMailingCtrl',
33 resolve: {
f4f103fa
TO
34 selectedMail: function selectedMail($route, crmMailingMgr) {
35 return crmMailingMgr.getOrCreate($route.current.params.id);
36 }
d4182dda
TO
37 }
38 });
96ac27bd 39 $routeProvider.when('/mailing/:id/unified2', {
d4182dda
TO
40 templateUrl: partialUrl('edit-unified2.html'),
41 controller: 'EditMailingCtrl',
42 resolve: {
f4f103fa
TO
43 selectedMail: function selectedMail($route, crmMailingMgr) {
44 return crmMailingMgr.getOrCreate($route.current.params.id);
45 }
d4182dda
TO
46 }
47 });
96ac27bd 48 $routeProvider.when('/mailing/:id/wizard', {
d4182dda
TO
49 templateUrl: partialUrl('edit-wizard.html'),
50 controller: 'EditMailingCtrl',
51 resolve: {
f4f103fa
TO
52 selectedMail: function selectedMail($route, crmMailingMgr) {
53 return crmMailingMgr.getOrCreate($route.current.params.id);
54 }
030dce01
TO
55 }
56 });
57 }
58 ]);
59
416abe87 60 angular.module('crmMailing').controller('ListMailingsCtrl', ['crmLegacy', 'crmNavigator', function ListMailingsCtrl(crmLegacy, crmNavigator) {
030dce01
TO
61 // We haven't implemented this in Angular, but some users may get clever
62 // about typing URLs, so we'll provide a redirect.
416abe87
PH
63 var new_url = crmLegacy.url('civicrm/mailing/browse/unscheduled', {reset: 1, scheduled: 'false'});
64 crmNavigator.redirect(new_url);
65 }]);
030dce01 66
58dfba8d 67 angular.module('crmMailing').controller('EditMailingCtrl', function EditMailingCtrl($scope, selectedMail, $location, crmMailingMgr, crmStatus, CrmAttachments, crmMailingPreviewMgr) {
27e690c2 68 $scope.mailing = selectedMail;
f4f103fa 69 $scope.attachments = new CrmAttachments(function () {
db083bf0
TO
70 return {entity_table: 'civicrm_mailing', entity_id: $scope.mailing.id};
71 });
ab0a4aec 72 $scope.attachments.load();
27e690c2
TO
73 $scope.crmMailingConst = CRM.crmMailing;
74
030dce01 75 $scope.partialUrl = partialUrl;
52f515c6 76 var ts = $scope.ts = CRM.ts('CiviMail');
27e690c2 77
86c3a327
TO
78 $scope.isSubmitted = function isSubmitted() {
79 return _.size($scope.mailing.jobs) > 0;
80 };
81
58dfba8d
TO
82 // @return Promise
83 $scope.previewMailing = function previewMailing(mailing, mode) {
84 return crmMailingPreviewMgr.preview(mailing, mode);
85 };
86
87 // @return Promise
88 $scope.sendTest = function sendTest(mailing, attachments, recipient) {
89 var savePromise = crmMailingMgr.save(mailing)
90 .then(function () {
91 return attachments.save();
86c3a327
TO
92 })
93 .then(updateUrl);
58dfba8d
TO
94 return crmStatus({start: ts('Saving...'), success: ''}, savePromise)
95 .then(function () {
96 crmMailingPreviewMgr.sendTest(mailing, recipient);
97 });
98 };
99
705c61e9
TO
100 // @return Promise
101 $scope.submit = function submit() {
db083bf0 102 var promise = crmMailingMgr.save($scope.mailing)
86c3a327
TO
103 .then(function () {
104 // pre-condition: the mailing exists *before* saving attachments to it
105 return $scope.attachments.save();
106 })
107 .then(function () {
108 return crmMailingMgr.submit($scope.mailing);
109 })
110 .then(function () {
b0797ac3 111 leave('scheduled');
86c3a327
TO
112 })
113 ;
db083bf0 114 return crmStatus({start: ts('Submitting...'), success: ts('Submitted')}, promise);
2d36e6bc 115 };
58dfba8d 116
705c61e9
TO
117 // @return Promise
118 $scope.save = function save() {
f286acec 119 return crmStatus(null,
db083bf0
TO
120 crmMailingMgr
121 .save($scope.mailing)
122 .then(function () {
123 // pre-condition: the mailing exists *before* saving attachments to it
124 return $scope.attachments.save();
125 })
b0797ac3 126 .then(updateUrl)
f286acec 127 );
2d36e6bc 128 };
58dfba8d 129
705c61e9
TO
130 // @return Promise
131 $scope.delete = function cancel() {
f286acec
TO
132 return crmStatus({start: ts('Deleting...'), success: ts('Deleted')},
133 crmMailingMgr.delete($scope.mailing)
b0797ac3
TO
134 .then(function () {
135 leave('unscheduled')
136 })
f286acec 137 );
2d36e6bc 138 };
58dfba8d 139
b0797ac3
TO
140 // @param string listingScreen 'archive', 'scheduled', 'unscheduled'
141 function leave(listingScreen) {
142 switch (listingScreen) {
143 case 'archive':
144 window.location = CRM.url('civicrm/mailing/browse/archived', {
145 reset: 1
146 });
147 break;
148 case 'scheduled':
149 window.location = CRM.url('civicrm/mailing/browse/scheduled', {
150 reset: 1,
151 scheduled: 'true'
152 });
153 break;
154 case 'unscheduled':
155 default:
156 window.location = CRM.url('civicrm/mailing/browse/unscheduled', {
157 reset: 1,
158 scheduled: 'false'
159 });
160 }
2d36e6bc 161 };
c54afefa 162
86c3a327
TO
163 // Transition URL "/mailing/new" => "/mailing/123"
164 function updateUrl() {
165 var parts = $location.path().split('/'); // e.g. "/mailing/new" or "/mailing/123/wizard"
166 if (parts[2] != $scope.mailing.id) {
167 parts[2] = $scope.mailing.id;
c54afefa
TO
168 $location.path(parts.join('/'));
169 $location.replace();
170 // FIXME: Angular unnecessarily refreshes UI
86c3a327
TO
171 // WARNING: Changing the URL triggers a full reload. Any pending AJAX operations
172 // could be inconsistently applied. Run updateUrl() after other changes complete.
c54afefa 173 }
86c3a327 174 }
030dce01
TO
175 });
176
7801d9b5
TO
177 // Controller for the edit-recipients fields (
178 // WISHLIST: Move most of this to a (cache-enabled) service
179 // Scope members:
180 // - [input] mailing: object
181 // - [output] recipients: array of recipient records
88e9e883 182 angular.module('crmMailing').controller('EditRecipCtrl', function EditRecipCtrl($scope, dialogService, crmApi, crmMailingMgr) {
52f515c6 183 var ts = $scope.ts = CRM.ts('CiviMail');
7801d9b5
TO
184 $scope.recipients = null;
185 $scope.getRecipientsEstimate = function () {
186 var ts = $scope.ts;
f4f103fa 187 if ($scope.recipients == null) {
7801d9b5 188 return ts('(Estimating)');
f4f103fa
TO
189 }
190 if ($scope.recipients.length == 0) {
7801d9b5 191 return ts('No recipients');
f4f103fa
TO
192 }
193 if ($scope.recipients.length == 1) {
7801d9b5 194 return ts('~1 recipient');
f4f103fa
TO
195 }
196 if (RECIPIENTS_PREVIEW_LIMIT > 0 && $scope.recipients.length >= RECIPIENTS_PREVIEW_LIMIT) {
b73e0c53 197 return ts('>%1 recipients', {1: RECIPIENTS_PREVIEW_LIMIT});
f4f103fa 198 }
7801d9b5
TO
199 return ts('~%1 recipients', {1: $scope.recipients.length});
200 };
f4f103fa 201 $scope.getIncludesAsString = function () {
47bacc20
TO
202 var first = true;
203 var names = '';
f4f103fa
TO
204 _.each($scope.mailing.groups.include, function (id) {
205 if (!first) {
206 names = names + ', ';
207 }
208 var group = _.where(CRM.crmMailing.groupNames, {id: '' + id});
47bacc20
TO
209 names = names + group[0].title;
210 first = false;
211 });
f4f103fa
TO
212 _.each($scope.mailing.mailings.include, function (id) {
213 if (!first) {
214 names = names + ', ';
215 }
216 var oldMailing = _.where(CRM.crmMailing.civiMails, {id: '' + id});
47bacc20
TO
217 names = names + oldMailing[0].name;
218 first = false;
219 });
220 return names;
221 };
f4f103fa 222 $scope.getExcludesAsString = function () {
47bacc20
TO
223 var first = true;
224 var names = '';
f4f103fa
TO
225 _.each($scope.mailing.groups.exclude, function (id) {
226 if (!first) {
227 names = names + ', ';
228 }
229 var group = _.where(CRM.crmMailing.groupNames, {id: '' + id});
47bacc20
TO
230 names = names + group[0].title;
231 first = false;
232 });
f4f103fa
TO
233 _.each($scope.mailing.mailings.exclude, function (id) {
234 if (!first) {
235 names = names + ', ';
236 }
237 var oldMailing = _.where(CRM.crmMailing.civiMails, {id: '' + id});
47bacc20
TO
238 names = names + oldMailing[0].name;
239 first = false;
240 });
241 return names;
242 };
243
7801d9b5
TO
244 // We monitor four fields -- use debounce so that changes across the
245 // four fields can settle-down before AJAX.
246 var refreshRecipients = _.debounce(function () {
247 $scope.$apply(function () {
248 $scope.recipients = null;
8dfd5110
TO
249 crmMailingMgr.previewRecipients($scope.mailing, RECIPIENTS_PREVIEW_LIMIT).then(function (recipients) {
250 $scope.recipients = recipients;
b73e0c53 251 });
7801d9b5
TO
252 });
253 }, RECIPIENTS_DEBOUNCE_MS);
254 $scope.$watchCollection("mailing.groups.include", refreshRecipients);
255 $scope.$watchCollection("mailing.groups.exclude", refreshRecipients);
256 $scope.$watchCollection("mailing.mailings.include", refreshRecipients);
257 $scope.$watchCollection("mailing.mailings.exclude", refreshRecipients);
258
4dd19229 259 $scope.previewRecipients = function previewRecipients() {
7801d9b5
TO
260 var model = {
261 recipients: $scope.recipients
262 };
263 var options = {
264 autoOpen: false,
265 modal: true,
266 title: ts('Preview (%1)', {
267 1: $scope.getRecipientsEstimate()
52f515c6 268 })
7801d9b5 269 };
4dd19229 270 dialogService.open('recipDialog', partialUrl('dialog/recipients.html'), model, options);
7801d9b5
TO
271 };
272 });
273
274 // Controller for the "Preview Recipients" dialog
275 // Note: Expects $scope.model to be an object with properties:
276 // - recipients: array of contacts
88e9e883 277 angular.module('crmMailing').controller('PreviewRecipCtrl', function ($scope) {
7801d9b5
TO
278 $scope.ts = CRM.ts('CiviMail');
279 });
493eb47a 280
493eb47a
TO
281 // Controller for the "Preview Mailing" dialog
282 // Note: Expects $scope.model to be an object with properties:
283 // - "subject"
284 // - "body_html"
285 // - "body_text"
870cbdbb 286 angular.module('crmMailing').controller('PreviewMailingDialogCtrl', function PreviewMailingDialogCtrl($scope) {
493eb47a
TO
287 $scope.ts = CRM.ts('CiviMail');
288 });
289
47bacc20
TO
290 // Controller for the "Preview Mailing Component" segment
291 // which displays header/footer/auto-responder
88e9e883 292 angular.module('crmMailing').controller('PreviewComponentCtrl', function PreviewMailingDialogCtrl($scope, dialogService) {
52f515c6
TO
293 var ts = $scope.ts = CRM.ts('CiviMail');
294
47bacc20 295 $scope.previewComponent = function previewComponent(title, componentId) {
f4f103fa 296 var component = _.where(CRM.crmMailing.headerfooterList, {id: "" + componentId});
47bacc20
TO
297 if (!component || !component[0]) {
298 CRM.alert(ts('Invalid component ID (%1)', {
299 1: componentId
300 }));
301 return;
302 }
303 var options = {
304 autoOpen: false,
305 modal: true,
306 title: title // component[0].name
307 };
308 dialogService.open('previewComponentDialog', partialUrl('dialog/previewComponent.html'), component[0], options);
309 };
310 });
311
312 // Controller for the "Preview Mailing" dialog
313 // Note: Expects $scope.model to be an object with properties:
314 // - "name"
315 // - "subject"
316 // - "body_html"
317 // - "body_text"
88e9e883 318 angular.module('crmMailing').controller('PreviewComponentDialogCtrl', function PreviewMailingDialogCtrl($scope) {
47bacc20
TO
319 $scope.ts = CRM.ts('CiviMail');
320 });
321
744bebee 322 // Controller for the in-place msg-template management
870cbdbb 323 angular.module('crmMailing').controller('MsgTemplateCtrl', function MsgTemplateCtrl($scope, crmMsgTemplates, dialogService) {
744bebee
TO
324 var ts = $scope.ts = CRM.ts('CiviMail');
325 $scope.crmMsgTemplates = crmMsgTemplates;
326
327 // @return Promise MessageTemplate (per APIv3)
74263d6b 328 $scope.saveTemplate = function saveTemplate(mailing) {
744bebee 329 var model = {
74263d6b 330 selected_id: mailing.msg_template_id,
744bebee
TO
331 tpl: {
332 msg_title: '',
74263d6b
TO
333 msg_subject: mailing.subject,
334 msg_text: mailing.body_text,
335 msg_html: mailing.body_html
744bebee
TO
336 }
337 };
338 var options = {
339 autoOpen: false,
340 modal: true,
341 title: ts('Save Template')
342 };
343 return dialogService.open('saveTemplateDialog', partialUrl('dialog/saveTemplate.html'), model, options)
f4f103fa 344 .then(function (item) {
74263d6b 345 mailing.msg_template_id = item.id;
744bebee
TO
346 return item;
347 });
348 };
349
350 // @param int id
351 // @return Promise
74263d6b 352 $scope.loadTemplate = function loadTemplate(mailing, id) {
744bebee 353 return crmMsgTemplates.get(id).then(function (tpl) {
74263d6b
TO
354 mailing.subject = tpl.msg_subject;
355 mailing.body_text = tpl.msg_text;
356 mailing.body_html = tpl.msg_html;
744bebee
TO
357 });
358 };
359 });
360
361 // Controller for the "Save Message Template" dialog
362 // Scope members:
363 // - [input] "model": Object
364 // - "selected_id": int
365 // - "tpl": Object
366 // - "msg_subject": string
367 // - "msg_text": string
368 // - "msg_html": string
88e9e883 369 angular.module('crmMailing').controller('SaveMsgTemplateDialogCtrl', function SaveMsgTemplateDialogCtrl($scope, crmMsgTemplates, dialogService) {
744bebee
TO
370 var ts = $scope.ts = CRM.ts('CiviMail');
371 $scope.saveOpt = {mode: '', newTitle: ''};
372 $scope.selected = null;
373
374 $scope.save = function save() {
375 var tpl = _.extend({}, $scope.model.tpl);
376 switch ($scope.saveOpt.mode) {
377 case 'add':
378 tpl.msg_title = $scope.saveOpt.newTitle;
379 break;
380 case 'update':
381 tpl.id = $scope.selected.id;
382 tpl.msg_title = $scope.selected.msg_title;
383 break;
384 default:
385 throw 'SaveMsgTemplateDialogCtrl: Unrecognized mode: ' + $scope.saveOpt.mode;
386 }
387 return crmMsgTemplates.save(tpl)
388 .then(function (item) {
389 CRM.status(ts('Saved'));
390 return item;
391 });
392 };
393
394 function scopeApply(f) {
395 return function () {
396 var args = arguments;
397 $scope.$apply(function () {
398 f.apply(args);
399 });
400 };
401 }
402
403 function init() {
404 crmMsgTemplates.get($scope.model.selected_id).then(
405 function (tpl) {
406 $scope.saveOpt.mode = 'update';
407 $scope.selected = tpl;
408 },
409 function () {
410 $scope.saveOpt.mode = 'add';
411 $scope.selected = null;
412 }
413 );
414 // When using dialogService with a button bar, the major button actions
415 // need to be registered with the dialog widget (and not embedded in
416 // the body of the dialog).
417 var buttons = {};
418 buttons[ts('Save')] = function () {
419 $scope.save().then(function (item) {
420 dialogService.close('saveTemplateDialog', item);
421 });
422 };
423 buttons[ts('Cancel')] = function () {
424 dialogService.cancel('saveTemplateDialog');
425 };
426 dialogService.setButtons('saveTemplateDialog', buttons);
427 }
f4f103fa 428
744bebee
TO
429 setTimeout(scopeApply(init), 0);
430 });
2d06b3b6 431
58dfba8d 432 angular.module('crmMailing').controller('EmailAddrCtrl', function EmailAddrCtrl($scope, crmFromAddresses) {
0a993c89
TO
433 $scope.crmFromAddresses = crmFromAddresses;
434 });
030dce01 435})(angular, CRM.$, CRM._);