Merge pull request #4745 from totten/master-civimail-previewbug
[civicrm-core.git] / js / angular-crmMailing.js
1 (function (angular, $, _) {
2 var partialUrl = function partialUrl(relPath) {
3 return CRM.resourceUrls['civicrm'] + '/partials/crmMailing/' + relPath;
4 };
5
6 angular.module('crmMailing', [
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 angular.module('crmMailing').config([
15 '$routeProvider',
16 function ($routeProvider) {
17 $routeProvider.when('/mailing', {
18 template: '<div></div>',
19 controller: 'ListMailingsCtrl'
20 });
21 $routeProvider.when('/mailing/: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('/mailing/: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('/mailing/: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('/mailing/: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 angular.module('crmMailing').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 angular.module('crmMailing').controller('EditMailingCtrl', function EditMailingCtrl($scope, selectedMail, $location, crmMailingMgr, crmStatus, CrmAttachments, crmMailingPreviewMgr) {
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
77 $scope.partialUrl = partialUrl;
78 var ts = $scope.ts = CRM.ts('CiviMail');
79
80 // @return Promise
81 $scope.previewMailing = function previewMailing(mailing, mode) {
82 return crmMailingPreviewMgr.preview(mailing, mode);
83 };
84
85 // @return Promise
86 $scope.sendTest = function sendTest(mailing, attachments, recipient) {
87 var savePromise = crmMailingMgr.save(mailing)
88 .then(function () {
89 return attachments.save();
90 });
91 return crmStatus({start: ts('Saving...'), success: ''}, savePromise)
92 .then(function () {
93 crmMailingPreviewMgr.sendTest(mailing, recipient);
94 });
95 };
96
97 // @return Promise
98 $scope.submit = function submit() {
99 var promise = crmMailingMgr.save($scope.mailing)
100 .then(function () {
101 // pre-condition: the mailing exists *before* saving attachments to it
102 return $scope.attachments.save();
103 })
104 .then(function () {
105 return crmMailingMgr.submit($scope.mailing);
106 });
107 return crmStatus({start: ts('Submitting...'), success: ts('Submitted')}, promise);
108 };
109
110 // @return Promise
111 $scope.save = function save() {
112 return crmStatus(null,
113 crmMailingMgr
114 .save($scope.mailing)
115 .then(function () {
116 // pre-condition: the mailing exists *before* saving attachments to it
117 return $scope.attachments.save();
118 })
119 );
120 };
121
122 // @return Promise
123 $scope.delete = function cancel() {
124 return crmStatus({start: ts('Deleting...'), success: ts('Deleted')},
125 crmMailingMgr.delete($scope.mailing)
126 );
127 };
128
129 $scope.leave = function leave() {
130 window.location = CRM.url('civicrm/mailing/browse/unscheduled', {
131 reset: 1,
132 scheduled: 'false'
133 });
134 };
135
136 // Transition URL "/mailing/new" => "/mailing/123" as soon as ID is known
137 $scope.$watch('mailing.id', function (newValue, oldValue) {
138 if (newValue && newValue != oldValue) {
139 var parts = $location.path().split('/'); // e.g. "/mailing/new" or "/mailing/123/wizard"
140 parts[2] = newValue;
141 $location.path(parts.join('/'));
142 $location.replace();
143 // FIXME: Angular unnecessarily refreshes UI
144 }
145 });
146 });
147
148 // Controller for the edit-recipients fields (
149 // WISHLIST: Move most of this to a (cache-enabled) service
150 // Scope members:
151 // - [input] mailing: object
152 // - [output] recipients: array of recipient records
153 angular.module('crmMailing').controller('EditRecipCtrl', function EditRecipCtrl($scope, dialogService, crmApi, crmMailingMgr) {
154 var ts = $scope.ts = CRM.ts('CiviMail');
155 $scope.recipients = null;
156 $scope.getRecipientsEstimate = function () {
157 var ts = $scope.ts;
158 if ($scope.recipients == null) {
159 return ts('(Estimating)');
160 }
161 if ($scope.recipients.length == 0) {
162 return ts('No recipients');
163 }
164 if ($scope.recipients.length == 1) {
165 return ts('~1 recipient');
166 }
167 if (RECIPIENTS_PREVIEW_LIMIT > 0 && $scope.recipients.length >= RECIPIENTS_PREVIEW_LIMIT) {
168 return ts('>%1 recipients', {1: RECIPIENTS_PREVIEW_LIMIT});
169 }
170 return ts('~%1 recipients', {1: $scope.recipients.length});
171 };
172 $scope.getIncludesAsString = function () {
173 var first = true;
174 var names = '';
175 _.each($scope.mailing.groups.include, function (id) {
176 if (!first) {
177 names = names + ', ';
178 }
179 var group = _.where(CRM.crmMailing.groupNames, {id: '' + id});
180 names = names + group[0].title;
181 first = false;
182 });
183 _.each($scope.mailing.mailings.include, function (id) {
184 if (!first) {
185 names = names + ', ';
186 }
187 var oldMailing = _.where(CRM.crmMailing.civiMails, {id: '' + id});
188 names = names + oldMailing[0].name;
189 first = false;
190 });
191 return names;
192 };
193 $scope.getExcludesAsString = function () {
194 var first = true;
195 var names = '';
196 _.each($scope.mailing.groups.exclude, function (id) {
197 if (!first) {
198 names = names + ', ';
199 }
200 var group = _.where(CRM.crmMailing.groupNames, {id: '' + id});
201 names = names + group[0].title;
202 first = false;
203 });
204 _.each($scope.mailing.mailings.exclude, function (id) {
205 if (!first) {
206 names = names + ', ';
207 }
208 var oldMailing = _.where(CRM.crmMailing.civiMails, {id: '' + id});
209 names = names + oldMailing[0].name;
210 first = false;
211 });
212 return names;
213 };
214
215 // We monitor four fields -- use debounce so that changes across the
216 // four fields can settle-down before AJAX.
217 var refreshRecipients = _.debounce(function () {
218 $scope.$apply(function () {
219 $scope.recipients = null;
220 crmMailingMgr.previewRecipients($scope.mailing, RECIPIENTS_PREVIEW_LIMIT).then(function (recipients) {
221 $scope.recipients = recipients;
222 });
223 });
224 }, RECIPIENTS_DEBOUNCE_MS);
225 $scope.$watchCollection("mailing.groups.include", refreshRecipients);
226 $scope.$watchCollection("mailing.groups.exclude", refreshRecipients);
227 $scope.$watchCollection("mailing.mailings.include", refreshRecipients);
228 $scope.$watchCollection("mailing.mailings.exclude", refreshRecipients);
229
230 $scope.previewRecipients = function previewRecipients() {
231 var model = {
232 recipients: $scope.recipients
233 };
234 var options = {
235 autoOpen: false,
236 modal: true,
237 title: ts('Preview (%1)', {
238 1: $scope.getRecipientsEstimate()
239 })
240 };
241 dialogService.open('recipDialog', partialUrl('dialog/recipients.html'), model, options);
242 };
243 });
244
245 // Controller for the "Preview Recipients" dialog
246 // Note: Expects $scope.model to be an object with properties:
247 // - recipients: array of contacts
248 angular.module('crmMailing').controller('PreviewRecipCtrl', function ($scope) {
249 $scope.ts = CRM.ts('CiviMail');
250 });
251
252 // Controller for the "Preview Mailing" dialog
253 // Note: Expects $scope.model to be an object with properties:
254 // - "subject"
255 // - "body_html"
256 // - "body_text"
257 angular.module('crmMailing').controller('PreviewMailingDialogCtrl', function PreviewMailingDialogCtrl($scope, crmMailingMgr) {
258 $scope.ts = CRM.ts('CiviMail');
259 });
260
261 // Controller for the "Preview Mailing Component" segment
262 // which displays header/footer/auto-responder
263 angular.module('crmMailing').controller('PreviewComponentCtrl', function PreviewMailingDialogCtrl($scope, dialogService) {
264 var ts = $scope.ts = CRM.ts('CiviMail');
265
266 $scope.previewComponent = function previewComponent(title, componentId) {
267 var component = _.where(CRM.crmMailing.headerfooterList, {id: "" + componentId});
268 if (!component || !component[0]) {
269 CRM.alert(ts('Invalid component ID (%1)', {
270 1: componentId
271 }));
272 return;
273 }
274 var options = {
275 autoOpen: false,
276 modal: true,
277 title: title // component[0].name
278 };
279 dialogService.open('previewComponentDialog', partialUrl('dialog/previewComponent.html'), component[0], options);
280 };
281 });
282
283 // Controller for the "Preview Mailing" dialog
284 // Note: Expects $scope.model to be an object with properties:
285 // - "name"
286 // - "subject"
287 // - "body_html"
288 // - "body_text"
289 angular.module('crmMailing').controller('PreviewComponentDialogCtrl', function PreviewMailingDialogCtrl($scope) {
290 $scope.ts = CRM.ts('CiviMail');
291 });
292
293 // Controller for the in-place msg-template management
294 angular.module('crmMailing').controller('MsgTemplateCtrl', function MsgTemplateCtrl($scope, crmMsgTemplates, dialogService, $parse) {
295 var ts = $scope.ts = CRM.ts('CiviMail');
296 $scope.crmMsgTemplates = crmMsgTemplates;
297
298 // @return Promise MessageTemplate (per APIv3)
299 $scope.saveTemplate = function saveTemplate(mailing) {
300 var model = {
301 selected_id: mailing.msg_template_id,
302 tpl: {
303 msg_title: '',
304 msg_subject: mailing.subject,
305 msg_text: mailing.body_text,
306 msg_html: mailing.body_html
307 }
308 };
309 var options = {
310 autoOpen: false,
311 modal: true,
312 title: ts('Save Template')
313 };
314 return dialogService.open('saveTemplateDialog', partialUrl('dialog/saveTemplate.html'), model, options)
315 .then(function (item) {
316 mailing.msg_template_id = item.id;
317 return item;
318 });
319 };
320
321 // @param int id
322 // @return Promise
323 $scope.loadTemplate = function loadTemplate(mailing, id) {
324 return crmMsgTemplates.get(id).then(function (tpl) {
325 mailing.subject = tpl.msg_subject;
326 mailing.body_text = tpl.msg_text;
327 mailing.body_html = tpl.msg_html;
328 });
329 };
330 });
331
332 // Controller for the "Save Message Template" dialog
333 // Scope members:
334 // - [input] "model": Object
335 // - "selected_id": int
336 // - "tpl": Object
337 // - "msg_subject": string
338 // - "msg_text": string
339 // - "msg_html": string
340 angular.module('crmMailing').controller('SaveMsgTemplateDialogCtrl', function SaveMsgTemplateDialogCtrl($scope, crmMsgTemplates, dialogService) {
341 var ts = $scope.ts = CRM.ts('CiviMail');
342 $scope.saveOpt = {mode: '', newTitle: ''};
343 $scope.selected = null;
344
345 $scope.save = function save() {
346 var tpl = _.extend({}, $scope.model.tpl);
347 switch ($scope.saveOpt.mode) {
348 case 'add':
349 tpl.msg_title = $scope.saveOpt.newTitle;
350 break;
351 case 'update':
352 tpl.id = $scope.selected.id;
353 tpl.msg_title = $scope.selected.msg_title;
354 break;
355 default:
356 throw 'SaveMsgTemplateDialogCtrl: Unrecognized mode: ' + $scope.saveOpt.mode;
357 }
358 return crmMsgTemplates.save(tpl)
359 .then(function (item) {
360 CRM.status(ts('Saved'));
361 return item;
362 });
363 };
364
365 function scopeApply(f) {
366 return function () {
367 var args = arguments;
368 $scope.$apply(function () {
369 f.apply(args);
370 });
371 };
372 }
373
374 function init() {
375 crmMsgTemplates.get($scope.model.selected_id).then(
376 function (tpl) {
377 $scope.saveOpt.mode = 'update';
378 $scope.selected = tpl;
379 },
380 function () {
381 $scope.saveOpt.mode = 'add';
382 $scope.selected = null;
383 }
384 );
385 // When using dialogService with a button bar, the major button actions
386 // need to be registered with the dialog widget (and not embedded in
387 // the body of the dialog).
388 var buttons = {};
389 buttons[ts('Save')] = function () {
390 $scope.save().then(function (item) {
391 dialogService.close('saveTemplateDialog', item);
392 });
393 };
394 buttons[ts('Cancel')] = function () {
395 dialogService.cancel('saveTemplateDialog');
396 };
397 dialogService.setButtons('saveTemplateDialog', buttons);
398 }
399
400 setTimeout(scopeApply(init), 0);
401 });
402
403 angular.module('crmMailing').controller('EmailAddrCtrl', function EmailAddrCtrl($scope, crmFromAddresses) {
404 $scope.crmFromAddresses = crmFromAddresses;
405 });
406 })(angular, CRM.$, CRM._);