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); | |
edc904c6 MD |
97 | var icon = (option.entity_type === 'civicrm_mailing') ? 'fa-envelope' : 'fa-users'; |
98 | var smartGroupMarker = item.is_smart ? '* ' : ''; | |
b396fc59 TO |
99 | var spanClass = (option.mode == 'exclude') ? 'crmMailing-exclude' : 'crmMailing-include'; |
100 | if (option.entity_type != 'civicrm_mailing' && isMandatory(option.entity_id)) { | |
101 | spanClass = 'crmMailing-mandatory'; | |
102 | } | |
edc904c6 | 103 | return '<i class="crm-i '+icon+'"></i> <span class="' + spanClass + '">' + smartGroupMarker + item.text + '</span>'; |
b396fc59 TO |
104 | } |
105 | ||
106 | function validate() { | |
107 | if (scope.$parent.$eval(attrs.ngRequired)) { | |
108 | var empty = (_.isEmpty(ngModel.$viewValue.groups.include) && _.isEmpty(ngModel.$viewValue.mailings.include)); | |
109 | ngModel.$setValidity('empty', !empty); | |
110 | } | |
111 | else { | |
112 | ngModel.$setValidity('empty', true); | |
113 | } | |
114 | } | |
115 | ||
836bf6b7 SL |
116 | var rcpAjaxState = { |
117 | input: '', | |
118 | entity: 'civicrm_group', | |
119 | type: 'include', | |
120 | page_n: 0, | |
121 | page_i: 0, | |
122 | }; | |
123 | ||
b396fc59 | 124 | $(element).select2({ |
836bf6b7 | 125 | width: '36em', |
b396fc59 TO |
126 | dropdownAutoWidth: true, |
127 | placeholder: "Groups or Past Recipients", | |
128 | formatResult: formatItem, | |
129 | formatSelection: formatItem, | |
130 | escapeMarkup: function(m) { | |
131 | return m; | |
836bf6b7 SL |
132 | }, |
133 | multiple: true, | |
134 | initSelection: function(el, cb) { | |
135 | var values = el.val().split(','); | |
136 | ||
137 | var gids = []; | |
138 | var mids = []; | |
139 | ||
83c73d12 | 140 | for (var i = 0; i < values.length; i++) { |
836bf6b7 SL |
141 | var dv = convertValueToObj(values[i]); |
142 | if (dv.entity_type == 'civicrm_group') { | |
143 | gids.push(dv.entity_id); | |
144 | } | |
145 | else if (dv.entity_type == 'civicrm_mailing') { | |
146 | mids.push(dv.entity_id); | |
147 | } | |
148 | } | |
6f3a35e0 | 149 | // push non existant 0 group/mailing id in order when no recipents group or prior mailing is selected |
150 | // this will allow to resuse the below code to handle datamap | |
6dd717a6 | 151 | if (gids.length === 0) { |
6f3a35e0 | 152 | gids.push(0); |
153 | } | |
154 | if (mids.length === 0) { | |
155 | mids.push(0); | |
6dd717a6 | 156 | } |
836bf6b7 | 157 | |
956b9960 | 158 | CRM.api3('Group', 'getlist', { params: { id: { IN: gids }, options: { limit: 0 } }, extra: ["is_hidden"] }).then( |
836bf6b7 | 159 | function(glist) { |
a5ac4db2 | 160 | CRM.api3('Mailing', 'getlist', { params: { id: { IN: mids }, options: { limit: 0 } } }).then( |
836bf6b7 SL |
161 | function(mlist) { |
162 | var datamap = []; | |
163 | ||
164 | var groupNames = []; | |
165 | var civiMails = []; | |
166 | ||
167 | $(glist.values).each(function (idx, group) { | |
168 | var key = group.id + ' civicrm_group include'; | |
836bf6b7 | 169 | |
956b9960 | 170 | groupNames.push({id: parseInt(group.id), title: group.label, is_hidden: group.extra.is_hidden}); |
836bf6b7 SL |
171 | if (values.indexOf(key) >= 0) { |
172 | datamap.push({id: key, text: group.label}); | |
173 | } | |
174 | ||
175 | key = group.id + ' civicrm_group exclude'; | |
176 | if (values.indexOf(key) >= 0) { | |
177 | datamap.push({id: key, text: group.label}); | |
178 | } | |
179 | }); | |
180 | ||
181 | $(mlist.values).each(function (idx, group) { | |
182 | var key = group.id + ' civicrm_mailing include'; | |
183 | civiMails.push({id: parseInt(group.id), name: group.label}); | |
184 | ||
185 | if (values.indexOf(key) >= 0) { | |
186 | datamap.push({id: key, text: group.label}); | |
187 | } | |
188 | ||
189 | key = group.id + ' civicrm_mailing exclude'; | |
190 | if (values.indexOf(key) >= 0) { | |
191 | datamap.push({id: key, text: group.label}); | |
192 | } | |
193 | }); | |
194 | ||
195 | scope.$parent.crmMailingConst.groupNames = groupNames; | |
196 | scope.$parent.crmMailingConst.civiMails = civiMails; | |
197 | ||
198 | refreshMandatory(); | |
199 | ||
200 | cb(datamap); | |
201 | }); | |
202 | }); | |
203 | }, | |
204 | ajax: { | |
205 | url: CRM.url('civicrm/ajax/rest'), | |
206 | quietMillis: 300, | |
207 | data: function(input, page_num) { | |
208 | if (page_num <= 1) { | |
209 | rcpAjaxState = { | |
210 | input: input, | |
211 | entity: 'civicrm_group', | |
212 | type: 'include', | |
213 | page_n: 0, | |
214 | }; | |
215 | } | |
216 | ||
217 | rcpAjaxState.page_i = page_num - rcpAjaxState.page_n; | |
b704ec05 SL |
218 | var filterParams = {}; |
219 | switch(rcpAjaxState.entity) { | |
220 | case 'civicrm_group': | |
221 | filterParams = { is_hidden: 0, is_active: 1, group_type: {"LIKE": "%2%"} }; | |
222 | break; | |
836bf6b7 | 223 | |
b704ec05 SL |
224 | case 'civicrm_mailing': |
225 | filterParams = { is_hidden: 0, is_active: 1 }; | |
226 | break; | |
227 | } | |
836bf6b7 SL |
228 | var params = { |
229 | input: input, | |
230 | page_num: rcpAjaxState.page_i, | |
b704ec05 | 231 | params: filterParams, |
836bf6b7 | 232 | }; |
323f9fe5 L |
233 | |
234 | if('civicrm_mailing' === rcpAjaxState.entity) { | |
235 | params["api.MailingRecipients.getcount"] = {}; | |
236 | } | |
edc904c6 MD |
237 | else if ('civicrm_group' === rcpAjaxState.entity) { |
238 | params.extra = ["saved_search_id"]; | |
239 | } | |
323f9fe5 | 240 | |
836bf6b7 SL |
241 | return params; |
242 | }, | |
243 | transport: function(params) { | |
244 | switch(rcpAjaxState.entity) { | |
245 | case 'civicrm_group': | |
edc904c6 | 246 | CRM.api3('Group', 'getlist', params.data).then(params.success, params.error); |
836bf6b7 SL |
247 | break; |
248 | ||
249 | case 'civicrm_mailing': | |
250 | params.data.params.options = { sort: "is_archived asc, scheduled_date desc" }; | |
251 | CRM.api3('Mailing', 'getlist', params.data).then(params.success, params.error); | |
252 | break; | |
253 | } | |
254 | }, | |
255 | results: function(data) { | |
256 | results = { | |
257 | children: $.map(data.values, function(obj) { | |
323f9fe5 L |
258 | if('civicrm_mailing' === rcpAjaxState.entity) { |
259 | return obj["api.MailingRecipients.getcount"] > 0 ? { id: obj.id + ' ' + rcpAjaxState.entity + ' ' + rcpAjaxState.type, | |
260 | text: obj.label } : ''; | |
261 | } | |
edc904c6 MD |
262 | else { |
263 | return { id: obj.id + ' ' + rcpAjaxState.entity + ' ' + rcpAjaxState.type, text: obj.label, | |
264 | is_smart: (!_.isEmpty(obj.extra.saved_search_id)) }; | |
323f9fe5 | 265 | } |
836bf6b7 SL |
266 | }) |
267 | }; | |
268 | ||
323f9fe5 | 269 | if (rcpAjaxState.page_i == 1 && data.count && results.children.length > 0) { |
836bf6b7 SL |
270 | results.text = ts((rcpAjaxState.type == 'include'? 'Include ' : 'Exclude ') + |
271 | (rcpAjaxState.entity == 'civicrm_group'? 'Group' : 'Mailing')); | |
272 | } | |
273 | ||
274 | more = data.more_results || !(rcpAjaxState.entity == 'civicrm_mailing' && rcpAjaxState.type == 'exclude'); | |
275 | ||
276 | if (more && !data.more_results) { | |
277 | if (rcpAjaxState.type == 'include') { | |
278 | rcpAjaxState.type = 'exclude'; | |
279 | } else { | |
280 | rcpAjaxState.type = 'include'; | |
281 | rcpAjaxState.entity = 'civicrm_mailing'; | |
282 | } | |
283 | rcpAjaxState.page_n += rcpAjaxState.page_i; | |
284 | } | |
285 | ||
286 | return { more: more, results: [ results ] }; | |
287 | }, | |
288 | }, | |
b396fc59 TO |
289 | }); |
290 | ||
291 | $(element).on('select2-selecting', function(e) { | |
292 | var option = convertValueToObj(e.val); | |
293 | var typeKey = option.entity_type == 'civicrm_mailing' ? 'mailings' : 'groups'; | |
294 | if (option.mode == 'exclude') { | |
295 | ngModel.$viewValue[typeKey].exclude.push(option.entity_id); | |
296 | arrayRemove(ngModel.$viewValue[typeKey].include, option.entity_id); | |
297 | } | |
298 | else { | |
299 | ngModel.$viewValue[typeKey].include.push(option.entity_id); | |
300 | arrayRemove(ngModel.$viewValue[typeKey].exclude, option.entity_id); | |
301 | } | |
302 | scope.$apply(); | |
303 | $(element).select2('close'); | |
304 | validate(); | |
305 | e.preventDefault(); | |
306 | }); | |
307 | ||
308 | $(element).on("select2-removing", function(e) { | |
309 | var option = convertValueToObj(e.val); | |
310 | var typeKey = option.entity_type == 'civicrm_mailing' ? 'mailings' : 'groups'; | |
311 | if (typeKey == 'groups' && isMandatory(option.entity_id)) { | |
312 | crmUiAlert({ | |
313 | text: ts('This mailing was generated based on search results. The search results cannot be removed.'), | |
314 | title: ts('Required') | |
315 | }); | |
316 | e.preventDefault(); | |
317 | return; | |
318 | } | |
319 | scope.$parent.$apply(function() { | |
320 | arrayRemove(ngModel.$viewValue[typeKey][option.mode], option.entity_id); | |
321 | }); | |
322 | validate(); | |
323 | e.preventDefault(); | |
324 | }); | |
325 | ||
326 | scope.$watchCollection("recips.groups.include", refreshUI); | |
327 | scope.$watchCollection("recips.groups.exclude", refreshUI); | |
328 | scope.$watchCollection("recips.mailings.include", refreshUI); | |
329 | scope.$watchCollection("recips.mailings.exclude", refreshUI); | |
330 | setTimeout(refreshUI, 50); | |
331 | ||
332 | scope.$watchCollection(attrs.crmAvailGroups, function() { | |
333 | scope.groups = scope.$parent.$eval(attrs.crmAvailGroups); | |
334 | }); | |
335 | scope.$watchCollection(attrs.crmAvailMailings, function() { | |
336 | scope.mailings = scope.$parent.$eval(attrs.crmAvailMailings); | |
337 | }); | |
338 | scope.$watchCollection(attrs.crmMandatoryGroups, function() { | |
339 | refreshMandatory(); | |
340 | }); | |
341 | } | |
342 | }; | |
343 | }); | |
344 | ||
345 | })(angular, CRM.$, CRM._); |