(function(angular, $, _) { // example: // FIXME: participate in ngModel's validation cycle angular.module('crmMailing').directive('crmMailingRecipients', function(crmUiAlert) { return { restrict: 'AE', require: 'ngModel', scope: { ngRequired: '@' }, link: function(scope, element, attrs, ngModel) { scope.recips = ngModel.$viewValue; scope.groups = scope.$parent.$eval(attrs.crmAvailGroups); scope.mailings = scope.$parent.$eval(attrs.crmAvailMailings); refreshMandatory(); var ts = scope.ts = CRM.ts(null); /// Convert MySQL date ("yyyy-mm-dd hh:mm:ss") to JS date object scope.parseDate = function(date) { if (!angular.isString(date)) { return date; } var p = date.split(/[\- :]/); return new Date(parseInt(p[0]), parseInt(p[1]) - 1, parseInt(p[2]), parseInt(p[3]), parseInt(p[4]), parseInt(p[5])); }; /// Remove {value} from {array} function arrayRemove(array, value) { var idx = array.indexOf(value); if (idx >= 0) { array.splice(idx, 1); } } // @param string id an encoded string like "4 civicrm_mailing include" // @return Object keys: entity_id, entity_type, mode function convertValueToObj(id) { var a = id.split(" "); return {entity_id: parseInt(a[0]), entity_type: a[1], mode: a[2]}; } // @param Object mailing // @return array list of values like "4 civicrm_mailing include" function convertMailingToValues(recipients) { var r = []; angular.forEach(recipients.groups.include, function(v) { r.push(v + " civicrm_group include"); }); angular.forEach(recipients.groups.exclude, function(v) { r.push(v + " civicrm_group exclude"); }); angular.forEach(recipients.mailings.include, function(v) { r.push(v + " civicrm_mailing include"); }); angular.forEach(recipients.mailings.exclude, function(v) { r.push(v + " civicrm_mailing exclude"); }); return r; } function refreshMandatory() { if (ngModel.$viewValue && ngModel.$viewValue.groups) { scope.mandatoryGroups = _.filter(scope.$parent.$eval(attrs.crmMandatoryGroups), function(grp) { return _.contains(ngModel.$viewValue.groups.include, parseInt(grp.id)); }); scope.mandatoryIds = _.map(_.pluck(scope.$parent.$eval(attrs.crmMandatoryGroups), 'id'), function(n) { return parseInt(n); }); } else { scope.mandatoryGroups = []; scope.mandatoryIds = []; } } function isMandatory(grpId) { return _.contains(scope.mandatoryIds, parseInt(grpId)); } var refreshUI = ngModel.$render = function refresuhUI() { scope.recips = ngModel.$viewValue; if (ngModel.$viewValue) { $(element).select2('val', convertMailingToValues(ngModel.$viewValue)); validate(); refreshMandatory(); } }; // @return string HTML representing an option function formatItem(item) { if (!item.id) { // return `text` for optgroup return item.text; } var option = convertValueToObj(item.id); var icon = (option.entity_type === 'civicrm_mailing') ? 'fa-envelope' : 'fa-users'; var spanClass = (option.mode == 'exclude') ? 'crmMailing-exclude' : 'crmMailing-include'; if (option.entity_type != 'civicrm_mailing' && isMandatory(option.entity_id)) { spanClass = 'crmMailing-mandatory'; } return ' ' + item.text + ''; } function validate() { if (scope.$parent.$eval(attrs.ngRequired)) { var empty = (_.isEmpty(ngModel.$viewValue.groups.include) && _.isEmpty(ngModel.$viewValue.mailings.include)); ngModel.$setValidity('empty', !empty); } else { ngModel.$setValidity('empty', true); } } var rcpAjaxState = { input: '', entity: 'civicrm_group', type: 'include', page_n: 0, page_i: 0, }; $(element).select2({ width: '36em', dropdownAutoWidth: true, placeholder: "Groups or Past Recipients", formatResult: formatItem, formatSelection: formatItem, escapeMarkup: function(m) { return m; }, multiple: true, initSelection: function(el, cb) { var values = el.val().split(','); var gids = []; var mids = []; for (var i in values) { var dv = convertValueToObj(values[i]); if (dv.entity_type == 'civicrm_group') { gids.push(dv.entity_id); } else if (dv.entity_type == 'civicrm_mailing') { mids.push(dv.entity_id); } } CRM.api3('Group', 'getlist', { params: { id: { IN: gids } }, extra: ["is_hidden"] }).then( function(glist) { CRM.api3('Mailing', 'getlist', { params: { id: { IN: mids } } }).then( function(mlist) { var datamap = []; var groupNames = []; var civiMails = []; $(glist.values).each(function (idx, group) { var key = group.id + ' civicrm_group include'; groupNames.push({id: parseInt(group.id), title: group.label, is_hidden: group.extra.is_hidden}); if (values.indexOf(key) >= 0) { datamap.push({id: key, text: group.label}); } key = group.id + ' civicrm_group exclude'; if (values.indexOf(key) >= 0) { datamap.push({id: key, text: group.label}); } }); $(mlist.values).each(function (idx, group) { var key = group.id + ' civicrm_mailing include'; civiMails.push({id: parseInt(group.id), name: group.label}); if (values.indexOf(key) >= 0) { datamap.push({id: key, text: group.label}); } key = group.id + ' civicrm_mailing exclude'; if (values.indexOf(key) >= 0) { datamap.push({id: key, text: group.label}); } }); scope.$parent.crmMailingConst.groupNames = groupNames; scope.$parent.crmMailingConst.civiMails = civiMails; refreshMandatory(); cb(datamap); }); }); }, ajax: { url: CRM.url('civicrm/ajax/rest'), quietMillis: 300, data: function(input, page_num) { if (page_num <= 1) { rcpAjaxState = { input: input, entity: 'civicrm_group', type: 'include', page_n: 0, }; } rcpAjaxState.page_i = page_num - rcpAjaxState.page_n; var filterParams = {}; switch(rcpAjaxState.entity) { case 'civicrm_group': filterParams = { is_hidden: 0, is_active: 1, group_type: {"LIKE": "%2%"} }; break; case 'civicrm_mailing': filterParams = { is_hidden: 0, is_active: 1 }; break; } var params = { input: input, page_num: rcpAjaxState.page_i, params: filterParams, }; return params; }, transport: function(params) { switch(rcpAjaxState.entity) { case 'civicrm_group': CRM.api3('Group', 'getlist', params.data).then(params.success, params.error); break; case 'civicrm_mailing': params.data.params.options = { sort: "is_archived asc, scheduled_date desc" }; CRM.api3('Mailing', 'getlist', params.data).then(params.success, params.error); break; } }, results: function(data) { results = { children: $.map(data.values, function(obj) { return { id: obj.id + ' ' + rcpAjaxState.entity + ' ' + rcpAjaxState.type, text: obj.label }; }) }; if (rcpAjaxState.page_i == 1 && data.count) { results.text = ts((rcpAjaxState.type == 'include'? 'Include ' : 'Exclude ') + (rcpAjaxState.entity == 'civicrm_group'? 'Group' : 'Mailing')); } more = data.more_results || !(rcpAjaxState.entity == 'civicrm_mailing' && rcpAjaxState.type == 'exclude'); if (more && !data.more_results) { if (rcpAjaxState.type == 'include') { rcpAjaxState.type = 'exclude'; } else { rcpAjaxState.type = 'include'; rcpAjaxState.entity = 'civicrm_mailing'; } rcpAjaxState.page_n += rcpAjaxState.page_i; } return { more: more, results: [ results ] }; }, }, }); $(element).on('select2-selecting', function(e) { var option = convertValueToObj(e.val); var typeKey = option.entity_type == 'civicrm_mailing' ? 'mailings' : 'groups'; if (option.mode == 'exclude') { ngModel.$viewValue[typeKey].exclude.push(option.entity_id); arrayRemove(ngModel.$viewValue[typeKey].include, option.entity_id); } else { ngModel.$viewValue[typeKey].include.push(option.entity_id); arrayRemove(ngModel.$viewValue[typeKey].exclude, option.entity_id); } scope.$apply(); $(element).select2('close'); validate(); e.preventDefault(); }); $(element).on("select2-removing", function(e) { var option = convertValueToObj(e.val); var typeKey = option.entity_type == 'civicrm_mailing' ? 'mailings' : 'groups'; if (typeKey == 'groups' && isMandatory(option.entity_id)) { crmUiAlert({ text: ts('This mailing was generated based on search results. The search results cannot be removed.'), title: ts('Required') }); e.preventDefault(); return; } scope.$parent.$apply(function() { arrayRemove(ngModel.$viewValue[typeKey][option.mode], option.entity_id); }); validate(); e.preventDefault(); }); scope.$watchCollection("recips.groups.include", refreshUI); scope.$watchCollection("recips.groups.exclude", refreshUI); scope.$watchCollection("recips.mailings.include", refreshUI); scope.$watchCollection("recips.mailings.exclude", refreshUI); setTimeout(refreshUI, 50); scope.$watchCollection(attrs.crmAvailGroups, function() { scope.groups = scope.$parent.$eval(attrs.crmAvailGroups); }); scope.$watchCollection(attrs.crmAvailMailings, function() { scope.mailings = scope.$parent.$eval(attrs.crmAvailMailings); }); scope.$watchCollection(attrs.crmMandatoryGroups, function() { refreshMandatory(); }); } }; }); })(angular, CRM.$, CRM._);