Commit | Line | Data |
---|---|---|
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); | |
a9e74675 | 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 | } | |
a9e74675 | 102 | return '<i class="crm-i '+icon+'"></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 | ||
139 | for (var i in values) { | |
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 | |
a5ac4db2 | 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'; | |
168 | groupNames.push({id: parseInt(group.id), title: group.label, is_hidden: group.extra.is_hidden}); | |
169 | ||
170 | if (values.indexOf(key) >= 0) { | |
171 | datamap.push({id: key, text: group.label}); | |
172 | } | |
173 | ||
174 | key = group.id + ' civicrm_group exclude'; | |
175 | if (values.indexOf(key) >= 0) { | |
176 | datamap.push({id: key, text: group.label}); | |
177 | } | |
178 | }); | |
179 | ||
180 | $(mlist.values).each(function (idx, group) { | |
181 | var key = group.id + ' civicrm_mailing include'; | |
182 | civiMails.push({id: parseInt(group.id), name: group.label}); | |
183 | ||
184 | if (values.indexOf(key) >= 0) { | |
185 | datamap.push({id: key, text: group.label}); | |
186 | } | |
187 | ||
188 | key = group.id + ' civicrm_mailing exclude'; | |
189 | if (values.indexOf(key) >= 0) { | |
190 | datamap.push({id: key, text: group.label}); | |
191 | } | |
192 | }); | |
193 | ||
194 | scope.$parent.crmMailingConst.groupNames = groupNames; | |
195 | scope.$parent.crmMailingConst.civiMails = civiMails; | |
196 | ||
197 | refreshMandatory(); | |
198 | ||
199 | cb(datamap); | |
200 | }); | |
201 | }); | |
202 | }, | |
203 | ajax: { | |
204 | url: CRM.url('civicrm/ajax/rest'), | |
205 | quietMillis: 300, | |
206 | data: function(input, page_num) { | |
207 | if (page_num <= 1) { | |
208 | rcpAjaxState = { | |
209 | input: input, | |
210 | entity: 'civicrm_group', | |
211 | type: 'include', | |
212 | page_n: 0, | |
213 | }; | |
214 | } | |
215 | ||
216 | rcpAjaxState.page_i = page_num - rcpAjaxState.page_n; | |
b704ec05 SL |
217 | var filterParams = {}; |
218 | switch(rcpAjaxState.entity) { | |
219 | case 'civicrm_group': | |
220 | filterParams = { is_hidden: 0, is_active: 1, group_type: {"LIKE": "%2%"} }; | |
221 | break; | |
836bf6b7 | 222 | |
b704ec05 SL |
223 | case 'civicrm_mailing': |
224 | filterParams = { is_hidden: 0, is_active: 1 }; | |
225 | break; | |
226 | } | |
836bf6b7 SL |
227 | var params = { |
228 | input: input, | |
229 | page_num: rcpAjaxState.page_i, | |
b704ec05 | 230 | params: filterParams, |
836bf6b7 | 231 | }; |
323f9fe5 L |
232 | |
233 | if('civicrm_mailing' === rcpAjaxState.entity) { | |
234 | params["api.MailingRecipients.getcount"] = {}; | |
235 | } | |
236 | ||
836bf6b7 SL |
237 | return params; |
238 | }, | |
239 | transport: function(params) { | |
240 | switch(rcpAjaxState.entity) { | |
241 | case 'civicrm_group': | |
242 | CRM.api3('Group', 'getlist', params.data).then(params.success, params.error); | |
243 | break; | |
244 | ||
245 | case 'civicrm_mailing': | |
246 | params.data.params.options = { sort: "is_archived asc, scheduled_date desc" }; | |
247 | CRM.api3('Mailing', 'getlist', params.data).then(params.success, params.error); | |
248 | break; | |
249 | } | |
250 | }, | |
251 | results: function(data) { | |
252 | results = { | |
253 | children: $.map(data.values, function(obj) { | |
323f9fe5 L |
254 | if('civicrm_mailing' === rcpAjaxState.entity) { |
255 | return obj["api.MailingRecipients.getcount"] > 0 ? { id: obj.id + ' ' + rcpAjaxState.entity + ' ' + rcpAjaxState.type, | |
256 | text: obj.label } : ''; | |
257 | } | |
258 | else { | |
259 | return { id: obj.id + ' ' + rcpAjaxState.entity + ' ' + rcpAjaxState.type, | |
260 | text: obj.label }; | |
261 | } | |
836bf6b7 SL |
262 | }) |
263 | }; | |
264 | ||
323f9fe5 | 265 | if (rcpAjaxState.page_i == 1 && data.count && results.children.length > 0) { |
836bf6b7 SL |
266 | results.text = ts((rcpAjaxState.type == 'include'? 'Include ' : 'Exclude ') + |
267 | (rcpAjaxState.entity == 'civicrm_group'? 'Group' : 'Mailing')); | |
268 | } | |
269 | ||
270 | more = data.more_results || !(rcpAjaxState.entity == 'civicrm_mailing' && rcpAjaxState.type == 'exclude'); | |
271 | ||
272 | if (more && !data.more_results) { | |
273 | if (rcpAjaxState.type == 'include') { | |
274 | rcpAjaxState.type = 'exclude'; | |
275 | } else { | |
276 | rcpAjaxState.type = 'include'; | |
277 | rcpAjaxState.entity = 'civicrm_mailing'; | |
278 | } | |
279 | rcpAjaxState.page_n += rcpAjaxState.page_i; | |
280 | } | |
281 | ||
282 | return { more: more, results: [ results ] }; | |
283 | }, | |
284 | }, | |
b396fc59 TO |
285 | }); |
286 | ||
287 | $(element).on('select2-selecting', function(e) { | |
288 | var option = convertValueToObj(e.val); | |
289 | var typeKey = option.entity_type == 'civicrm_mailing' ? 'mailings' : 'groups'; | |
290 | if (option.mode == 'exclude') { | |
291 | ngModel.$viewValue[typeKey].exclude.push(option.entity_id); | |
292 | arrayRemove(ngModel.$viewValue[typeKey].include, option.entity_id); | |
293 | } | |
294 | else { | |
295 | ngModel.$viewValue[typeKey].include.push(option.entity_id); | |
296 | arrayRemove(ngModel.$viewValue[typeKey].exclude, option.entity_id); | |
297 | } | |
298 | scope.$apply(); | |
299 | $(element).select2('close'); | |
300 | validate(); | |
301 | e.preventDefault(); | |
302 | }); | |
303 | ||
304 | $(element).on("select2-removing", function(e) { | |
305 | var option = convertValueToObj(e.val); | |
306 | var typeKey = option.entity_type == 'civicrm_mailing' ? 'mailings' : 'groups'; | |
307 | if (typeKey == 'groups' && isMandatory(option.entity_id)) { | |
308 | crmUiAlert({ | |
309 | text: ts('This mailing was generated based on search results. The search results cannot be removed.'), | |
310 | title: ts('Required') | |
311 | }); | |
312 | e.preventDefault(); | |
313 | return; | |
314 | } | |
315 | scope.$parent.$apply(function() { | |
316 | arrayRemove(ngModel.$viewValue[typeKey][option.mode], option.entity_id); | |
317 | }); | |
318 | validate(); | |
319 | e.preventDefault(); | |
320 | }); | |
321 | ||
322 | scope.$watchCollection("recips.groups.include", refreshUI); | |
323 | scope.$watchCollection("recips.groups.exclude", refreshUI); | |
324 | scope.$watchCollection("recips.mailings.include", refreshUI); | |
325 | scope.$watchCollection("recips.mailings.exclude", refreshUI); | |
326 | setTimeout(refreshUI, 50); | |
327 | ||
328 | scope.$watchCollection(attrs.crmAvailGroups, function() { | |
329 | scope.groups = scope.$parent.$eval(attrs.crmAvailGroups); | |
330 | }); | |
331 | scope.$watchCollection(attrs.crmAvailMailings, function() { | |
332 | scope.mailings = scope.$parent.$eval(attrs.crmAvailMailings); | |
333 | }); | |
334 | scope.$watchCollection(attrs.crmMandatoryGroups, function() { | |
335 | refreshMandatory(); | |
336 | }); | |
337 | } | |
338 | }; | |
339 | }); | |
340 | ||
341 | })(angular, CRM.$, CRM._); |