From 3aed34464ae66c45f783f78aea4b590eb5118078 Mon Sep 17 00:00:00 2001 From: Coleman Watts Date: Tue, 3 Aug 2021 22:32:38 -0400 Subject: [PATCH] Afform - support default values for fields This adds an "afform_default" property to the field definition. It does not use the "default_value" property from getFields because that's more to do with Civi's schema and often not appropriate for a form. Fixes dev/core#2734 --- css/civicrm.css | 2 +- ext/afform/admin/ang/afGuiEditor.css | 15 ++- ext/afform/admin/ang/afGuiEditor.js | 2 + .../afGuiEditor/afGuiFieldValue.directive.js | 56 +++++------ .../afGuiEditor/elements/afGuiField-menu.html | 19 +++- .../elements/afGuiField.component.js | 92 ++++++++++++++++--- .../ang/afGuiEditor/inputType/CheckBox.html | 4 +- .../ang/afGuiEditor/inputType/Radio.html | 4 +- .../ang/afGuiEditor/inputType/Select.html | 7 +- ext/afform/core/ang/af/afField.component.js | 10 +- 10 files changed, 146 insertions(+), 65 deletions(-) diff --git a/css/civicrm.css b/css/civicrm.css index 27277e73b7..5aa2cf87fb 100644 --- a/css/civicrm.css +++ b/css/civicrm.css @@ -3231,7 +3231,7 @@ span.crm-select-item-color { top: 4px; } .crm-container ul.crm-checkbox-list li label { - display: block; + display: block !important; padding: 2px 0 2px 22px; margin: 0; word-break: break-all; diff --git a/ext/afform/admin/ang/afGuiEditor.css b/ext/afform/admin/ang/afGuiEditor.css index 7e5c2f219e..a18dc73834 100644 --- a/ext/afform/admin/ang/afGuiEditor.css +++ b/ext/afform/admin/ang/afGuiEditor.css @@ -133,16 +133,18 @@ text-align: right; } -#afGuiEditor .af-gui-bar { +#afGuiEditor-canvas .af-gui-bar { height: 22px; width: 100%; - opacity: 0; transition: opacity .2s; position:relative; font-family: "Courier New", Courier, monospace; font-size: 12px; } -#afGuiEditor [ui-sortable] .af-gui-bar { +#afGuiEditor-canvas:not(.af-gui-menu-open) .af-gui-bar { + opacity: 0; +} +#afGuiEditor-canvas [ui-sortable] .af-gui-bar { cursor: move; background-color: #f2f2f2; position: absolute; @@ -154,7 +156,7 @@ opacity: 1; transition: opacity .2s; } -#afGuiEditor-canvas .af-gui-dragtarget > .af-gui-bar { +#afGuiEditor #afGuiEditor-canvas .af-gui-dragtarget > .af-gui-bar { background-color: #d7e6ff; opacity: 1; transition: opacity .1s; @@ -218,7 +220,7 @@ body.af-gui-dragging { margin-bottom: 20px; } -#afGuiEditor .af-gui-container:hover, +#afGuiEditor-canvas:not(.af-gui-menu-open) .af-gui-container:hover, .af-gui-dragging #afGuiEditor .af-gui-container { border: 2px dashed #757575; } @@ -456,9 +458,6 @@ body.af-gui-dragging { width: 100%; right: 0; } -#afGuiEditor .af-gui-field-input-type-select .input-group .dropdown-menu a { - cursor: default; -} #afGuiEditor .af-gui-text-h1 { font-weight: bolder; diff --git a/ext/afform/admin/ang/afGuiEditor.js b/ext/afform/admin/ang/afGuiEditor.js index 6fde62204a..3ecfdeadc2 100644 --- a/ext/afform/admin/ang/afGuiEditor.js +++ b/ext/afform/admin/ang/afGuiEditor.js @@ -210,11 +210,13 @@ .on('show.bs.dropdown', function() { $scope.$apply(function() { $scope.menu.open = true; + element.closest('#afGuiEditor-canvas').addClass('af-gui-menu-open'); }); }) .on('hidden.bs.dropdown', function() { $scope.$apply(function() { $scope.menu.open = false; + element.closest('#afGuiEditor-canvas').removeClass('af-gui-menu-open'); }); }); } diff --git a/ext/afform/admin/ang/afGuiEditor/afGuiFieldValue.directive.js b/ext/afform/admin/ang/afGuiEditor/afGuiFieldValue.directive.js index 9ca8f4c98b..ba3a108592 100644 --- a/ext/afform/admin/ang/afGuiEditor/afGuiFieldValue.directive.js +++ b/ext/afform/admin/ang/afGuiEditor/afGuiFieldValue.directive.js @@ -5,34 +5,29 @@ // Cribbed from the Api4 Explorer angular.module('afGuiEditor').directive('afGuiFieldValue', function(afGui) { return { - scope: { - field: '=afGuiFieldValue' + bindToController: { + field: '
  • - + {{:: ts('Required') }}
  • - + + + {{:: ts('Default value') }} + +
  • +
  • +
    + +
    +
  • +
  • + {{:: ts('Label') }}
  • - + {{:: ts('Pre help text') }}
  • - + {{:: ts('Post help text') }} diff --git a/ext/afform/admin/ang/afGuiEditor/elements/afGuiField.component.js b/ext/afform/admin/ang/afGuiEditor/elements/afGuiField.component.js index 07408c989d..b20511ae61 100644 --- a/ext/afform/admin/ang/afGuiEditor/elements/afGuiField.component.js +++ b/ext/afform/admin/ang/afGuiEditor/elements/afGuiField.component.js @@ -12,24 +12,25 @@ editor: '^^afGuiEditor', container: '^^afGuiContainer' }, - controller: function($scope, afGui) { + controller: function($scope, afGui, $timeout) { var ts = $scope.ts = CRM.ts('org.civicrm.afform_admin'), - ctrl = this; - - $scope.editingOptions = false; - var yesNo = [ - {id: '1', label: ts('Yes')}, - {id: '0', label: ts('No')} - ], + ctrl = this, entityRefOptions = [], 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); + relativeDatesWithoutPickRange = relativeDatesWithPickRange.slice(1), + yesNo = [ + {id: '1', label: ts('Yes')}, + {id: '0', label: ts('No')} + ]; + $scope.editingOptions = false; this.$onInit = function() { + ctrl.hasDefaultValue = !!getSet('afform_default'); + ctrl.fieldDefn = angular.extend({}, ctrl.getDefn(), ctrl.node.defn); ctrl.inputTypes = _.transform(_.cloneDeep(afGui.meta.inputType), function(inputTypes, type) { if (inputTypeCanBe(type.name)) { // Change labels for EntityRef fields @@ -92,7 +93,9 @@ label: ts('Untitled'), required: false }; - defn.input_attrs = _.isEmpty(defn.input_attrs) ? {} : defn.input_attrs; + if (_.isEmpty(defn.input_attrs)) { + defn.input_attrs = {}; + } return defn; }; @@ -211,12 +214,52 @@ $scope.toggleRequired = function() { getSet('required', !getSet('required')); - return false; }; $scope.toggleHelp = function(position) { getSet('help_' + position, $scope.propIsset('help_' + position) ? null : (ctrl.getDefn()['help_' + position] || ts('Enter text'))); - return false; + }; + + function defaultValueShouldBeArray() { + return ($scope.getProp('data_type') !== 'Boolean' && + ($scope.getProp('input_type') === 'CheckBox' || $scope.getProp('input_attrs.multiple'))); + } + + + $scope.toggleDefaultValue = function() { + if (ctrl.hasDefaultValue) { + getSet('afform_default', undefined); + ctrl.hasDefaultValue = false; + } else { + ctrl.hasDefaultValue = true; + } + }; + + $scope.defaultValueContains = function(val) { + var defaultVal = getSet('afform_default'); + return defaultVal === val || (_.isArray(defaultVal) && _.includes(defaultVal, val)); + }; + + $scope.toggleDefaultValueItem = function(val) { + if (defaultValueShouldBeArray()) { + if (!_.isArray(getSet('afform_default'))) { + ctrl.node.defn.afform_default = []; + } + if (_.includes(ctrl.node.defn.afform_default, val)) { + var newVal = _.without(ctrl.node.defn.afform_default, val); + getSet('afform_default', newVal.length ? newVal : undefined); + ctrl.hasDefaultValue = !!newVal.length; + } else { + ctrl.node.defn.afform_default.push(val); + ctrl.hasDefaultValue = true; + } + } else if (getSet('afform_default') === val) { + getSet('afform_default', undefined); + ctrl.hasDefaultValue = false; + } else { + getSet('afform_default', val); + ctrl.hasDefaultValue = true; + } }; // Getter/setter for definition props @@ -239,8 +282,29 @@ 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; + if (propName === 'input_type') { + if (ctrl.node.defn && ctrl.node.defn.search_range && !ctrl.canBeRange()) { + delete ctrl.node.defn.search_range; + clearOut(ctrl.node, ['defn']); + } + if (ctrl.node.defn && ctrl.node.defn.input_attrs && 'multiple' in ctrl.node.defn.input_attrs && !ctrl.canBeMultiple()) { + delete ctrl.node.defn.input_attrs.multiple; + clearOut(ctrl.node, ['defn', 'input_attrs']); + } + } + ctrl.fieldDefn = angular.extend({}, ctrl.getDefn(), ctrl.node.defn); + + // When changing the multiple property, force-reset the default value widget + if (ctrl.hasDefaultValue && _.includes(['input_type', 'input_attrs.multiple'], propName)) { + ctrl.hasDefaultValue = false; + if (!defaultValueShouldBeArray() && _.isArray(getSet('afform_default'))) { + ctrl.node.defn.afform_default = ctrl.node.defn.afform_default[0]; + } else if (defaultValueShouldBeArray() && _.isString(getSet('afform_default')) && ctrl.node.defn.afform_default.length) { + ctrl.node.defn.afform_default = ctrl.node.defn.afform_default.split(','); + } + $timeout(function() { + ctrl.hasDefaultValue = true; + }); } return val; } diff --git a/ext/afform/admin/ang/afGuiEditor/inputType/CheckBox.html b/ext/afform/admin/ang/afGuiEditor/inputType/CheckBox.html index 5bc73d186c..2f7935f7b5 100644 --- a/ext/afform/admin/ang/afGuiEditor/inputType/CheckBox.html +++ b/ext/afform/admin/ang/afGuiEditor/inputType/CheckBox.html @@ -1,6 +1,6 @@ -
      +
      • - +
      diff --git a/ext/afform/admin/ang/afGuiEditor/inputType/Radio.html b/ext/afform/admin/ang/afGuiEditor/inputType/Radio.html index 729d0ea225..b0042d68f7 100644 --- a/ext/afform/admin/ang/afGuiEditor/inputType/Radio.html +++ b/ext/afform/admin/ang/afGuiEditor/inputType/Radio.html @@ -1,6 +1,6 @@ -
      +
      diff --git a/ext/afform/admin/ang/afGuiEditor/inputType/Select.html b/ext/afform/admin/ang/afGuiEditor/inputType/Select.html index 542a304c48..33dd6f932b 100644 --- a/ext/afform/admin/ang/afGuiEditor/inputType/Select.html +++ b/ext/afform/admin/ang/afGuiEditor/inputType/Select.html @@ -5,9 +5,12 @@ diff --git a/ext/afform/core/ang/af/afField.component.js b/ext/afform/core/ang/af/afField.component.js index 3453ac7682..545351ca4a 100644 --- a/ext/afform/core/ang/af/afField.component.js +++ b/ext/afform/core/ang/af/afField.component.js @@ -12,7 +12,7 @@ fieldName: '@name', defn: '=' }, - controller: function($scope, $element, crmApi4) { + controller: function($scope, $element, crmApi4, $timeout) { var ts = $scope.ts = CRM.ts('org.civicrm.afform'), ctrl = this, boolOptions = [{id: true, label: ts('Yes')}, {id: false, label: ts('No')}], @@ -86,6 +86,14 @@ }); } + // Set default value + if (ctrl.defn.afform_default) { + // Wait for parent controllers to initialize + $timeout(function() { + $scope.dataProvider.getFieldData()[ctrl.fieldName] = ctrl.defn.afform_default; + }); + } + }; $scope.getOptions = function () { -- 2.25.1