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