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