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