From 50d0b2a803faa94b4bf39799bec235d96429ec77 Mon Sep 17 00:00:00 2001 From: Valentin Serra Date: Sat, 23 Sep 2023 01:35:15 +0200 Subject: [PATCH] feat: add/remove items in dynamic lists Related to #796 --- .../form-js-viewer/assets/form-js-base.css | 35 +++++++++++ packages/form-js-viewer/src/Form.js | 32 ++++++++-- .../repeatRender/RepeatRenderManager.js | 61 +++++++++++++++---- 3 files changed, 110 insertions(+), 18 deletions(-) diff --git a/packages/form-js-viewer/assets/form-js-base.css b/packages/form-js-viewer/assets/form-js-base.css index 06e11eb41..237795298 100644 --- a/packages/form-js-viewer/assets/form-js-base.css +++ b/packages/form-js-viewer/assets/form-js-base.css @@ -934,6 +934,41 @@ margin: 4px 0; } +.fjs-container .fjs-repeat-row-container { + position: relative; +} + +.fjs-container .fjs-repeat-row-container:focus-within .fjs-repeat-row-delete, +.fjs-container .fjs-repeat-row-container:hover .fjs-repeat-row-delete-container .fjs-repeat-row-delete, +.fjs-container .fjs-repeat-row-container .fjs-repeat-row-delete-container:hover .fjs-repeat-row-delete { + display: flex; +} + +.fjs-container .fjs-repeat-row-container .fjs-repeat-row-delete-container { + display: flex; + position: absolute; + height: 100px; + width: 30px; + top: calc(50% - 45px); + right: -35px; + align-items: center; + justify-content: center; +} + +.fjs-container .fjs-repeat-row-container .fjs-repeat-row-delete { + display: none; + font-family: inherit; + font-size: inherit; + cursor: pointer; + color: var(--color-accent); + background: white; + border: 2px solid var(--color-accent); + height: 24px; + width: 24px; + align-items: center; + justify-content: center; +} + .fjs-container .fjs-repeat-render-footer { display: flex; align-items: center; diff --git a/packages/form-js-viewer/src/Form.js b/packages/form-js-viewer/src/Form.js index a9270e7ab..ea92ce941 100644 --- a/packages/form-js-viewer/src/Form.js +++ b/packages/form-js-viewer/src/Form.js @@ -137,7 +137,7 @@ export default class Form { warnings } = this.get('importer').importSchema(schema); - const initializedData = this._initializeFieldData(clone(data)); + const initializedData = this._getInitializedFieldData(clone(data)); this._setState({ data: initializedData, @@ -477,11 +477,16 @@ export default class Form { /** * @internal */ - _initializeFieldData(data) { + _getInitializedFieldData(data, options = {}) { const formFieldRegistry = this.get('formFieldRegistry'); const formFields = this.get('formFields'); const pathRegistry = this.get('pathRegistry'); + const { + customRoot, + customIndexes + } = options; + function initializeFieldDataRecursively(initializedData, formField, indexes) { const { defaultValue, type, isRepeating } = formField; const { config: fieldConfig } = formFields.get(type); @@ -518,7 +523,16 @@ export default class Form { // (c) Initialize field value in output data set(initializedData, valuePath, valueData); - // (d) Recurse repeatable parents both across the indexes of repetition and the children + // (d) If indexed ahead of time, recurse repeatable simply across the children + if (!isUndefined(indexes[formField.id])) { + formField.components.forEach( + (component) => initializeFieldDataRecursively(initializedData, component, { ...indexes }) + ); + + return; + } + + // (e) Recurse repeatable parents both across the indexes of repetition and the children valueData.forEach((_, index) => { formField.components.forEach( (component) => initializeFieldDataRecursively(initializedData, component, { ...indexes, [formField.id]: index }) @@ -533,9 +547,17 @@ export default class Form { } } + // allows definition of a specific subfield to generate the data for + const root = customRoot || formFieldRegistry.getForm(); + const indexes = customIndexes || {}; + const basePath = pathRegistry.getValuePath(root, { indexes }) || []; + + // if indexing ahead of time, we must add this index to the data path at the end + const path = !isUndefined(indexes[root.id]) ? [ ...basePath, indexes[root.id] ] : basePath; + const workingData = clone(data); - initializeFieldDataRecursively(workingData, formFieldRegistry.getForm(), {}); - return workingData; + initializeFieldDataRecursively(workingData, root, indexes); + return get(workingData, path, {}); } } diff --git a/packages/form-js-viewer/src/features/repeatRender/RepeatRenderManager.js b/packages/form-js-viewer/src/features/repeatRender/RepeatRenderManager.js index d9c5d1ac1..cc255017f 100644 --- a/packages/form-js-viewer/src/features/repeatRender/RepeatRenderManager.js +++ b/packages/form-js-viewer/src/features/repeatRender/RepeatRenderManager.js @@ -2,6 +2,7 @@ import { get } from 'min-dash'; import ExpandSvg from '../../render/components/form-fields/icons/Expand.svg'; import CollapseSvg from '../../render/components/form-fields/icons/Collapse.svg'; +import XMarkSvg from '../../render/components/form-fields/icons/XMark.svg'; export default class RepeatRenderManager { @@ -48,15 +49,34 @@ export default class RepeatRenderManager { const displayValues = isCollapsed ? values.slice(0, nonCollapsedItems) : values; + const onDeleteItem = (index) => { + + const updatedValues = values.slice(); + updatedValues.splice(index, 1); + + props.onChange({ + field: repeaterField, + value: updatedValues, + indexes + }); + }; + return ( <> {displayValues.map((_, index) => { const elementProps = { ...restProps, - indexes: { ...(indexes || {}), [ repeaterField.id ]: index }, + indexes: { ...(indexes || {}), [ repeaterField.id ]: index } }; - return ; + return
+ +
+ +
+
; })} ); @@ -81,17 +101,32 @@ export default class RepeatRenderManager { setSharedRepeatState(state => ({ ...state, isCollapsed: !isCollapsed })); }; - return togglingEnabled - ?
- -
- : null; + const onAddItem = () => { + const updatedValues = values.slice(); + const newItem = this._form._getInitializedFieldData(this._form._state.data, { + customRoot : repeaterField, + customIndexes : { ...indexes, [ repeaterField.id ]: updatedValues.length } + }); + + updatedValues.push(newItem); + + props.onChange({ + field: repeaterField, + value: updatedValues, + indexes + }); + }; + + return
+ + { togglingEnabled ? : null } +
; } _getNonCollapsedItems(field) {