CRM-19227 - Add entityRefFilters hook and support different widget types
authorColeman Watts <coleman@civicrm.org>
Sat, 13 Aug 2016 04:17:48 +0000 (00:17 -0400)
committerColeman Watts <coleman@civicrm.org>
Sat, 13 Aug 2016 16:32:20 +0000 (12:32 -0400)
CRM/Core/Resources.php
CRM/Utils/Hook.php
css/civicrm.css
js/Common.js

index 67ec542aa7d48b500c933497307379ede38a8493..fef18a071ca8229095c824fb04a0199a6d10c1da 100644 (file)
@@ -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;
   }
 
index 7a35a3395c4e3b7b9731a3e51e4e8f9de8e6c890..f4b714dc9037e1385bb886886b1a37622f851490 100644 (file)
@@ -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;
index 24d06ce55a39073e613d8d05d2d3f5ad909cbb93..955218bb292b20e038bc7eb29bee9ecb05ad81fd 100644 (file)
@@ -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;
index f3aae295005b78780b1fbd03472c18dec6cbbce2..48aaafe1c893981b331bb93c22a3eb2b38075db9 100644 (file)
@@ -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 = {};
       '<select class="crm-entityref-filter-key' + (filter.key ? ' active' : '') + '">' +
       '<option value="">' + ts('Refine search...') + '</option>' +
       CRM.utils.renderOptions(filters, filter.key) +
-      '</select> &nbsp; ' +
-      '<select class="crm-entityref-filter-value' + (filter.key ? ' active"' : '"') + (filter.key ? '' : ' style="display:none;"') + '>' +
-      '<option value="">' + ts('- select -') + '</option>';
-    if (filterSpec && filterSpec.options) {
-      markup += CRM.utils.renderOptions(filterSpec.options, filter.value);
+      '</select>' + entityRefFilterValueMarkup(filter, filterSpec) + '</div>';
+    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 = '<select' + attrs + '><option value="">' + ts('- select -') + '</option>';
+        if (filterSpec.options) {
+          markup += CRM.utils.renderOptions(filterSpec.options, filter.value);
+        }
+        markup += '</select>';
+      } else {
+        markup = '<input' + attrs + '/>';
+      }
     }
-    markup += '</select></div>';
     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)) {