crmMailing2 - Autoformat
[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.crmMailingConst = CRM.crmMailing;
75 $scope.crmFromAddresses = crmFromAddresses;
76
77 $scope.partialUrl = partialUrl;
78 var ts = $scope.ts = CRM.ts('CiviMail');
79
80 // @return Promise
81 $scope.submit = function submit() {
82 var promise = crmMailingMgr.save($scope.mailing)
83 .then(function () {
84 // pre-condition: the mailing exists *before* saving attachments to it
85 return $scope.attachments.save();
86 })
87 .then(function () {
88 return crmMailingMgr.submit($scope.mailing);
89 });
90 return crmStatus({start: ts('Submitting...'), success: ts('Submitted')}, promise);
91 };
92 // @return Promise
93 $scope.save = function save() {
94 return crmStatus(null,
95 crmMailingMgr
96 .save($scope.mailing)
97 .then(function () {
98 // pre-condition: the mailing exists *before* saving attachments to it
99 return $scope.attachments.save();
100 })
101 );
102 };
103 // @return Promise
104 $scope.delete = function cancel() {
105 return crmStatus({start: ts('Deleting...'), success: ts('Deleted')},
106 crmMailingMgr.delete($scope.mailing)
107 );
108 };
109 $scope.leave = function leave() {
110 window.location = CRM.url('civicrm/mailing/browse/unscheduled', {
111 reset: 1,
112 scheduled: 'false'
113 });
114 };
115
116 // Transition URL "/mailing2/new" => "/mailing2/123" as soon as ID is known
117 $scope.$watch('mailing.id', function (newValue, oldValue) {
118 if (newValue && newValue != oldValue) {
119 var parts = $location.path().split('/'); // e.g. "/mailing2/new" or "/mailing2/123/wizard"
120 parts[2] = newValue;
121 $location.path(parts.join('/'));
122 $location.replace();
123 // FIXME: Angular unnecessarily refreshes UI
124 }
125 });
126
127 $scope.fromPlaceholder = {
128 label: crmFromAddresses.getByAuthorEmail($scope.mailing.from_name, $scope.mailing.from_email, true).label
129 };
130 $scope.$watch('fromPlaceholder.label', function (newValue) {
131 var addr = crmFromAddresses.getByLabel(newValue);
132 $scope.mailing.from_name = addr.author;
133 $scope.mailing.from_email = addr.email;
134 });
135 });
136
137 // Controller for the edit-recipients fields (
138 // WISHLIST: Move most of this to a (cache-enabled) service
139 // Scope members:
140 // - [input] mailing: object
141 // - [output] recipients: array of recipient records
142 crmMailing2.controller('EditRecipCtrl', function EditRecipCtrl($scope, dialogService, crmApi, crmMailingMgr) {
143 var ts = $scope.ts = CRM.ts('CiviMail');
144 $scope.recipients = null;
145 $scope.getRecipientsEstimate = function () {
146 var ts = $scope.ts;
147 if ($scope.recipients == null) {
148 return ts('(Estimating)');
149 }
150 if ($scope.recipients.length == 0) {
151 return ts('No recipients');
152 }
153 if ($scope.recipients.length == 1) {
154 return ts('~1 recipient');
155 }
156 if (RECIPIENTS_PREVIEW_LIMIT > 0 && $scope.recipients.length >= RECIPIENTS_PREVIEW_LIMIT) {
157 return ts('>%1 recipients', {1: RECIPIENTS_PREVIEW_LIMIT});
158 }
159 return ts('~%1 recipients', {1: $scope.recipients.length});
160 };
161 $scope.getIncludesAsString = function () {
162 var first = true;
163 var names = '';
164 _.each($scope.mailing.groups.include, function (id) {
165 if (!first) {
166 names = names + ', ';
167 }
168 var group = _.where(CRM.crmMailing.groupNames, {id: '' + id});
169 names = names + group[0].title;
170 first = false;
171 });
172 _.each($scope.mailing.mailings.include, function (id) {
173 if (!first) {
174 names = names + ', ';
175 }
176 var oldMailing = _.where(CRM.crmMailing.civiMails, {id: '' + id});
177 names = names + oldMailing[0].name;
178 first = false;
179 });
180 return names;
181 };
182 $scope.getExcludesAsString = function () {
183 var first = true;
184 var names = '';
185 _.each($scope.mailing.groups.exclude, function (id) {
186 if (!first) {
187 names = names + ', ';
188 }
189 var group = _.where(CRM.crmMailing.groupNames, {id: '' + id});
190 names = names + group[0].title;
191 first = false;
192 });
193 _.each($scope.mailing.mailings.exclude, function (id) {
194 if (!first) {
195 names = names + ', ';
196 }
197 var oldMailing = _.where(CRM.crmMailing.civiMails, {id: '' + id});
198 names = names + oldMailing[0].name;
199 first = false;
200 });
201 return names;
202 };
203
204 // We monitor four fields -- use debounce so that changes across the
205 // four fields can settle-down before AJAX.
206 var refreshRecipients = _.debounce(function () {
207 $scope.$apply(function () {
208 $scope.recipients = null;
209 crmMailingMgr.previewRecipients($scope.mailing, RECIPIENTS_PREVIEW_LIMIT).then(function (recipients) {
210 $scope.recipients = recipients;
211 });
212 });
213 }, RECIPIENTS_DEBOUNCE_MS);
214 $scope.$watchCollection("mailing.groups.include", refreshRecipients);
215 $scope.$watchCollection("mailing.groups.exclude", refreshRecipients);
216 $scope.$watchCollection("mailing.mailings.include", refreshRecipients);
217 $scope.$watchCollection("mailing.mailings.exclude", refreshRecipients);
218
219 $scope.previewRecipients = function previewRecipients() {
220 var model = {
221 recipients: $scope.recipients
222 };
223 var options = {
224 autoOpen: false,
225 modal: true,
226 title: ts('Preview (%1)', {
227 1: $scope.getRecipientsEstimate()
228 })
229 };
230 dialogService.open('recipDialog', partialUrl('dialog/recipients.html'), model, options);
231 };
232 });
233
234 // Controller for the "Preview Recipients" dialog
235 // Note: Expects $scope.model to be an object with properties:
236 // - recipients: array of contacts
237 crmMailing2.controller('PreviewRecipCtrl', function ($scope) {
238 $scope.ts = CRM.ts('CiviMail');
239 });
240
241 // Controller for the "Preview Mailing" segment
242 // Note: Expects $scope.model to be an object with properties:
243 // - mailing: object
244 // - attachments: object
245 crmMailing2.controller('PreviewMailingCtrl', function ($scope, dialogService, crmMailingMgr, crmStatus) {
246 var ts = $scope.ts = CRM.ts('CiviMail');
247
248 $scope.testContact = {email: CRM.crmMailing.defaultTestEmail};
249 $scope.testGroup = {gid: null};
250
251 $scope.previewHtml = function previewHtml() {
252 $scope.previewDialog(partialUrl('dialog/previewHtml.html'));
253 };
254 $scope.previewText = function previewText() {
255 $scope.previewDialog(partialUrl('dialog/previewText.html'));
256 };
257 $scope.previewFull = function previewFull() {
258 $scope.previewDialog(partialUrl('dialog/previewFull.html'));
259 };
260 // Open a dialog with a preview of the current mailing
261 // @param template string URL of the template to use in the preview dialog
262 $scope.previewDialog = function previewDialog(template) {
263 var p = crmMailingMgr
264 .preview($scope.mailing)
265 .then(function (content) {
266 var options = {
267 autoOpen: false,
268 modal: true,
269 title: ts('Subject: %1', {
270 1: content.subject
271 })
272 };
273 dialogService.open('previewDialog', template, content, options);
274 });
275 CRM.status({start: ts('Previewing'), success: ''}, CRM.toJqPromise(p));
276 };
277 $scope.sendTestToContact = function sendTestToContact() {
278 $scope.sendTest($scope.mailing, $scope.attachments, $scope.testContact.email, null);
279 };
280 $scope.sendTestToGroup = function sendTestToGroup() {
281 $scope.sendTest($scope.mailing, $scope.attachments, null, $scope.testGroup.gid);
282 };
283 $scope.sendTest = function sendTest(mailing, attachments, testEmail, testGroup) {
284 var promise = crmMailingMgr.save(mailing)
285 .then(function () {
286 return attachments.save();
287 })
288 .then(function () {
289 return crmMailingMgr.sendTest(mailing, testEmail, testGroup);
290 })
291 .then(function (deliveryInfos) {
292 var count = Object.keys(deliveryInfos).length;
293 if (count === 0) {
294 CRM.alert(ts('Could not identify any recipients. Perhaps the group is empty?'));
295 }
296 })
297 ;
298 return crmStatus({start: ts('Sending...'), success: ts('Sent')}, promise);
299 };
300 });
301
302 // Controller for the "Preview Mailing" dialog
303 // Note: Expects $scope.model to be an object with properties:
304 // - "subject"
305 // - "body_html"
306 // - "body_text"
307 crmMailing2.controller('PreviewMailingDialogCtrl', function PreviewMailingDialogCtrl($scope, crmMailingMgr) {
308 $scope.ts = CRM.ts('CiviMail');
309 });
310
311 // Controller for the "Preview Mailing Component" segment
312 // which displays header/footer/auto-responder
313 crmMailing2.controller('PreviewComponentCtrl', function PreviewMailingDialogCtrl($scope, dialogService) {
314 var ts = $scope.ts = CRM.ts('CiviMail');
315
316 $scope.previewComponent = function previewComponent(title, componentId) {
317 var component = _.where(CRM.crmMailing.headerfooterList, {id: "" + componentId});
318 if (!component || !component[0]) {
319 CRM.alert(ts('Invalid component ID (%1)', {
320 1: componentId
321 }));
322 return;
323 }
324 var options = {
325 autoOpen: false,
326 modal: true,
327 title: title // component[0].name
328 };
329 dialogService.open('previewComponentDialog', partialUrl('dialog/previewComponent.html'), component[0], options);
330 };
331 });
332
333 // Controller for the "Preview Mailing" dialog
334 // Note: Expects $scope.model to be an object with properties:
335 // - "name"
336 // - "subject"
337 // - "body_html"
338 // - "body_text"
339 crmMailing2.controller('PreviewComponentDialogCtrl', function PreviewMailingDialogCtrl($scope) {
340 $scope.ts = CRM.ts('CiviMail');
341 });
342
343 // Controller for the in-place msg-template management
344 // Scope members:
345 // - [input] mailing: object
346 crmMailing2.controller('MsgTemplateCtrl', function MsgTemplateCtrl($scope, crmMsgTemplates, dialogService, $parse) {
347 var ts = $scope.ts = CRM.ts('CiviMail');
348 $scope.crmMsgTemplates = crmMsgTemplates;
349
350 // @return Promise MessageTemplate (per APIv3)
351 $scope.saveTemplate = function saveTemplate() {
352 var model = {
353 selected_id: $scope.mailing.msg_template_id,
354 tpl: {
355 msg_title: '',
356 msg_subject: $scope.mailing.subject,
357 msg_text: $scope.mailing.body_text,
358 msg_html: $scope.mailing.body_html
359 }
360 };
361 var options = {
362 autoOpen: false,
363 modal: true,
364 title: ts('Save Template')
365 };
366 return dialogService.open('saveTemplateDialog', partialUrl('dialog/saveTemplate.html'), model, options)
367 .then(function (item) {
368 $parse('mailing.msg_template_id').assign($scope, item.id);
369 return item;
370 });
371 };
372
373 // @param int id
374 // @return Promise
375 $scope.loadTemplate = function loadTemplate(id) {
376 return crmMsgTemplates.get(id).then(function (tpl) {
377 $scope.mailing.subject = tpl.msg_subject;
378 $scope.mailing.body_text = tpl.msg_text;
379 $scope.mailing.body_html = tpl.msg_html;
380 });
381 };
382 });
383
384 // Controller for the "Save Message Template" dialog
385 // Scope members:
386 // - [input] "model": Object
387 // - "selected_id": int
388 // - "tpl": Object
389 // - "msg_subject": string
390 // - "msg_text": string
391 // - "msg_html": string
392 crmMailing2.controller('SaveMsgTemplateDialogCtrl', function SaveMsgTemplateDialogCtrl($scope, crmMsgTemplates, dialogService) {
393 var ts = $scope.ts = CRM.ts('CiviMail');
394 $scope.saveOpt = {mode: '', newTitle: ''};
395 $scope.selected = null;
396
397 $scope.save = function save() {
398 var tpl = _.extend({}, $scope.model.tpl);
399 switch ($scope.saveOpt.mode) {
400 case 'add':
401 tpl.msg_title = $scope.saveOpt.newTitle;
402 break;
403 case 'update':
404 tpl.id = $scope.selected.id;
405 tpl.msg_title = $scope.selected.msg_title;
406 break;
407 default:
408 throw 'SaveMsgTemplateDialogCtrl: Unrecognized mode: ' + $scope.saveOpt.mode;
409 }
410 return crmMsgTemplates.save(tpl)
411 .then(function (item) {
412 CRM.status(ts('Saved'));
413 return item;
414 });
415 };
416
417 function scopeApply(f) {
418 return function () {
419 var args = arguments;
420 $scope.$apply(function () {
421 f.apply(args);
422 });
423 };
424 }
425
426 function init() {
427 crmMsgTemplates.get($scope.model.selected_id).then(
428 function (tpl) {
429 $scope.saveOpt.mode = 'update';
430 $scope.selected = tpl;
431 },
432 function () {
433 $scope.saveOpt.mode = 'add';
434 $scope.selected = null;
435 }
436 );
437 // When using dialogService with a button bar, the major button actions
438 // need to be registered with the dialog widget (and not embedded in
439 // the body of the dialog).
440 var buttons = {};
441 buttons[ts('Save')] = function () {
442 $scope.save().then(function (item) {
443 dialogService.close('saveTemplateDialog', item);
444 });
445 };
446 buttons[ts('Cancel')] = function () {
447 dialogService.cancel('saveTemplateDialog');
448 };
449 dialogService.setButtons('saveTemplateDialog', buttons);
450 }
451
452 setTimeout(scopeApply(init), 0);
453 });
454
455 // Controller for schedule-editing widget.
456 // Scope members:
457 // - [input] mailing: object
458 // - scheduled_date: null|string(YYYY-MM-DD hh:mm)
459 crmMailing2.controller('EditScheduleCtrl', function EditScheduleCtrl($scope, $parse) {
460 var schedModelExpr = 'mailing.scheduled_date';
461 var schedModel = $parse(schedModelExpr);
462
463 $scope.schedule = {
464 mode: 'now',
465 datetime: ''
466 };
467 var updateChildren = (function () {
468 var sched = schedModel($scope);
469 if (sched) {
470 $scope.schedule.mode = 'at';
471 $scope.schedule.datetime = sched;
472 }
473 else {
474 $scope.schedule.mode = 'now';
475 }
476 });
477 var updateParent = (function () {
478 switch ($scope.schedule.mode) {
479 case 'now':
480 schedModel.assign($scope, null);
481 break;
482 case 'at':
483 schedModel.assign($scope, $scope.schedule.datetime);
484 break;
485 default:
486 throw 'Unrecognized schedule mode: ' + $scope.schedule.mode;
487 }
488 });
489
490 $scope.$watch(schedModelExpr, updateChildren);
491 $scope.$watch('schedule.mode', updateParent);
492 $scope.$watch('schedule.datetime', function (newValue, oldValue) {
493 // automatically switch mode based on datetime entry
494 if (oldValue != newValue) {
495 if (!newValue || newValue == " ") {
496 $scope.schedule.mode = 'now';
497 }
498 else {
499 $scope.schedule.mode = 'at';
500 }
501 }
502 updateParent();
503 });
504 });
505
506 })(angular, CRM.$, CRM._);