Merge pull request #17926 from civicrm/5.28
[civicrm-core.git] / ang / crmMailing / Recipients.js
CommitLineData
b396fc59
TO
1(function(angular, $, _) {
2 // example: <select multiple crm-mailing-recipients crm-mailing="mymailing" crm-avail-groups="myGroups" crm-avail-mailings="myMailings"></select>
3 // FIXME: participate in ngModel's validation cycle
4 angular.module('crmMailing').directive('crmMailingRecipients', function(crmUiAlert) {
5 return {
6 restrict: 'AE',
7 require: 'ngModel',
8 scope: {
b396fc59
TO
9 ngRequired: '@'
10 },
b396fc59
TO
11 link: function(scope, element, attrs, ngModel) {
12 scope.recips = ngModel.$viewValue;
13 scope.groups = scope.$parent.$eval(attrs.crmAvailGroups);
14 scope.mailings = scope.$parent.$eval(attrs.crmAvailMailings);
15 refreshMandatory();
16
17 var ts = scope.ts = CRM.ts(null);
18
19 /// Convert MySQL date ("yyyy-mm-dd hh:mm:ss") to JS date object
20 scope.parseDate = function(date) {
21 if (!angular.isString(date)) {
22 return date;
23 }
24 var p = date.split(/[\- :]/);
25 return new Date(parseInt(p[0]), parseInt(p[1]) - 1, parseInt(p[2]), parseInt(p[3]), parseInt(p[4]), parseInt(p[5]));
26 };
27
28 /// Remove {value} from {array}
29 function arrayRemove(array, value) {
30 var idx = array.indexOf(value);
31 if (idx >= 0) {
32 array.splice(idx, 1);
33 }
34 }
35
36 // @param string id an encoded string like "4 civicrm_mailing include"
37 // @return Object keys: entity_id, entity_type, mode
38 function convertValueToObj(id) {
39 var a = id.split(" ");
40 return {entity_id: parseInt(a[0]), entity_type: a[1], mode: a[2]};
41 }
42
43 // @param Object mailing
44 // @return array list of values like "4 civicrm_mailing include"
45 function convertMailingToValues(recipients) {
46 var r = [];
47 angular.forEach(recipients.groups.include, function(v) {
48 r.push(v + " civicrm_group include");
49 });
50 angular.forEach(recipients.groups.exclude, function(v) {
51 r.push(v + " civicrm_group exclude");
52 });
53 angular.forEach(recipients.mailings.include, function(v) {
54 r.push(v + " civicrm_mailing include");
55 });
56 angular.forEach(recipients.mailings.exclude, function(v) {
57 r.push(v + " civicrm_mailing exclude");
58 });
59 return r;
60 }
61
62 function refreshMandatory() {
63 if (ngModel.$viewValue && ngModel.$viewValue.groups) {
64 scope.mandatoryGroups = _.filter(scope.$parent.$eval(attrs.crmMandatoryGroups), function(grp) {
65 return _.contains(ngModel.$viewValue.groups.include, parseInt(grp.id));
66 });
67 scope.mandatoryIds = _.map(_.pluck(scope.$parent.$eval(attrs.crmMandatoryGroups), 'id'), function(n) {
68 return parseInt(n);
69 });
70 }
71 else {
72 scope.mandatoryGroups = [];
73 scope.mandatoryIds = [];
74 }
75 }
76
77 function isMandatory(grpId) {
78 return _.contains(scope.mandatoryIds, parseInt(grpId));
79 }
80
81 var refreshUI = ngModel.$render = function refresuhUI() {
82 scope.recips = ngModel.$viewValue;
83 if (ngModel.$viewValue) {
84 $(element).select2('val', convertMailingToValues(ngModel.$viewValue));
85 validate();
86 refreshMandatory();
87 }
88 };
89
90 // @return string HTML representing an option
91 function formatItem(item) {
92 if (!item.id) {
93 // return `text` for optgroup
94 return item.text;
95 }
96 var option = convertValueToObj(item.id);
edc904c6 97 var icon = (option.entity_type === 'civicrm_mailing') ? 'fa-envelope' : 'fa-users';
b396fc59
TO
98 var spanClass = (option.mode == 'exclude') ? 'crmMailing-exclude' : 'crmMailing-include';
99 if (option.entity_type != 'civicrm_mailing' && isMandatory(option.entity_id)) {
100 spanClass = 'crmMailing-mandatory';
101 }
8aed4f8f 102 return '<i class="crm-i '+icon+'" aria-hidden="true"></i> <span class="' + spanClass + '">' + item.text + '</span>';
b396fc59
TO
103 }
104
105 function validate() {
106 if (scope.$parent.$eval(attrs.ngRequired)) {
107 var empty = (_.isEmpty(ngModel.$viewValue.groups.include) && _.isEmpty(ngModel.$viewValue.mailings.include));
108 ngModel.$setValidity('empty', !empty);
109 }
110 else {
111 ngModel.$setValidity('empty', true);
112 }
113 }
114
836bf6b7
SL
115 var rcpAjaxState = {
116 input: '',
117 entity: 'civicrm_group',
118 type: 'include',
119 page_n: 0,
120 page_i: 0,
121 };
122
b396fc59 123 $(element).select2({
836bf6b7 124 width: '36em',
b396fc59
TO
125 dropdownAutoWidth: true,
126 placeholder: "Groups or Past Recipients",
127 formatResult: formatItem,
128 formatSelection: formatItem,
129 escapeMarkup: function(m) {
130 return m;
836bf6b7
SL
131 },
132 multiple: true,
133 initSelection: function(el, cb) {
134 var values = el.val().split(',');
135
136 var gids = [];
137 var mids = [];
138
83c73d12 139 for (var i = 0; i < values.length; i++) {
836bf6b7
SL
140 var dv = convertValueToObj(values[i]);
141 if (dv.entity_type == 'civicrm_group') {
142 gids.push(dv.entity_id);
143 }
144 else if (dv.entity_type == 'civicrm_mailing') {
145 mids.push(dv.entity_id);
146 }
147 }
6f3a35e0 148 // push non existant 0 group/mailing id in order when no recipents group or prior mailing is selected
149 // this will allow to resuse the below code to handle datamap
6dd717a6 150 if (gids.length === 0) {
6f3a35e0 151 gids.push(0);
152 }
153 if (mids.length === 0) {
154 mids.push(0);
6dd717a6 155 }
836bf6b7 156
ba2ad46f 157 CRM.api3('Group', 'getlist', { params: { id: { IN: gids }, options: { limit: 0 } }, extra: ["is_hidden"] } ).then(
836bf6b7 158 function(glist) {
a5ac4db2 159 CRM.api3('Mailing', 'getlist', { params: { id: { IN: mids }, options: { limit: 0 } } }).then(
836bf6b7
SL
160 function(mlist) {
161 var datamap = [];
162
163 var groupNames = [];
164 var civiMails = [];
165
166 $(glist.values).each(function (idx, group) {
167 var key = group.id + ' civicrm_group include';
956b9960 168 groupNames.push({id: parseInt(group.id), title: group.label, is_hidden: group.extra.is_hidden});
836bf6b7
SL
169 if (values.indexOf(key) >= 0) {
170 datamap.push({id: key, text: group.label});
171 }
172
173 key = group.id + ' civicrm_group exclude';
174 if (values.indexOf(key) >= 0) {
175 datamap.push({id: key, text: group.label});
176 }
177 });
178
179 $(mlist.values).each(function (idx, group) {
180 var key = group.id + ' civicrm_mailing include';
181 civiMails.push({id: parseInt(group.id), name: group.label});
182
183 if (values.indexOf(key) >= 0) {
184 datamap.push({id: key, text: group.label});
185 }
186
187 key = group.id + ' civicrm_mailing exclude';
188 if (values.indexOf(key) >= 0) {
189 datamap.push({id: key, text: group.label});
190 }
191 });
192
193 scope.$parent.crmMailingConst.groupNames = groupNames;
194 scope.$parent.crmMailingConst.civiMails = civiMails;
195
196 refreshMandatory();
197
198 cb(datamap);
199 });
200 });
201 },
202 ajax: {
203 url: CRM.url('civicrm/ajax/rest'),
204 quietMillis: 300,
205 data: function(input, page_num) {
206 if (page_num <= 1) {
207 rcpAjaxState = {
208 input: input,
209 entity: 'civicrm_group',
210 type: 'include',
211 page_n: 0,
212 };
213 }
214
215 rcpAjaxState.page_i = page_num - rcpAjaxState.page_n;
b704ec05
SL
216 var filterParams = {};
217 switch(rcpAjaxState.entity) {
218 case 'civicrm_group':
219 filterParams = { is_hidden: 0, is_active: 1, group_type: {"LIKE": "%2%"} };
220 break;
836bf6b7 221
b704ec05
SL
222 case 'civicrm_mailing':
223 filterParams = { is_hidden: 0, is_active: 1 };
224 break;
225 }
836bf6b7
SL
226 var params = {
227 input: input,
228 page_num: rcpAjaxState.page_i,
b704ec05 229 params: filterParams,
836bf6b7 230 };
323f9fe5
L
231
232 if('civicrm_mailing' === rcpAjaxState.entity) {
233 params["api.MailingRecipients.getcount"] = {};
234 }
235
836bf6b7
SL
236 return params;
237 },
238 transport: function(params) {
239 switch(rcpAjaxState.entity) {
240 case 'civicrm_group':
edc904c6 241 CRM.api3('Group', 'getlist', params.data).then(params.success, params.error);
836bf6b7
SL
242 break;
243
244 case 'civicrm_mailing':
245 params.data.params.options = { sort: "is_archived asc, scheduled_date desc" };
246 CRM.api3('Mailing', 'getlist', params.data).then(params.success, params.error);
247 break;
248 }
249 },
250 results: function(data) {
251 results = {
252 children: $.map(data.values, function(obj) {
323f9fe5
L
253 if('civicrm_mailing' === rcpAjaxState.entity) {
254 return obj["api.MailingRecipients.getcount"] > 0 ? { id: obj.id + ' ' + rcpAjaxState.entity + ' ' + rcpAjaxState.type,
255 text: obj.label } : '';
256 }
edc904c6 257 else {
ba2ad46f 258 return { id: obj.id + ' ' + rcpAjaxState.entity + ' ' + rcpAjaxState.type,
259 text: obj.label };
323f9fe5 260 }
836bf6b7
SL
261 })
262 };
263
323f9fe5 264 if (rcpAjaxState.page_i == 1 && data.count && results.children.length > 0) {
836bf6b7
SL
265 results.text = ts((rcpAjaxState.type == 'include'? 'Include ' : 'Exclude ') +
266 (rcpAjaxState.entity == 'civicrm_group'? 'Group' : 'Mailing'));
267 }
268
269 more = data.more_results || !(rcpAjaxState.entity == 'civicrm_mailing' && rcpAjaxState.type == 'exclude');
270
271 if (more && !data.more_results) {
272 if (rcpAjaxState.type == 'include') {
273 rcpAjaxState.type = 'exclude';
274 } else {
275 rcpAjaxState.type = 'include';
276 rcpAjaxState.entity = 'civicrm_mailing';
277 }
278 rcpAjaxState.page_n += rcpAjaxState.page_i;
279 }
280
281 return { more: more, results: [ results ] };
282 },
283 },
b396fc59
TO
284 });
285
286 $(element).on('select2-selecting', function(e) {
287 var option = convertValueToObj(e.val);
288 var typeKey = option.entity_type == 'civicrm_mailing' ? 'mailings' : 'groups';
289 if (option.mode == 'exclude') {
290 ngModel.$viewValue[typeKey].exclude.push(option.entity_id);
291 arrayRemove(ngModel.$viewValue[typeKey].include, option.entity_id);
292 }
293 else {
294 ngModel.$viewValue[typeKey].include.push(option.entity_id);
295 arrayRemove(ngModel.$viewValue[typeKey].exclude, option.entity_id);
296 }
297 scope.$apply();
298 $(element).select2('close');
299 validate();
300 e.preventDefault();
301 });
302
303 $(element).on("select2-removing", function(e) {
304 var option = convertValueToObj(e.val);
305 var typeKey = option.entity_type == 'civicrm_mailing' ? 'mailings' : 'groups';
306 if (typeKey == 'groups' && isMandatory(option.entity_id)) {
307 crmUiAlert({
308 text: ts('This mailing was generated based on search results. The search results cannot be removed.'),
309 title: ts('Required')
310 });
311 e.preventDefault();
312 return;
313 }
314 scope.$parent.$apply(function() {
315 arrayRemove(ngModel.$viewValue[typeKey][option.mode], option.entity_id);
316 });
317 validate();
318 e.preventDefault();
319 });
320
321 scope.$watchCollection("recips.groups.include", refreshUI);
322 scope.$watchCollection("recips.groups.exclude", refreshUI);
323 scope.$watchCollection("recips.mailings.include", refreshUI);
324 scope.$watchCollection("recips.mailings.exclude", refreshUI);
325 setTimeout(refreshUI, 50);
326
327 scope.$watchCollection(attrs.crmAvailGroups, function() {
328 scope.groups = scope.$parent.$eval(attrs.crmAvailGroups);
329 });
330 scope.$watchCollection(attrs.crmAvailMailings, function() {
331 scope.mailings = scope.$parent.$eval(attrs.crmAvailMailings);
332 });
333 scope.$watchCollection(attrs.crmMandatoryGroups, function() {
334 refreshMandatory();
335 });
336 }
337 };
338 });
339
340})(angular, CRM.$, CRM._);