From fd7c068f028df024236a3612e8cedfe73dc93e51 Mon Sep 17 00:00:00 2001 From: Coleman Watts Date: Sat, 13 Aug 2016 00:17:48 -0400 Subject: [PATCH] CRM-19227 - Add entityRefFilters hook and support different widget types --- CRM/Core/Resources.php | 7 ++- CRM/Utils/Hook.php | 14 +++++ css/civicrm.css | 15 ++++-- js/Common.js | 117 ++++++++++++++++++++++++++--------------- 4 files changed, 106 insertions(+), 47 deletions(-) diff --git a/CRM/Core/Resources.php b/CRM/Core/Resources.php index 67ec542aa7..fef18a071c 100644 --- a/CRM/Core/Resources.php +++ b/CRM/Core/Resources.php @@ -788,8 +788,8 @@ class CRM_Core_Resources { /** * Provide a list of available entityRef filters. - * FIXME: This function doesn't really belong in this class - * @TODO: Provide a sane way to extend this list for other entities - a hook or?? + * @todo: move component filters into their respective components (e.g. CiviEvent) + * * @return array */ public static function getEntityRefFilters() { @@ -834,6 +834,7 @@ class CRM_Core_Resources { array('key' => 'country', 'value' => ts('Country'), 'entity' => 'address'), array('key' => 'gender_id', 'value' => ts('Gender')), array('key' => 'is_deceased', 'value' => ts('Deceased')), + array('key' => 'source', 'value' => ts('Contact Source'), 'type' => 'text'), ); if (in_array('CiviCase', $config->enableComponents)) { @@ -856,6 +857,8 @@ class CRM_Core_Resources { } } + CRM_Utils_Hook::entityRefFilters($filters); + return $filters; } diff --git a/CRM/Utils/Hook.php b/CRM/Utils/Hook.php index 7a35a3395c..f4b714dc90 100644 --- a/CRM/Utils/Hook.php +++ b/CRM/Utils/Hook.php @@ -2149,6 +2149,20 @@ abstract class CRM_Utils_Hook { ); } + /** + * Allows the list of filters on the EntityRef widget to be altered. + * + * @see CRM_Core_Resources::entityRefFilters + * + * @param array $filters + */ + public static function entityRefFilters(&$filters) { + self::singleton()->invoke(1, $filters, self::$_nullObject, self::$_nullObject, + self::$_nullObject, self::$_nullObject, self::$_nullObject, + 'civicrm_entityRefFilters' + ); + } + /** * This hook is called for bypass a few civicrm urls from IDS check * @param array $skip list of civicrm url; diff --git a/css/civicrm.css b/css/civicrm.css index 24d06ce55a..955218bb29 100644 --- a/css/civicrm.css +++ b/css/civicrm.css @@ -3024,19 +3024,28 @@ div.m ul#civicrm-menu, .select2-drop .crm-entityref-filters { margin-top: 4px; } -.select2-drop .crm-entityref-filters select { +.select2-drop .crm-entityref-filters select, +.select2-drop .crm-entityref-filters input { border-radius: 3px; border: 1px solid #f2f2f2; background-color: #f6f6f6; color: #494949; font-size: 11px; - max-width: 70%; + max-width: 60%; } .select2-drop .crm-entityref-filters select:hover, .select2-drop .crm-entityref-filters select:focus, -.select2-drop .crm-entityref-filters select.active { +.select2-drop .crm-entityref-filters select.active, +.select2-drop .crm-entityref-filters input { border: 1px solid #808080; } +.select2-drop .crm-entityref-filter-value { + margin-left: 1em; +} +.select2-drop .crm-entityref-filters input { + padding-left: .5em; + background-color: #fefefe; +} /* Style autocomplete results */ .crm-container .select2-results { font-size: 12px; diff --git a/js/Common.js b/js/Common.js index f3aae29500..48aaafe1c8 100644 --- a/js/Common.js +++ b/js/Common.js @@ -518,17 +518,17 @@ if (!CRM.vars) CRM.vars = {}; else { selectParams.formatInputTooShort = function() { var txt = $el.data('select-params').formatInputTooShort || $.fn.select2.defaults.formatInputTooShort.call(this); - txt += renderEntityRefFilters($el) + renderEntityRefCreateLinks($el); + txt += entityRefFiltersMarkup($el) + renderEntityRefCreateLinks($el); return txt; }; selectParams.formatNoMatches = function() { var txt = $el.data('select-params').formatNoMatches || $.fn.select2.defaults.formatNoMatches; - txt += renderEntityRefFilters($el) + renderEntityRefCreateLinks($el); + txt += entityRefFiltersMarkup($el) + renderEntityRefCreateLinks($el); return txt; }; $el.on('select2-open.crmEntity', function() { var $el = $(this); - loadEntityRefFilterOptions($el); + renderEntityRefFilterValue($el); $('#select2-drop') .off('.crmEntity') .on('click.crmEntity', 'a.crm-add-entity', function(e) { @@ -549,7 +549,7 @@ if (!CRM.vars) CRM.vars = {}; }); return false; }) - .on('change.crmEntity', 'select.crm-entityref-filter-value', function() { + .on('change.crmEntity', '.crm-entityref-filter-value', function() { var filter = $el.data('user-filter') || {}; filter.value = $(this).val(); $(this).toggleClass('active', !!filter.value); @@ -566,7 +566,8 @@ if (!CRM.vars) CRM.vars = {}; var filter = {key: $(this).val()}; $(this).toggleClass('active', !!filter.key); $el.data('user-filter', filter); - loadEntityRefFilterOptions($el); + renderEntityRefFilterValue($el); + $('.crm-entityref-filter-key', '#select2-drop').focus(); }); }); } @@ -840,24 +841,27 @@ if (!CRM.vars) CRM.vars = {}; var entity = $el.data('api-entity').toLowerCase(), filters = $.extend([], CRM.config.entityRef.filters[entity] || []), - filter = $el.data('user-filter') || {}, params = $.extend({params: {}}, $el.data('api-params') || {}).params, result = []; $.each(filters, function() { - if (typeof params[this.key] === 'undefined') { - result.push(this); + var filter = $.extend({type: 'select', 'attributes': {}, entity: entity}, this); + if (typeof params[filter.key] === 'undefined') { + result.push(filter); } - else if (this.key == 'contact_type' && typeof params.contact_sub_type === 'undefined') { - this.options = _.remove(this.options, function(option) { + else if (filter.key == 'contact_type' && typeof params.contact_sub_type === 'undefined') { + filter.options = _.remove(filter.options, function(option) { return option.key.indexOf(params.contact_type + '__') === 0; }); - result.push(this); + result.push(filter); } }); return result; } - function renderEntityRefFilters($el) { + /** + * Provide markup for entity ref filters + */ + function entityRefFiltersMarkup($el) { var filters = getEntityRefFilters($el), filter = $el.data('user-filter') || {}, @@ -869,50 +873,79 @@ if (!CRM.vars) CRM.vars = {}; '   ' + - '' + entityRefFilterValueMarkup(filter, filterSpec) + ''; + return markup; + } + + /** + * Provide markup for entity ref filter value field + */ + function entityRefFilterValueMarkup(filter, filterSpec) { + var markup = ''; + if (filterSpec) { + var attrs = '', + attributes = _.cloneDeep(filterSpec.attributes); + if (filterSpec.type !== 'select') { + attributes.type = filterSpec.type; + attributes.value = typeof filter.value !== 'undefined' ? filter.value : ''; + } + attributes.class = 'crm-entityref-filter-value' + (filter.value ? ' active' : ''); + $.each(attributes, function (attr, val) { + attrs += ' ' + attr + '="' + val + '"'; + }); + if (filterSpec.type === 'select') { + markup = ''; + if (filterSpec.options) { + markup += CRM.utils.renderOptions(filterSpec.options, filter.value); + } + markup += ''; + } else { + markup = ''; + } } - markup += ''; return markup; } /** - * Fetch options for a filter (via ajax if necessary) and populate the appropriate select list - * @param $el + * Render the entity ref filter value field */ - function loadEntityRefFilterOptions($el) { + function renderEntityRefFilterValue($el) { var - filters = getEntityRefFilters($el), filter = $el.data('user-filter') || {}, - filterSpec = filter.key ? _.find(filters, {key: filter.key}) : null, - $valField = $('.crm-entityref-filter-value', '#select2-drop'); + filterSpec = filter.key ? _.find(getEntityRefFilters($el), {key: filter.key}) : null, + $keyField = $('.crm-entityref-filter-key', '#select2-drop'), + $valField = null; if (filterSpec) { - $valField.show().val(''); - if (filterSpec.options) { - CRM.utils.setOptions($valField, filterSpec.options, false, filter.value); - } else { - $valField.prop('disabled', true); - // Fieldname may be prefixed with joins - strip those out - var fieldName = _.last(filter.key.split('.')); - CRM.api3(filterSpec.entity || $el.data('api-entity'), 'getoptions', {field: fieldName, context: 'search', sequential: 1}) - .done(function(result) { - var entity = $el.data('api-entity').toLowerCase(), - globalFilterSpec = _.find(CRM.config.entityRef.filters[entity], {key: filter.key}) || {}; - // Store options globally so we don't have to look them up again - globalFilterSpec.options = result.values; - $valField.prop('disabled', false); - CRM.utils.setOptions($valField, result.values); - $valField.val(filter.value || ''); - }); + $('.crm-entityref-filter-value', '#select2-drop').remove(); + $valField = $(entityRefFilterValueMarkup(filter, filterSpec)); + $keyField.after($valField); + if (filterSpec.type === 'select' && !filterSpec.options) { + loadEntityRefFilterOptions(filter, filterSpec, $valField, $el); } } else { - $valField.hide().val('').change(); + $('.crm-entityref-filter-value', '#select2-drop').hide().val('').change(); } } + /** + * Fetch options for a filter via ajax api + */ + function loadEntityRefFilterOptions(filter, filterSpec, $valField, $el) { + $valField.prop('disabled', true); + // Fieldname may be prefixed with joins - strip those out + var fieldName = _.last(filter.key.split('.')); + CRM.api3(filterSpec.entity, 'getoptions', {field: fieldName, context: 'search', sequential: 1}) + .done(function(result) { + var entity = $el.data('api-entity').toLowerCase(), + globalFilterSpec = _.find(CRM.config.entityRef.filters[entity], {key: filter.key}) || {}; + // Store options globally so we don't have to look them up again + globalFilterSpec.options = result.values; + $valField.prop('disabled', false); + CRM.utils.setOptions($valField, result.values); + $valField.val(filter.value || ''); + }); + } + //CRM-15598 - Override url validator method to allow relative url's (e.g. /index.htm) $.validator.addMethod("url", function(value, element) { if (/^\//.test(value)) { -- 2.25.1