CRM-15578 - MsgTemplateCtrl - Don't hardcode dependence on upstream "scope.mailing"
[civicrm-core.git] / js / angular-crmMailing2.js
1 (function (angular, $, _) {
2 var partialUrl = function partialUrl(relPath) {
3 return CRM.resourceUrls['civicrm'] + '/partials/crmMailing2/' + relPath;
4 };
5
6 var crmMailing2 = angular.module('crmMailing2', [
7 'crmUtil', 'crmAttachment', 'ngRoute', 'ui.utils', 'crmUi', 'dialogService'
8 ]); // TODO ngSanitize, unsavedChanges
9
10 // Time to wait before triggering AJAX update to recipients list
11 var RECIPIENTS_DEBOUNCE_MS = 100;
12 var RECIPIENTS_PREVIEW_LIMIT = 10000;
13
14 crmMailing2.config([
15 '$routeProvider',
16 function ($routeProvider) {
17 $routeProvider.when('/mailing2', {
18 template: '<div></div>',
19 controller: 'ListMailingsCtrl'
20 });
21 $routeProvider.when('/mailing2/:id', {
22 templateUrl: partialUrl('edit.html'),
23 controller: 'EditMailingCtrl',
24 resolve: {
25 selectedMail: function selectedMail($route, crmMailingMgr) {
26 return crmMailingMgr.getOrCreate($route.current.params.id);
27 }
28 }
29 });
30 $routeProvider.when('/mailing2/:id/unified', {
31 templateUrl: partialUrl('edit-unified.html'),
32 controller: 'EditMailingCtrl',
33 resolve: {
34 selectedMail: function selectedMail($route, crmMailingMgr) {
35 return crmMailingMgr.getOrCreate($route.current.params.id);
36 }
37 }
38 });
39 $routeProvider.when('/mailing2/:id/unified2', {
40 templateUrl: partialUrl('edit-unified2.html'),
41 controller: 'EditMailingCtrl',
42 resolve: {
43 selectedMail: function selectedMail($route, crmMailingMgr) {
44 return crmMailingMgr.getOrCreate($route.current.params.id);
45 }
46 }
47 });
48 $routeProvider.when('/mailing2/:id/wizard', {
49 templateUrl: partialUrl('edit-wizard.html'),
50 controller: 'EditMailingCtrl',
51 resolve: {
52 selectedMail: function selectedMail($route, crmMailingMgr) {
53 return crmMailingMgr.getOrCreate($route.current.params.id);
54 }
55 }
56 });
57 }
58 ]);
59
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', {
64 reset: 1,
65 scheduled: 'false'
66 });
67 });
68
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};
73 });
74 $scope.attachments.load();
75 $scope.crmMailingConst = CRM.crmMailing;
76 $scope.crmFromAddresses = crmFromAddresses;
77
78 $scope.partialUrl = partialUrl;
79 var ts = $scope.ts = CRM.ts('CiviMail');
80
81 // @return Promise
82 $scope.submit = function submit() {
83 var promise = crmMailingMgr.save($scope.mailing)
84 .then(function () {
85 // pre-condition: the mailing exists *before* saving attachments to it
86 return $scope.attachments.save();
87 })
88 .then(function () {
89 return crmMailingMgr.submit($scope.mailing);
90 });
91 return crmStatus({start: ts('Submitting...'), success: ts('Submitted')}, promise);
92 };
93 // @return Promise
94 $scope.save = function save() {
95 return crmStatus(null,
96 crmMailingMgr
97 .save($scope.mailing)
98 .then(function () {
99 // pre-condition: the mailing exists *before* saving attachments to it
100 return $scope.attachments.save();
101 })
102 );
103 };
104 // @return Promise
105 $scope.delete = function cancel() {
106 return crmStatus({start: ts('Deleting...'), success: ts('Deleted')},
107 crmMailingMgr.delete($scope.mailing)
108 );
109 };
110 $scope.leave = function leave() {
111 window.location = CRM.url('civicrm/mailing/browse/unscheduled', {
112 reset: 1,
113 scheduled: 'false'
114 });
115 };
116
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"
121 parts[2] = newValue;
122 $location.path(parts.join('/'));
123 $location.replace();
124 // FIXME: Angular unnecessarily refreshes UI
125 }
126 });
127
128 $scope.fromPlaceholder = {
129 label: crmFromAddresses.getByAuthorEmail($scope.mailing.from_name, $scope.mailing.from_email, true).label
130 };
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;
135 });
136 });
137
138 // Controller for the edit-recipients fields (
139 // WISHLIST: Move most of this to a (cache-enabled) service
140 // Scope members:
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 () {
147 var ts = $scope.ts;
148 if ($scope.recipients == null) {
149 return ts('(Estimating)');
150 }
151 if ($scope.recipients.length == 0) {
152 return ts('No recipients');
153 }
154 if ($scope.recipients.length == 1) {
155 return ts('~1 recipient');
156 }
157 if (RECIPIENTS_PREVIEW_LIMIT > 0 && $scope.recipients.length >= RECIPIENTS_PREVIEW_LIMIT) {
158 return ts('>%1 recipients', {1: RECIPIENTS_PREVIEW_LIMIT});
159 }
160 return ts('~%1 recipients', {1: $scope.recipients.length});
161 };
162 $scope.getIncludesAsString = function () {
163 var first = true;
164 var names = '';
165 _.each($scope.mailing.groups.include, function (id) {
166 if (!first) {
167 names = names + ', ';
168 }
169 var group = _.where(CRM.crmMailing.groupNames, {id: '' + id});
170 names = names + group[0].title;
171 first = false;
172 });
173 _.each($scope.mailing.mailings.include, function (id) {
174 if (!first) {
175 names = names + ', ';
176 }
177 var oldMailing = _.where(CRM.crmMailing.civiMails, {id: '' + id});
178 names = names + oldMailing[0].name;
179 first = false;
180 });
181 return names;
182 };
183 $scope.getExcludesAsString = function () {
184 var first = true;
185 var names = '';
186 _.each($scope.mailing.groups.exclude, function (id) {
187 if (!first) {
188 names = names + ', ';
189 }
190 var group = _.where(CRM.crmMailing.groupNames, {id: '' + id});
191 names = names + group[0].title;
192 first = false;
193 });
194 _.each($scope.mailing.mailings.exclude, function (id) {
195 if (!first) {
196 names = names + ', ';
197 }
198 var oldMailing = _.where(CRM.crmMailing.civiMails, {id: '' + id});
199 names = names + oldMailing[0].name;
200 first = false;
201 });
202 return names;
203 };
204
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;
212 });
213 });
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);
219
220 $scope.previewRecipients = function previewRecipients() {
221 var model = {
222 recipients: $scope.recipients
223 };
224 var options = {
225 autoOpen: false,
226 modal: true,
227 title: ts('Preview (%1)', {
228 1: $scope.getRecipientsEstimate()
229 })
230 };
231 dialogService.open('recipDialog', partialUrl('dialog/recipients.html'), model, options);
232 };
233 });
234
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');
240 });
241
242 // Controller for the "Preview Mailing" segment
243 // Note: Expects $scope.model to be an object with properties:
244 // - mailing: object
245 // - attachments: object
246 crmMailing2.controller('PreviewMailingCtrl', function ($scope, dialogService, crmMailingMgr, crmStatus) {
247 var ts = $scope.ts = CRM.ts('CiviMail');
248
249 $scope.testContact = {email: CRM.crmMailing.defaultTestEmail};
250 $scope.testGroup = {gid: null};
251
252 $scope.previewHtml = function previewHtml() {
253 $scope.previewDialog(partialUrl('dialog/previewHtml.html'));
254 };
255 $scope.previewText = function previewText() {
256 $scope.previewDialog(partialUrl('dialog/previewText.html'));
257 };
258 $scope.previewFull = function previewFull() {
259 $scope.previewDialog(partialUrl('dialog/previewFull.html'));
260 };
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) {
267 var options = {
268 autoOpen: false,
269 modal: true,
270 title: ts('Subject: %1', {
271 1: content.subject
272 })
273 };
274 dialogService.open('previewDialog', template, content, options);
275 });
276 CRM.status({start: ts('Previewing'), success: ''}, CRM.toJqPromise(p));
277 };
278 $scope.sendTestToContact = function sendTestToContact() {
279 $scope.sendTest($scope.mailing, $scope.attachments, $scope.testContact.email, null);
280 };
281 $scope.sendTestToGroup = function sendTestToGroup() {
282 $scope.sendTest($scope.mailing, $scope.attachments, null, $scope.testGroup.gid);
283 };
284 $scope.sendTest = function sendTest(mailing, attachments, testEmail, testGroup) {
285 var promise = crmMailingMgr.save(mailing)
286 .then(function () {
287 return attachments.save();
288 })
289 .then(function () {
290 return crmMailingMgr.sendTest(mailing, testEmail, testGroup);
291 })
292 .then(function (deliveryInfos) {
293 var count = Object.keys(deliveryInfos).length;
294 if (count === 0) {
295 CRM.alert(ts('Could not identify any recipients. Perhaps the group is empty?'));
296 }
297 })
298 ;
299 return crmStatus({start: ts('Sending...'), success: ts('Sent')}, promise);
300 };
301 });
302
303 // Controller for the "Preview Mailing" dialog
304 // Note: Expects $scope.model to be an object with properties:
305 // - "subject"
306 // - "body_html"
307 // - "body_text"
308 crmMailing2.controller('PreviewMailingDialogCtrl', function PreviewMailingDialogCtrl($scope, crmMailingMgr) {
309 $scope.ts = CRM.ts('CiviMail');
310 });
311
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');
316
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)', {
321 1: componentId
322 }));
323 return;
324 }
325 var options = {
326 autoOpen: false,
327 modal: true,
328 title: title // component[0].name
329 };
330 dialogService.open('previewComponentDialog', partialUrl('dialog/previewComponent.html'), component[0], options);
331 };
332 });
333
334 // Controller for the "Preview Mailing" dialog
335 // Note: Expects $scope.model to be an object with properties:
336 // - "name"
337 // - "subject"
338 // - "body_html"
339 // - "body_text"
340 crmMailing2.controller('PreviewComponentDialogCtrl', function PreviewMailingDialogCtrl($scope) {
341 $scope.ts = CRM.ts('CiviMail');
342 });
343
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;
348
349 // @return Promise MessageTemplate (per APIv3)
350 $scope.saveTemplate = function saveTemplate(mailing) {
351 var model = {
352 selected_id: mailing.msg_template_id,
353 tpl: {
354 msg_title: '',
355 msg_subject: mailing.subject,
356 msg_text: mailing.body_text,
357 msg_html: mailing.body_html
358 }
359 };
360 var options = {
361 autoOpen: false,
362 modal: true,
363 title: ts('Save Template')
364 };
365 return dialogService.open('saveTemplateDialog', partialUrl('dialog/saveTemplate.html'), model, options)
366 .then(function (item) {
367 mailing.msg_template_id = item.id;
368 return item;
369 });
370 };
371
372 // @param int id
373 // @return Promise
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;
379 });
380 };
381 });
382
383 // Controller for the "Save Message Template" dialog
384 // Scope members:
385 // - [input] "model": Object
386 // - "selected_id": int
387 // - "tpl": Object
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;
395
396 $scope.save = function save() {
397 var tpl = _.extend({}, $scope.model.tpl);
398 switch ($scope.saveOpt.mode) {
399 case 'add':
400 tpl.msg_title = $scope.saveOpt.newTitle;
401 break;
402 case 'update':
403 tpl.id = $scope.selected.id;
404 tpl.msg_title = $scope.selected.msg_title;
405 break;
406 default:
407 throw 'SaveMsgTemplateDialogCtrl: Unrecognized mode: ' + $scope.saveOpt.mode;
408 }
409 return crmMsgTemplates.save(tpl)
410 .then(function (item) {
411 CRM.status(ts('Saved'));
412 return item;
413 });
414 };
415
416 function scopeApply(f) {
417 return function () {
418 var args = arguments;
419 $scope.$apply(function () {
420 f.apply(args);
421 });
422 };
423 }
424
425 function init() {
426 crmMsgTemplates.get($scope.model.selected_id).then(
427 function (tpl) {
428 $scope.saveOpt.mode = 'update';
429 $scope.selected = tpl;
430 },
431 function () {
432 $scope.saveOpt.mode = 'add';
433 $scope.selected = null;
434 }
435 );
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).
439 var buttons = {};
440 buttons[ts('Save')] = function () {
441 $scope.save().then(function (item) {
442 dialogService.close('saveTemplateDialog', item);
443 });
444 };
445 buttons[ts('Cancel')] = function () {
446 dialogService.cancel('saveTemplateDialog');
447 };
448 dialogService.setButtons('saveTemplateDialog', buttons);
449 }
450
451 setTimeout(scopeApply(init), 0);
452 });
453
454 // Controller for schedule-editing widget.
455 // Scope members:
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);
461
462 $scope.schedule = {
463 mode: 'now',
464 datetime: ''
465 };
466 var updateChildren = (function () {
467 var sched = schedModel($scope);
468 if (sched) {
469 $scope.schedule.mode = 'at';
470 $scope.schedule.datetime = sched;
471 }
472 else {
473 $scope.schedule.mode = 'now';
474 }
475 });
476 var updateParent = (function () {
477 switch ($scope.schedule.mode) {
478 case 'now':
479 schedModel.assign($scope, null);
480 break;
481 case 'at':
482 schedModel.assign($scope, $scope.schedule.datetime);
483 break;
484 default:
485 throw 'Unrecognized schedule mode: ' + $scope.schedule.mode;
486 }
487 });
488
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';
496 }
497 else {
498 $scope.schedule.mode = 'at';
499 }
500 }
501 updateParent();
502 });
503 });
504
505 })(angular, CRM.$, CRM._);