From: Coleman Watts Date: Wed, 31 Mar 2021 18:30:12 +0000 (-0400) Subject: Afform - Support search-by-range and search-by-multiple-values X-Git-Url: https://vcs.fsf.org/?a=commitdiff_plain;h=2aa241b54a3ff0e5ff82f66414d40e1c618160fc;p=civicrm-core.git Afform - Support search-by-range and search-by-multiple-values This adds support for filter operators in SearchKit. It does not expose an operator selector to Afform but allows an operator to be implied through the type of field configured. e.g. a multiselect implies the IN operator & a range select implies BETWEEN. --- diff --git a/ext/afform/admin/Civi/AfformAdmin/AfformAdminMeta.php b/ext/afform/admin/Civi/AfformAdmin/AfformAdminMeta.php index 1b20accd47..2c572eb9b9 100644 --- a/ext/afform/admin/Civi/AfformAdmin/AfformAdminMeta.php +++ b/ext/afform/admin/Civi/AfformAdmin/AfformAdminMeta.php @@ -234,7 +234,8 @@ class AfformAdminMeta { ]; } - $data['dateRanges'] = \CRM_Utils_Array::makeNonAssociative(\CRM_Core_OptionGroup::values('relative_date_filters'), 'id', 'label'); + $dateRanges = \CRM_Utils_Array::makeNonAssociative(\CRM_Core_OptionGroup::values('relative_date_filters'), 'id', 'label'); + $data['dateRanges'] = array_merge([['id' => '{}', 'label' => E::ts('Choose Date Range')]], $dateRanges); return $data; } diff --git a/ext/afform/admin/ang/afGuiEditor/elements/afGuiButton-menu.html b/ext/afform/admin/ang/afGuiEditor/elements/afGuiButton-menu.html index d1069acf94..d121b827d6 100644 --- a/ext/afform/admin/ang/afGuiEditor/elements/afGuiButton-menu.html +++ b/ext/afform/admin/ang/afGuiEditor/elements/afGuiButton-menu.html @@ -7,4 +7,4 @@ -
  • {{:: ts('Delete this button') }}
  • +
  • {{:: ts('Delete this button') }}
  • diff --git a/ext/afform/admin/ang/afGuiEditor/elements/afGuiContainer-menu.html b/ext/afform/admin/ang/afGuiEditor/elements/afGuiContainer-menu.html index 63d2ff6d94..9121bfeb7d 100644 --- a/ext/afform/admin/ang/afGuiEditor/elements/afGuiContainer-menu.html +++ b/ext/afform/admin/ang/afGuiEditor/elements/afGuiContainer-menu.html @@ -33,4 +33,4 @@
  • -
  • {{ !block ? ts('Delete this container') : ts('Delete this block') }}
  • +
  • {{ !block ? ts('Delete this container') : ts('Delete this block') }}
  • diff --git a/ext/afform/admin/ang/afGuiEditor/elements/afGuiField-menu.html b/ext/afform/admin/ang/afGuiEditor/elements/afGuiField-menu.html index 853be65e92..75652a8bc6 100644 --- a/ext/afform/admin/ang/afGuiEditor/elements/afGuiField-menu.html +++ b/ext/afform/admin/ang/afGuiEditor/elements/afGuiField-menu.html @@ -8,28 +8,41 @@
  • - + {{:: ts('Required') }}
  • - + {{:: ts('Label') }}
  • - + {{:: ts('Pre help text') }}
  • - + {{:: ts('Post help text') }}
  • + +
  • + + + {{:: ts('Multi-Select') }} + +
  • +
  • + + + {{:: ts('Search by range') }} + +
  • @@ -46,6 +59,6 @@
  • - {{:: ts('Delete this field') }} + {{:: ts('Delete this field') }}
  • diff --git a/ext/afform/admin/ang/afGuiEditor/elements/afGuiField.component.js b/ext/afform/admin/ang/afGuiEditor/elements/afGuiField.component.js index 95d71176f0..cecb660b0d 100644 --- a/ext/afform/admin/ang/afGuiEditor/elements/afGuiField.component.js +++ b/ext/afform/admin/ang/afGuiEditor/elements/afGuiField.component.js @@ -20,7 +20,13 @@ var yesNo = [ {id: '1', label: ts('Yes')}, {id: '0', label: ts('No')} - ]; + ], + singleElement = [''], + // When search-by-range is enabled the second element gets a suffix for some properties like "placeholder2" + rangeElements = ['', '2'], + dateRangeElements = ['1', '2'], + relativeDatesWithPickRange = CRM.afGuiEditor.dateRanges, + relativeDatesWithoutPickRange = relativeDatesWithPickRange.slice(1); this.$onInit = function() { $scope.meta = afGui.meta; @@ -30,14 +36,35 @@ return !_.isEmpty($scope.meta.searchDisplays); }; + this.canBeRange = function() { + return this.isSearch() && + !ctrl.getDefn().input_attrs.multiple && + _.includes(['Date', 'Timestamp', 'Integer', 'Float'], ctrl.getDefn().data_type) && + _.includes(['Date', 'Number', 'Select'], $scope.getProp('input_type')); + }; + + this.canBeMultiple = function() { + return this.isSearch() && + !_.includes(['Date', 'Timestamp'], ctrl.getDefn().data_type) && + $scope.getProp('input_type') === 'Select'; + }; + + this.getRangeElements = function(type) { + if (!$scope.getProp('search_range') || (type === 'Select' && ctrl.getDefn().input_type === 'Date')) { + return singleElement; + } + return type === 'Date' ? dateRangeElements : rangeElements; + }; + // Returns the original field definition from metadata this.getDefn = function() { var defn = afGui.getField(ctrl.container.getFieldEntityType(ctrl.node.name), ctrl.node.name); - return defn || { + defn = defn || { label: ts('Untitled'), - requred: false, - input_attrs: [] + required: false }; + defn.input_attrs = _.isEmpty(defn.input_attrs) ? {} : defn.input_attrs; + return defn; }; $scope.getOriginalLabel = function() { @@ -52,12 +79,12 @@ return _.contains(['CheckBox', 'Radio', 'Select'], inputType) && !(inputType === 'CheckBox' && !ctrl.getDefn().options); }; - $scope.getOptions = this.getOptions = function() { + this.getOptions = function() { if (ctrl.node.defn && ctrl.node.defn.options) { return ctrl.node.defn.options; } if (_.includes(['Date', 'Timestamp'], $scope.getProp('data_type'))) { - return CRM.afGuiEditor.dateRanges; + return $scope.getProp('search_range') ? relativeDatesWithPickRange : relativeDatesWithoutPickRange; } return ctrl.getDefn().options || ($scope.getProp('input_type') === 'CheckBox' ? null : yesNo); }; @@ -122,6 +149,20 @@ } }; + $scope.toggleMultiple = function() { + var newVal = getSet('input_attrs.multiple', !getSet('input_attrs.multiple')); + if (newVal && getSet('search_range')) { + getSet('search_range', false); + } + }; + + $scope.toggleSearchRange = function() { + var newVal = getSet('search_range', !getSet('search_range')); + if (newVal && getSet('input_attrs.multiple')) { + getSet('input_attrs.multiple', false); + } + }; + $scope.toggleRequired = function() { getSet('required', !getSet('required')); return false; @@ -151,6 +192,10 @@ delete localDefn[item]; clearOut(ctrl.node, ['defn'].concat(path)); } + // When changing input_type + if (propName === 'input_type' && ctrl.node.defn && ctrl.node.defn.search_range && !ctrl.canBeRange()) { + delete ctrl.node.defn.search_range; + } return val; } return $scope.getProp(propName); @@ -171,10 +216,15 @@ return container; } + // Returns true only if value is [], {}, '', null, or undefined. + function isEmpty(val) { + return typeof val !== 'boolean' && typeof val !== 'number' && _.isEmpty(val); + } + // Recursively clears out empty arrays and objects function clearOut(parent, path) { var item; - while (path.length && _.every(drillDown(parent, path), _.isEmpty)) { + while (path.length && _.every(drillDown(parent, path), isEmpty)) { item = path.pop(); delete drillDown(parent, path)[item]; } diff --git a/ext/afform/admin/ang/afGuiEditor/elements/afGuiMarkup-menu.html b/ext/afform/admin/ang/afGuiEditor/elements/afGuiMarkup-menu.html index e4e5c71f06..6537d36de8 100644 --- a/ext/afform/admin/ang/afGuiEditor/elements/afGuiMarkup-menu.html +++ b/ext/afform/admin/ang/afGuiEditor/elements/afGuiMarkup-menu.html @@ -6,5 +6,5 @@
  • - {{:: ts('Delete this content') }} + {{:: ts('Delete this content') }}
  • diff --git a/ext/afform/admin/ang/afGuiEditor/elements/afGuiText-menu.html b/ext/afform/admin/ang/afGuiEditor/elements/afGuiText-menu.html index eaef3b9781..2678f5abee 100644 --- a/ext/afform/admin/ang/afGuiEditor/elements/afGuiText-menu.html +++ b/ext/afform/admin/ang/afGuiEditor/elements/afGuiText-menu.html @@ -25,5 +25,5 @@
  • - {{:: ts('Delete this text') }} + {{:: ts('Delete this text') }}
  • diff --git a/ext/afform/admin/ang/afGuiEditor/inputType/CheckBox.html b/ext/afform/admin/ang/afGuiEditor/inputType/CheckBox.html index 08baba1ade..5bc73d186c 100644 --- a/ext/afform/admin/ang/afGuiEditor/inputType/CheckBox.html +++ b/ext/afform/admin/ang/afGuiEditor/inputType/CheckBox.html @@ -1,7 +1,7 @@ -