Merge pull request #4744 from totten/master-civimail-rename
[civicrm-core.git] / js / angular-crmMailing / directives.js
CommitLineData
5d72b4e2
TO
1(function (angular, $, _) {
2 var partialUrl = function (relPath) {
dcc7d5c9 3 return CRM.resourceUrls['civicrm'] + '/partials/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'),
11 crmMailingBlockPreview: partialUrl('preview.html'),
12 crmMailingBlockPublication: partialUrl('publication.html'),
13 crmMailingBlockResponses: partialUrl('responses.html'),
62d2e387 14 crmMailingBlockRecipients: partialUrl('recipients.html'),
18da0e87
TO
15 crmMailingBlockReview: partialUrl('review.html'),
16 crmMailingBlockSchedule: partialUrl('schedule.html'),
17 crmMailingBlockSummary: partialUrl('summary.html'),
18 crmMailingBlockTracking: partialUrl('tracking.html'),
19 crmMailingBodyHtml: partialUrl('body_html.html'),
20 crmMailingBodyText: partialUrl('body_text.html')
21 };
22 _.each(simpleBlocks, function(templateUrl, directiveName){
88e9e883 23 angular.module('crmMailing').directive(directiveName, function ($parse) {
18da0e87
TO
24 return {
25 scope: {
26 crmMailing: '@'
27 },
28 templateUrl: templateUrl,
29 link: function (scope, elm, attr) {
30 var model = $parse(attr.crmMailing);
31 scope.mailing = model(scope.$parent);
32 scope.crmMailingConst = CRM.crmMailing;
33 scope.ts = CRM.ts('CiviMail');
62d2e387 34 scope[directiveName] = attr[directiveName] ? scope.$parent.$eval(attr[directiveName]) : {};
18da0e87
TO
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}">
88e9e883 44 angular.module('crmMailing').directive('crmMailingFromAddress', function ($parse, crmFromAddresses) {
0a993c89
TO
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
e47964f0
TO
63 // Represent a datetime field as if it were a radio ('schedule.mode') and a datetime ('schedule.datetime').
64 // example: <div crm-mailing-radio-date="mySchedule" crm-model="mailing.scheduled_date">...</div>
65 // FIXME: use ngModel instead of adhoc crmModel
88e9e883 66 angular.module('crmMailing').directive('crmMailingRadioDate', function ($parse) {
e47964f0
TO
67 return {
68 link: function ($scope, element, attrs) {
69 var schedModel = $parse(attrs.crmModel);
70
71 var schedule = $scope[attrs.crmMailingRadioDate] = {
72 mode: 'now',
73 datetime: ''
74 };
75 var updateChildren = (function () {
76 var sched = schedModel($scope);
77 if (sched) {
78 schedule.mode = 'at';
79 schedule.datetime = sched;
80 }
81 else {
82 schedule.mode = 'now';
83 }
84 });
85 var updateParent = (function () {
86 switch (schedule.mode) {
87 case 'now':
88 schedModel.assign($scope, null);
89 break;
90 case 'at':
91 schedModel.assign($scope, schedule.datetime);
92 break;
93 default:
94 throw 'Unrecognized schedule mode: ' + schedule.mode;
95 }
96 });
97
98 $scope.$watch(attrs.crmModel, updateChildren);
99 $scope.$watch(attrs.crmMailingRadioDate + '.mode', updateParent);
100 $scope.$watch(attrs.crmMailingRadioDate + '.datetime', function (newValue, oldValue) {
101 // automatically switch mode based on datetime entry
102 if (oldValue != newValue) {
103 if (!newValue || newValue == " ") {
104 schedule.mode = 'now';
105 }
106 else {
107 schedule.mode = 'at';
108 }
109 }
110 updateParent();
111 });
112 }
113 };
114 });
115
88e9e883 116 angular.module('crmMailing').directive('crmMailingReviewBool', function () {
47bacc20
TO
117 return {
118 scope: {
119 crmOn: '@',
120 crmTitle: '@'
121 },
122 template: '<span ng-class="spanClasses"><span class="icon" ng-class="iconClasses"></span>{{crmTitle}} </span>',
f4f103fa 123 link: function (scope, element, attrs) {
47bacc20
TO
124 function refresh() {
125 if (scope.$parent.$eval(attrs.crmOn)) {
88e9e883 126 scope.spanClasses = {'crmMailing-active': true};
47bacc20 127 scope.iconClasses = {'ui-icon-check': true};
f4f103fa
TO
128 }
129 else {
88e9e883 130 scope.spanClasses = {'crmMailing-inactive': true};
47bacc20
TO
131 scope.iconClasses = {'ui-icon-close': true};
132 }
133 scope.crmTitle = scope.$parent.$eval(attrs.crmTitle);
134 }
f4f103fa 135
47bacc20
TO
136 refresh();
137 scope.$parent.$watch(attrs.crmOn, refresh);
138 scope.$parent.$watch(attrs.crmTitle, refresh);
139 }
140 };
141 });
142
5d72b4e2
TO
143 // example: <input name="subject" /> <input crm-mailing-token crm-for="subject"/>
144 // WISHLIST: Instead of global CRM.crmMailing.mailTokens, accept token list as an input
88e9e883 145 angular.module('crmMailing').directive('crmMailingToken', function () {
5d72b4e2 146 return {
3cc9c048 147 require: '^crmUiIdScope',
5d72b4e2
TO
148 scope: {
149 crmFor: '@'
150 },
151 template: '<input type="text" class="crmMailingToken" />',
3cc9c048 152 link: function (scope, element, attrs, crmUiIdCtrl) {
38737af8
TO
153 $(element).select2({
154 width: "10em",
5d72b4e2
TO
155 dropdownAutoWidth: true,
156 data: CRM.crmMailing.mailTokens,
157 placeholder: ts('Insert')
158 });
159 $(element).on('select2-selecting', function (e) {
3cc9c048 160 var id = crmUiIdCtrl.get(attrs.crmFor);
38737af8
TO
161 if (CKEDITOR.instances[id]) {
162 CKEDITOR.instances[id].insertText(e.val);
163 $(element).select2('close').select2('val', '');
164 CKEDITOR.instances[id].focus();
165 }
166 else {
3cc9c048 167 var crmForEl = $('#' + id);
38737af8
TO
168 var origVal = crmForEl.val();
169 var origPos = crmForEl[0].selectionStart;
170 var newVal = origVal.substring(0, origPos) + e.val + origVal.substring(origPos, origVal.length);
171 crmForEl.val(newVal);
172 var newPos = (origPos + e.val.length);
173 crmForEl[0].selectionStart = newPos;
174 crmForEl[0].selectionEnd = newPos;
5d72b4e2 175
38737af8
TO
176 $(element).select2('close').select2('val', '');
177 crmForEl.triggerHandler('change');
178 crmForEl.focus();
179 }
5d72b4e2
TO
180
181 e.preventDefault();
182 });
183 }
184 };
185 });
186
b0461279 187 // example: <select multiple crm-mailing-recipients crm-mailing="mymailing" crm-avail-groups="myGroups" crm-avail-mailings="myMailings"></select>
f8601d61 188 // FIXME: participate in ngModel's validation cycle
88e9e883 189 angular.module('crmMailing').directive('crmMailingRecipients', function () {
b0461279
TO
190 return {
191 restrict: 'AE',
192 scope: {
193 crmAvailGroups: '@', // available groups
194 crmAvailMailings: '@', // available mailings
195 crmMailing: '@' // the mailing for which we are choosing recipients
196 },
197 templateUrl: partialUrl('directive/recipients.html'),
198 link: function (scope, element, attrs) {
199 scope.mailing = scope.$parent.$eval(attrs.crmMailing);
200 scope.groups = scope.$parent.$eval(attrs.crmAvailGroups);
201 scope.mailings = scope.$parent.$eval(attrs.crmAvailMailings);
202
203 scope.ts = CRM.ts('CiviMail');
204
205 /// Convert MySQL date ("yyyy-mm-dd hh:mm:ss") to JS date object
206 scope.parseDate = function (date) {
f4f103fa 207 if (!angular.isString(date)) {
b0461279 208 return date;
f4f103fa 209 }
b0461279
TO
210 var p = date.split(/[\- :]/);
211 return new Date(p[0], p[1], p[2], p[3], p[4], p[5]);
212 };
213
214 /// Remove {value} from {array}
215 function arrayRemove(array, value) {
216 var idx = array.indexOf(value);
217 if (idx >= 0) {
218 array.splice(idx, 1);
219 }
220 }
221
222 // @param string id an encoded string like "4 civicrm_mailing include"
223 // @return Object keys: entity_id, entity_type, mode
224 function convertValueToObj(id) {
225 var a = id.split(" ");
226 return {entity_id: parseInt(a[0]), entity_type: a[1], mode: a[2]};
227 }
228
229 // @param Object mailing
230 // @return array list of values like "4 civicrm_mailing include"
231 function convertMailingToValues(mailing) {
232 var r = [];
233 angular.forEach(mailing.groups.include, function (v) {
234 r.push(v + " civicrm_group include");
235 });
236 angular.forEach(mailing.groups.exclude, function (v) {
237 r.push(v + " civicrm_group exclude");
238 });
239 angular.forEach(mailing.mailings.include, function (v) {
240 r.push(v + " civicrm_mailing include");
241 });
242 angular.forEach(mailing.mailings.exclude, function (v) {
243 r.push(v + " civicrm_mailing exclude");
244 });
245 return r;
246 }
247
248 // Update $(element) view based on latest data
249 function refreshUI() {
250 $(element).select2('val', convertMailingToValues(scope.mailing));
251 }
252
253 /// @return string HTML representingn an option
254 function formatItem(item) {
255 if (!item.id) {
256 // return `text` for optgroup
257 return item.text;
258 }
259 var option = convertValueToObj(item.id);
260 var icon = (option.entity_type === 'civicrm_mailing') ? 'EnvelopeIn.gif' : 'group.png';
88e9e883 261 var spanClass = (option.mode == 'exclude') ? 'crmMailing-exclude' : 'crmMailing-include';
b0461279
TO
262 return "<img src='../../sites/all/modules/civicrm/i/" + icon + "' height=12 width=12 /> <span class='" + spanClass + "'>" + item.text + "</span>";
263 }
264
265 $(element).select2({
266 dropdownAutoWidth: true,
267 placeholder: "Groups or Past Recipients",
268 formatResult: formatItem,
269 formatSelection: formatItem,
270 escapeMarkup: function (m) {
271 return m;
272 },
273 });
274
275 $(element).on('select2-selecting', function (e) {
276 var option = convertValueToObj(e.val);
277 var typeKey = option.entity_type == 'civicrm_mailing' ? 'mailings' : 'groups';
278 if (option.mode == 'exclude') {
279 scope.mailing[typeKey].exclude.push(option.entity_id);
280 arrayRemove(scope.mailing[typeKey].include, option.entity_id);
f4f103fa
TO
281 }
282 else {
b0461279
TO
283 scope.mailing[typeKey].include.push(option.entity_id);
284 arrayRemove(scope.mailing[typeKey].exclude, option.entity_id);
285 }
286 scope.$apply();
287 $(element).select2('close');
288 e.preventDefault();
289 });
290
291 $(element).on("select2-removing", function (e) {
292 var option = convertValueToObj(e.val);
293 var typeKey = option.entity_type == 'civicrm_mailing' ? 'mailings' : 'groups';
f4f103fa 294 scope.$parent.$apply(function () {
89a50c67
TO
295 arrayRemove(scope.mailing[typeKey][option.mode], option.entity_id);
296 });
b0461279
TO
297 e.preventDefault();
298 });
299
300 scope.$watchCollection(attrs.crmMailing + ".groups.include", refreshUI);
301 scope.$watchCollection(attrs.crmMailing + ".groups.exclude", refreshUI);
302 scope.$watchCollection(attrs.crmMailing + ".mailings.include", refreshUI);
303 scope.$watchCollection(attrs.crmMailing + ".mailings.exclude", refreshUI);
304 setTimeout(refreshUI, 50);
305 }
306 };
307 });
308
5d72b4e2 309})(angular, CRM.$, CRM._);