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
);
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
150 if (gids
.length
=== 0) {
153 if (mids
.length
=== 0) {
157 CRM
.api3('Group', 'getlist', { params
: { id
: { IN
: gids
}, options
: { limit
: 0 } }, extra
: ["is_hidden"] } ).then(
159 CRM
.api3('Mailing', 'getlist', { params
: { id
: { IN
: mids
}, options
: { limit
: 0 } } }).then(
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
});
170 if (values
.indexOf(key
) >= 0) {
171 datamap
.push({id
: key
, text
: group
.label
});
174 key
= group
.id
+ ' civicrm_group exclude';
175 if (values
.indexOf(key
) >= 0) {
176 datamap
.push({id
: key
, text
: group
.label
});
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
});
184 if (values
.indexOf(key
) >= 0) {
185 datamap
.push({id
: key
, text
: group
.label
});
188 key
= group
.id
+ ' civicrm_mailing exclude';
189 if (values
.indexOf(key
) >= 0) {
190 datamap
.push({id
: key
, text
: group
.label
});
194 scope
.$parent
.crmMailingConst
.groupNames
= groupNames
;
195 scope
.$parent
.crmMailingConst
.civiMails
= civiMails
;
204 url
: CRM
.url('civicrm/ajax/rest'),
206 data: function(input
, page_num
) {
210 entity
: 'civicrm_group',
216 rcpAjaxState
.page_i
= page_num
- rcpAjaxState
.page_n
;
217 var filterParams
= {};
218 switch(rcpAjaxState
.entity
) {
219 case 'civicrm_group':
220 filterParams
= { is_hidden
: 0, is_active
: 1, group_type
: {"LIKE": "%2%"} };
223 case 'civicrm_mailing':
224 filterParams
= { is_hidden
: 0, is_active
: 1 };
229 page_num
: rcpAjaxState
.page_i
,
230 params
: filterParams
,
234 transport: function(params
) {
235 switch(rcpAjaxState
.entity
) {
236 case 'civicrm_group':
237 CRM
.api3('Group', 'getlist', params
.data
).then(params
.success
, params
.error
);
240 case 'civicrm_mailing':
241 params
.data
.params
.options
= { sort
: "is_archived asc, scheduled_date desc" };
242 CRM
.api3('Mailing', 'getlist', params
.data
).then(params
.success
, params
.error
);
246 results: function(data
) {
248 children
: $.map(data
.values
, function(obj
) {
249 return { id
: obj
.id
+ ' ' + rcpAjaxState
.entity
+ ' ' + rcpAjaxState
.type
,
254 if (rcpAjaxState
.page_i
== 1 && data
.count
) {
255 results
.text
= ts((rcpAjaxState
.type
== 'include'? 'Include ' : 'Exclude ') +
256 (rcpAjaxState
.entity
== 'civicrm_group'? 'Group' : 'Mailing'));
259 more
= data
.more_results
|| !(rcpAjaxState
.entity
== 'civicrm_mailing' && rcpAjaxState
.type
== 'exclude');
261 if (more
&& !data
.more_results
) {
262 if (rcpAjaxState
.type
== 'include') {
263 rcpAjaxState
.type
= 'exclude';
265 rcpAjaxState
.type
= 'include';
266 rcpAjaxState
.entity
= 'civicrm_mailing';
268 rcpAjaxState
.page_n
+= rcpAjaxState
.page_i
;
271 return { more
: more
, results
: [ results
] };
276 $(element
).on('select2-selecting', function(e
) {
277 var option
= convertValueToObj(e
.val
);
278 var typeKey
= option
.entity_type
== 'civicrm_mailing' ? 'mailings' : 'groups';
279 if (option
.mode
== 'exclude') {
280 ngModel
.$viewValue
[typeKey
].exclude
.push(option
.entity_id
);
281 arrayRemove(ngModel
.$viewValue
[typeKey
].include
, option
.entity_id
);
284 ngModel
.$viewValue
[typeKey
].include
.push(option
.entity_id
);
285 arrayRemove(ngModel
.$viewValue
[typeKey
].exclude
, option
.entity_id
);
288 $(element
).select2('close');
293 $(element
).on("select2-removing", function(e
) {
294 var option
= convertValueToObj(e
.val
);
295 var typeKey
= option
.entity_type
== 'civicrm_mailing' ? 'mailings' : 'groups';
296 if (typeKey
== 'groups' && isMandatory(option
.entity_id
)) {
298 text
: ts('This mailing was generated based on search results. The search results cannot be removed.'),
299 title
: ts('Required')
304 scope
.$parent
.$apply(function() {
305 arrayRemove(ngModel
.$viewValue
[typeKey
][option
.mode
], option
.entity_id
);
311 scope
.$watchCollection("recips.groups.include", refreshUI
);
312 scope
.$watchCollection("recips.groups.exclude", refreshUI
);
313 scope
.$watchCollection("recips.mailings.include", refreshUI
);
314 scope
.$watchCollection("recips.mailings.exclude", refreshUI
);
315 setTimeout(refreshUI
, 50);
317 scope
.$watchCollection(attrs
.crmAvailGroups
, function() {
318 scope
.groups
= scope
.$parent
.$eval(attrs
.crmAvailGroups
);
320 scope
.$watchCollection(attrs
.crmAvailMailings
, function() {
321 scope
.mailings
= scope
.$parent
.$eval(attrs
.crmAvailMailings
);
323 scope
.$watchCollection(attrs
.crmMandatoryGroups
, function() {
330 })(angular
, CRM
.$, CRM
._
);