| 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._); |