Merge pull request #6570 from eileenmcnaughton/CRM-17023
[civicrm-core.git] / ang / crmMailing / Recipients.js
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);
101 var icon = (option.entity_type === 'civicrm_mailing') ? 'EnvelopeIn.gif' : 'group.png';
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 }
106 return '<img src="' + CRM.config.resourceBase + 'i/' + icon + '" height="12" width="12" /> <span class="' + spanClass + '">' + item.text + '</span>';
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._);