From: colemanw Date: Fri, 9 Jun 2023 12:06:13 +0000 (-0400) Subject: Afform - Enable search operators to be exposed on the form X-Git-Url: https://vcs.fsf.org/?a=commitdiff_plain;h=64afffad3a34c93475dec65f059ceb50fded3443;p=civicrm-core.git Afform - Enable search operators to be exposed on the form --- diff --git a/ext/afform/admin/Civi/AfformAdmin/AfformAdminMeta.php b/ext/afform/admin/Civi/AfformAdmin/AfformAdminMeta.php index ed1ef31e78..e788dfa74f 100644 --- a/ext/afform/admin/Civi/AfformAdmin/AfformAdminMeta.php +++ b/ext/afform/admin/Civi/AfformAdmin/AfformAdminMeta.php @@ -31,6 +31,7 @@ class AfformAdminMeta { } return [ 'afform_type' => $afformTypes, + 'search_operators' => \Civi\Afform\Utils::getSearchOperators(), ]; } @@ -88,7 +89,7 @@ class AfformAdminMeta { 'checkPermissions' => FALSE, 'loadOptions' => ['id', 'label'], 'action' => 'create', - 'select' => ['name', 'label', 'input_type', 'input_attrs', 'required', 'options', 'help_pre', 'help_post', 'serialize', 'data_type', 'entity', 'fk_entity', 'readonly'], + 'select' => ['name', 'label', 'input_type', 'input_attrs', 'required', 'options', 'help_pre', 'help_post', 'serialize', 'data_type', 'entity', 'fk_entity', 'readonly', 'operators'], 'where' => [['deprecated', '=', FALSE], ['input_type', 'IS NOT NULL']], ]; if (in_array($entityName, \CRM_Contact_BAO_ContactType::basicTypes(TRUE), TRUE)) { diff --git a/ext/afform/admin/ang/afGuiEditor/elements/afGuiField-menu.html b/ext/afform/admin/ang/afGuiEditor/elements/afGuiField-menu.html index 61a5a76c14..7e729c1842 100644 --- a/ext/afform/admin/ang/afGuiEditor/elements/afGuiField-menu.html +++ b/ext/afform/admin/ang/afGuiEditor/elements/afGuiField-menu.html @@ -92,10 +92,18 @@ {{:: ts('Search by range') }} -
  • -
    +
  • +
    - + + + + +
    +
    + +
    diff --git a/ext/afform/admin/ang/afGuiEditor/elements/afGuiField.component.js b/ext/afform/admin/ang/afGuiEditor/elements/afGuiField.component.js index 087b27832e..69c304e5ab 100644 --- a/ext/afform/admin/ang/afGuiEditor/elements/afGuiField.component.js +++ b/ext/afform/admin/ang/afGuiEditor/elements/afGuiField.component.js @@ -47,6 +47,11 @@ inputTypes.push(type); } }); + this.searchOperators = CRM.afAdmin.search_operators; + // If field has limited operators, set appropriately + if (ctrl.fieldDefn.operators && ctrl.fieldDefn.operators.length) { + this.searchOperators = _.pick(this.searchOperators, ctrl.fieldDefn.operators); + } setDateOptions(); }; @@ -290,7 +295,24 @@ } }; - // Getter/setter for definition props + // Getter/setter for search_operator and expose_operator combo-field + // The expose_operator flag changes the behavior of the search_operator field + // to either set the value on the backend, or set the default value for the user-select list on the form + $scope.getSetOperator = function(val) { + if (arguments.length) { + // _EXPOSE_ is not a real option for search_operator, instead it sets the expose_operator boolean + getSet('expose_operator', val === '_EXPOSE_'); + if (val === '_EXPOSE_') { + getSet('search_operator', _.keys(ctrl.searchOperators)[0]); + } else { + getSet('search_operator', val); + } + return val; + } + return getSet('expose_operator') ? '_EXPOSE_' : getSet('search_operator'); + }; + + // Generic getter/setter for definition props $scope.getSet = function(propName) { return _.wrap(propName, getSet); }; @@ -344,24 +366,6 @@ $scope.editingOptions = val; }; - this.searchOperators = { - '': ts('Auto'), - '=': '=', - '!=': '≠', - '>': '>', - '<': '<', - '>=': '≥', - '<=': '≤', - 'CONTAINS': ts('Contains'), - 'NOT CONTAINS': ts("Doesn't Contain"), - 'IN': ts('Is One Of'), - 'NOT IN': ts('Not One Of'), - 'LIKE': ts('Is Like'), - 'NOT LIKE': ts('Not Like'), - 'REGEXP': ts('Matches Pattern'), - 'NOT REGEXP': ts("Doesn't Match Pattern"), - }; - // Returns a reference to a path n-levels deep within an object function drillDown(parent, path) { var container = parent; diff --git a/ext/afform/core/Civi/Afform/AfformMetadataInjector.php b/ext/afform/core/Civi/Afform/AfformMetadataInjector.php index 0d6df28eb2..d793f59082 100644 --- a/ext/afform/core/Civi/Afform/AfformMetadataInjector.php +++ b/ext/afform/core/Civi/Afform/AfformMetadataInjector.php @@ -121,6 +121,19 @@ class AfformMetadataInjector { // On a search form, search_range will present a pair of fields (or possibly 3 fields for date select + range) $isSearchRange = !empty($fieldDefn['search_range']) && \CRM_Utils_JS::decode($fieldDefn['search_range']); + // On a search form, the exposed operator requires a list of options. + if (!empty($fieldDefn['expose_operator'])) { + $operators = Utils::getSearchOperators(); + // If 'operators' is present in the field definition, use it as a limiter + // Afform expects 'operators' in the fieldDefn to be associative key/label, not just a flat array + // like it is in the schema. + if (!empty($fieldInfo['operators'])) { + $operators = array_intersect_key($operators, array_flip($fieldInfo['operators'])); + } + $fieldDefn['operators'] = \CRM_Utils_JS::encode($operators); + } + unset($fieldInfo['operators']); + // Default placeholder for select inputs if ($inputType === 'Select' || $inputType === 'ChainSelect') { $fieldInfo['input_attrs']['placeholder'] = E::ts('Select'); diff --git a/ext/afform/core/Civi/Afform/FormDataModel.php b/ext/afform/core/Civi/Afform/FormDataModel.php index 646f2641f5..a5e07402ea 100644 --- a/ext/afform/core/Civi/Afform/FormDataModel.php +++ b/ext/afform/core/Civi/Afform/FormDataModel.php @@ -207,10 +207,14 @@ class FormDataModel { if ($action === 'get' && strpos($fieldName, '.')) { $namesToMatch[] = substr($fieldName, 0, strrpos($fieldName, '.')); } + $select = ['name', 'label', 'input_type', 'input_attrs', 'help_pre', 'help_post', 'options', 'fk_entity', 'required']; + if ($action === 'get') { + $select[] = 'operators'; + } $params = [ 'action' => $action, 'where' => [['name', 'IN', $namesToMatch]], - 'select' => ['name', 'label', 'input_type', 'input_attrs', 'help_pre', 'help_post', 'options', 'fk_entity', 'required'], + 'select' => $select, 'loadOptions' => ['id', 'label'], // If the admin included this field on the form, then it's OK to get metadata about the field regardless of user permissions. 'checkPermissions' => FALSE, diff --git a/ext/afform/core/Civi/Afform/Utils.php b/ext/afform/core/Civi/Afform/Utils.php index c183ae6794..2dc219cb75 100644 --- a/ext/afform/core/Civi/Afform/Utils.php +++ b/ext/afform/core/Civi/Afform/Utils.php @@ -49,4 +49,31 @@ class Utils { return $sorter->sort(); } + /** + * Subset of APIv4 operators that are appropriate for use on Afforms + * + * This list may be further reduced by fields which declare a limited number of + * operators in their metadata. + * + * @return array + */ + public static function getSearchOperators() { + return [ + '=' => '=', + '!=' => '≠', + '>' => '>', + '<' => '<', + '>=' => '≥', + '<=' => '≤', + 'CONTAINS' => ts('Contains'), + 'NOT CONTAINS' => ts("Doesn't Contain"), + 'IN' => ts('Is One Of'), + 'NOT IN' => ts('Not One Of'), + 'LIKE' => ts('Is Like'), + 'NOT LIKE' => ts('Not Like'), + 'REGEXP' => ts('Matches Pattern'), + 'NOT REGEXP' => ts("Doesn't Match Pattern"), + ]; + } + } diff --git a/ext/afform/core/ang/af/afField.component.js b/ext/afform/core/ang/af/afField.component.js index 1e6173d58a..f579eedb79 100644 --- a/ext/afform/core/ang/af/afField.component.js +++ b/ext/afform/core/ang/af/afField.component.js @@ -37,6 +37,10 @@ namePrefix = this.fieldName.substr(0, this.fieldName.length - this.defn.name.length); } + if (this.defn.search_operator) { + this.search_operator = this.defn.search_operator; + } + // is_primary field - watch others in this afRepeat block to ensure only one is selected if (ctrl.fieldName === 'is_primary' && 'repeatIndex' in $scope.dataProvider) { $scope.$watch('dataProvider.afRepeat.getEntityController().getData()', function (items, prev) { @@ -226,22 +230,26 @@ }; }; + this.onChangeOperator = function() { + $scope.dataProvider.getFieldData()[ctrl.fieldName] = {}; + }; + // Getter/Setter function for most fields (except select & entityRef) $scope.getSetValue = function(val) { var currentVal = $scope.dataProvider.getFieldData()[ctrl.fieldName]; // Setter if (arguments.length) { - if (ctrl.defn.search_operator) { + if (ctrl.search_operator) { if (typeof currentVal !== 'object') { $scope.dataProvider.getFieldData()[ctrl.fieldName] = {}; } - return ($scope.dataProvider.getFieldData()[ctrl.fieldName][ctrl.defn.search_operator] = val); + return ($scope.dataProvider.getFieldData()[ctrl.fieldName][ctrl.search_operator] = val); } return ($scope.dataProvider.getFieldData()[ctrl.fieldName] = val); } // Getter - if (ctrl.defn.search_operator) { - return (currentVal || {})[ctrl.defn.search_operator]; + if (ctrl.search_operator) { + return (currentVal || {})[ctrl.search_operator]; } return currentVal; }; @@ -261,11 +269,11 @@ else if (ctrl.defn.search_range) { return ($scope.dataProvider.getFieldData()[ctrl.fieldName]['>='] = val); } - else if (ctrl.defn.search_operator) { + else if (ctrl.search_operator) { if (typeof currentVal !== 'object') { $scope.dataProvider.getFieldData()[ctrl.fieldName] = {}; } - return ($scope.dataProvider.getFieldData()[ctrl.fieldName][ctrl.defn.search_operator] = val); + return ($scope.dataProvider.getFieldData()[ctrl.fieldName][ctrl.search_operator] = val); } return ($scope.dataProvider.getFieldData()[ctrl.fieldName] = val); } @@ -277,8 +285,8 @@ else if (ctrl.defn.search_range) { return currentVal['>=']; } - else if (ctrl.defn.search_operator) { - return (currentVal || {})[ctrl.defn.search_operator]; + else if (ctrl.search_operator) { + return (currentVal || {})[ctrl.search_operator]; } return currentVal; }; diff --git a/ext/afform/core/ang/af/afField.html b/ext/afform/core/ang/af/afField.html index 1965f74c7f..d7e6b7a165 100644 --- a/ext/afform/core/ang/af/afField.html +++ b/ext/afform/core/ang/af/afField.html @@ -3,5 +3,6 @@ *

    {{:: $ctrl.defn.help_pre }}

    -
    +
    +

    {{:: $ctrl.defn.help_post }}

    diff --git a/ext/afform/core/ang/af/afFieldWithSearchOperator.html b/ext/afform/core/ang/af/afFieldWithSearchOperator.html new file mode 100644 index 0000000000..3260716756 --- /dev/null +++ b/ext/afform/core/ang/af/afFieldWithSearchOperator.html @@ -0,0 +1,4 @@ + +
    diff --git a/ext/afform/core/ang/afCore.css b/ext/afform/core/ang/afCore.css index f0ce53c951..35e095925c 100644 --- a/ext/afform/core/ang/afCore.css +++ b/ext/afform/core/ang/afCore.css @@ -30,6 +30,10 @@ af-form { display: block; } +#bootstrap-theme .input-group .crm-af-field { + display: inline-block; +} + [af-repeat-item] { position: relative; }