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