CRM-15832 - crmResource - Fix handling of domains
[civicrm-core.git] / js / angular-crmMailing / directives.js
CommitLineData
5d72b4e2
TO
1(function (angular, $, _) {
2 var partialUrl = function (relPath) {
ad7abea2 3 return '~/crmMailing/' + relPath;
5d72b4e2
TO
4 };
5
18da0e87
TO
6 // The following directives have the same simple implementation -- load
7 // a template and export a "mailing" object into scope.
8 var simpleBlocks = {
9 crmMailingBlockHeaderFooter: partialUrl('headerFooter.html'),
10 crmMailingBlockMailing: partialUrl('mailing.html'),
18da0e87
TO
11 crmMailingBlockPublication: partialUrl('publication.html'),
12 crmMailingBlockResponses: partialUrl('responses.html'),
62d2e387 13 crmMailingBlockRecipients: partialUrl('recipients.html'),
18da0e87
TO
14 crmMailingBlockSchedule: partialUrl('schedule.html'),
15 crmMailingBlockSummary: partialUrl('summary.html'),
16 crmMailingBlockTracking: partialUrl('tracking.html'),
17 crmMailingBodyHtml: partialUrl('body_html.html'),
18 crmMailingBodyText: partialUrl('body_text.html')
19 };
20 _.each(simpleBlocks, function(templateUrl, directiveName){
88e9e883 21 angular.module('crmMailing').directive(directiveName, function ($parse) {
18da0e87
TO
22 return {
23 scope: {
24 crmMailing: '@'
25 },
26 templateUrl: templateUrl,
27 link: function (scope, elm, attr) {
28 var model = $parse(attr.crmMailing);
29 scope.mailing = model(scope.$parent);
30 scope.crmMailingConst = CRM.crmMailing;
31 scope.ts = CRM.ts('CiviMail');
62d2e387 32 scope[directiveName] = attr[directiveName] ? scope.$parent.$eval(attr[directiveName]) : {};
18da0e87
TO
33 }
34 };
35 });
f8601d61
TO
36 });
37
58dfba8d
TO
38 // example: <div crm-mailing-block-preview crm-mailing="myMailing" on-preview="openPreview(myMailing, preview.mode)" on-send="sendEmail(myMailing,preview.recipient)">
39 // note: the directive defines a variable called "preview" with any inputs supplied by the user (e.g. the target recipient for an example mailing)
40 angular.module('crmMailing').directive('crmMailingBlockPreview', function ($parse) {
41 return {
42 templateUrl: partialUrl('preview.html'),
43 link: function (scope, elm, attr) {
44 var mailingModel = $parse(attr.crmMailing);
45 scope.mailing = mailingModel(scope);
46 scope.crmMailingConst = CRM.crmMailing;
47 scope.ts = CRM.ts('CiviMail');
48 scope.testContact = {email: CRM.crmMailing.defaultTestEmail};
49 scope.testGroup = {gid: null};
50
51 scope.doPreview = function(mode) {
52 scope.$eval(attr.onPreview, {
53 preview: {mode: mode}
54 });
55 };
56 scope.doSend = function doSend(recipient) {
57 scope.$eval(attr.onSend, {
58 preview: {recipient: recipient}
59 });
60 };
61 }
62 };
63 });
64
65 angular.module('crmMailing').directive('crmMailingBlockReview', function ($parse, crmMailingPreviewMgr) {
66 return {
67 scope: {
68 crmMailing: '@'
69 },
70 templateUrl: partialUrl('review.html'),
71 link: function (scope, elm, attr) {
72 var mailingModel = $parse(attr.crmMailing);
73 scope.mailing = mailingModel(scope.$parent);
74 scope.crmMailingConst = CRM.crmMailing;
75 scope.ts = CRM.ts('CiviMail');
76 scope.previewMailing = function previewMailing(mailing, mode) {
77 return crmMailingPreviewMgr.preview(mailing, mode);
78 };
79 }
80 };
81 });
82
0a993c89
TO
83 // Convert between a mailing "From Address" (mailing.from_name,mailing.from_email) and a unified label ("Name" <e@ma.il>)
84 // example: <span crm-mailing-from-address="myPlaceholder" crm-mailing="myMailing"><select ng-model="myPlaceholder.label"></select></span>
85 // NOTE: This really doesn't belong in a directive. I've tried (and failed) to make this work with a getterSetter binding, eg
86 // <select ng-model="mailing.convertFromAddress" ng-model-options="{getterSetter: true}">
88e9e883 87 angular.module('crmMailing').directive('crmMailingFromAddress', function ($parse, crmFromAddresses) {
0a993c89
TO
88 return {
89 link: function (scope, element, attrs) {
90 var placeholder = attrs.crmMailingFromAddress;
91 var model = $parse(attrs.crmMailing);
92 var mailing = model(scope.$parent);
93 scope[placeholder] = {
94 label: crmFromAddresses.getByAuthorEmail(mailing.from_name, mailing.from_email, true).label
95 };
96 scope.$watch(placeholder + '.label', function (newValue) {
97 var addr = crmFromAddresses.getByLabel(newValue);
98 mailing.from_name = addr.author;
99 mailing.from_email = addr.email;
100 });
101 // FIXME: Shouldn't we also be watching mailing.from_name and mailing.from_email?
102 }
103 };
104 });
105
e47964f0
TO
106 // Represent a datetime field as if it were a radio ('schedule.mode') and a datetime ('schedule.datetime').
107 // example: <div crm-mailing-radio-date="mySchedule" crm-model="mailing.scheduled_date">...</div>
108 // FIXME: use ngModel instead of adhoc crmModel
88e9e883 109 angular.module('crmMailing').directive('crmMailingRadioDate', function ($parse) {
e47964f0
TO
110 return {
111 link: function ($scope, element, attrs) {
112 var schedModel = $parse(attrs.crmModel);
113
114 var schedule = $scope[attrs.crmMailingRadioDate] = {
115 mode: 'now',
116 datetime: ''
117 };
118 var updateChildren = (function () {
119 var sched = schedModel($scope);
120 if (sched) {
121 schedule.mode = 'at';
122 schedule.datetime = sched;
123 }
124 else {
125 schedule.mode = 'now';
126 }
127 });
128 var updateParent = (function () {
129 switch (schedule.mode) {
130 case 'now':
131 schedModel.assign($scope, null);
132 break;
133 case 'at':
134 schedModel.assign($scope, schedule.datetime);
135 break;
136 default:
137 throw 'Unrecognized schedule mode: ' + schedule.mode;
138 }
139 });
140
141 $scope.$watch(attrs.crmModel, updateChildren);
142 $scope.$watch(attrs.crmMailingRadioDate + '.mode', updateParent);
143 $scope.$watch(attrs.crmMailingRadioDate + '.datetime', function (newValue, oldValue) {
144 // automatically switch mode based on datetime entry
145 if (oldValue != newValue) {
146 if (!newValue || newValue == " ") {
147 schedule.mode = 'now';
148 }
149 else {
150 schedule.mode = 'at';
151 }
152 }
153 updateParent();
154 });
155 }
156 };
157 });
158
88e9e883 159 angular.module('crmMailing').directive('crmMailingReviewBool', function () {
47bacc20
TO
160 return {
161 scope: {
162 crmOn: '@',
163 crmTitle: '@'
164 },
165 template: '<span ng-class="spanClasses"><span class="icon" ng-class="iconClasses"></span>{{crmTitle}} </span>',
f4f103fa 166 link: function (scope, element, attrs) {
47bacc20
TO
167 function refresh() {
168 if (scope.$parent.$eval(attrs.crmOn)) {
88e9e883 169 scope.spanClasses = {'crmMailing-active': true};
47bacc20 170 scope.iconClasses = {'ui-icon-check': true};
f4f103fa
TO
171 }
172 else {
88e9e883 173 scope.spanClasses = {'crmMailing-inactive': true};
47bacc20
TO
174 scope.iconClasses = {'ui-icon-close': true};
175 }
176 scope.crmTitle = scope.$parent.$eval(attrs.crmTitle);
177 }
f4f103fa 178
47bacc20
TO
179 refresh();
180 scope.$parent.$watch(attrs.crmOn, refresh);
181 scope.$parent.$watch(attrs.crmTitle, refresh);
182 }
183 };
184 });
185
5d72b4e2
TO
186 // example: <input name="subject" /> <input crm-mailing-token crm-for="subject"/>
187 // WISHLIST: Instead of global CRM.crmMailing.mailTokens, accept token list as an input
88e9e883 188 angular.module('crmMailing').directive('crmMailingToken', function () {
5d72b4e2 189 return {
3cc9c048 190 require: '^crmUiIdScope',
5d72b4e2
TO
191 scope: {
192 crmFor: '@'
193 },
194 template: '<input type="text" class="crmMailingToken" />',
3cc9c048 195 link: function (scope, element, attrs, crmUiIdCtrl) {
38737af8
TO
196 $(element).select2({
197 width: "10em",
5d72b4e2
TO
198 dropdownAutoWidth: true,
199 data: CRM.crmMailing.mailTokens,
200 placeholder: ts('Insert')
201 });
202 $(element).on('select2-selecting', function (e) {
3cc9c048 203 var id = crmUiIdCtrl.get(attrs.crmFor);
38737af8
TO
204 if (CKEDITOR.instances[id]) {
205 CKEDITOR.instances[id].insertText(e.val);
206 $(element).select2('close').select2('val', '');
207 CKEDITOR.instances[id].focus();
208 }
209 else {
3cc9c048 210 var crmForEl = $('#' + id);
38737af8
TO
211 var origVal = crmForEl.val();
212 var origPos = crmForEl[0].selectionStart;
213 var newVal = origVal.substring(0, origPos) + e.val + origVal.substring(origPos, origVal.length);
214 crmForEl.val(newVal);
215 var newPos = (origPos + e.val.length);
216 crmForEl[0].selectionStart = newPos;
217 crmForEl[0].selectionEnd = newPos;
5d72b4e2 218
38737af8
TO
219 $(element).select2('close').select2('val', '');
220 crmForEl.triggerHandler('change');
221 crmForEl.focus();
222 }
5d72b4e2
TO
223
224 e.preventDefault();
225 });
226 }
227 };
228 });
229
b0461279 230 // example: <select multiple crm-mailing-recipients crm-mailing="mymailing" crm-avail-groups="myGroups" crm-avail-mailings="myMailings"></select>
f8601d61 231 // FIXME: participate in ngModel's validation cycle
88e9e883 232 angular.module('crmMailing').directive('crmMailingRecipients', function () {
b0461279
TO
233 return {
234 restrict: 'AE',
235 scope: {
236 crmAvailGroups: '@', // available groups
237 crmAvailMailings: '@', // available mailings
238 crmMailing: '@' // the mailing for which we are choosing recipients
239 },
240 templateUrl: partialUrl('directive/recipients.html'),
241 link: function (scope, element, attrs) {
242 scope.mailing = scope.$parent.$eval(attrs.crmMailing);
243 scope.groups = scope.$parent.$eval(attrs.crmAvailGroups);
244 scope.mailings = scope.$parent.$eval(attrs.crmAvailMailings);
245
246 scope.ts = CRM.ts('CiviMail');
247
248 /// Convert MySQL date ("yyyy-mm-dd hh:mm:ss") to JS date object
249 scope.parseDate = function (date) {
f4f103fa 250 if (!angular.isString(date)) {
b0461279 251 return date;
f4f103fa 252 }
b0461279 253 var p = date.split(/[\- :]/);
b3cb1cce 254 return new Date(parseInt(p[0]), parseInt(p[1])-1, parseInt(p[2]), parseInt(p[3]), parseInt(p[4]), parseInt(p[5]));
b0461279
TO
255 };
256
257 /// Remove {value} from {array}
258 function arrayRemove(array, value) {
259 var idx = array.indexOf(value);
260 if (idx >= 0) {
261 array.splice(idx, 1);
262 }
263 }
264
265 // @param string id an encoded string like "4 civicrm_mailing include"
266 // @return Object keys: entity_id, entity_type, mode
267 function convertValueToObj(id) {
268 var a = id.split(" ");
269 return {entity_id: parseInt(a[0]), entity_type: a[1], mode: a[2]};
270 }
271
272 // @param Object mailing
273 // @return array list of values like "4 civicrm_mailing include"
274 function convertMailingToValues(mailing) {
275 var r = [];
276 angular.forEach(mailing.groups.include, function (v) {
277 r.push(v + " civicrm_group include");
278 });
279 angular.forEach(mailing.groups.exclude, function (v) {
280 r.push(v + " civicrm_group exclude");
281 });
282 angular.forEach(mailing.mailings.include, function (v) {
283 r.push(v + " civicrm_mailing include");
284 });
285 angular.forEach(mailing.mailings.exclude, function (v) {
286 r.push(v + " civicrm_mailing exclude");
287 });
288 return r;
289 }
290
291 // Update $(element) view based on latest data
292 function refreshUI() {
58dfba8d
TO
293 if (scope.mailing) {
294 $(element).select2('val', convertMailingToValues(scope.mailing));
295 }
b0461279
TO
296 }
297
298 /// @return string HTML representingn an option
299 function formatItem(item) {
300 if (!item.id) {
301 // return `text` for optgroup
302 return item.text;
303 }
304 var option = convertValueToObj(item.id);
305 var icon = (option.entity_type === 'civicrm_mailing') ? 'EnvelopeIn.gif' : 'group.png';
88e9e883 306 var spanClass = (option.mode == 'exclude') ? 'crmMailing-exclude' : 'crmMailing-include';
b0461279
TO
307 return "<img src='../../sites/all/modules/civicrm/i/" + icon + "' height=12 width=12 /> <span class='" + spanClass + "'>" + item.text + "</span>";
308 }
309
310 $(element).select2({
311 dropdownAutoWidth: true,
312 placeholder: "Groups or Past Recipients",
313 formatResult: formatItem,
314 formatSelection: formatItem,
315 escapeMarkup: function (m) {
316 return m;
317 },
318 });
319
320 $(element).on('select2-selecting', function (e) {
321 var option = convertValueToObj(e.val);
322 var typeKey = option.entity_type == 'civicrm_mailing' ? 'mailings' : 'groups';
323 if (option.mode == 'exclude') {
324 scope.mailing[typeKey].exclude.push(option.entity_id);
325 arrayRemove(scope.mailing[typeKey].include, option.entity_id);
f4f103fa
TO
326 }
327 else {
b0461279
TO
328 scope.mailing[typeKey].include.push(option.entity_id);
329 arrayRemove(scope.mailing[typeKey].exclude, option.entity_id);
330 }
331 scope.$apply();
332 $(element).select2('close');
333 e.preventDefault();
334 });
335
336 $(element).on("select2-removing", function (e) {
337 var option = convertValueToObj(e.val);
338 var typeKey = option.entity_type == 'civicrm_mailing' ? 'mailings' : 'groups';
f4f103fa 339 scope.$parent.$apply(function () {
89a50c67
TO
340 arrayRemove(scope.mailing[typeKey][option.mode], option.entity_id);
341 });
b0461279
TO
342 e.preventDefault();
343 });
344
345 scope.$watchCollection(attrs.crmMailing + ".groups.include", refreshUI);
346 scope.$watchCollection(attrs.crmMailing + ".groups.exclude", refreshUI);
347 scope.$watchCollection(attrs.crmMailing + ".mailings.include", refreshUI);
348 scope.$watchCollection(attrs.crmMailing + ".mailings.exclude", refreshUI);
349 setTimeout(refreshUI, 50);
350 }
351 };
352 });
353
5d72b4e2 354})(angular, CRM.$, CRM._);