CRM-15578 - crmMailingAB2 - Make crmMailingAbBlockMailing more consistent+flexible
[civicrm-core.git] / js / angular-crmMailing2-directives.js
CommitLineData
5d72b4e2
TO
1(function (angular, $, _) {
2 var partialUrl = function (relPath) {
3 return CRM.resourceUrls['civicrm'] + '/partials/crmMailing2/' + relPath;
4 };
5
6 var crmMailing2 = angular.module('crmMailing2');
7
18da0e87
TO
8 // The following directives have the same simple implementation -- load
9 // a template and export a "mailing" object into scope.
10 var simpleBlocks = {
11 crmMailingBlockHeaderFooter: partialUrl('headerFooter.html'),
12 crmMailingBlockMailing: partialUrl('mailing.html'),
13 crmMailingBlockPreview: partialUrl('preview.html'),
14 crmMailingBlockPublication: partialUrl('publication.html'),
15 crmMailingBlockResponses: partialUrl('responses.html'),
16 crmMailingBlockReview: partialUrl('review.html'),
17 crmMailingBlockSchedule: partialUrl('schedule.html'),
18 crmMailingBlockSummary: partialUrl('summary.html'),
19 crmMailingBlockTracking: partialUrl('tracking.html'),
20 crmMailingBodyHtml: partialUrl('body_html.html'),
21 crmMailingBodyText: partialUrl('body_text.html')
22 };
23 _.each(simpleBlocks, function(templateUrl, directiveName){
24 crmMailing2.directive(directiveName, function ($parse) {
25 return {
26 scope: {
27 crmMailing: '@'
28 },
29 templateUrl: templateUrl,
30 link: function (scope, elm, attr) {
31 var model = $parse(attr.crmMailing);
32 scope.mailing = model(scope.$parent);
33 scope.crmMailingConst = CRM.crmMailing;
34 scope.ts = CRM.ts('CiviMail');
35 }
36 };
37 });
f8601d61
TO
38 });
39
0a993c89
TO
40 // Convert between a mailing "From Address" (mailing.from_name,mailing.from_email) and a unified label ("Name" <e@ma.il>)
41 // example: <span crm-mailing-from-address="myPlaceholder" crm-mailing="myMailing"><select ng-model="myPlaceholder.label"></select></span>
42 // NOTE: This really doesn't belong in a directive. I've tried (and failed) to make this work with a getterSetter binding, eg
43 // <select ng-model="mailing.convertFromAddress" ng-model-options="{getterSetter: true}">
44 crmMailing2.directive('crmMailingFromAddress', function ($parse, crmFromAddresses) {
45 return {
46 link: function (scope, element, attrs) {
47 var placeholder = attrs.crmMailingFromAddress;
48 var model = $parse(attrs.crmMailing);
49 var mailing = model(scope.$parent);
50 scope[placeholder] = {
51 label: crmFromAddresses.getByAuthorEmail(mailing.from_name, mailing.from_email, true).label
52 };
53 scope.$watch(placeholder + '.label', function (newValue) {
54 var addr = crmFromAddresses.getByLabel(newValue);
55 mailing.from_name = addr.author;
56 mailing.from_email = addr.email;
57 });
58 // FIXME: Shouldn't we also be watching mailing.from_name and mailing.from_email?
59 }
60 };
61 });
62
f4f103fa 63 crmMailing2.directive('crmMailingReviewBool', function () {
47bacc20
TO
64 return {
65 scope: {
66 crmOn: '@',
67 crmTitle: '@'
68 },
69 template: '<span ng-class="spanClasses"><span class="icon" ng-class="iconClasses"></span>{{crmTitle}} </span>',
f4f103fa 70 link: function (scope, element, attrs) {
47bacc20
TO
71 function refresh() {
72 if (scope.$parent.$eval(attrs.crmOn)) {
73 scope.spanClasses = {'crmMailing2-active': true};
74 scope.iconClasses = {'ui-icon-check': true};
f4f103fa
TO
75 }
76 else {
47bacc20
TO
77 scope.spanClasses = {'crmMailing2-inactive': true};
78 scope.iconClasses = {'ui-icon-close': true};
79 }
80 scope.crmTitle = scope.$parent.$eval(attrs.crmTitle);
81 }
f4f103fa 82
47bacc20
TO
83 refresh();
84 scope.$parent.$watch(attrs.crmOn, refresh);
85 scope.$parent.$watch(attrs.crmTitle, refresh);
86 }
87 };
88 });
89
5d72b4e2
TO
90 // example: <input name="subject" /> <input crm-mailing-token crm-for="subject"/>
91 // WISHLIST: Instead of global CRM.crmMailing.mailTokens, accept token list as an input
38737af8 92 crmMailing2.directive('crmMailingToken', function (crmUiId) {
5d72b4e2
TO
93 return {
94 scope: {
95 crmFor: '@'
96 },
97 template: '<input type="text" class="crmMailingToken" />',
98 link: function (scope, element, attrs) {
99 // 1. Find the corresponding input element (crmFor)
100
101 var form = $(element).closest('form');
102 var crmForEl = $('input[name="' + attrs.crmFor + '"],textarea[name="' + attrs.crmFor + '"]', form);
103 if (form.length != 1 || crmForEl.length != 1) {
38737af8 104 if (console.log) {
5d72b4e2 105 console.log('crmMailingToken cannot be matched to input element. Expected to find one form and one input.', form.length, crmForEl.length);
38737af8 106 }
5d72b4e2
TO
107 return;
108 }
109
110 // 2. Setup the token selector
38737af8
TO
111 $(element).select2({
112 width: "10em",
5d72b4e2
TO
113 dropdownAutoWidth: true,
114 data: CRM.crmMailing.mailTokens,
115 placeholder: ts('Insert')
116 });
117 $(element).on('select2-selecting', function (e) {
38737af8
TO
118 var id = crmUiId(crmForEl);
119 if (CKEDITOR.instances[id]) {
120 CKEDITOR.instances[id].insertText(e.val);
121 $(element).select2('close').select2('val', '');
122 CKEDITOR.instances[id].focus();
123 }
124 else {
125 var origVal = crmForEl.val();
126 var origPos = crmForEl[0].selectionStart;
127 var newVal = origVal.substring(0, origPos) + e.val + origVal.substring(origPos, origVal.length);
128 crmForEl.val(newVal);
129 var newPos = (origPos + e.val.length);
130 crmForEl[0].selectionStart = newPos;
131 crmForEl[0].selectionEnd = newPos;
5d72b4e2 132
38737af8
TO
133 $(element).select2('close').select2('val', '');
134 crmForEl.triggerHandler('change');
135 crmForEl.focus();
136 }
5d72b4e2
TO
137
138 e.preventDefault();
139 });
140 }
141 };
142 });
143
b0461279 144 // example: <select multiple crm-mailing-recipients crm-mailing="mymailing" crm-avail-groups="myGroups" crm-avail-mailings="myMailings"></select>
f8601d61 145 // FIXME: participate in ngModel's validation cycle
b0461279
TO
146 crmMailing2.directive('crmMailingRecipients', function () {
147 return {
148 restrict: 'AE',
149 scope: {
150 crmAvailGroups: '@', // available groups
151 crmAvailMailings: '@', // available mailings
152 crmMailing: '@' // the mailing for which we are choosing recipients
153 },
154 templateUrl: partialUrl('directive/recipients.html'),
155 link: function (scope, element, attrs) {
156 scope.mailing = scope.$parent.$eval(attrs.crmMailing);
157 scope.groups = scope.$parent.$eval(attrs.crmAvailGroups);
158 scope.mailings = scope.$parent.$eval(attrs.crmAvailMailings);
159
160 scope.ts = CRM.ts('CiviMail');
161
162 /// Convert MySQL date ("yyyy-mm-dd hh:mm:ss") to JS date object
163 scope.parseDate = function (date) {
f4f103fa 164 if (!angular.isString(date)) {
b0461279 165 return date;
f4f103fa 166 }
b0461279
TO
167 var p = date.split(/[\- :]/);
168 return new Date(p[0], p[1], p[2], p[3], p[4], p[5]);
169 };
170
171 /// Remove {value} from {array}
172 function arrayRemove(array, value) {
173 var idx = array.indexOf(value);
174 if (idx >= 0) {
175 array.splice(idx, 1);
176 }
177 }
178
179 // @param string id an encoded string like "4 civicrm_mailing include"
180 // @return Object keys: entity_id, entity_type, mode
181 function convertValueToObj(id) {
182 var a = id.split(" ");
183 return {entity_id: parseInt(a[0]), entity_type: a[1], mode: a[2]};
184 }
185
186 // @param Object mailing
187 // @return array list of values like "4 civicrm_mailing include"
188 function convertMailingToValues(mailing) {
189 var r = [];
190 angular.forEach(mailing.groups.include, function (v) {
191 r.push(v + " civicrm_group include");
192 });
193 angular.forEach(mailing.groups.exclude, function (v) {
194 r.push(v + " civicrm_group exclude");
195 });
196 angular.forEach(mailing.mailings.include, function (v) {
197 r.push(v + " civicrm_mailing include");
198 });
199 angular.forEach(mailing.mailings.exclude, function (v) {
200 r.push(v + " civicrm_mailing exclude");
201 });
202 return r;
203 }
204
205 // Update $(element) view based on latest data
206 function refreshUI() {
207 $(element).select2('val', convertMailingToValues(scope.mailing));
208 }
209
210 /// @return string HTML representingn an option
211 function formatItem(item) {
212 if (!item.id) {
213 // return `text` for optgroup
214 return item.text;
215 }
216 var option = convertValueToObj(item.id);
217 var icon = (option.entity_type === 'civicrm_mailing') ? 'EnvelopeIn.gif' : 'group.png';
218 var spanClass = (option.mode == 'exclude') ? 'crmMailing2-exclude' : 'crmMailing2-include';
219 return "<img src='../../sites/all/modules/civicrm/i/" + icon + "' height=12 width=12 /> <span class='" + spanClass + "'>" + item.text + "</span>";
220 }
221
222 $(element).select2({
223 dropdownAutoWidth: true,
224 placeholder: "Groups or Past Recipients",
225 formatResult: formatItem,
226 formatSelection: formatItem,
227 escapeMarkup: function (m) {
228 return m;
229 },
230 });
231
232 $(element).on('select2-selecting', function (e) {
233 var option = convertValueToObj(e.val);
234 var typeKey = option.entity_type == 'civicrm_mailing' ? 'mailings' : 'groups';
235 if (option.mode == 'exclude') {
236 scope.mailing[typeKey].exclude.push(option.entity_id);
237 arrayRemove(scope.mailing[typeKey].include, option.entity_id);
f4f103fa
TO
238 }
239 else {
b0461279
TO
240 scope.mailing[typeKey].include.push(option.entity_id);
241 arrayRemove(scope.mailing[typeKey].exclude, option.entity_id);
242 }
243 scope.$apply();
244 $(element).select2('close');
245 e.preventDefault();
246 });
247
248 $(element).on("select2-removing", function (e) {
249 var option = convertValueToObj(e.val);
250 var typeKey = option.entity_type == 'civicrm_mailing' ? 'mailings' : 'groups';
f4f103fa 251 scope.$parent.$apply(function () {
89a50c67
TO
252 arrayRemove(scope.mailing[typeKey][option.mode], option.entity_id);
253 });
b0461279
TO
254 e.preventDefault();
255 });
256
257 scope.$watchCollection(attrs.crmMailing + ".groups.include", refreshUI);
258 scope.$watchCollection(attrs.crmMailing + ".groups.exclude", refreshUI);
259 scope.$watchCollection(attrs.crmMailing + ".mailings.include", refreshUI);
260 scope.$watchCollection(attrs.crmMailing + ".mailings.exclude", refreshUI);
261 setTimeout(refreshUI, 50);
262 }
263 };
264 });
265
5d72b4e2 266})(angular, CRM.$, CRM._);