From b396fc596635a3c13e357b0282a39786e2012207 Mon Sep 17 00:00:00 2001 From: Tim Otten Date: Tue, 7 Apr 2015 23:26:31 -0700 Subject: [PATCH] CRM-16145 - crmMailing - Split complex directives into separate files --- .../{preview.html => BlockPreview.html} | 0 ang/crmMailing/BlockPreview.js | 65 +++ .../{review.html => BlockReview.html} | 0 ang/crmMailing/BlockReview.js | 22 + ang/crmMailing/FromAddress.js | 26 ++ ang/crmMailing/RadioDate.js | 76 ++++ .../recipients.html => Recipients.html} | 0 ang/crmMailing/Recipients.js | 183 ++++++++ ang/crmMailing/ReviewBool.js | 28 ++ ang/crmMailing/Token.js | 28 ++ ang/crmMailing/directives.js | 410 ------------------ 11 files changed, 428 insertions(+), 410 deletions(-) rename ang/crmMailing/{preview.html => BlockPreview.html} (100%) create mode 100644 ang/crmMailing/BlockPreview.js rename ang/crmMailing/{review.html => BlockReview.html} (100%) create mode 100644 ang/crmMailing/BlockReview.js create mode 100644 ang/crmMailing/FromAddress.js create mode 100644 ang/crmMailing/RadioDate.js rename ang/crmMailing/{directive/recipients.html => Recipients.html} (100%) create mode 100644 ang/crmMailing/Recipients.js create mode 100644 ang/crmMailing/ReviewBool.js create mode 100644 ang/crmMailing/Token.js delete mode 100644 ang/crmMailing/directives.js diff --git a/ang/crmMailing/preview.html b/ang/crmMailing/BlockPreview.html similarity index 100% rename from ang/crmMailing/preview.html rename to ang/crmMailing/BlockPreview.html diff --git a/ang/crmMailing/BlockPreview.js b/ang/crmMailing/BlockPreview.js new file mode 100644 index 0000000000..0d2c5ba395 --- /dev/null +++ b/ang/crmMailing/BlockPreview.js @@ -0,0 +1,65 @@ +(function(angular, $, _) { + // example:
+ // note: the directive defines a variable called "preview" with any inputs supplied by the user (e.g. the target recipient for an example mailing) + + angular.module('crmMailing').directive('crmMailingBlockPreview', function(crmUiHelp) { + return { + templateUrl: '~/crmMailing/BlockPreview.html', + link: function(scope, elm, attr) { + scope.$watch(attr.crmMailing, function(newValue) { + scope.mailing = newValue; + }); + scope.crmMailingConst = CRM.crmMailing; + scope.ts = CRM.ts(null); + scope.hs = crmUiHelp({file: 'CRM/Mailing/MailingUI'}); + scope.testContact = {email: CRM.crmMailing.defaultTestEmail}; + scope.testGroup = {gid: null}; + + scope.doPreview = function(mode) { + scope.$eval(attr.onPreview, { + preview: {mode: mode} + }); + }; + scope.doSend = function doSend(recipient) { + scope.$eval(attr.onSend, { + preview: {recipient: recipient} + }); + }; + + scope.previewTestGroup = function(e) { + var $dialog = $(this); + $dialog.html('
').parent().find('button[data-op=yes]').prop('disabled', true); + $dialog.dialog('option', 'title', ts('Send to %1', {1: _.pluck(_.where(scope.crmMailingConst.groupNames, {id: scope.testGroup.gid}), 'title')[0]})); + CRM.api3('contact', 'get', { + group: scope.testGroup.gid, + options: {limit: 0}, + return: 'display_name,email' + }).done(function(data) { + var count = 0, + // Fixme: should this be in a template? + markup = '
    '; + _.each(data.values, function(row) { + // Fixme: contact api doesn't seem capable of filtering out contacts with no email, so we're doing it client-side + if (row.email) { + count++; + markup += '
  1. ' + row.display_name + ' - ' + row.email + '
  2. '; + } + }); + markup += '
'; + markup = '

' + ts('A test message will be sent to %1 people:', {1: count}) + '

' + markup; + if (!count) { + markup = '
' + + (data.count ? ts('None of the contacts in this group have an email address.') : ts('Group is empty.')) + + '
'; + } + $dialog + .html(markup) + .trigger('crmLoad') + .parent().find('button[data-op=yes]').prop('disabled', !count); + }); + }; + } + }; + }); + +})(angular, CRM.$, CRM._); diff --git a/ang/crmMailing/review.html b/ang/crmMailing/BlockReview.html similarity index 100% rename from ang/crmMailing/review.html rename to ang/crmMailing/BlockReview.html diff --git a/ang/crmMailing/BlockReview.js b/ang/crmMailing/BlockReview.js new file mode 100644 index 0000000000..1a0220f328 --- /dev/null +++ b/ang/crmMailing/BlockReview.js @@ -0,0 +1,22 @@ +(function(angular, $, _) { + + angular.module('crmMailing').directive('crmMailingBlockReview', function (crmMailingPreviewMgr) { + return { + scope: { + crmMailing: '@' + }, + templateUrl: '~/crmMailing/BlockReview.html', + link: function (scope, elm, attr) { + scope.$parent.$watch(attr.crmMailing, function(newValue){ + scope.mailing = newValue; + }); + scope.crmMailingConst = CRM.crmMailing; + scope.ts = CRM.ts(null); + scope.previewMailing = function previewMailing(mailing, mode) { + return crmMailingPreviewMgr.preview(mailing, mode); + }; + } + }; + }); + +})(angular, CRM.$, CRM._); diff --git a/ang/crmMailing/FromAddress.js b/ang/crmMailing/FromAddress.js new file mode 100644 index 0000000000..9034da6a22 --- /dev/null +++ b/ang/crmMailing/FromAddress.js @@ -0,0 +1,26 @@ +(function(angular, $, _) { + // Convert between a mailing "From Address" (mailing.from_name,mailing.from_email) and a unified label ("Name" ) + // example: + // NOTE: This really doesn't belong in a directive. I've tried (and failed) to make this work with a getterSetter binding, eg + // + // FIXME: participate in ngModel's validation cycle + angular.module('crmMailing').directive('crmMailingRecipients', function(crmUiAlert) { + return { + restrict: 'AE', + require: 'ngModel', + scope: { + crmAvailGroups: '@', // available groups + crmAvailMailings: '@', // available mailings + crmMandatoryGroups: '@', // hard-coded/mandatory groups + ngRequired: '@' + }, + templateUrl: '~/crmMailing/Recipients.html', + 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') ? 'EnvelopeIn.gif' : 'group.png'; + 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); + } + } + + $(element).select2({ + dropdownAutoWidth: true, + placeholder: "Groups or Past Recipients", + formatResult: formatItem, + formatSelection: formatItem, + escapeMarkup: function(m) { + return m; + } + }); + + $(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._); diff --git a/ang/crmMailing/ReviewBool.js b/ang/crmMailing/ReviewBool.js new file mode 100644 index 0000000000..83bd7090db --- /dev/null +++ b/ang/crmMailing/ReviewBool.js @@ -0,0 +1,28 @@ +(function(angular, $, _) { + angular.module('crmMailing').directive('crmMailingReviewBool', function() { + return { + scope: { + crmOn: '@', + crmTitle: '@' + }, + template: '{{evalTitle}} ', + link: function(scope, element, attrs) { + function refresh() { + if (scope.$parent.$eval(attrs.crmOn)) { + scope.spanClasses = {'crmMailing-active': true}; + scope.iconClasses = {'ui-icon-check': true}; + } + else { + scope.spanClasses = {'crmMailing-inactive': true}; + scope.iconClasses = {'ui-icon-close': true}; + } + scope.evalTitle = scope.$parent.$eval(attrs.crmTitle); + } + + refresh(); + scope.$parent.$watch(attrs.crmOn, refresh); + scope.$parent.$watch(attrs.crmTitle, refresh); + } + }; + }); +})(angular, CRM.$, CRM._); diff --git a/ang/crmMailing/Token.js b/ang/crmMailing/Token.js new file mode 100644 index 0000000000..f5d94a5967 --- /dev/null +++ b/ang/crmMailing/Token.js @@ -0,0 +1,28 @@ +(function(angular, $, _) { + // example: + // WISHLIST: Instead of global CRM.crmMailing.mailTokens, accept token list as an input + angular.module('crmMailing').directive('crmMailingToken', function() { + return { + require: '^crmUiIdScope', + scope: { + onSelect: '@' + }, + template: '', + link: function(scope, element, attrs, crmUiIdCtrl) { + $(element).addClass('crm-action-menu action-icon-token').select2({ + width: "12em", + dropdownAutoWidth: true, + data: CRM.crmMailing.mailTokens, + placeholder: ts('Tokens') + }); + $(element).on('select2-selecting', function(e) { + e.preventDefault(); + $(element).select2('close').select2('val', ''); + scope.$parent.$eval(attrs.onSelect, { + token: {name: e.val} + }); + }); + } + }; + }); +})(angular, CRM.$, CRM._); diff --git a/ang/crmMailing/directives.js b/ang/crmMailing/directives.js deleted file mode 100644 index e82c8b59fa..0000000000 --- a/ang/crmMailing/directives.js +++ /dev/null @@ -1,410 +0,0 @@ -(function (angular, $, _) { - - // example:
- // note: the directive defines a variable called "preview" with any inputs supplied by the user (e.g. the target recipient for an example mailing) - angular.module('crmMailing').directive('crmMailingBlockPreview', function (crmUiHelp) { - return { - templateUrl: '~/crmMailing/preview.html', - link: function (scope, elm, attr) { - scope.$watch(attr.crmMailing, function(newValue){ - scope.mailing = newValue; - }); - scope.crmMailingConst = CRM.crmMailing; - scope.ts = CRM.ts(null); - scope.hs = crmUiHelp({file: 'CRM/Mailing/MailingUI'}); - scope.testContact = {email: CRM.crmMailing.defaultTestEmail}; - scope.testGroup = {gid: null}; - - scope.doPreview = function(mode) { - scope.$eval(attr.onPreview, { - preview: {mode: mode} - }); - }; - scope.doSend = function doSend(recipient) { - scope.$eval(attr.onSend, { - preview: {recipient: recipient} - }); - }; - - scope.previewTestGroup = function(e) { - var $dialog = $(this); - $dialog.html('
').parent().find('button[data-op=yes]').prop('disabled', true); - $dialog.dialog('option', 'title', ts('Send to %1', {1: _.pluck(_.where(scope.crmMailingConst.groupNames, {id: scope.testGroup.gid}), 'title')[0]})); - CRM.api3('contact', 'get', {group: scope.testGroup.gid, options: {limit: 0}, return: 'display_name,email'}).done(function(data) { - var count = 0, - // Fixme: should this be in a template? - markup = '
    '; - _.each(data.values, function(row) { - // Fixme: contact api doesn't seem capable of filtering out contacts with no email, so we're doing it client-side - if (row.email) { - count++; - markup += '
  1. ' + row.display_name + ' - ' + row.email + '
  2. '; - } - }); - markup += '
'; - markup = '

' + ts('A test message will be sent to %1 people:', {1: count}) + '

' + markup; - if (!count) { - markup = '
' + - (data.count ? ts('None of the contacts in this group have an email address.') : ts('Group is empty.')) + - '
'; - } - $dialog - .html(markup) - .trigger('crmLoad') - .parent().find('button[data-op=yes]').prop('disabled', !count); - }); - }; - } - }; - }); - - angular.module('crmMailing').directive('crmMailingBlockReview', function (crmMailingPreviewMgr) { - return { - scope: { - crmMailing: '@' - }, - templateUrl: '~/crmMailing/review.html', - link: function (scope, elm, attr) { - scope.$parent.$watch(attr.crmMailing, function(newValue){ - scope.mailing = newValue; - }); - scope.crmMailingConst = CRM.crmMailing; - scope.ts = CRM.ts(null); - scope.previewMailing = function previewMailing(mailing, mode) { - return crmMailingPreviewMgr.preview(mailing, mode); - }; - } - }; - }); - - // Convert between a mailing "From Address" (mailing.from_name,mailing.from_email) and a unified label ("Name" ) - // example: - // NOTE: This really doesn't belong in a directive. I've tried (and failed) to make this work with a getterSetter binding, eg - // - // WISHLIST: Instead of global CRM.crmMailing.mailTokens, accept token list as an input - angular.module('crmMailing').directive('crmMailingToken', function () { - return { - require: '^crmUiIdScope', - scope: { - onSelect: '@' - }, - template: '', - link: function (scope, element, attrs, crmUiIdCtrl) { - $(element).addClass('crm-action-menu action-icon-token').select2({ - width: "12em", - dropdownAutoWidth: true, - data: CRM.crmMailing.mailTokens, - placeholder: ts('Tokens') - }); - $(element).on('select2-selecting', function (e) { - e.preventDefault(); - $(element).select2('close').select2('val', ''); - scope.$parent.$eval(attrs.onSelect, { - token: {name: e.val} - }); - }); - } - }; - }); - - // example: - // FIXME: participate in ngModel's validation cycle - angular.module('crmMailing').directive('crmMailingRecipients', function (crmUiAlert) { - return { - restrict: 'AE', - require: 'ngModel', - scope: { - crmAvailGroups: '@', // available groups - crmAvailMailings: '@', // available mailings - crmMandatoryGroups: '@', // hard-coded/mandatory groups - ngRequired: '@' - }, - templateUrl: '~/crmMailing/directive/recipients.html', - 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') ? 'EnvelopeIn.gif' : 'group.png'; - 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); - } - } - - $(element).select2({ - dropdownAutoWidth: true, - placeholder: "Groups or Past Recipients", - formatResult: formatItem, - formatSelection: formatItem, - escapeMarkup: function (m) { - return m; - } - }); - - $(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._); -- 2.25.1