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
) {
9 crmAvailGroups
: '@', // available groups
10 crmAvailMailings
: '@', // available mailings
11 crmMandatoryGroups
: '@', // hard-coded/mandatory groups
14 templateUrl
: '~/crmMailing/Recipients.html',
15 link: function(scope
, element
, attrs
, ngModel
) {
16 scope
.recips
= ngModel
.$viewValue
;
17 scope
.groups
= scope
.$parent
.$eval(attrs
.crmAvailGroups
);
18 scope
.mailings
= scope
.$parent
.$eval(attrs
.crmAvailMailings
);
21 var ts
= scope
.ts
= CRM
.ts(null);
23 /// Convert MySQL date ("yyyy-mm-dd hh:mm:ss") to JS date object
24 scope
.parseDate = function(date
) {
25 if (!angular
.isString(date
)) {
28 var p
= date
.split(/[\- :]/);
29 return new Date(parseInt(p
[0]), parseInt(p
[1]) - 1, parseInt(p
[2]), parseInt(p
[3]), parseInt(p
[4]), parseInt(p
[5]));
32 /// Remove {value} from {array}
33 function arrayRemove(array
, value
) {
34 var idx
= array
.indexOf(value
);
40 // @param string id an encoded string like "4 civicrm_mailing include"
41 // @return Object keys: entity_id, entity_type, mode
42 function convertValueToObj(id
) {
43 var a
= id
.split(" ");
44 return {entity_id
: parseInt(a
[0]), entity_type
: a
[1], mode
: a
[2]};
47 // @param Object mailing
48 // @return array list of values like "4 civicrm_mailing include"
49 function convertMailingToValues(recipients
) {
51 angular
.forEach(recipients
.groups
.include
, function(v
) {
52 r
.push(v
+ " civicrm_group include");
54 angular
.forEach(recipients
.groups
.exclude
, function(v
) {
55 r
.push(v
+ " civicrm_group exclude");
57 angular
.forEach(recipients
.mailings
.include
, function(v
) {
58 r
.push(v
+ " civicrm_mailing include");
60 angular
.forEach(recipients
.mailings
.exclude
, function(v
) {
61 r
.push(v
+ " civicrm_mailing exclude");
66 function refreshMandatory() {
67 if (ngModel
.$viewValue
&& ngModel
.$viewValue
.groups
) {
68 scope
.mandatoryGroups
= _
.filter(scope
.$parent
.$eval(attrs
.crmMandatoryGroups
), function(grp
) {
69 return _
.contains(ngModel
.$viewValue
.groups
.include
, parseInt(grp
.id
));
71 scope
.mandatoryIds
= _
.map(_
.pluck(scope
.$parent
.$eval(attrs
.crmMandatoryGroups
), 'id'), function(n
) {
76 scope
.mandatoryGroups
= [];
77 scope
.mandatoryIds
= [];
81 function isMandatory(grpId
) {
82 return _
.contains(scope
.mandatoryIds
, parseInt(grpId
));
85 var refreshUI
= ngModel
.$render
= function refresuhUI() {
86 scope
.recips
= ngModel
.$viewValue
;
87 if (ngModel
.$viewValue
) {
88 $(element
).select2('val', convertMailingToValues(ngModel
.$viewValue
));
94 // @return string HTML representing an option
95 function formatItem(item
) {
97 // return `text` for optgroup
100 var option
= convertValueToObj(item
.id
);
101 var icon
= (option
.entity_type
=== 'civicrm_mailing') ? 'fa-envelope' : 'fa-users';
102 var spanClass
= (option
.mode
== 'exclude') ? 'crmMailing-exclude' : 'crmMailing-include';
103 if (option
.entity_type
!= 'civicrm_mailing' && isMandatory(option
.entity_id
)) {
104 spanClass
= 'crmMailing-mandatory';
106 return '<i class="crm-i '+icon
+'"></i> <span class="' + spanClass
+ '">' + item
.text
+ '</span>';
109 function validate() {
110 if (scope
.$parent
.$eval(attrs
.ngRequired
)) {
111 var empty
= (_
.isEmpty(ngModel
.$viewValue
.groups
.include
) && _
.isEmpty(ngModel
.$viewValue
.mailings
.include
));
112 ngModel
.$setValidity('empty', !empty
);
115 ngModel
.$setValidity('empty', true);
120 dropdownAutoWidth
: true,
121 placeholder
: "Groups or Past Recipients",
122 formatResult
: formatItem
,
123 formatSelection
: formatItem
,
124 escapeMarkup: function(m
) {
129 $(element
).on('select2-selecting', function(e
) {
130 var option
= convertValueToObj(e
.val
);
131 var typeKey
= option
.entity_type
== 'civicrm_mailing' ? 'mailings' : 'groups';
132 if (option
.mode
== 'exclude') {
133 ngModel
.$viewValue
[typeKey
].exclude
.push(option
.entity_id
);
134 arrayRemove(ngModel
.$viewValue
[typeKey
].include
, option
.entity_id
);
137 ngModel
.$viewValue
[typeKey
].include
.push(option
.entity_id
);
138 arrayRemove(ngModel
.$viewValue
[typeKey
].exclude
, option
.entity_id
);
141 $(element
).select2('close');
146 $(element
).on("select2-removing", function(e
) {
147 var option
= convertValueToObj(e
.val
);
148 var typeKey
= option
.entity_type
== 'civicrm_mailing' ? 'mailings' : 'groups';
149 if (typeKey
== 'groups' && isMandatory(option
.entity_id
)) {
151 text
: ts('This mailing was generated based on search results. The search results cannot be removed.'),
152 title
: ts('Required')
157 scope
.$parent
.$apply(function() {
158 arrayRemove(ngModel
.$viewValue
[typeKey
][option
.mode
], option
.entity_id
);
164 scope
.$watchCollection("recips.groups.include", refreshUI
);
165 scope
.$watchCollection("recips.groups.exclude", refreshUI
);
166 scope
.$watchCollection("recips.mailings.include", refreshUI
);
167 scope
.$watchCollection("recips.mailings.exclude", refreshUI
);
168 setTimeout(refreshUI
, 50);
170 scope
.$watchCollection(attrs
.crmAvailGroups
, function() {
171 scope
.groups
= scope
.$parent
.$eval(attrs
.crmAvailGroups
);
173 scope
.$watchCollection(attrs
.crmAvailMailings
, function() {
174 scope
.mailings
= scope
.$parent
.$eval(attrs
.crmAvailMailings
);
176 scope
.$watchCollection(attrs
.crmMandatoryGroups
, function() {
183 })(angular
, CRM
.$, CRM
._
);