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: { | |
9 | crmAvailGroups: '@', // available groups | |
10 | crmAvailMailings: '@', // available mailings | |
11 | crmMandatoryGroups: '@', // hard-coded/mandatory groups | |
12 | ngRequired: '@' | |
13 | }, | |
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); | |
19 | refreshMandatory(); | |
20 | ||
21 | var ts = scope.ts = CRM.ts(null); | |
22 | ||
23 | /// Convert MySQL date ("yyyy-mm-dd hh:mm:ss") to JS date object | |
24 | scope.parseDate = function(date) { | |
25 | if (!angular.isString(date)) { | |
26 | return date; | |
27 | } | |
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])); | |
30 | }; | |
31 | ||
32 | /// Remove {value} from {array} | |
33 | function arrayRemove(array, value) { | |
34 | var idx = array.indexOf(value); | |
35 | if (idx >= 0) { | |
36 | array.splice(idx, 1); | |
37 | } | |
38 | } | |
39 | ||
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]}; | |
45 | } | |
46 | ||
47 | // @param Object mailing | |
48 | // @return array list of values like "4 civicrm_mailing include" | |
49 | function convertMailingToValues(recipients) { | |
50 | var r = []; | |
51 | angular.forEach(recipients.groups.include, function(v) { | |
52 | r.push(v + " civicrm_group include"); | |
53 | }); | |
54 | angular.forEach(recipients.groups.exclude, function(v) { | |
55 | r.push(v + " civicrm_group exclude"); | |
56 | }); | |
57 | angular.forEach(recipients.mailings.include, function(v) { | |
58 | r.push(v + " civicrm_mailing include"); | |
59 | }); | |
60 | angular.forEach(recipients.mailings.exclude, function(v) { | |
61 | r.push(v + " civicrm_mailing exclude"); | |
62 | }); | |
63 | return r; | |
64 | } | |
65 | ||
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)); | |
70 | }); | |
71 | scope.mandatoryIds = _.map(_.pluck(scope.$parent.$eval(attrs.crmMandatoryGroups), 'id'), function(n) { | |
72 | return parseInt(n); | |
73 | }); | |
74 | } | |
75 | else { | |
76 | scope.mandatoryGroups = []; | |
77 | scope.mandatoryIds = []; | |
78 | } | |
79 | } | |
80 | ||
81 | function isMandatory(grpId) { | |
82 | return _.contains(scope.mandatoryIds, parseInt(grpId)); | |
83 | } | |
84 | ||
85 | var refreshUI = ngModel.$render = function refresuhUI() { | |
86 | scope.recips = ngModel.$viewValue; | |
87 | if (ngModel.$viewValue) { | |
88 | $(element).select2('val', convertMailingToValues(ngModel.$viewValue)); | |
89 | validate(); | |
90 | refreshMandatory(); | |
91 | } | |
92 | }; | |
93 | ||
94 | // @return string HTML representing an option | |
95 | function formatItem(item) { | |
96 | if (!item.id) { | |
97 | // return `text` for optgroup | |
98 | return item.text; | |
99 | } | |
100 | var option = convertValueToObj(item.id); | |
a9e74675 | 101 | var icon = (option.entity_type === 'civicrm_mailing') ? 'fa-envelope' : 'fa-users'; |
b396fc59 TO |
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'; | |
105 | } | |
a9e74675 | 106 | return '<i class="crm-i '+icon+'"></i> <span class="' + spanClass + '">' + item.text + '</span>'; |
b396fc59 TO |
107 | } |
108 | ||
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); | |
113 | } | |
114 | else { | |
115 | ngModel.$setValidity('empty', true); | |
116 | } | |
117 | } | |
118 | ||
119 | $(element).select2({ | |
120 | dropdownAutoWidth: true, | |
121 | placeholder: "Groups or Past Recipients", | |
122 | formatResult: formatItem, | |
123 | formatSelection: formatItem, | |
124 | escapeMarkup: function(m) { | |
125 | return m; | |
126 | } | |
127 | }); | |
128 | ||
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); | |
135 | } | |
136 | else { | |
137 | ngModel.$viewValue[typeKey].include.push(option.entity_id); | |
138 | arrayRemove(ngModel.$viewValue[typeKey].exclude, option.entity_id); | |
139 | } | |
140 | scope.$apply(); | |
141 | $(element).select2('close'); | |
142 | validate(); | |
143 | e.preventDefault(); | |
144 | }); | |
145 | ||
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)) { | |
150 | crmUiAlert({ | |
151 | text: ts('This mailing was generated based on search results. The search results cannot be removed.'), | |
152 | title: ts('Required') | |
153 | }); | |
154 | e.preventDefault(); | |
155 | return; | |
156 | } | |
157 | scope.$parent.$apply(function() { | |
158 | arrayRemove(ngModel.$viewValue[typeKey][option.mode], option.entity_id); | |
159 | }); | |
160 | validate(); | |
161 | e.preventDefault(); | |
162 | }); | |
163 | ||
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); | |
169 | ||
170 | scope.$watchCollection(attrs.crmAvailGroups, function() { | |
171 | scope.groups = scope.$parent.$eval(attrs.crmAvailGroups); | |
172 | }); | |
173 | scope.$watchCollection(attrs.crmAvailMailings, function() { | |
174 | scope.mailings = scope.$parent.$eval(attrs.crmAvailMailings); | |
175 | }); | |
176 | scope.$watchCollection(attrs.crmMandatoryGroups, function() { | |
177 | refreshMandatory(); | |
178 | }); | |
179 | } | |
180 | }; | |
181 | }); | |
182 | ||
183 | })(angular, CRM.$, CRM._); |