From 51c5da4a03fb2ea222ff5fd7cd65bfd2521ebbc6 Mon Sep 17 00:00:00 2001 From: Valentin Serra Date: Thu, 11 Apr 2024 15:28:15 +0200 Subject: [PATCH] feat: use field instance validation in formfields Closes #1147 --- .../features/viewerCommands/ViewerCommands.js | 14 ++++ .../UpdateFieldInstanceValidationHandler.js | 31 ++++++++ .../cmd/UpdateFieldValidationHandler.js | 3 + .../src/render/components/FormField.js | 17 ++--- .../form-js-viewer/src/util/expressions.js | 72 +++++++++---------- .../render/components/helper/mocks/index.js | 1 + 6 files changed, 90 insertions(+), 48 deletions(-) create mode 100644 packages/form-js-viewer/src/features/viewerCommands/cmd/UpdateFieldInstanceValidationHandler.js diff --git a/packages/form-js-viewer/src/features/viewerCommands/ViewerCommands.js b/packages/form-js-viewer/src/features/viewerCommands/ViewerCommands.js index 9d7a383a9..154108daa 100644 --- a/packages/form-js-viewer/src/features/viewerCommands/ViewerCommands.js +++ b/packages/form-js-viewer/src/features/viewerCommands/ViewerCommands.js @@ -1,4 +1,5 @@ import { UpdateFieldValidationHandler } from './cmd/UpdateFieldValidationHandler'; +import { UpdateFieldInstanceValidationHandler } from './cmd/UpdateFieldInstanceValidationHandler'; export class ViewerCommands { constructor(commandStack, eventBus) { @@ -18,9 +19,13 @@ export class ViewerCommands { getHandlers() { return { 'formField.validation.update': UpdateFieldValidationHandler, + 'formFieldInstance.validation.update': UpdateFieldInstanceValidationHandler, }; } + /** + * @deprecated + */ updateFieldValidation(field, value, indexes) { const context = { field, @@ -30,6 +35,15 @@ export class ViewerCommands { this._commandStack.execute('formField.validation.update', context); } + + updateFieldInstanceValidation(fieldInstance, value) { + const context = { + fieldInstance, + value, + }; + + this._commandStack.execute('formFieldInstance.validation.update', context); + } } ViewerCommands.$inject = ['commandStack', 'eventBus']; diff --git a/packages/form-js-viewer/src/features/viewerCommands/cmd/UpdateFieldInstanceValidationHandler.js b/packages/form-js-viewer/src/features/viewerCommands/cmd/UpdateFieldInstanceValidationHandler.js new file mode 100644 index 000000000..9f763e007 --- /dev/null +++ b/packages/form-js-viewer/src/features/viewerCommands/cmd/UpdateFieldInstanceValidationHandler.js @@ -0,0 +1,31 @@ +import { set } from 'min-dash'; +import { clone } from '../../../util'; + +export class UpdateFieldInstanceValidationHandler { + constructor(form, validator) { + this._form = form; + this._validator = validator; + } + + execute(context) { + const { fieldInstance, value } = context; + const { id, indexes } = fieldInstance; + const { errors } = this._form._getState(); + + context.oldErrors = clone(errors); + + const fieldErrors = this._validator.validateFieldInstance(fieldInstance, value); + const updatedErrors = set( + errors, + [id, ...Object.values(indexes || {})], + fieldErrors.length ? fieldErrors : undefined, + ); + this._form._setState({ errors: updatedErrors }); + } + + revert(context) { + this._form._setState({ errors: context.oldErrors }); + } +} + +UpdateFieldInstanceValidationHandler.$inject = ['form', 'validator']; diff --git a/packages/form-js-viewer/src/features/viewerCommands/cmd/UpdateFieldValidationHandler.js b/packages/form-js-viewer/src/features/viewerCommands/cmd/UpdateFieldValidationHandler.js index 66b393c6d..650010635 100644 --- a/packages/form-js-viewer/src/features/viewerCommands/cmd/UpdateFieldValidationHandler.js +++ b/packages/form-js-viewer/src/features/viewerCommands/cmd/UpdateFieldValidationHandler.js @@ -1,6 +1,9 @@ import { set } from 'min-dash'; import { clone } from '../../../util'; +/** + * @deprecated + */ export class UpdateFieldValidationHandler { constructor(form, validator) { this._form = form; diff --git a/packages/form-js-viewer/src/render/components/FormField.js b/packages/form-js-viewer/src/render/components/FormField.js index 03c522ac0..ca9c6be94 100644 --- a/packages/form-js-viewer/src/render/components/FormField.js +++ b/packages/form-js-viewer/src/render/components/FormField.js @@ -98,20 +98,20 @@ export function FormField(props) { if (initialValidationTrigger && hasInitialValue) { setInitialValidationTrigger(false); - viewerCommands.updateFieldValidation(field, initialValue, indexes); + viewerCommands.updateFieldInstanceValidation(fieldInstance, initialValue); } - }, [viewerCommands, field, initialValue, initialValidationTrigger, indexes]); + }, [fieldInstance, initialValidationTrigger, initialValue, viewerCommands]); const onBlur = useCallback(() => { const value = get(data, valuePath); if (initialValidationTrigger) { setInitialValidationTrigger(false); - viewerCommands.updateFieldValidation(field, value, indexes); + viewerCommands.updateFieldInstanceValidation(fieldInstance, value); } eventBus.fire('formField.blur', { formField: field }); - }, [eventBus, field, indexes, viewerCommands, initialValidationTrigger, data, valuePath]); + }, [data, eventBus, field, fieldInstance, initialValidationTrigger, valuePath, viewerCommands]); const onFocus = useCallback(() => { eventBus.fire('formField.focus', { formField: field }); @@ -119,14 +119,10 @@ export function FormField(props) { const onChange = useCallback( (update) => { - if (!fieldConfig.keyed) { - return; - } - setInitialValidationTrigger(false); - _onChange({ ...update, field, indexes, fieldInstance }); + _onChange({ field, indexes, fieldInstance, ...update }); }, - [_onChange, field, fieldConfig.keyed, fieldInstance, indexes], + [_onChange, field, fieldInstance, indexes], ); if (hidden) { @@ -147,6 +143,7 @@ export function FormField(props) { onFocus={disabled || readonly ? noop : onFocus} readonly={readonly} value={value} + fieldInstance={fieldInstance} /> ); diff --git a/packages/form-js-viewer/src/util/expressions.js b/packages/form-js-viewer/src/util/expressions.js index b15916760..81640b281 100644 --- a/packages/form-js-viewer/src/util/expressions.js +++ b/packages/form-js-viewer/src/util/expressions.js @@ -1,38 +1,34 @@ -import { wrapObjectKeysWithUnderscores } from './simple'; - - -/** - * Transform a LocalExpressionContext object into a usable FEEL context. - * - * @param {Object} context - The LocalExpressionContext object. - * @returns {Object} The usable FEEL context. - */ - -export function buildExpressionContext(context) { - const { - data, - ...specialContextKeys - } = context; - - return { - ...specialContextKeys, - ...data, - ...wrapObjectKeysWithUnderscores(specialContextKeys) - }; -} - -/** - * Evaluate a string based on the expressionLanguage and context information. - * If the string is not an expression, it is returned as is. - * - * @param {any} expressionLanguage - The expression language to use. - * @param {string} value - The string to evaluate. - * @param {Object} expressionContextInfo - The context information to use. - * @returns {any} - Evaluated value or the original value if not an expression. - */ -export function runExpressionEvaluation(expressionLanguage, value, expressionContextInfo) { - if (expressionLanguage && expressionLanguage.isExpression(value)) { - return expressionLanguage.evaluate(value, buildExpressionContext(expressionContextInfo)); - } - return value; -} \ No newline at end of file +import { wrapObjectKeysWithUnderscores } from './simple'; + +/** + * Transform a LocalExpressionContext object into a usable FEEL context. + * + * @param {Object} context - The LocalExpressionContext object. + * @returns {Object} The usable FEEL context. + */ + +export function buildExpressionContext(context) { + const { data, ...specialContextKeys } = context; + + return { + ...specialContextKeys, + ...data, + ...wrapObjectKeysWithUnderscores(specialContextKeys), + }; +} + +/** + * Evaluate a string based on the expressionLanguage and context information. + * If the string is not an expression, it is returned as is. + * + * @param {any} expressionLanguage - The expression language to use. + * @param {string} value - The string to evaluate. + * @param {Object} expressionContextInfo - The context information to use. + * @returns {any} - Evaluated value or the original value if not an expression. + */ +export function runExpressionEvaluation(expressionLanguage, value, expressionContextInfo) { + if (expressionLanguage && expressionLanguage.isExpression(value)) { + return expressionLanguage.evaluate(value, buildExpressionContext(expressionContextInfo)); + } + return value; +} diff --git a/packages/form-js-viewer/test/spec/render/components/helper/mocks/index.js b/packages/form-js-viewer/test/spec/render/components/helper/mocks/index.js index 0d26457f0..ed7daad26 100644 --- a/packages/form-js-viewer/test/spec/render/components/helper/mocks/index.js +++ b/packages/form-js-viewer/test/spec/render/components/helper/mocks/index.js @@ -147,4 +147,5 @@ export class ExpressionLanguageMock { export class ViewerCommandsMock { updateFieldValidation() {} + updateFieldInstanceValidation() {} }