From b09c4eee9028e35ba4194be25eb0ccf8b23e734a Mon Sep 17 00:00:00 2001 From: alexandraRamanenka Date: Thu, 13 Mar 2025 15:52:04 +0200 Subject: [PATCH 1/3] FIO-9813: Add validate when hidden property to the Nested Form in any on the children has it --- src/components/form/Form.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/components/form/Form.js b/src/components/form/Form.js index 4b9652baac..3cdb51c49e 100644 --- a/src/components/form/Form.js +++ b/src/components/form/Form.js @@ -437,6 +437,12 @@ export default class FormComponent extends Component { // Iterate through every component and hide the submit button. eachComponent(form.components, (component) => { this.hideSubmitButton(component); + if (component.validateWhenHidden) { + this.component.validateWhenHidden = true; + // Change original component as well, so it won't cause any custom logic to be continuously triggered + // because the original component does not have this property + this.originalComponent.validateWhenHidden = true; + } }); // If the subform is already created then destroy the old one. From 5e72a9767c98ebe780490f7b8ebd8bf0073f27df Mon Sep 17 00:00:00 2001 From: alexandraRamanenka Date: Mon, 31 Mar 2025 14:29:15 +0300 Subject: [PATCH 2/3] Make validateWhenHidden depend on parent --- src/Webform.js | 7 +++++++ src/components/_classes/component/Component.js | 13 ++++++++++++- src/components/form/Form.js | 10 ++++------ src/components/form/editForm/Form.edit.data.js | 8 ++++++++ 4 files changed, 31 insertions(+), 7 deletions(-) diff --git a/src/Webform.js b/src/Webform.js index b76b422d48..3a0a5a918a 100644 --- a/src/Webform.js +++ b/src/Webform.js @@ -358,6 +358,13 @@ export default class Webform extends NestedDataComponent { return this._data; } + get validateWhenHidden() { + if (this.parent?.component?.type === 'form' && this.parent.component.validateWhenHidden) { + return this.parent.validateWhenHidden; + } + return false; + } + /** * Sets the language for this form. * @param {string} lang - The language to use (e.g. 'en', 'sp', etc.) diff --git a/src/components/_classes/component/Component.js b/src/components/_classes/component/Component.js index 31c49baeef..2f831812ad 100644 --- a/src/components/_classes/component/Component.js +++ b/src/components/_classes/component/Component.js @@ -2536,6 +2536,17 @@ export default class Component extends Element { element.setAttribute('aria-invalid', invalid ? 'true' : 'false'); } + get validateWhenHidden() { + if (this.component.validateWhenHidden) { + if (this.parent && (this.parent !== this.parent.root)) { + return this.parent.validateWhenHidden; + } else { + return this.component.validateWhenHidden; + } + } + return false; + } + /** * Clears the components data if it is conditionally hidden AND clearOnHide is set to true for this component. */ @@ -3778,7 +3789,7 @@ export default class Component extends Element { () => this.isValueHidden(), // Force valid if component is hidden. () => { - if (!this.component.validateWhenHidden && (!this.visible || !this.checkCondition(row, data))) { + if (!this.validateWhenHidden && (!this.visible || !this.checkCondition(row, data))) { // If this component is forced valid when it is hidden, then we also need to reset the errors for this component. this._errors = []; return true; diff --git a/src/components/form/Form.js b/src/components/form/Form.js index 3cdb51c49e..a5a06592c1 100644 --- a/src/components/form/Form.js +++ b/src/components/form/Form.js @@ -18,6 +18,7 @@ export default class FormComponent extends Component { form: '', path: '', tableView: true, + validateWhenHidden: false, }, ...extend); } @@ -437,12 +438,6 @@ export default class FormComponent extends Component { // Iterate through every component and hide the submit button. eachComponent(form.components, (component) => { this.hideSubmitButton(component); - if (component.validateWhenHidden) { - this.component.validateWhenHidden = true; - // Change original component as well, so it won't cause any custom logic to be continuously triggered - // because the original component does not have this property - this.originalComponent.validateWhenHidden = true; - } }); // If the subform is already created then destroy the old one. @@ -530,6 +525,9 @@ export default class FormComponent extends Component { if (this.options.pdf && this.component.useOriginalRevision) { this.formObj.display = 'form'; } + if (this.component.validateWhenHidden) { + this.formObj.validateWhenHidden = true; + } this.subFormLoading = false; return formObj; }) diff --git a/src/components/form/editForm/Form.edit.data.js b/src/components/form/editForm/Form.edit.data.js index cea1404203..31953745cf 100644 --- a/src/components/form/editForm/Form.edit.data.js +++ b/src/components/form/editForm/Form.edit.data.js @@ -18,5 +18,13 @@ export default [ tooltip: 'When a field is hidden, clear the value.', input: true }, + { + weight: 100, + type: 'checkbox', + label: 'Validate When Hidden', + tooltip: 'Validates the component when it is hidden/conditionally hidden. Vaildation errors are displayed in the error alert on the form submission. Use caution when enabling this setting, as it can cause a hidden component to be invalid with no way for the form user to correct it.', + key: 'validateWhenHidden', + input: true + }, ]; /* eslint-enable max-len */ From 4ac2a71dfdcf00f1bfe86632f4d25d02774eaf78 Mon Sep 17 00:00:00 2001 From: alexandraRamanenka Date: Tue, 13 May 2025 13:10:07 +0300 Subject: [PATCH 3/3] Adjusted tests according to behaviour changes --- .../_classes/component/Component.js | 5 +- test/unit/validateWhenHidden.unit.js | 81 ++++++++++++++++++- 2 files changed, 82 insertions(+), 4 deletions(-) diff --git a/src/components/_classes/component/Component.js b/src/components/_classes/component/Component.js index 2f831812ad..a778ea8979 100644 --- a/src/components/_classes/component/Component.js +++ b/src/components/_classes/component/Component.js @@ -2537,11 +2537,12 @@ export default class Component extends Element { } get validateWhenHidden() { - if (this.component.validateWhenHidden) { + if (this.component.validateWhenHidden || !this.component.input) { if (this.parent && (this.parent !== this.parent.root)) { return this.parent.validateWhenHidden; } else { - return this.component.validateWhenHidden; + // Skip layout components since they don't have validateWhenHidden + return this.component.input ? this.component.validateWhenHidden : true; } } return false; diff --git a/test/unit/validateWhenHidden.unit.js b/test/unit/validateWhenHidden.unit.js index 4634b28d6b..e9c6e57d74 100644 --- a/test/unit/validateWhenHidden.unit.js +++ b/test/unit/validateWhenHidden.unit.js @@ -424,7 +424,8 @@ describe("Validate When Hidden behavior", function () { assert.equal(errors.length, 0); }); - it('Should validate components that are children of an intentionally hidden container component if those components have the `validateWhenHidden` property', async function () { + it('Should not validate components that are children of an intentionally hidden container component if those' + + ' components have the `validateWhenHidden` property, but the container does not', async function () { const formWithIntentionallyHiddenContainer = { components: [ { @@ -452,6 +453,39 @@ describe("Validate When Hidden behavior", function () { ); assert.equal(form.getComponent('foo').visible, false, 'The textfield should be hidden'); const errors = form.validate(); + assert.equal(errors.length, 0); + }); + + it('Should validate components that are children of an intentionally hidden container component if those' + + ' components and the container have the `validateWhenHidden` property', async function () { + const formWithIntentionallyHiddenContainer = { + components: [ + { + type: 'container', + key: 'container', + hidden: true, + clearOnHide: false, + validateWhenHidden: true, + components: [ + { + type: 'textfield', + key: 'foo', + label: 'Foo', + validateWhenHidden: true, + validate: { + required: true + } + } + ] + } + ] + }; + const form = await Formio.createForm( + document.createElement('div'), + formWithIntentionallyHiddenContainer + ); + assert.equal(form.getComponent('foo').visible, false, 'The textfield should be hidden'); + const errors = form.validate(); assert.equal(errors.length, 1); }); @@ -491,7 +525,9 @@ describe("Validate When Hidden behavior", function () { assert.equal(errors.length, 0); }); - it('Should validate components that are children of a conditionally hidden container component if those components include the `validateWhenHidden` parameter (NOTE THAT CLEAR ON HIDE MUST BE FALSE)', async function () { + it('Should not validate components that are children of a conditionally hidden container component if those' + + ' components include the `validateWhenHidden` parameter, but the container does not (NOTE THAT CLEAR ON HIDE' + + ' MUST BE FALSE)', async function () { const formWithConditionallyHiddenContainer = { components: [ { @@ -526,6 +562,47 @@ describe("Validate When Hidden behavior", function () { const form = await Formio.createForm(document.createElement('div'), formWithConditionallyHiddenContainer); assert.equal(form.getComponent('foo').visible, false, 'The textfield should be hidden'); const errors = form.validate(); + assert.equal(errors.length, 0); + }); + + it('Should validate components that are children of a conditionally hidden container component if those' + + ' components and the container include the `validateWhenHidden` parameter (NOTE THAT CLEAR ON HIDE' + + ' MUST BE FALSE)', async function () { + const formWithConditionallyHiddenContainer = { + components: [ + { + type: 'checkbox', + key: 'checkbox', + label: 'Checkbox', + input: true + }, + { + type: 'container', + key: 'container', + clearOnHide: false, + validateWhenHidden: true, + conditional: { + json: { + var: 'data.checkbox' + } + }, + components: [ + { + type: 'textfield', + key: 'foo', + label: 'Foo', + validateWhenHidden: true, + validate: { + required: true + } + } + ] + } + ] + }; + const form = await Formio.createForm(document.createElement('div'), formWithConditionallyHiddenContainer); + assert.equal(form.getComponent('foo').visible, false, 'The textfield should be hidden'); + const errors = form.validate(); assert.equal(errors.length, 1); }); })