From b36751e1942306d59118da94a2feeb05540c5b57 Mon Sep 17 00:00:00 2001 From: "me@jeffersonbledsoe.com" Date: Thu, 27 Apr 2023 22:04:04 +0100 Subject: [PATCH 1/8] Initial UI & implementation of 'show_when' --- src/components/Field.jsx | 3 ++ src/components/FormView.jsx | 31 ++++++++++++++--- src/components/Sidebar.jsx | 2 +- src/fieldSchema.js | 66 ++++++++++++++++++++++++++++++++++++- src/helpers/show_when.js | 10 ++++++ 5 files changed, 105 insertions(+), 7 deletions(-) create mode 100644 src/helpers/show_when.js diff --git a/src/components/Field.jsx b/src/components/Field.jsx index a5a022b..26e7484 100644 --- a/src/components/Field.jsx +++ b/src/components/Field.jsx @@ -44,9 +44,12 @@ const Field = ({ disabled = false, formHasErrors = false, id, + ...rest }) => { const intl = useIntl(); + console.log('rest', rest); + const isInvalid = () => { return !isOnEdit && !valid; }; diff --git a/src/components/FormView.jsx b/src/components/FormView.jsx index db65cea..b44569f 100644 --- a/src/components/FormView.jsx +++ b/src/components/FormView.jsx @@ -10,6 +10,7 @@ import { } from 'semantic-ui-react'; import { getFieldName } from 'volto-form-block/components/utils'; import Field from 'volto-form-block/components/Field'; +import { showWhenValidator } from 'volto-form-block/helpers/show_when'; import config from '@plone/volto/registry'; /* Style */ @@ -134,6 +135,30 @@ const FormView = ({ }), ); + const value = + subblock.field_type === 'static_text' + ? subblock.value + : formData[name]?.value; + const { show_when, target_value } = subblock; + + const shouldShowValidator = showWhenValidator[show_when]; + const shouldShowTargetValue = + formData[subblock.target_field]?.value; + + // Only checking for false here to preserve backwards compatibility with blocks that haven't been updated and so have a value of 'undefined' or 'null' + const shouldShow = shouldShowValidator + ? shouldShowValidator({ + value: shouldShowTargetValue, + target_value: target_value, + }) !== false + : true; + + const shouldHide = __CLIENT__ && !shouldShow; + + if (shouldHide) { + return

Empty

; + } + return ( @@ -148,11 +173,7 @@ const FormView = ({ fields_to_send_with_value, ) } - value={ - subblock.field_type === 'static_text' - ? subblock.value - : formData[name]?.value - } + value={value} valid={isValidField(name)} formHasErrors={formErrors?.length > 0} /> diff --git a/src/components/Sidebar.jsx b/src/components/Sidebar.jsx index 7edf0b2..e2696cd 100644 --- a/src/components/Sidebar.jsx +++ b/src/components/Sidebar.jsx @@ -191,7 +191,7 @@ const Sidebar = ({ { var update_values = {}; diff --git a/src/fieldSchema.js b/src/fieldSchema.js index 95916b5..0ae6771 100644 --- a/src/fieldSchema.js +++ b/src/fieldSchema.js @@ -1,6 +1,7 @@ import config from '@plone/volto/registry'; import { defineMessages } from 'react-intl'; import { useIntl } from 'react-intl'; +import { getFieldName } from 'volto-form-block/components/utils'; const messages = defineMessages({ field_label: { @@ -63,6 +64,22 @@ const messages = defineMessages({ id: 'form_field_type_static_text', defaultMessage: 'Static text', }, + field_show_when: { + id: 'form_field_show_when', + defaultMessage: 'Show when', + }, + field_show_when_option_always: { + id: 'form_field_show_when_option_', + defaultMessage: 'Always', + }, + field_show_when_option_value_is: { + id: 'form_field_show_when_option_value_is', + defaultMessage: 'Value is', + }, + field_show_when_option_value_is_not: { + id: 'form_field_show_when_option_value_is_not', + defaultMessage: 'Value is not', + }, }); export default (props) => { @@ -96,6 +113,7 @@ export default (props) => { const schemaExtenderValues = schemaExtender ? schemaExtender(intl) : { properties: [], fields: [], required: [] }; + return { title: props?.label || '', fieldsets: [ @@ -108,6 +126,9 @@ export default (props) => { 'field_type', ...schemaExtenderValues.fields, 'required', + 'show_when', + ...((props.show_when === 'value_is' || props.show_when === 'value_is_not') ? ['target_field'] : []), + ...((props.show_when === 'value_is' || props.show_when === 'value_is_not') ? ['target_value'] : []), ], }, ], @@ -122,7 +143,7 @@ export default (props) => { }, field_type: { title: intl.formatMessage(messages.field_type), - type: 'array', + type: 'string', choices: [ ...baseFieldTypeChoices, ...(config.blocks.blocksConfig.form.additionalFields?.map( @@ -136,6 +157,49 @@ export default (props) => { type: 'boolean', default: false, }, + show_when: { + title: intl.formatMessage(messages.field_show_when), + type: 'string', + choices: [ + [ + 'always', + intl.formatMessage(messages.field_show_when_option_always), + ], + [ + 'value_is', + intl.formatMessage(messages.field_show_when_option_value_is), + ], + [ + 'value_is_not', + intl.formatMessage(messages.field_show_when_option_value_is_not), + ], + ], + noValueOption: false, + }, + // TODO: i18n + target_field: { + title: 'Target field', + type: 'string', + choices: [ + // Add properties for each of the fields for use in the data mapping + ...(props?.formData?.subblocks + ? props.formData.subblocks.map((subblock) => { + // Using getFieldName as it is what is used for the formData later. Saves + // performing `getFieldName` for every block every render. + return [ + getFieldName(subblock.label, subblock.field_id), + subblock.label, + ]; + }) + : []), + ], + noValueOption: false, + }, + // TODO: i18n + target_value: { + title: 'Target value', + type: 'string', + }, ...schemaExtenderValues.properties, }, required: [ diff --git a/src/helpers/show_when.js b/src/helpers/show_when.js new file mode 100644 index 0000000..1e6ad93 --- /dev/null +++ b/src/helpers/show_when.js @@ -0,0 +1,10 @@ +const always = () => true; +const value_is = ({ value, target_value }) => value === target_value; +const value_is_not = ({ value, target_value }) => value !== target_value; + +export const showWhenValidator = { + '': always, + always: always, + value_is: value_is, + value_is_not: value_is_not, +}; From eb16a64270fa5451718d7d3aebcc1e5201572fcf Mon Sep 17 00:00:00 2001 From: "me@jeffersonbledsoe.com" Date: Wed, 3 May 2023 17:39:12 +0100 Subject: [PATCH 2/8] Return to using only field ID for target field value --- src/fieldSchema.js | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/fieldSchema.js b/src/fieldSchema.js index 0ae6771..7f97f72 100644 --- a/src/fieldSchema.js +++ b/src/fieldSchema.js @@ -1,7 +1,6 @@ import config from '@plone/volto/registry'; import { defineMessages } from 'react-intl'; import { useIntl } from 'react-intl'; -import { getFieldName } from 'volto-form-block/components/utils'; const messages = defineMessages({ field_label: { @@ -74,11 +73,11 @@ const messages = defineMessages({ }, field_show_when_option_value_is: { id: 'form_field_show_when_option_value_is', - defaultMessage: 'Value is', + defaultMessage: 'value is', }, field_show_when_option_value_is_not: { id: 'form_field_show_when_option_value_is_not', - defaultMessage: 'Value is not', + defaultMessage: 'value is not', }, }); @@ -127,8 +126,14 @@ export default (props) => { ...schemaExtenderValues.fields, 'required', 'show_when', - ...((props.show_when === 'value_is' || props.show_when === 'value_is_not') ? ['target_field'] : []), - ...((props.show_when === 'value_is' || props.show_when === 'value_is_not') ? ['target_value'] : []), + ...(props.show_when === 'value_is' || + props.show_when === 'value_is_not' + ? ['target_field'] + : []), + ...(props.show_when === 'value_is' || + props.show_when === 'value_is_not' + ? ['target_value'] + : []), ], }, ], @@ -186,10 +191,7 @@ export default (props) => { ? props.formData.subblocks.map((subblock) => { // Using getFieldName as it is what is used for the formData later. Saves // performing `getFieldName` for every block every render. - return [ - getFieldName(subblock.label, subblock.field_id), - subblock.label, - ]; + return [subblock.field_id, subblock.label]; }) : []), ], From aa47bdc6d56b947bee1807edcae57c5feff4d2bf Mon Sep 17 00:00:00 2001 From: "me@jeffersonbledsoe.com" Date: Wed, 17 May 2023 12:58:40 +0100 Subject: [PATCH 3/8] Update UI --- src/fieldSchema.js | 71 +++++++++++++++++++++++++--------------------- 1 file changed, 39 insertions(+), 32 deletions(-) diff --git a/src/fieldSchema.js b/src/fieldSchema.js index 7f97f72..f46df71 100644 --- a/src/fieldSchema.js +++ b/src/fieldSchema.js @@ -63,9 +63,21 @@ const messages = defineMessages({ id: 'form_field_type_static_text', defaultMessage: 'Static text', }, - field_show_when: { + field_type_hidden: { + id: 'form_field_type_hidden', + defaultMessage: 'Hidden', + }, + field_show_when_when: { id: 'form_field_show_when', - defaultMessage: 'Show when', + defaultMessage: 'When', + }, + field_show_when_is: { + id: 'form_field_show_is', + defaultMessage: 'Is', + }, + field_show_when_to: { + id: 'form_field_show_to', + defaultMessage: 'To', }, field_show_when_option_always: { id: 'form_field_show_when_option_', @@ -73,11 +85,11 @@ const messages = defineMessages({ }, field_show_when_option_value_is: { id: 'form_field_show_when_option_value_is', - defaultMessage: 'value is', + defaultMessage: 'equal', }, field_show_when_option_value_is_not: { id: 'form_field_show_when_option_value_is_not', - defaultMessage: 'value is not', + defaultMessage: 'not equal', }, }); @@ -125,14 +137,12 @@ export default (props) => { 'field_type', ...schemaExtenderValues.fields, 'required', - 'show_when', - ...(props.show_when === 'value_is' || - props.show_when === 'value_is_not' - ? ['target_field'] + 'show_when_when', + ...(props.show_when_when && props.show_when_when !== 'always' + ? ['show_when_is'] : []), - ...(props.show_when === 'value_is' || - props.show_when === 'value_is_not' - ? ['target_value'] + ...(props.show_when_when && props.show_when_when !== 'always' + ? ['show_when_to'] : []), ], }, @@ -162,14 +172,28 @@ export default (props) => { type: 'boolean', default: false, }, - show_when: { - title: intl.formatMessage(messages.field_show_when), + show_when_when: { + title: intl.formatMessage(messages.field_show_when_when), type: 'string', choices: [ [ 'always', intl.formatMessage(messages.field_show_when_option_always), ], + ...(props?.formData?.subblocks + ? props.formData.subblocks.map((subblock) => { + // Using getFieldName as it is what is used for the formData later. Saves + // performing `getFieldName` for every block every render. + return [subblock.field_id, subblock.label]; + }) + : []), + ], + default: 'always', + }, + show_when_is: { + title: intl.formatMessage(messages.field_show_when_is), + type: 'string', + choices: [ [ 'value_is', intl.formatMessage(messages.field_show_when_option_value_is), @@ -181,25 +205,8 @@ export default (props) => { ], noValueOption: false, }, - // TODO: i18n - target_field: { - title: 'Target field', - type: 'string', - choices: [ - // Add properties for each of the fields for use in the data mapping - ...(props?.formData?.subblocks - ? props.formData.subblocks.map((subblock) => { - // Using getFieldName as it is what is used for the formData later. Saves - // performing `getFieldName` for every block every render. - return [subblock.field_id, subblock.label]; - }) - : []), - ], - noValueOption: false, - }, - // TODO: i18n - target_value: { - title: 'Target value', + show_when_to: { + title: intl.formatMessage(messages.field_show_when_to), type: 'string', }, ...schemaExtenderValues.properties, From 08bb75eed34d2300ccc4504715397374c9d3036d Mon Sep 17 00:00:00 2001 From: "me@jeffersonbledsoe.com" Date: Thu, 25 May 2023 17:12:17 +0100 Subject: [PATCH 4/8] Don't show fields after the current field that aren't the current value for _when choices --- src/fieldSchema.js | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/src/fieldSchema.js b/src/fieldSchema.js index f46df71..cc1e703 100644 --- a/src/fieldSchema.js +++ b/src/fieldSchema.js @@ -181,11 +181,22 @@ export default (props) => { intl.formatMessage(messages.field_show_when_option_always), ], ...(props?.formData?.subblocks - ? props.formData.subblocks.map((subblock) => { - // Using getFieldName as it is what is used for the formData later. Saves - // performing `getFieldName` for every block every render. - return [subblock.field_id, subblock.label]; - }) + ? props.formData.subblocks.reduce((choices, subblock, index) => { + const currentFieldIndex = props.formData.subblocks.findIndex( + (field) => field.field_id === props.field_id, + ); + if (index > currentFieldIndex) { + if (props.show_when_when === subblock.field_id) { + choices.push([subblock.field_id, subblock.label]); + } + return choices; + } + if (subblock.field_id === props.field_id) { + return choices; + } + choices.push([subblock.field_id, subblock.label]); + return choices; + }, []) : []), ], default: 'always', @@ -204,10 +215,12 @@ export default (props) => { ], ], noValueOption: false, + required: true, }, show_when_to: { title: intl.formatMessage(messages.field_show_when_to), type: 'string', + required: true, }, ...schemaExtenderValues.properties, }, From 52c423defef308a3055db47f4217241f56e75598 Mon Sep 17 00:00:00 2001 From: "me@jeffersonbledsoe.com" Date: Thu, 25 May 2023 17:23:24 +0100 Subject: [PATCH 5/8] Required fields --- src/fieldSchema.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/fieldSchema.js b/src/fieldSchema.js index cc1e703..22d1dcb 100644 --- a/src/fieldSchema.js +++ b/src/fieldSchema.js @@ -228,6 +228,9 @@ export default (props) => { 'label', 'field_type', 'input_values', + ...(props.show_when_when && props.show_when_when !== 'always' + ? ['show_when_is', 'show_when_to'] + : []), ...schemaExtenderValues.required, ], }; From e2792b949f1eead86d25f791881a65c5aadf189c Mon Sep 17 00:00:00 2001 From: "me@jeffersonbledsoe.com" Date: Fri, 26 May 2023 17:32:29 +0100 Subject: [PATCH 6/8] Allow multiple choices --- src/fieldSchema.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fieldSchema.js b/src/fieldSchema.js index 22d1dcb..83fbf40 100644 --- a/src/fieldSchema.js +++ b/src/fieldSchema.js @@ -219,7 +219,7 @@ export default (props) => { }, show_when_to: { title: intl.formatMessage(messages.field_show_when_to), - type: 'string', + type: 'array', required: true, }, ...schemaExtenderValues.properties, From a7573497b1bdcac43e1390525643be5429a21020 Mon Sep 17 00:00:00 2001 From: "me@jeffersonbledsoe.com" Date: Fri, 26 May 2023 17:32:50 +0100 Subject: [PATCH 7/8] Pre-fill choices if the targeted field is a 'chooser' --- src/fieldSchema.js | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/fieldSchema.js b/src/fieldSchema.js index 83fbf40..cba071b 100644 --- a/src/fieldSchema.js +++ b/src/fieldSchema.js @@ -93,6 +93,8 @@ const messages = defineMessages({ }, }); +const choiceTypes = ['select', 'single_choice', 'multiple_choice']; + export default (props) => { var intl = useIntl(); const baseFieldTypeChoices = [ @@ -125,6 +127,13 @@ export default (props) => { ? schemaExtender(intl) : { properties: [], fields: [], required: [] }; + const show_when_when_field = + props.show_when_when && props.show_when_when + ? props.formData?.subblocks?.find( + (field) => field.field_id === props.show_when_when, + ) + : undefined; + return { title: props?.label || '', fieldsets: [ @@ -221,6 +230,19 @@ export default (props) => { title: intl.formatMessage(messages.field_show_when_to), type: 'array', required: true, + creatable: true, + noValueOption: false, + ...(show_when_when_field && + choiceTypes.includes(show_when_when_field.field_type) && { + choices: show_when_when_field.input_values, + }), + ...(show_when_when_field && + show_when_when_field.field_type === 'yes_no' && { + choices: [ + [true, 'Yes'], + [false, 'No'], + ], + }), }, ...schemaExtenderValues.properties, }, From bb36e43e5868bbb36f36c34289cdcb0269b89806 Mon Sep 17 00:00:00 2001 From: "me@jeffersonbledsoe.com" Date: Fri, 26 May 2023 19:16:50 +0100 Subject: [PATCH 8/8] Update 'Show when' name --- src/fieldSchema.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fieldSchema.js b/src/fieldSchema.js index cba071b..a6f8e0c 100644 --- a/src/fieldSchema.js +++ b/src/fieldSchema.js @@ -69,7 +69,7 @@ const messages = defineMessages({ }, field_show_when_when: { id: 'form_field_show_when', - defaultMessage: 'When', + defaultMessage: 'Show when', }, field_show_when_is: { id: 'form_field_show_is',