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