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
) {
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
);
17 var ts
= scope
.ts
= CRM
.ts(null);
19 /// Convert MySQL date ("yyyy-mm-dd hh:mm:ss") to JS date object
20 scope
.parseDate = function(date
) {
21 if (!angular
.isString(date
)) {
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]));
28 /// Remove {value} from {array}
29 function arrayRemove(array
, value
) {
30 var idx
= array
.indexOf(value
);
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]};
43 // @param Object mailing
44 // @return array list of values like "4 civicrm_mailing include"
45 function convertMailingToValues(recipients
) {
47 angular
.forEach(recipients
.groups
.include
, function(v
) {
48 r
.push(v
+ " civicrm_group include");
50 angular
.forEach(recipients
.groups
.exclude
, function(v
) {
51 r
.push(v
+ " civicrm_group exclude");
53 angular
.forEach(recipients
.mailings
.include
, function(v
) {
54 r
.push(v
+ " civicrm_mailing include");
56 angular
.forEach(recipients
.mailings
.exclude
, function(v
) {
57 r
.push(v
+ " civicrm_mailing exclude");
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
));
67 scope
.mandatoryIds
= _
.map(_
.pluck(scope
.$parent
.$eval(attrs
.crmMandatoryGroups
), 'id'), function(n
) {
72 scope
.mandatoryGroups
= [];
73 scope
.mandatoryIds
= [];
77 function isMandatory(grpId
) {
78 return _
.contains(scope
.mandatoryIds
, parseInt(grpId
));
81 var refreshUI
= ngModel
.$render
= function refresuhUI() {
82 scope
.recips
= ngModel
.$viewValue
;
83 if (ngModel
.$viewValue
) {
84 $(element
).select2('val', convertMailingToValues(ngModel
.$viewValue
));
90 // @return string HTML representing an option
91 function formatItem(item
) {
93 // return `text` for optgroup
96 var option
= convertValueToObj(item
.id
);
97 var icon
= (option
.entity_type
=== 'civicrm_mailing') ? 'fa-envelope' : 'fa-users';
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';
102 return '<i class="crm-i '+icon
+'"></i> <span class="' + spanClass
+ '">' + item
.text
+ '</span>';
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
);
111 ngModel
.$setValidity('empty', true);
117 entity
: 'civicrm_group',
125 dropdownAutoWidth
: true,
126 placeholder
: "Groups or Past Recipients",
127 formatResult
: formatItem
,
128 formatSelection
: formatItem
,
129 escapeMarkup: function(m
) {
133 initSelection: function(el
, cb
) {
134 var values
= el
.val().split(',');
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
);
144 else if (dv
.entity_type
== 'civicrm_mailing') {
145 mids
.push(dv
.entity_id
);
149 CRM
.api3('Group', 'getlist', { params
: { id
: { IN
: gids
}, options
: { limit
: 0 } }, extra
: ["is_hidden"] } ).then(
151 CRM
.api3('Mailing', 'getlist', { params
: { id
: { IN
: mids
}, options
: { limit
: 0 } } }).then(
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
});
162 if (values
.indexOf(key
) >= 0) {
163 datamap
.push({id
: key
, text
: group
.label
});
166 key
= group
.id
+ ' civicrm_group exclude';
167 if (values
.indexOf(key
) >= 0) {
168 datamap
.push({id
: key
, text
: group
.label
});
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
});
176 if (values
.indexOf(key
) >= 0) {
177 datamap
.push({id
: key
, text
: group
.label
});
180 key
= group
.id
+ ' civicrm_mailing exclude';
181 if (values
.indexOf(key
) >= 0) {
182 datamap
.push({id
: key
, text
: group
.label
});
186 scope
.$parent
.crmMailingConst
.groupNames
= groupNames
;
187 scope
.$parent
.crmMailingConst
.civiMails
= civiMails
;
196 url
: CRM
.url('civicrm/ajax/rest'),
198 data: function(input
, page_num
) {
202 entity
: 'civicrm_group',
208 rcpAjaxState
.page_i
= page_num
- rcpAjaxState
.page_n
;
209 var filterParams
= {};
210 switch(rcpAjaxState
.entity
) {
211 case 'civicrm_group':
212 filterParams
= { is_hidden
: 0, is_active
: 1, group_type
: {"LIKE": "%2%"} };
215 case 'civicrm_mailing':
216 filterParams
= { is_hidden
: 0, is_active
: 1 };
221 page_num
: rcpAjaxState
.page_i
,
222 params
: filterParams
,
226 transport: function(params
) {
227 switch(rcpAjaxState
.entity
) {
228 case 'civicrm_group':
229 CRM
.api3('Group', 'getlist', params
.data
).then(params
.success
, params
.error
);
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
);
238 results: function(data
) {
240 children
: $.map(data
.values
, function(obj
) {
241 return { id
: obj
.id
+ ' ' + rcpAjaxState
.entity
+ ' ' + rcpAjaxState
.type
,
246 if (rcpAjaxState
.page_i
== 1 && data
.count
) {
247 results
.text
= ts((rcpAjaxState
.type
== 'include'? 'Include ' : 'Exclude ') +
248 (rcpAjaxState
.entity
== 'civicrm_group'? 'Group' : 'Mailing'));
251 more
= data
.more_results
|| !(rcpAjaxState
.entity
== 'civicrm_mailing' && rcpAjaxState
.type
== 'exclude');
253 if (more
&& !data
.more_results
) {
254 if (rcpAjaxState
.type
== 'include') {
255 rcpAjaxState
.type
= 'exclude';
257 rcpAjaxState
.type
= 'include';
258 rcpAjaxState
.entity
= 'civicrm_mailing';
260 rcpAjaxState
.page_n
+= rcpAjaxState
.page_i
;
263 return { more
: more
, results
: [ results
] };
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
);
276 ngModel
.$viewValue
[typeKey
].include
.push(option
.entity_id
);
277 arrayRemove(ngModel
.$viewValue
[typeKey
].exclude
, option
.entity_id
);
280 $(element
).select2('close');
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
)) {
290 text
: ts('This mailing was generated based on search results. The search results cannot be removed.'),
291 title
: ts('Required')
296 scope
.$parent
.$apply(function() {
297 arrayRemove(ngModel
.$viewValue
[typeKey
][option
.mode
], option
.entity_id
);
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);
309 scope
.$watchCollection(attrs
.crmAvailGroups
, function() {
310 scope
.groups
= scope
.$parent
.$eval(attrs
.crmAvailGroups
);
312 scope
.$watchCollection(attrs
.crmAvailMailings
, function() {
313 scope
.mailings
= scope
.$parent
.$eval(attrs
.crmAvailMailings
);
315 scope
.$watchCollection(attrs
.crmMandatoryGroups
, function() {
322 })(angular
, CRM
.$, CRM
._
);