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..a778ea8979 100644 --- a/src/components/_classes/component/Component.js +++ b/src/components/_classes/component/Component.js @@ -2536,6 +2536,18 @@ export default class Component extends Element { element.setAttribute('aria-invalid', invalid ? 'true' : 'false'); } + get validateWhenHidden() { + if (this.component.validateWhenHidden || !this.component.input) { + if (this.parent && (this.parent !== this.parent.root)) { + return this.parent.validateWhenHidden; + } else { + // Skip layout components since they don't have validateWhenHidden + return this.component.input ? this.component.validateWhenHidden : true; + } + } + return false; + } + /** * Clears the components data if it is conditionally hidden AND clearOnHide is set to true for this component. */ @@ -3778,7 +3790,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 4b9652baac..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); } @@ -524,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 */ 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); }); })