From 3b5bdf1bf19cf100a81cf4227d13bdb27f6c0896 Mon Sep 17 00:00:00 2001 From: Coleman Watts Date: Wed, 6 Oct 2021 11:49:46 -0400 Subject: [PATCH] Afform - Fix chain-select of contry and address to work on SearchKit forms Multiple fixes to the chainSelect mechanics - Provides visual feedback when reloading options - Works with defalut values from url args - Works with multi-valued fields - Works with searchKit joins --- .../ang/afGuiEditor/afGuiSearch.component.js | 2 +- .../elements/afGuiField.component.js | 2 +- .../Civi/Afform/AfformMetadataInjector.php | 4 +- ext/afform/core/ang/af/afField.component.js | 38 ++++++++++++++++--- 4 files changed, 36 insertions(+), 10 deletions(-) diff --git a/ext/afform/admin/ang/afGuiEditor/afGuiSearch.component.js b/ext/afform/admin/ang/afGuiEditor/afGuiSearch.component.js index ed1a98f73a..14937f6540 100644 --- a/ext/afform/admin/ang/afGuiEditor/afGuiSearch.component.js +++ b/ext/afform/admin/ang/afGuiEditor/afGuiSearch.component.js @@ -86,7 +86,7 @@ "#tag": "af-field", name: (prefix ? prefix + '.' : '') + field.name }; - if (field.input_type === 'Select') { + if (field.input_type === 'Select' || field.input_type === 'ChainSelect') { tag.defn = {input_attrs: {multiple: true}}; } else if (field.input_type === 'Date') { tag.defn = {input_type: 'Select', search_range: true}; diff --git a/ext/afform/admin/ang/afGuiEditor/elements/afGuiField.component.js b/ext/afform/admin/ang/afGuiEditor/elements/afGuiField.component.js index 2cc5e04a7d..f740d3d3f6 100644 --- a/ext/afform/admin/ang/afGuiEditor/elements/afGuiField.component.js +++ b/ext/afform/admin/ang/afGuiEditor/elements/afGuiField.component.js @@ -76,7 +76,7 @@ this.canBeMultiple = function() { return this.isSearch() && !_.includes(['Date', 'Timestamp'], ctrl.getDefn().data_type) && - _.includes(['Select', 'EntityRef'], $scope.getProp('input_type')); + _.includes(['Select', 'EntityRef', 'ChainSelect'], $scope.getProp('input_type')); }; this.getRangeElements = function(type) { diff --git a/ext/afform/core/Civi/Afform/AfformMetadataInjector.php b/ext/afform/core/Civi/Afform/AfformMetadataInjector.php index fca28b8694..a8e596e720 100644 --- a/ext/afform/core/Civi/Afform/AfformMetadataInjector.php +++ b/ext/afform/core/Civi/Afform/AfformMetadataInjector.php @@ -104,7 +104,7 @@ class AfformMetadataInjector { $isSearchRange = !empty($fieldDefn['search_range']) && \CRM_Utils_JS::decode($fieldDefn['search_range']); // Default placeholder for select inputs - if ($inputType === 'Select') { + if ($inputType === 'Select' || $inputType === 'ChainSelect') { $fieldInfo['input_attrs']['placeholder'] = E::ts('Select'); } elseif ($inputType === 'EntityRef') { @@ -163,7 +163,7 @@ class AfformMetadataInjector { $params = [ 'action' => $action, 'where' => [['name', 'IN', $namesToMatch]], - 'select' => ['name', 'label', 'input_type', 'input_attrs', 'help_pre', 'help_post', 'options', 'fk_entity'], + 'select' => ['name', 'label', 'input_type', 'input_attrs', 'help_pre', 'help_post', 'options', 'entity', 'fk_entity'], '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/ang/af/afField.component.js b/ext/afform/core/ang/af/afField.component.js index 60d41c0cdf..34d425c378 100644 --- a/ext/afform/core/ang/af/afField.component.js +++ b/ext/afform/core/ang/af/afField.component.js @@ -15,7 +15,11 @@ controller: function($scope, $element, crmApi4, $timeout, $location) { var ts = $scope.ts = CRM.ts('org.civicrm.afform'), ctrl = this, + // Prefix used for SearchKit explicit joins + namePrefix = '', boolOptions = [{id: true, label: ts('Yes')}, {id: false, label: ts('No')}], + // Used to store chain select options loaded on-the-fly + chainSelectOptions = null, // Only used for is_primary radio button noOptions = [{id: true, label: ''}]; @@ -29,6 +33,9 @@ $element.addClass('af-field-type-' + _.kebabCase(ctrl.defn.input_type)); + if (this.defn.name !== this.fieldName) { + namePrefix = this.fieldName.substr(0, this.fieldName.length - this.defn.name.length); + } if (ctrl.defn.search_range) { // Initialize value as object unless using relative date select @@ -69,21 +76,40 @@ // ChainSelect - watch control field & reload options as needed if (ctrl.defn.input_type === 'ChainSelect') { - $scope.$watch('dataProvider.getFieldData()[defn.input_attrs.control_field]', function(val) { + var controlField = namePrefix + ctrl.defn.input_attrs.control_field; + $scope.$watch('dataProvider.getFieldData()["' + controlField + '"]', function(val) { + // After switching option list, remove invalid options + function validateValue() { + var options = $scope.getOptions(), + value = $scope.dataProvider.getFieldData()[ctrl.fieldName]; + if (_.isArray(value)) { + _.remove(value, function(item) { + return !_.find(options, function(option) {return option.id == item;}); + }); + } else if (value && !_.find(options, function(option) {return option.id == value;})) { + $scope.dataProvider.getFieldData()[ctrl.fieldName] = ''; + } + } if (val) { + $('input[crm-ui-select]', $element).addClass('loading').prop('disabled', true); var params = { - where: [['name', '=', ctrl.fieldName]], + where: [['name', '=', ctrl.defn.name]], select: ['options'], loadOptions: ['id', 'label'], values: {} }; params.values[ctrl.defn.input_attrs.control_field] = val; - crmApi4($scope.dataProvider.getEntityType(), 'getFields', params, 0) + crmApi4(ctrl.defn.entity, 'getFields', params, 0) .then(function(data) { - ctrl.defn.options = data.options; + $('input[crm-ui-select]', $element).removeClass('loading').prop('disabled', false); + chainSelectOptions = data.options; + validateValue(); }); + } else { + chainSelectOptions = null; + validateValue(); } - }); + }, true); } // Wait for parent controllers to initialize @@ -155,7 +181,7 @@ }; $scope.getOptions = function () { - return ctrl.defn.options || (ctrl.fieldName === 'is_primary' && ctrl.defn.input_type === 'Radio' ? noOptions : boolOptions); + return chainSelectOptions || ctrl.defn.options || (ctrl.fieldName === 'is_primary' && ctrl.defn.input_type === 'Radio' ? noOptions : boolOptions); }; $scope.select2Options = function() { -- 2.25.1