Merge pull request #5059 from totten/master-validation
[civicrm-core.git] / js / angular-crmMailing.js
1 (function (angular, $, _) {
2
3 angular.module('crmMailing', [
4 'crmUtil', 'crmAttachment', 'crmAutosave', 'ngRoute', 'ui.utils', 'crmUi', 'dialogService'
5 ]); // TODO ngSanitize, unsavedChanges
6
7 // Time to wait before triggering AJAX update to recipients list
8 var RECIPIENTS_DEBOUNCE_MS = 100;
9 var RECIPIENTS_PREVIEW_LIMIT = 10000;
10
11 angular.module('crmMailing').config([
12 '$routeProvider',
13 function ($routeProvider) {
14 $routeProvider.when('/mailing', {
15 template: '<div></div>',
16 controller: 'ListMailingsCtrl'
17 });
18
19 var editorPaths = {
20 '': '~/crmMailing/edit.html',
21 '/unified': '~/crmMailing/edit-unified.html',
22 '/unified2': '~/crmMailing/edit-unified2.html',
23 '/wizard': '~/crmMailing/edit-wizard.html'
24 };
25 angular.forEach(editorPaths, function(editTemplate, pathSuffix) {
26 $routeProvider.when('/mailing/new' + pathSuffix, {
27 template: '<p>' + ts('Initializing...') + '</p>',
28 controller: 'CreateMailingCtrl',
29 resolve: {
30 selectedMail: function(crmMailingMgr) {
31 var m = crmMailingMgr.create();
32 return crmMailingMgr.save(m);
33 }
34 }
35 });
36 $routeProvider.when('/mailing/:id' + pathSuffix, {
37 templateUrl: editTemplate,
38 controller: 'EditMailingCtrl',
39 resolve: {
40 selectedMail: function($route, crmMailingMgr) {
41 return crmMailingMgr.get($route.current.params.id);
42 },
43 attachments: function($route, CrmAttachments) {
44 var attachments = new CrmAttachments(function () {
45 return {entity_table: 'civicrm_mailing', entity_id: $route.current.params.id};
46 });
47 return attachments.load();
48 }
49 }
50 });
51 });
52 }
53 ]);
54
55 angular.module('crmMailing').controller('ListMailingsCtrl', ['crmLegacy', 'crmNavigator', function ListMailingsCtrl(crmLegacy, crmNavigator) {
56 // We haven't implemented this in Angular, but some users may get clever
57 // about typing URLs, so we'll provide a redirect.
58 var new_url = crmLegacy.url('civicrm/mailing/browse/unscheduled', {reset: 1, scheduled: 'false'});
59 crmNavigator.redirect(new_url);
60 }]);
61
62 angular.module('crmMailing').controller('CreateMailingCtrl', function EditMailingCtrl($scope, selectedMail, $location) {
63 // Transition URL "/mailing/new/foo" => "/mailing/123/foo"
64 var parts = $location.path().split('/'); // e.g. "/mailing/new" or "/mailing/123/wizard"
65 parts[2] = selectedMail.id;
66 $location.path(parts.join('/'));
67 $location.replace();
68 });
69
70 angular.module('crmMailing').controller('EditMailingCtrl', function EditMailingCtrl($scope, selectedMail, $location, crmMailingMgr, crmStatus, attachments, crmMailingPreviewMgr, crmBlocker) {
71 $scope.mailing = selectedMail;
72 $scope.attachments = attachments;
73 $scope.crmMailingConst = CRM.crmMailing;
74
75 var ts = $scope.ts = CRM.ts(null);
76 var block = $scope.block = crmBlocker();
77
78 $scope.isSubmitted = function isSubmitted() {
79 return _.size($scope.mailing.jobs) > 0;
80 };
81
82 // @return Promise
83 $scope.previewMailing = function previewMailing(mailing, mode) {
84 return crmMailingPreviewMgr.preview(mailing, mode);
85 };
86
87 // @return Promise
88 $scope.sendTest = function sendTest(mailing, attachments, recipient) {
89 var savePromise = crmMailingMgr.save(mailing)
90 .then(function () {
91 return attachments.save();
92 });
93 return block(crmStatus({start: ts('Saving...'), success: ''}, savePromise)
94 .then(function () {
95 crmMailingPreviewMgr.sendTest(mailing, recipient);
96 }));
97 };
98
99 // @return Promise
100 $scope.submit = function submit() {
101 if (block.check() || $scope.crmMailing.$invalid) {
102 return;
103 }
104
105 var promise = crmMailingMgr.save($scope.mailing)
106 .then(function () {
107 // pre-condition: the mailing exists *before* saving attachments to it
108 return $scope.attachments.save();
109 })
110 .then(function () {
111 return crmMailingMgr.submit($scope.mailing);
112 })
113 .then(function () {
114 $scope.leave('scheduled');
115 })
116 ;
117 return block(crmStatus({start: ts('Submitting...'), success: ts('Submitted')}, promise));
118 };
119
120 // @return Promise
121 $scope.save = function save() {
122 return block(crmStatus(null,
123 crmMailingMgr
124 .save($scope.mailing)
125 .then(function () {
126 // pre-condition: the mailing exists *before* saving attachments to it
127 return $scope.attachments.save();
128 })
129 ));
130 };
131
132 // @return Promise
133 $scope.delete = function cancel() {
134 return block(crmStatus({start: ts('Deleting...'), success: ts('Deleted')},
135 crmMailingMgr.delete($scope.mailing)
136 .then(function () {
137 $scope.leave('unscheduled');
138 })
139 ));
140 };
141
142 // @param string listingScreen 'archive', 'scheduled', 'unscheduled'
143 $scope.leave = 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 /* falls through */
158 default:
159 window.location = CRM.url('civicrm/mailing/browse/unscheduled', {
160 reset: 1,
161 scheduled: 'false'
162 });
163 }
164 };
165 });
166
167 // Controller for the edit-recipients fields (
168 // WISHLIST: Move most of this to a (cache-enabled) service
169 // Scope members:
170 // - [input] mailing: object
171 // - [output] recipients: array of recipient records
172 angular.module('crmMailing').controller('EditRecipCtrl', function EditRecipCtrl($scope, dialogService, crmApi, crmMailingMgr) {
173 var ts = $scope.ts = CRM.ts(null);
174 $scope.recipients = null;
175 $scope.getRecipientsEstimate = function () {
176 var ts = $scope.ts;
177 if ($scope.recipients === null) {
178 return ts('(Estimating)');
179 }
180 if ($scope.recipients.length === 0) {
181 return ts('No recipients');
182 }
183 if ($scope.recipients.length === 1) {
184 return ts('~1 recipient');
185 }
186 if (RECIPIENTS_PREVIEW_LIMIT > 0 && $scope.recipients.length >= RECIPIENTS_PREVIEW_LIMIT) {
187 return ts('>%1 recipients', {1: RECIPIENTS_PREVIEW_LIMIT});
188 }
189 return ts('~%1 recipients', {1: $scope.recipients.length});
190 };
191 $scope.getIncludesAsString = function () {
192 var first = true;
193 var names = '';
194 _.each($scope.mailing.groups.include, function (id) {
195 if (!first) {
196 names = names + ', ';
197 }
198 var group = _.where(CRM.crmMailing.groupNames, {id: '' + id});
199 names = names + group[0].title;
200 first = false;
201 });
202 _.each($scope.mailing.mailings.include, function (id) {
203 if (!first) {
204 names = names + ', ';
205 }
206 var oldMailing = _.where(CRM.crmMailing.civiMails, {id: '' + id});
207 names = names + oldMailing[0].name;
208 first = false;
209 });
210 return names;
211 };
212 $scope.getExcludesAsString = function () {
213 var first = true;
214 var names = '';
215 _.each($scope.mailing.groups.exclude, function (id) {
216 if (!first) {
217 names = names + ', ';
218 }
219 var group = _.where(CRM.crmMailing.groupNames, {id: '' + id});
220 names = names + group[0].title;
221 first = false;
222 });
223 _.each($scope.mailing.mailings.exclude, function (id) {
224 if (!first) {
225 names = names + ', ';
226 }
227 var oldMailing = _.where(CRM.crmMailing.civiMails, {id: '' + id});
228 names = names + oldMailing[0].name;
229 first = false;
230 });
231 return names;
232 };
233
234 // We monitor four fields -- use debounce so that changes across the
235 // four fields can settle-down before AJAX.
236 var refreshRecipients = _.debounce(function () {
237 $scope.$apply(function () {
238 $scope.recipients = null;
239 crmMailingMgr.previewRecipients($scope.mailing, RECIPIENTS_PREVIEW_LIMIT).then(function (recipients) {
240 $scope.recipients = recipients;
241 });
242 });
243 }, RECIPIENTS_DEBOUNCE_MS);
244 $scope.$watchCollection("mailing.groups.include", refreshRecipients);
245 $scope.$watchCollection("mailing.groups.exclude", refreshRecipients);
246 $scope.$watchCollection("mailing.mailings.include", refreshRecipients);
247 $scope.$watchCollection("mailing.mailings.exclude", refreshRecipients);
248
249 $scope.previewRecipients = function previewRecipients() {
250 var model = {
251 recipients: $scope.recipients
252 };
253 var options = {
254 autoOpen: false,
255 modal: true,
256 title: ts('Preview (%1)', {
257 1: $scope.getRecipientsEstimate()
258 })
259 };
260 dialogService.open('recipDialog', '~/crmMailing/dialog/recipients.html', model, options);
261 };
262 });
263
264 // Controller for the "Preview Recipients" dialog
265 // Note: Expects $scope.model to be an object with properties:
266 // - recipients: array of contacts
267 angular.module('crmMailing').controller('PreviewRecipCtrl', function ($scope) {
268 $scope.ts = CRM.ts(null);
269 });
270
271 // Controller for the "Preview Mailing" dialog
272 // Note: Expects $scope.model to be an object with properties:
273 // - "subject"
274 // - "body_html"
275 // - "body_text"
276 angular.module('crmMailing').controller('PreviewMailingDialogCtrl', function PreviewMailingDialogCtrl($scope) {
277 $scope.ts = CRM.ts(null);
278 });
279
280 // Controller for the "Preview Mailing Component" segment
281 // which displays header/footer/auto-responder
282 angular.module('crmMailing').controller('PreviewComponentCtrl', function PreviewComponentCtrl($scope, dialogService) {
283 var ts = $scope.ts = CRM.ts(null);
284
285 $scope.previewComponent = function previewComponent(title, componentId) {
286 var component = _.where(CRM.crmMailing.headerfooterList, {id: "" + componentId});
287 if (!component || !component[0]) {
288 CRM.alert(ts('Invalid component ID (%1)', {
289 1: componentId
290 }));
291 return;
292 }
293 var options = {
294 autoOpen: false,
295 modal: true,
296 title: title // component[0].name
297 };
298 dialogService.open('previewComponentDialog', '~/crmMailing/dialog/previewComponent.html', component[0], options);
299 };
300 });
301
302 // Controller for the "Preview Mailing Component" dialog
303 // Note: Expects $scope.model to be an object with properties:
304 // - "name"
305 // - "subject"
306 // - "body_html"
307 // - "body_text"
308 angular.module('crmMailing').controller('PreviewComponentDialogCtrl', function PreviewComponentDialogCtrl($scope) {
309 $scope.ts = CRM.ts(null);
310 });
311
312 // Controller for the in-place msg-template management
313 angular.module('crmMailing').controller('MsgTemplateCtrl', function MsgTemplateCtrl($scope, crmMsgTemplates, dialogService) {
314 var ts = $scope.ts = CRM.ts(null);
315 $scope.crmMsgTemplates = crmMsgTemplates;
316
317 // @return Promise MessageTemplate (per APIv3)
318 $scope.saveTemplate = function saveTemplate(mailing) {
319 var model = {
320 selected_id: mailing.msg_template_id,
321 tpl: {
322 msg_title: '',
323 msg_subject: mailing.subject,
324 msg_text: mailing.body_text,
325 msg_html: mailing.body_html
326 }
327 };
328 var options = {
329 autoOpen: false,
330 modal: true,
331 title: ts('Save Template')
332 };
333 return dialogService.open('saveTemplateDialog', '~/crmMailing/dialog/saveTemplate.html', model, options)
334 .then(function (item) {
335 mailing.msg_template_id = item.id;
336 return item;
337 });
338 };
339
340 // @param int id
341 // @return Promise
342 $scope.loadTemplate = function loadTemplate(mailing, id) {
343 return crmMsgTemplates.get(id).then(function (tpl) {
344 mailing.subject = tpl.msg_subject;
345 mailing.body_text = tpl.msg_text;
346 mailing.body_html = tpl.msg_html;
347 });
348 };
349 });
350
351 // Controller for the "Save Message Template" dialog
352 // Scope members:
353 // - [input] "model": Object
354 // - "selected_id": int
355 // - "tpl": Object
356 // - "msg_subject": string
357 // - "msg_text": string
358 // - "msg_html": string
359 angular.module('crmMailing').controller('SaveMsgTemplateDialogCtrl', function SaveMsgTemplateDialogCtrl($scope, crmMsgTemplates, dialogService) {
360 var ts = $scope.ts = CRM.ts(null);
361 $scope.saveOpt = {mode: '', newTitle: ''};
362 $scope.selected = null;
363
364 $scope.save = function save() {
365 var tpl = _.extend({}, $scope.model.tpl);
366 switch ($scope.saveOpt.mode) {
367 case 'add':
368 tpl.msg_title = $scope.saveOpt.newTitle;
369 break;
370 case 'update':
371 tpl.id = $scope.selected.id;
372 tpl.msg_title = $scope.selected.msg_title;
373 break;
374 default:
375 throw 'SaveMsgTemplateDialogCtrl: Unrecognized mode: ' + $scope.saveOpt.mode;
376 }
377 return crmMsgTemplates.save(tpl)
378 .then(function (item) {
379 CRM.status(ts('Saved'));
380 return item;
381 });
382 };
383
384 function scopeApply(f) {
385 return function () {
386 var args = arguments;
387 $scope.$apply(function () {
388 f.apply(args);
389 });
390 };
391 }
392
393 function init() {
394 crmMsgTemplates.get($scope.model.selected_id).then(
395 function (tpl) {
396 $scope.saveOpt.mode = 'update';
397 $scope.selected = tpl;
398 },
399 function () {
400 $scope.saveOpt.mode = 'add';
401 $scope.selected = null;
402 }
403 );
404 // When using dialogService with a button bar, the major button actions
405 // need to be registered with the dialog widget (and not embedded in
406 // the body of the dialog).
407 var buttons = [
408 {
409 text: ts('Save'),
410 icons: {primary: 'ui-icon-check'},
411 click: function () {
412 $scope.save().then(function (item) {
413 dialogService.close('saveTemplateDialog', item);
414 });
415 }
416 },
417 {
418 text: ts('Cancel'),
419 icons: {primary: 'ui-icon-close'},
420 click: function () {
421 dialogService.cancel('saveTemplateDialog');
422 }
423 }
424 ];
425 dialogService.setButtons('saveTemplateDialog', buttons);
426 }
427
428 setTimeout(scopeApply(init), 0);
429 });
430
431 angular.module('crmMailing').controller('EmailAddrCtrl', function EmailAddrCtrl($scope, crmFromAddresses) {
432 $scope.crmFromAddresses = crmFromAddresses;
433 });
434 })(angular, CRM.$, CRM._);