Merge pull request #4681 from totten/master-civimail-attachment
[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', ['crmUtil', 'crmAttachment', 'ngRoute', 'ui.utils', 'crmUi', 'dialogService']); // TODO ngSanitize, unsavedChanges
7
8 // Time to wait before triggering AJAX update to recipients list
9 var RECIPIENTS_DEBOUNCE_MS = 100;
10 var RECIPIENTS_PREVIEW_LIMIT = 10000;
11
12 crmMailing2.config(['$routeProvider',
13 function ($routeProvider) {
14 $routeProvider.when('/mailing2', {
15 template: '<div></div>',
16 controller: 'ListMailingsCtrl'
17 });
18 $routeProvider.when('/mailing2/:id', {
19 templateUrl: partialUrl('edit.html'),
20 controller: 'EditMailingCtrl',
21 resolve: {
22 selectedMail: function selectedMail($route, crmMailingMgr) { return crmMailingMgr.getOrCreate($route.current.params.id); }
23 }
24 });
25 $routeProvider.when('/mailing2/:id/unified', {
26 templateUrl: partialUrl('edit-unified.html'),
27 controller: 'EditMailingCtrl',
28 resolve: {
29 selectedMail: function selectedMail($route, crmMailingMgr) { return crmMailingMgr.getOrCreate($route.current.params.id); }
30 }
31 });
32 $routeProvider.when('/mailing2/:id/unified2', {
33 templateUrl: partialUrl('edit-unified2.html'),
34 controller: 'EditMailingCtrl',
35 resolve: {
36 selectedMail: function selectedMail($route, crmMailingMgr) { return crmMailingMgr.getOrCreate($route.current.params.id); }
37 }
38 });
39 $routeProvider.when('/mailing2/:id/wizard', {
40 templateUrl: partialUrl('edit-wizard.html'),
41 controller: 'EditMailingCtrl',
42 resolve: {
43 selectedMail: function selectedMail($route, crmMailingMgr) { return crmMailingMgr.getOrCreate($route.current.params.id); }
44 }
45 });
46 }
47 ]);
48
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', {
53 reset: 1,
54 scheduled: 'false'
55 });
56 });
57
58 crmMailing2.controller('EditMailingCtrl', function EditMailingCtrl($scope, selectedMail, $location, crmMailingMgr, crmFromAddresses, crmStatus, CrmAttachments) {
59 $scope.mailing = selectedMail;
60 $scope.attachments = new CrmAttachments(function() {
61 return {entity_table: 'civicrm_mailing', entity_id: $scope.mailing.id};
62 });
63 $scope.crmMailingConst = CRM.crmMailing;
64 $scope.crmFromAddresses = crmFromAddresses;
65
66 $scope.partialUrl = partialUrl;
67 var ts = $scope.ts = CRM.ts('CiviMail');
68
69 // @return Promise
70 $scope.submit = function submit() {
71 var promise = crmMailingMgr.save($scope.mailing)
72 .then(function () {
73 // pre-condition: the mailing exists *before* saving attachments to it
74 return $scope.attachments.save();
75 })
76 .then(function () {
77 return crmMailingMgr.submit($scope.mailing);
78 });
79 return crmStatus({start: ts('Submitting...'), success: ts('Submitted')}, promise);
80 };
81 // @return Promise
82 $scope.save = function save() {
83 return crmStatus(null,
84 crmMailingMgr
85 .save($scope.mailing)
86 .then(function () {
87 // pre-condition: the mailing exists *before* saving attachments to it
88 return $scope.attachments.save();
89 })
90 );
91 };
92 // @return Promise
93 $scope.delete = function cancel() {
94 return crmStatus({start: ts('Deleting...'), success: ts('Deleted')},
95 crmMailingMgr.delete($scope.mailing)
96 );
97 };
98 $scope.leave = function leave() {
99 window.location = CRM.url('civicrm/mailing/browse/unscheduled', {
100 reset: 1,
101 scheduled: 'false'
102 });
103 };
104
105 // Transition URL "/mailing2/new" => "/mailing2/123" as soon as ID is known
106 $scope.$watch('mailing.id', function(newValue, oldValue) {
107 if (newValue && newValue != oldValue) {
108 var parts = $location.path().split('/'); // e.g. "/mailing2/new" or "/mailing2/123/wizard"
109 parts[2] = newValue;
110 $location.path(parts.join('/'));
111 $location.replace();
112 // FIXME: Angular unnecessarily refreshes UI
113 }
114 });
115
116 $scope.fromPlaceholder = {
117 label: crmFromAddresses.getByAuthorEmail($scope.mailing.from_name, $scope.mailing.from_email, true).label
118 };
119 $scope.$watch('fromPlaceholder.label', function(newValue){
120 var addr = crmFromAddresses.getByLabel(newValue);
121 $scope.mailing.from_name = addr.author;
122 $scope.mailing.from_email = addr.email;
123 });
124 });
125
126 // Controller for the edit-recipients fields (
127 // WISHLIST: Move most of this to a (cache-enabled) service
128 // Scope members:
129 // - [input] mailing: object
130 // - [output] recipients: array of recipient records
131 crmMailing2.controller('EditRecipCtrl', function EditRecipCtrl($scope, dialogService, crmApi, crmMailingMgr) {
132 var ts = $scope.ts = CRM.ts('CiviMail');
133 $scope.recipients = null;
134 $scope.getRecipientsEstimate = function () {
135 var ts = $scope.ts;
136 if ($scope.recipients == null)
137 return ts('(Estimating)');
138 if ($scope.recipients.length == 0)
139 return ts('No recipients');
140 if ($scope.recipients.length == 1)
141 return ts('~1 recipient');
142 if (RECIPIENTS_PREVIEW_LIMIT > 0 && $scope.recipients.length >= RECIPIENTS_PREVIEW_LIMIT)
143 return ts('>%1 recipients', {1: RECIPIENTS_PREVIEW_LIMIT});
144 return ts('~%1 recipients', {1: $scope.recipients.length});
145 };
146 $scope.getIncludesAsString = function() {
147 var first = true;
148 var names = '';
149 _.each($scope.mailing.groups.include, function(id){
150 if (!first) names = names + ', ';
151 var group = _.where(CRM.crmMailing.groupNames, {id: ''+id});
152 names = names + group[0].title;
153 first = false;
154 });
155 _.each($scope.mailing.mailings.include, function(id){
156 if (!first) names = names + ', ';
157 var oldMailing = _.where(CRM.crmMailing.civiMails, {id: ''+id});
158 names = names + oldMailing[0].name;
159 first = false;
160 });
161 return names;
162 };
163 $scope.getExcludesAsString = function() {
164 var first = true;
165 var names = '';
166 _.each($scope.mailing.groups.exclude, function(id){
167 if (!first) names = names + ', ';
168 var group = _.where(CRM.crmMailing.groupNames, {id: ''+id});
169 names = names + group[0].title;
170 first = false;
171 });
172 _.each($scope.mailing.mailings.exclude, function(id){
173 if (!first) names = names + ', ';
174 var oldMailing = _.where(CRM.crmMailing.civiMails, {id: ''+id});
175 names = names + oldMailing[0].name;
176 first = false;
177 });
178 return names;
179 };
180
181 // We monitor four fields -- use debounce so that changes across the
182 // four fields can settle-down before AJAX.
183 var refreshRecipients = _.debounce(function () {
184 $scope.$apply(function () {
185 $scope.recipients = null;
186 crmMailingMgr.previewRecipients($scope.mailing, RECIPIENTS_PREVIEW_LIMIT).then(function (recipients) {
187 $scope.recipients = recipients;
188 });
189 });
190 }, RECIPIENTS_DEBOUNCE_MS);
191 $scope.$watchCollection("mailing.groups.include", refreshRecipients);
192 $scope.$watchCollection("mailing.groups.exclude", refreshRecipients);
193 $scope.$watchCollection("mailing.mailings.include", refreshRecipients);
194 $scope.$watchCollection("mailing.mailings.exclude", refreshRecipients);
195
196 $scope.previewRecipients = function previewRecipients() {
197 var model = {
198 recipients: $scope.recipients
199 };
200 var options = {
201 autoOpen: false,
202 modal: true,
203 title: ts('Preview (%1)', {
204 1: $scope.getRecipientsEstimate()
205 })
206 };
207 dialogService.open('recipDialog', partialUrl('dialog/recipients.html'), model, options);
208 };
209 });
210
211 // Controller for the "Preview Recipients" dialog
212 // Note: Expects $scope.model to be an object with properties:
213 // - recipients: array of contacts
214 crmMailing2.controller('PreviewRecipCtrl', function ($scope) {
215 $scope.ts = CRM.ts('CiviMail');
216 });
217
218 // Controller for the "Preview Mailing" segment
219 // Note: Expects $scope.model to be an object with properties:
220 // - mailing: object
221 // - attachments: object
222 crmMailing2.controller('PreviewMailingCtrl', function ($scope, dialogService, crmMailingMgr, crmStatus) {
223 var ts = $scope.ts = CRM.ts('CiviMail');
224
225 $scope.testContact = {email: CRM.crmMailing.defaultTestEmail};
226 $scope.testGroup = {gid: null};
227
228 $scope.previewHtml = function previewHtml() {
229 $scope.previewDialog(partialUrl('dialog/previewHtml.html'));
230 };
231 $scope.previewText = function previewText() {
232 $scope.previewDialog(partialUrl('dialog/previewText.html'));
233 };
234 $scope.previewFull = function previewFull() {
235 $scope.previewDialog(partialUrl('dialog/previewFull.html'));
236 };
237 // Open a dialog with a preview of the current mailing
238 // @param template string URL of the template to use in the preview dialog
239 $scope.previewDialog = function previewDialog(template) {
240 var p = crmMailingMgr
241 .preview($scope.mailing)
242 .then(function(content){
243 var options = {
244 autoOpen: false,
245 modal: true,
246 title: ts('Subject: %1', {
247 1: content.subject
248 })
249 };
250 dialogService.open('previewDialog', template, content, options);
251 });
252 CRM.status({start: ts('Previewing'), success: ''}, CRM.toJqPromise(p));
253 };
254 $scope.sendTestToContact = function sendTestToContact() {
255 $scope.sendTest($scope.mailing, $scope.attachments, $scope.testContact.email, null);
256 };
257 $scope.sendTestToGroup = function sendTestToGroup() {
258 $scope.sendTest($scope.mailing, $scope.attachments, null, $scope.testGroup.gid);
259 };
260 $scope.sendTest = function sendTest(mailing, attachments, testEmail, testGroup) {
261 var promise = crmMailingMgr.save(mailing)
262 .then(function () {
263 return attachments.save();
264 })
265 .then(function () {
266 return crmMailingMgr.sendTest(mailing, testEmail, testGroup);
267 })
268 .then(function (deliveryInfos) {
269 var count = Object.keys(deliveryInfos).length;
270 if (count === 0) {
271 CRM.alert(ts('Could not identify any recipients. Perhaps the group is empty?'));
272 }
273 })
274 ;
275 return crmStatus({start: ts('Sending...'), success: ts('Sent')}, promise);
276 };
277 });
278
279 // Controller for the "Preview Mailing" dialog
280 // Note: Expects $scope.model to be an object with properties:
281 // - "subject"
282 // - "body_html"
283 // - "body_text"
284 crmMailing2.controller('PreviewMailingDialogCtrl', function PreviewMailingDialogCtrl($scope, crmMailingMgr) {
285 $scope.ts = CRM.ts('CiviMail');
286 });
287
288 // Controller for the "Preview Mailing Component" segment
289 // which displays header/footer/auto-responder
290 crmMailing2.controller('PreviewComponentCtrl', function PreviewMailingDialogCtrl($scope, dialogService) {
291 var ts = $scope.ts = CRM.ts('CiviMail');
292
293 $scope.previewComponent = function previewComponent(title, componentId) {
294 var component = _.where(CRM.crmMailing.headerfooterList, {id: ""+componentId});
295 if (!component || !component[0]) {
296 CRM.alert(ts('Invalid component ID (%1)', {
297 1: componentId
298 }));
299 return;
300 }
301 var options = {
302 autoOpen: false,
303 modal: true,
304 title: title // component[0].name
305 };
306 dialogService.open('previewComponentDialog', partialUrl('dialog/previewComponent.html'), component[0], options);
307 };
308 });
309
310 // Controller for the "Preview Mailing" dialog
311 // Note: Expects $scope.model to be an object with properties:
312 // - "name"
313 // - "subject"
314 // - "body_html"
315 // - "body_text"
316 crmMailing2.controller('PreviewComponentDialogCtrl', function PreviewMailingDialogCtrl($scope) {
317 $scope.ts = CRM.ts('CiviMail');
318 });
319
320 // Controller for the in-place msg-template management
321 // Scope members:
322 // - [input] mailing: object
323 crmMailing2.controller('MsgTemplateCtrl', function MsgTemplateCtrl($scope, crmMsgTemplates, dialogService, $parse) {
324 var ts = $scope.ts = CRM.ts('CiviMail');
325 $scope.crmMsgTemplates = crmMsgTemplates;
326
327 // @return Promise MessageTemplate (per APIv3)
328 $scope.saveTemplate = function saveTemplate() {
329 var model = {
330 selected_id: $scope.mailing.msg_template_id,
331 tpl: {
332 msg_title: '',
333 msg_subject: $scope.mailing.subject,
334 msg_text: $scope.mailing.body_text,
335 msg_html: $scope.mailing.body_html
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)
344 .then(function(item){
345 $parse('mailing.msg_template_id').assign($scope, item.id);
346 return item;
347 });
348 };
349
350 // @param int id
351 // @return Promise
352 $scope.loadTemplate = function loadTemplate(id) {
353 return crmMsgTemplates.get(id).then(function (tpl) {
354 $scope.mailing.subject = tpl.msg_subject;
355 $scope.mailing.body_text = tpl.msg_text;
356 $scope.mailing.body_html = tpl.msg_html;
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
369 crmMailing2.controller('SaveMsgTemplateDialogCtrl', function SaveMsgTemplateDialogCtrl($scope, crmMsgTemplates, dialogService) {
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 }
428 setTimeout(scopeApply(init), 0);
429 });
430
431 // Controller for schedule-editing widget.
432 // Scope members:
433 // - [input] mailing: object
434 // - scheduled_date: null|string(YYYY-MM-DD hh:mm)
435 crmMailing2.controller('EditScheduleCtrl', function EditScheduleCtrl($scope, $parse) {
436 var schedModelExpr = 'mailing.scheduled_date';
437 var schedModel = $parse(schedModelExpr);
438
439 $scope.schedule = {
440 mode: 'now',
441 datetime: ''
442 };
443 var updateChildren = (function () {
444 var sched = schedModel($scope);
445 if (sched) {
446 $scope.schedule.mode = 'at';
447 $scope.schedule.datetime = sched;
448 }
449 else {
450 $scope.schedule.mode = 'now';
451 }
452 });
453 var updateParent = (function () {
454 switch ($scope.schedule.mode) {
455 case 'now':
456 schedModel.assign($scope, null);
457 break;
458 case 'at':
459 schedModel.assign($scope, $scope.schedule.datetime);
460 break;
461 default:
462 throw 'Unrecognized schedule mode: ' + $scope.schedule.mode;
463 }
464 });
465
466 $scope.$watch(schedModelExpr, updateChildren);
467 $scope.$watch('schedule.mode', updateParent);
468 $scope.$watch('schedule.datetime', function (newValue, oldValue) {
469 // automatically switch mode based on datetime entry
470 if (oldValue != newValue) {
471 if (!newValue || newValue == " ") {
472 $scope.schedule.mode = 'now';
473 }
474 else {
475 $scope.schedule.mode = 'at';
476 }
477 }
478 updateParent();
479 });
480 });
481
482 })(angular, CRM.$, CRM._);