Commit | Line | Data |
---|---|---|
030dce01 | 1 | (function (angular, $, _) { |
4dd19229 | 2 | var partialUrl = function partialUrl(relPath) { |
dcc7d5c9 | 3 | return CRM.resourceUrls['civicrm'] + '/partials/crmMailing/' + relPath; |
030dce01 TO |
4 | }; |
5 | ||
88e9e883 | 6 | angular.module('crmMailing', [ |
f4f103fa TO |
7 | 'crmUtil', 'crmAttachment', 'ngRoute', 'ui.utils', 'crmUi', 'dialogService' |
8 | ]); // TODO ngSanitize, unsavedChanges | |
030dce01 | 9 | |
7801d9b5 TO |
10 | // Time to wait before triggering AJAX update to recipients list |
11 | var RECIPIENTS_DEBOUNCE_MS = 100; | |
b73e0c53 | 12 | var RECIPIENTS_PREVIEW_LIMIT = 10000; |
7801d9b5 | 13 | |
88e9e883 | 14 | angular.module('crmMailing').config([ |
f4f103fa | 15 | '$routeProvider', |
030dce01 | 16 | function ($routeProvider) { |
96ac27bd | 17 | $routeProvider.when('/mailing', { |
030dce01 TO |
18 | template: '<div></div>', |
19 | controller: 'ListMailingsCtrl' | |
20 | }); | |
96ac27bd | 21 | $routeProvider.when('/mailing/:id', { |
030dce01 TO |
22 | templateUrl: partialUrl('edit.html'), |
23 | controller: 'EditMailingCtrl', | |
24 | resolve: { | |
f4f103fa TO |
25 | selectedMail: function selectedMail($route, crmMailingMgr) { |
26 | return crmMailingMgr.getOrCreate($route.current.params.id); | |
27 | } | |
d4182dda TO |
28 | } |
29 | }); | |
96ac27bd | 30 | $routeProvider.when('/mailing/:id/unified', { |
d4182dda TO |
31 | templateUrl: partialUrl('edit-unified.html'), |
32 | controller: 'EditMailingCtrl', | |
33 | resolve: { | |
f4f103fa TO |
34 | selectedMail: function selectedMail($route, crmMailingMgr) { |
35 | return crmMailingMgr.getOrCreate($route.current.params.id); | |
36 | } | |
d4182dda TO |
37 | } |
38 | }); | |
96ac27bd | 39 | $routeProvider.when('/mailing/:id/unified2', { |
d4182dda TO |
40 | templateUrl: partialUrl('edit-unified2.html'), |
41 | controller: 'EditMailingCtrl', | |
42 | resolve: { | |
f4f103fa TO |
43 | selectedMail: function selectedMail($route, crmMailingMgr) { |
44 | return crmMailingMgr.getOrCreate($route.current.params.id); | |
45 | } | |
d4182dda TO |
46 | } |
47 | }); | |
96ac27bd | 48 | $routeProvider.when('/mailing/:id/wizard', { |
d4182dda TO |
49 | templateUrl: partialUrl('edit-wizard.html'), |
50 | controller: 'EditMailingCtrl', | |
51 | resolve: { | |
f4f103fa TO |
52 | selectedMail: function selectedMail($route, crmMailingMgr) { |
53 | return crmMailingMgr.getOrCreate($route.current.params.id); | |
54 | } | |
030dce01 TO |
55 | } |
56 | }); | |
57 | } | |
58 | ]); | |
59 | ||
88e9e883 | 60 | angular.module('crmMailing').controller('ListMailingsCtrl', function ListMailingsCtrl() { |
030dce01 TO |
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 | ||
58dfba8d | 69 | angular.module('crmMailing').controller('EditMailingCtrl', function EditMailingCtrl($scope, selectedMail, $location, crmMailingMgr, crmStatus, CrmAttachments, crmMailingPreviewMgr) { |
27e690c2 | 70 | $scope.mailing = selectedMail; |
f4f103fa | 71 | $scope.attachments = new CrmAttachments(function () { |
db083bf0 TO |
72 | return {entity_table: 'civicrm_mailing', entity_id: $scope.mailing.id}; |
73 | }); | |
ab0a4aec | 74 | $scope.attachments.load(); |
27e690c2 TO |
75 | $scope.crmMailingConst = CRM.crmMailing; |
76 | ||
030dce01 | 77 | $scope.partialUrl = partialUrl; |
52f515c6 | 78 | var ts = $scope.ts = CRM.ts('CiviMail'); |
27e690c2 | 79 | |
86c3a327 TO |
80 | $scope.isSubmitted = function isSubmitted() { |
81 | return _.size($scope.mailing.jobs) > 0; | |
82 | }; | |
83 | ||
58dfba8d TO |
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(); | |
86c3a327 TO |
94 | }) |
95 | .then(updateUrl); | |
58dfba8d TO |
96 | return crmStatus({start: ts('Saving...'), success: ''}, savePromise) |
97 | .then(function () { | |
98 | crmMailingPreviewMgr.sendTest(mailing, recipient); | |
99 | }); | |
100 | }; | |
101 | ||
705c61e9 TO |
102 | // @return Promise |
103 | $scope.submit = function submit() { | |
db083bf0 | 104 | var promise = crmMailingMgr.save($scope.mailing) |
86c3a327 TO |
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 | updateUrl(); | |
114 | return $scope.mailing; | |
115 | }) | |
116 | ; | |
db083bf0 | 117 | return crmStatus({start: ts('Submitting...'), success: ts('Submitted')}, promise); |
2d36e6bc | 118 | }; |
58dfba8d | 119 | |
705c61e9 TO |
120 | // @return Promise |
121 | $scope.save = function save() { | |
f286acec | 122 | return crmStatus(null, |
db083bf0 TO |
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 | }) | |
86c3a327 TO |
129 | .then(function () { |
130 | updateUrl(); | |
131 | return $scope.mailing; | |
132 | }) | |
f286acec | 133 | ); |
2d36e6bc | 134 | }; |
58dfba8d | 135 | |
705c61e9 TO |
136 | // @return Promise |
137 | $scope.delete = function cancel() { | |
f286acec TO |
138 | return crmStatus({start: ts('Deleting...'), success: ts('Deleted')}, |
139 | crmMailingMgr.delete($scope.mailing) | |
140 | ); | |
2d36e6bc | 141 | }; |
58dfba8d | 142 | |
705c61e9 | 143 | $scope.leave = function leave() { |
2d36e6bc TO |
144 | window.location = CRM.url('civicrm/mailing/browse/unscheduled', { |
145 | reset: 1, | |
146 | scheduled: 'false' | |
147 | }); | |
148 | }; | |
c54afefa | 149 | |
86c3a327 TO |
150 | // Transition URL "/mailing/new" => "/mailing/123" |
151 | function updateUrl() { | |
152 | var parts = $location.path().split('/'); // e.g. "/mailing/new" or "/mailing/123/wizard" | |
153 | if (parts[2] != $scope.mailing.id) { | |
154 | parts[2] = $scope.mailing.id; | |
c54afefa TO |
155 | $location.path(parts.join('/')); |
156 | $location.replace(); | |
157 | // FIXME: Angular unnecessarily refreshes UI | |
86c3a327 TO |
158 | // WARNING: Changing the URL triggers a full reload. Any pending AJAX operations |
159 | // could be inconsistently applied. Run updateUrl() after other changes complete. | |
c54afefa | 160 | } |
86c3a327 | 161 | } |
030dce01 TO |
162 | }); |
163 | ||
7801d9b5 TO |
164 | // Controller for the edit-recipients fields ( |
165 | // WISHLIST: Move most of this to a (cache-enabled) service | |
166 | // Scope members: | |
167 | // - [input] mailing: object | |
168 | // - [output] recipients: array of recipient records | |
88e9e883 | 169 | angular.module('crmMailing').controller('EditRecipCtrl', function EditRecipCtrl($scope, dialogService, crmApi, crmMailingMgr) { |
52f515c6 | 170 | var ts = $scope.ts = CRM.ts('CiviMail'); |
7801d9b5 TO |
171 | $scope.recipients = null; |
172 | $scope.getRecipientsEstimate = function () { | |
173 | var ts = $scope.ts; | |
f4f103fa | 174 | if ($scope.recipients == null) { |
7801d9b5 | 175 | return ts('(Estimating)'); |
f4f103fa TO |
176 | } |
177 | if ($scope.recipients.length == 0) { | |
7801d9b5 | 178 | return ts('No recipients'); |
f4f103fa TO |
179 | } |
180 | if ($scope.recipients.length == 1) { | |
7801d9b5 | 181 | return ts('~1 recipient'); |
f4f103fa TO |
182 | } |
183 | if (RECIPIENTS_PREVIEW_LIMIT > 0 && $scope.recipients.length >= RECIPIENTS_PREVIEW_LIMIT) { | |
b73e0c53 | 184 | return ts('>%1 recipients', {1: RECIPIENTS_PREVIEW_LIMIT}); |
f4f103fa | 185 | } |
7801d9b5 TO |
186 | return ts('~%1 recipients', {1: $scope.recipients.length}); |
187 | }; | |
f4f103fa | 188 | $scope.getIncludesAsString = function () { |
47bacc20 TO |
189 | var first = true; |
190 | var names = ''; | |
f4f103fa TO |
191 | _.each($scope.mailing.groups.include, function (id) { |
192 | if (!first) { | |
193 | names = names + ', '; | |
194 | } | |
195 | var group = _.where(CRM.crmMailing.groupNames, {id: '' + id}); | |
47bacc20 TO |
196 | names = names + group[0].title; |
197 | first = false; | |
198 | }); | |
f4f103fa TO |
199 | _.each($scope.mailing.mailings.include, function (id) { |
200 | if (!first) { | |
201 | names = names + ', '; | |
202 | } | |
203 | var oldMailing = _.where(CRM.crmMailing.civiMails, {id: '' + id}); | |
47bacc20 TO |
204 | names = names + oldMailing[0].name; |
205 | first = false; | |
206 | }); | |
207 | return names; | |
208 | }; | |
f4f103fa | 209 | $scope.getExcludesAsString = function () { |
47bacc20 TO |
210 | var first = true; |
211 | var names = ''; | |
f4f103fa TO |
212 | _.each($scope.mailing.groups.exclude, function (id) { |
213 | if (!first) { | |
214 | names = names + ', '; | |
215 | } | |
216 | var group = _.where(CRM.crmMailing.groupNames, {id: '' + id}); | |
47bacc20 TO |
217 | names = names + group[0].title; |
218 | first = false; | |
219 | }); | |
f4f103fa TO |
220 | _.each($scope.mailing.mailings.exclude, function (id) { |
221 | if (!first) { | |
222 | names = names + ', '; | |
223 | } | |
224 | var oldMailing = _.where(CRM.crmMailing.civiMails, {id: '' + id}); | |
47bacc20 TO |
225 | names = names + oldMailing[0].name; |
226 | first = false; | |
227 | }); | |
228 | return names; | |
229 | }; | |
230 | ||
7801d9b5 TO |
231 | // We monitor four fields -- use debounce so that changes across the |
232 | // four fields can settle-down before AJAX. | |
233 | var refreshRecipients = _.debounce(function () { | |
234 | $scope.$apply(function () { | |
235 | $scope.recipients = null; | |
8dfd5110 TO |
236 | crmMailingMgr.previewRecipients($scope.mailing, RECIPIENTS_PREVIEW_LIMIT).then(function (recipients) { |
237 | $scope.recipients = recipients; | |
b73e0c53 | 238 | }); |
7801d9b5 TO |
239 | }); |
240 | }, RECIPIENTS_DEBOUNCE_MS); | |
241 | $scope.$watchCollection("mailing.groups.include", refreshRecipients); | |
242 | $scope.$watchCollection("mailing.groups.exclude", refreshRecipients); | |
243 | $scope.$watchCollection("mailing.mailings.include", refreshRecipients); | |
244 | $scope.$watchCollection("mailing.mailings.exclude", refreshRecipients); | |
245 | ||
4dd19229 | 246 | $scope.previewRecipients = function previewRecipients() { |
7801d9b5 TO |
247 | var model = { |
248 | recipients: $scope.recipients | |
249 | }; | |
250 | var options = { | |
251 | autoOpen: false, | |
252 | modal: true, | |
253 | title: ts('Preview (%1)', { | |
254 | 1: $scope.getRecipientsEstimate() | |
52f515c6 | 255 | }) |
7801d9b5 | 256 | }; |
4dd19229 | 257 | dialogService.open('recipDialog', partialUrl('dialog/recipients.html'), model, options); |
7801d9b5 TO |
258 | }; |
259 | }); | |
260 | ||
261 | // Controller for the "Preview Recipients" dialog | |
262 | // Note: Expects $scope.model to be an object with properties: | |
263 | // - recipients: array of contacts | |
88e9e883 | 264 | angular.module('crmMailing').controller('PreviewRecipCtrl', function ($scope) { |
7801d9b5 TO |
265 | $scope.ts = CRM.ts('CiviMail'); |
266 | }); | |
493eb47a | 267 | |
493eb47a TO |
268 | // Controller for the "Preview Mailing" dialog |
269 | // Note: Expects $scope.model to be an object with properties: | |
270 | // - "subject" | |
271 | // - "body_html" | |
272 | // - "body_text" | |
870cbdbb | 273 | angular.module('crmMailing').controller('PreviewMailingDialogCtrl', function PreviewMailingDialogCtrl($scope) { |
493eb47a TO |
274 | $scope.ts = CRM.ts('CiviMail'); |
275 | }); | |
276 | ||
47bacc20 TO |
277 | // Controller for the "Preview Mailing Component" segment |
278 | // which displays header/footer/auto-responder | |
88e9e883 | 279 | angular.module('crmMailing').controller('PreviewComponentCtrl', function PreviewMailingDialogCtrl($scope, dialogService) { |
52f515c6 TO |
280 | var ts = $scope.ts = CRM.ts('CiviMail'); |
281 | ||
47bacc20 | 282 | $scope.previewComponent = function previewComponent(title, componentId) { |
f4f103fa | 283 | var component = _.where(CRM.crmMailing.headerfooterList, {id: "" + componentId}); |
47bacc20 TO |
284 | if (!component || !component[0]) { |
285 | CRM.alert(ts('Invalid component ID (%1)', { | |
286 | 1: componentId | |
287 | })); | |
288 | return; | |
289 | } | |
290 | var options = { | |
291 | autoOpen: false, | |
292 | modal: true, | |
293 | title: title // component[0].name | |
294 | }; | |
295 | dialogService.open('previewComponentDialog', partialUrl('dialog/previewComponent.html'), component[0], options); | |
296 | }; | |
297 | }); | |
298 | ||
299 | // Controller for the "Preview Mailing" dialog | |
300 | // Note: Expects $scope.model to be an object with properties: | |
301 | // - "name" | |
302 | // - "subject" | |
303 | // - "body_html" | |
304 | // - "body_text" | |
88e9e883 | 305 | angular.module('crmMailing').controller('PreviewComponentDialogCtrl', function PreviewMailingDialogCtrl($scope) { |
47bacc20 TO |
306 | $scope.ts = CRM.ts('CiviMail'); |
307 | }); | |
308 | ||
744bebee | 309 | // Controller for the in-place msg-template management |
870cbdbb | 310 | angular.module('crmMailing').controller('MsgTemplateCtrl', function MsgTemplateCtrl($scope, crmMsgTemplates, dialogService) { |
744bebee TO |
311 | var ts = $scope.ts = CRM.ts('CiviMail'); |
312 | $scope.crmMsgTemplates = crmMsgTemplates; | |
313 | ||
314 | // @return Promise MessageTemplate (per APIv3) | |
74263d6b | 315 | $scope.saveTemplate = function saveTemplate(mailing) { |
744bebee | 316 | var model = { |
74263d6b | 317 | selected_id: mailing.msg_template_id, |
744bebee TO |
318 | tpl: { |
319 | msg_title: '', | |
74263d6b TO |
320 | msg_subject: mailing.subject, |
321 | msg_text: mailing.body_text, | |
322 | msg_html: mailing.body_html | |
744bebee TO |
323 | } |
324 | }; | |
325 | var options = { | |
326 | autoOpen: false, | |
327 | modal: true, | |
328 | title: ts('Save Template') | |
329 | }; | |
330 | return dialogService.open('saveTemplateDialog', partialUrl('dialog/saveTemplate.html'), model, options) | |
f4f103fa | 331 | .then(function (item) { |
74263d6b | 332 | mailing.msg_template_id = item.id; |
744bebee TO |
333 | return item; |
334 | }); | |
335 | }; | |
336 | ||
337 | // @param int id | |
338 | // @return Promise | |
74263d6b | 339 | $scope.loadTemplate = function loadTemplate(mailing, id) { |
744bebee | 340 | return crmMsgTemplates.get(id).then(function (tpl) { |
74263d6b TO |
341 | mailing.subject = tpl.msg_subject; |
342 | mailing.body_text = tpl.msg_text; | |
343 | mailing.body_html = tpl.msg_html; | |
744bebee TO |
344 | }); |
345 | }; | |
346 | }); | |
347 | ||
348 | // Controller for the "Save Message Template" dialog | |
349 | // Scope members: | |
350 | // - [input] "model": Object | |
351 | // - "selected_id": int | |
352 | // - "tpl": Object | |
353 | // - "msg_subject": string | |
354 | // - "msg_text": string | |
355 | // - "msg_html": string | |
88e9e883 | 356 | angular.module('crmMailing').controller('SaveMsgTemplateDialogCtrl', function SaveMsgTemplateDialogCtrl($scope, crmMsgTemplates, dialogService) { |
744bebee TO |
357 | var ts = $scope.ts = CRM.ts('CiviMail'); |
358 | $scope.saveOpt = {mode: '', newTitle: ''}; | |
359 | $scope.selected = null; | |
360 | ||
361 | $scope.save = function save() { | |
362 | var tpl = _.extend({}, $scope.model.tpl); | |
363 | switch ($scope.saveOpt.mode) { | |
364 | case 'add': | |
365 | tpl.msg_title = $scope.saveOpt.newTitle; | |
366 | break; | |
367 | case 'update': | |
368 | tpl.id = $scope.selected.id; | |
369 | tpl.msg_title = $scope.selected.msg_title; | |
370 | break; | |
371 | default: | |
372 | throw 'SaveMsgTemplateDialogCtrl: Unrecognized mode: ' + $scope.saveOpt.mode; | |
373 | } | |
374 | return crmMsgTemplates.save(tpl) | |
375 | .then(function (item) { | |
376 | CRM.status(ts('Saved')); | |
377 | return item; | |
378 | }); | |
379 | }; | |
380 | ||
381 | function scopeApply(f) { | |
382 | return function () { | |
383 | var args = arguments; | |
384 | $scope.$apply(function () { | |
385 | f.apply(args); | |
386 | }); | |
387 | }; | |
388 | } | |
389 | ||
390 | function init() { | |
391 | crmMsgTemplates.get($scope.model.selected_id).then( | |
392 | function (tpl) { | |
393 | $scope.saveOpt.mode = 'update'; | |
394 | $scope.selected = tpl; | |
395 | }, | |
396 | function () { | |
397 | $scope.saveOpt.mode = 'add'; | |
398 | $scope.selected = null; | |
399 | } | |
400 | ); | |
401 | // When using dialogService with a button bar, the major button actions | |
402 | // need to be registered with the dialog widget (and not embedded in | |
403 | // the body of the dialog). | |
404 | var buttons = {}; | |
405 | buttons[ts('Save')] = function () { | |
406 | $scope.save().then(function (item) { | |
407 | dialogService.close('saveTemplateDialog', item); | |
408 | }); | |
409 | }; | |
410 | buttons[ts('Cancel')] = function () { | |
411 | dialogService.cancel('saveTemplateDialog'); | |
412 | }; | |
413 | dialogService.setButtons('saveTemplateDialog', buttons); | |
414 | } | |
f4f103fa | 415 | |
744bebee TO |
416 | setTimeout(scopeApply(init), 0); |
417 | }); | |
2d06b3b6 | 418 | |
58dfba8d | 419 | angular.module('crmMailing').controller('EmailAddrCtrl', function EmailAddrCtrl($scope, crmFromAddresses) { |
0a993c89 TO |
420 | $scope.crmFromAddresses = crmFromAddresses; |
421 | }); | |
030dce01 | 422 | })(angular, CRM.$, CRM._); |