From bf03c40fe49f15987f005333d107840c410195bd Mon Sep 17 00:00:00 2001 From: alexandraRamanenka Date: Fri, 6 Dec 2024 15:54:29 +0200 Subject: [PATCH 1/2] FIO-7937/FIO-7953: Fixes issues where Reset action does not clear values and restore the default ones --- .../_classes/component/Component.js | 7 + .../_classes/nested/NestedComponent.js | 2 +- .../nesteddata/NestedDataComponent.js | 2 +- src/components/address/Address.js | 2 +- src/components/editgrid/EditGrid.js | 2 +- src/components/radio/Radio.js | 8 +- .../resetActionDefaultValueBasicComponents.js | 187 +++++++++++++++++ ...etActionDefaultValuesDataComponentsForm.js | 194 ++++++++++++++++++ test/unit/Webform.unit.js | 190 +++++++++++++++++ 9 files changed, 586 insertions(+), 8 deletions(-) create mode 100644 test/forms/resetActionDefaultValueBasicComponents.js create mode 100644 test/forms/resetActionDefaultValuesDataComponentsForm.js diff --git a/src/components/_classes/component/Component.js b/src/components/_classes/component/Component.js index 1f5f374733..9cf5734e45 100644 --- a/src/components/_classes/component/Component.js +++ b/src/components/_classes/component/Component.js @@ -2964,6 +2964,13 @@ export default class Component extends Element { this.setValueAt(i, isArray && !this.isSingleInputValue() ? value[i] : value, flags); } } + + // Also reset value of the modal component, otherwise it will keep the old value locally and the preview + // element won't refresh + if (this.componentModal && flags && flags.resetValue) { + this.componentModal.setValue(value); + } + return changed; } diff --git a/src/components/_classes/nested/NestedComponent.js b/src/components/_classes/nested/NestedComponent.js index e429f71ff8..6d3a5eb7e4 100644 --- a/src/components/_classes/nested/NestedComponent.js +++ b/src/components/_classes/nested/NestedComponent.js @@ -921,8 +921,8 @@ export default class NestedComponent extends Field { } resetValue() { - super.resetValue(); this.getComponents().forEach((comp) => comp.resetValue()); + super.resetValue(); this.setPristine(true); } diff --git a/src/components/_classes/nesteddata/NestedDataComponent.js b/src/components/_classes/nesteddata/NestedDataComponent.js index 82b62b41fe..54c4fc71c5 100644 --- a/src/components/_classes/nesteddata/NestedDataComponent.js +++ b/src/components/_classes/nesteddata/NestedDataComponent.js @@ -4,7 +4,7 @@ import NestedComponent from '../nested/NestedComponent'; import _ from 'lodash'; import { componentValueTypes, getComponentSavedTypes } from '../../../utils/utils'; -export default class NestedDataComponent extends NestedComponent { +export default class NestedDataComponent extends NestedComponent { hasChanged(newValue, oldValue) { // If we do not have a value and are getting set to anything other than undefined or null, then we changed. if ( diff --git a/src/components/address/Address.js b/src/components/address/Address.js index c2e02e3ee2..9de6e41d1d 100644 --- a/src/components/address/Address.js +++ b/src/components/address/Address.js @@ -287,7 +287,7 @@ export default class AddressComponent extends ContainerComponent { this.restoreComponentsContext(); } - if (changed || !_.isEmpty(value) && flags.fromSubmission) { + if (changed || !_.isEmpty(value) && flags.fromSubmission || flags.resetValue) { this.redraw(); } diff --git a/src/components/editgrid/EditGrid.js b/src/components/editgrid/EditGrid.js index 819cf1872d..2f51f678d3 100644 --- a/src/components/editgrid/EditGrid.js +++ b/src/components/editgrid/EditGrid.js @@ -1417,7 +1417,7 @@ export default class EditGridComponent extends NestedArrayComponent { resetValue() { super.resetValue(); - this.emptyRows(); + // this.emptyRows(); } } diff --git a/src/components/radio/Radio.js b/src/components/radio/Radio.js index 1299021087..70dcb0139e 100644 --- a/src/components/radio/Radio.js +++ b/src/components/radio/Radio.js @@ -93,13 +93,15 @@ export default class RadioComponent extends ListComponent { return defaultValue; } - resetValue() { + uncheckValue(flags = {}) { this.unset(); this.setValue(this.emptyValue, { noUpdateEvent: true, noValidate: true, resetValue: true }); + this.triggerChange(flags); + this.setSelectedClasses(); } get inputInfo() { @@ -459,9 +461,7 @@ export default class RadioComponent extends ListComponent { this.currentValue = this.dataValue; const shouldResetValue = flags && flags.modified && !flags.noUpdateEvent && this.previousValue === this.currentValue; if (shouldResetValue) { - this.resetValue(); - this.triggerChange(flags); - this.setSelectedClasses(); + this.uncheckValue(flags); } this.previousValue = this.dataValue; return changed; diff --git a/test/forms/resetActionDefaultValueBasicComponents.js b/test/forms/resetActionDefaultValueBasicComponents.js new file mode 100644 index 0000000000..8601e5740c --- /dev/null +++ b/test/forms/resetActionDefaultValueBasicComponents.js @@ -0,0 +1,187 @@ +export default { + "title": "Basic", + "type": "form", + "name": "basic", + "path": "basic", + "display": "form", + "components": [ + { + "label": "Text Field - populate", + "applyMaskOn": "change", + "tableView": true, + "key": "textFieldPopulate", + "type": "textfield", + "input": true, + "defaultValue": "12345" + }, + { + "label": "Number - populate", + "applyMaskOn": "change", + "mask": false, + "tableView": false, + "delimiter": false, + "requireDecimal": false, + "inputFormat": "plain", + "truncateMultipleSpaces": false, + "key": "numberPopulate", + "type": "number", + "input": true, + "defaultValue": 25 + }, + { + "label": "Text Field", + "applyMaskOn": "change", + "tableView": true, + "customDefaultValue": "value = data.textFieldPopulate + \"new\";", + "key": "textField", + "type": "textfield", + "input": true + }, + { + "label": "Text Area", + "applyMaskOn": "change", + "autoExpand": false, + "tableView": true, + "customDefaultValue": "value = data.textFieldPopulate + \"new1\";", + "key": "textArea", + "type": "textarea", + "input": true + }, + { + "label": "Number", + "applyMaskOn": "change", + "mask": false, + "tableView": false, + "delimiter": false, + "requireDecimal": false, + "inputFormat": "plain", + "truncateMultipleSpaces": false, + "customDefaultValue": "value = data.numberPopulate + 25", + "key": "number", + "type": "number", + "input": true + }, + { + "label": "Checkbox", + "tableView": false, + "defaultValue": false, + "customDefaultValue": "value = true", + "key": "checkbox", + "type": "checkbox", + "input": true + }, + { + "label": "Select Boxes", + "optionsLabelPosition": "right", + "tableView": false, + "defaultValue": { + "1": false, + "2": false, + "3": false + }, + "values": [ + { + "label": "one", + "value": "1", + "shortcut": "" + }, + { + "label": "two", + "value": "2", + "shortcut": "" + }, + { + "label": "three", + "value": "3", + "shortcut": "" + } + ], + "customDefaultValue": "value = {\n \"1\": true,\n \"2\": false,\n \"3\": true\n }", + "key": "selectBoxes", + "type": "selectboxes", + "input": true, + "inputType": "checkbox" + }, + { + "label": "Select", + "widget": "choicesjs", + "tableView": true, + "data": { + "values": [ + { + "label": "one", + "value": "1" + }, + { + "label": "two", + "value": "2" + }, + { + "label": "three", + "value": "3" + } + ] + }, + "customDefaultValue": "value = 1", + "key": "select", + "type": "select", + "input": true + }, + { + "label": "Radio", + "optionsLabelPosition": "right", + "inline": false, + "tableView": false, + "values": [ + { + "label": "one", + "value": "1", + "shortcut": "" + }, + { + "label": "two", + "value": "2", + "shortcut": "" + }, + { + "label": "three", + "value": "3", + "shortcut": "" + } + ], + "customDefaultValue": "value = 2", + "key": "radio", + "type": "radio", + "input": true + }, + { + "label": "Reset", + "action": "reset", + "showValidations": false, + "theme": "danger", + "tableView": false, + "key": "reset", + "type": "button", + "input": true + }, + { + "label": "Reset custom", + "action": "custom", + "showValidations": false, + "theme": "secondary", + "tableView": false, + "key": "resetCustom", + "type": "button", + "custom": "instance.emit('resetForm')", + "input": true + }, + { + "type": "button", + "label": "Submit", + "key": "submit", + "disableOnInvalid": true, + "input": true, + "tableView": false + } + ] +}; diff --git a/test/forms/resetActionDefaultValuesDataComponentsForm.js b/test/forms/resetActionDefaultValuesDataComponentsForm.js new file mode 100644 index 0000000000..a8acf99240 --- /dev/null +++ b/test/forms/resetActionDefaultValuesDataComponentsForm.js @@ -0,0 +1,194 @@ +export default { + "title": "Data", + "type": "form", + "name": "data", + "path": "data", + "display": "form", + "components": [ + { + "label": "Hidden", + "defaultValue": "500", + "key": "hidden", + "type": "hidden", + "input": true, + "tableView": false + }, + { + "label": "Container", + "tableView": false, + "customDefaultValue": "value = {\n \"textField\": \"Officia consequuntur\",\n \"number\": 1000,\n \"currency\": 1001\n }", + "key": "container", + "type": "container", + "input": true, + "components": [ + { + "label": "Text Field", + "applyMaskOn": "change", + "tableView": true, + "key": "textField", + "type": "textfield", + "input": true + }, + { + "label": "Number", + "applyMaskOn": "change", + "mask": false, + "tableView": true, + "delimiter": false, + "requireDecimal": false, + "inputFormat": "plain", + "truncateMultipleSpaces": false, + "key": "number", + "type": "number", + "input": true + }, + { + "label": "Currency", + "applyMaskOn": "change", + "mask": false, + "spellcheck": true, + "tableView": true, + "defaultValue": 200, + "currency": "USD", + "inputFormat": "plain", + "truncateMultipleSpaces": false, + "key": "currency", + "type": "currency", + "input": true, + "delimiter": true + } + ] + }, + { + "label": "Data Grid", + "reorder": false, + "addAnotherPosition": "bottom", + "layoutFixed": false, + "enableRowGroups": false, + "initEmpty": false, + "tableView": false, + "defaultValue": [ + { + "textField1": "" + } + ], + "customDefaultValue": "value = [\n {\n \"textField1\": \"Eaque esse est dele\",\n \"number1\": 796,\n \"currency2\": 0\n },\n {\n \"textField1\": \"Default Value \",\n \"number1\": 100,\n \"currency2\": 200\n },\n {\n \"textField1\": \"Default Value \",\n \"number1\": 100,\n \"currency2\": 200\n }\n ]", + "key": "dataGrid", + "type": "datagrid", + "input": true, + "components": [ + { + "label": "Text Field", + "applyMaskOn": "change", + "tableView": true, + "key": "textField1", + "type": "textfield", + "input": true + }, + { + "label": "Number", + "applyMaskOn": "change", + "mask": false, + "tableView": true, + "delimiter": false, + "requireDecimal": false, + "inputFormat": "plain", + "truncateMultipleSpaces": false, + "key": "number1", + "type": "number", + "input": true + }, + { + "label": "Currency", + "applyMaskOn": "change", + "mask": false, + "spellcheck": true, + "tableView": true, + "currency": "USD", + "inputFormat": "plain", + "truncateMultipleSpaces": false, + "key": "currency2", + "type": "currency", + "input": true, + "delimiter": true + } + ] + }, + { + "label": "Edit Grid", + "tableView": false, + "customDefaultValue": "value = [\n {\n \"textField2\": \"Default Value \",\n \"number2\": 100,\n \"currency1\": 200\n },\n {\n \"textField2\": \"Default Value \",\n \"number2\": 100,\n \"currency1\": 200\n }\n ]", + "rowDrafts": false, + "key": "editGrid", + "type": "editgrid", + "displayAsTable": false, + "input": true, + "components": [ + { + "label": "Text Field", + "applyMaskOn": "change", + "tableView": true, + "key": "textField2", + "type": "textfield", + "input": true + }, + { + "label": "Number", + "applyMaskOn": "change", + "mask": false, + "tableView": true, + "delimiter": false, + "requireDecimal": false, + "inputFormat": "plain", + "truncateMultipleSpaces": false, + "key": "number2", + "type": "number", + "input": true + }, + { + "label": "Currency", + "applyMaskOn": "change", + "mask": false, + "spellcheck": true, + "tableView": true, + "currency": "USD", + "inputFormat": "plain", + "truncateMultipleSpaces": false, + "key": "currency1", + "type": "currency", + "input": true, + "delimiter": true + } + ] + }, + { + "label": "Reset", + "action": "reset", + "showValidations": false, + "theme": "danger", + "tableView": false, + "key": "reset", + "type": "button", + "input": true + }, + { + "label": "Reset custom", + "action": "custom", + "showValidations": false, + "theme": "secondary", + "tableView": false, + "key": "resetCustom", + "type": "button", + "custom": "instance.emit('resetForm')", + "input": true + }, + { + "type": "button", + "label": "Submit", + "key": "submit", + "disableOnInvalid": true, + "input": true, + "tableView": false + } + ] +}; diff --git a/test/unit/Webform.unit.js b/test/unit/Webform.unit.js index 145d157693..bcac767125 100644 --- a/test/unit/Webform.unit.js +++ b/test/unit/Webform.unit.js @@ -2,6 +2,8 @@ import assert from 'power-assert'; import { expect } from 'chai'; import sinon from 'sinon'; import _ from 'lodash'; +import resetActionDefaultValueBasicComponents from '../forms/resetActionDefaultValueBasicComponents'; +import resetActionDefaultValuesDataComponentsForm from '../forms/resetActionDefaultValuesDataComponentsForm'; import Harness from '../harness.js'; import FormTests from '../forms/index.js'; import Webform from '../../src/Webform.js'; @@ -2899,6 +2901,194 @@ describe('Webform tests', function() { }); }); }); + + it('Should reset all default values correctly (Data components).', (done) => { + formElement.innerHTML = ''; + Formio.createForm(formElement, resetActionDefaultValuesDataComponentsForm).then((form) => { + const nonDefaultValue = { + hidden: '500', + container: { + textField: 'Test', + number: 1, + currency: 1, + }, + dataGrid: [ + { + textField1: 'Test', + number1: 1, + currency2: 1, + }, + { + textField1: 'Test', + number1: 1, + currency2: 1, + }, + { + textField1: 'Test', + number1: 1, + currency2: 1, + }, + { + textField1: 'Test', + number1: 1, + currency2: 1, + }, + ], + editGrid: [ + { + textField2: 'Test', + number2: 1, + currency1: 1, + }, + { + textField2: 'Test', + number2: 1, + currency1: 1, + }, + { + textField2: 'Test', + number2: 1, + currency1: 1, + }, + ], + reset: false, + resetCustom: false, + submit: true, + }; + const defaultValue = { + hidden: '500', + container: { + textField: 'Officia consequuntur', + number: 1000, + currency: 1001, + }, + dataGrid: [ + { + textField1: 'Eaque esse est dele', + number1: 796, + currency2: 0, + }, + { + textField1: 'Default Value ', + number1: 100, + currency2: 200, + }, + { + textField1: 'Default Value ', + number1: 100, + currency2: 200, + }, + ], + editGrid: [ + { + textField2: 'Default Value ', + number2: 100, + currency1: 200, + }, + { + textField2: 'Default Value ', + number2: 100, + currency1: 200, + }, + ], + reset: false, + resetCustom: false, + submit: true, + }; + form.setSubmission({ data: { ...nonDefaultValue } }).then(() => { + expect(form.submission).to.deep.equal({ data: nonDefaultValue }); + form.resetValue(); + + setTimeout(() => { + expect(form.submission).to.deep.equal({ data: defaultValue }); + done(); + }, 200); + }); + }).catch(done); + }); + + it('Should reset all default values correctly (Basic components).', (done) => { + formElement.innerHTML = ''; + Formio.createForm(formElement, resetActionDefaultValueBasicComponents).then((form) => { + const nonDefaultValue = { + hidden: '500', + container: { + textField: 'Test', + number: 1, + currency: 1, + }, + dataGrid: [ + { + textField1: 'Test', + number1: 1, + currency2: 1, + }, + { + textField1: 'Test', + number1: 1, + currency2: 1, + }, + { + textField1: 'Test', + number1: 1, + currency2: 1, + }, + { + textField1: 'Test', + number1: 1, + currency2: 1, + }, + ], + editGrid: [ + { + textField2: 'Test', + number2: 1, + currency1: 1, + }, + { + textField2: 'Test', + number2: 1, + currency1: 1, + }, + { + textField2: 'Test', + number2: 1, + currency1: 1, + }, + ], + reset: false, + resetCustom: false, + submit: true, + }; + const defaultValue = { + textFieldPopulate: '1', + numberPopulate: 1, + textField: '1', + textArea: '1', + number: 1, + checkbox: false, + selectBoxes: { + '1': false, + '2': true, + '3': true, + }, + select: 2, + radio: 3, + reset: false, + resetCustom: false, + submit: true, + }; + form.setSubmission({ data: { ...nonDefaultValue } }).then(() => { + expect(form.submission).to.deep.equal({ data: nonDefaultValue }); + form.resetValue(); + + setTimeout(() => { + expect(form.submission).to.deep.equal({ data: defaultValue }); + done(); + }, 200); + }); + }).catch(done); + }); }); describe('New Simple Conditions', () => { From f5ad6dceb682df60df7f0e84ab113abd6f12fa8e Mon Sep 17 00:00:00 2001 From: alexandraRamanenka Date: Fri, 6 Dec 2024 15:58:36 +0200 Subject: [PATCH 2/2] Refactoring --- src/components/_classes/nested/NestedComponent.js | 2 ++ .../_classes/nesteddata/NestedDataComponent.js | 2 +- src/components/editgrid/EditGrid.js | 10 ---------- src/components/selectboxes/SelectBoxes.js | 2 +- 4 files changed, 4 insertions(+), 12 deletions(-) diff --git a/src/components/_classes/nested/NestedComponent.js b/src/components/_classes/nested/NestedComponent.js index 6d3a5eb7e4..3f700cf79c 100644 --- a/src/components/_classes/nested/NestedComponent.js +++ b/src/components/_classes/nested/NestedComponent.js @@ -921,6 +921,8 @@ export default class NestedComponent extends Field { } resetValue() { + // Reset values of child components first, then reset the parent one, otherwise it will restore the default + // value of parent component and clear it one by one while resetting child components this.getComponents().forEach((comp) => comp.resetValue()); super.resetValue(); this.setPristine(true); diff --git a/src/components/_classes/nesteddata/NestedDataComponent.js b/src/components/_classes/nesteddata/NestedDataComponent.js index 54c4fc71c5..82b62b41fe 100644 --- a/src/components/_classes/nesteddata/NestedDataComponent.js +++ b/src/components/_classes/nesteddata/NestedDataComponent.js @@ -4,7 +4,7 @@ import NestedComponent from '../nested/NestedComponent'; import _ from 'lodash'; import { componentValueTypes, getComponentSavedTypes } from '../../../utils/utils'; -export default class NestedDataComponent extends NestedComponent { +export default class NestedDataComponent extends NestedComponent { hasChanged(newValue, oldValue) { // If we do not have a value and are getting set to anything other than undefined or null, then we changed. if ( diff --git a/src/components/editgrid/EditGrid.js b/src/components/editgrid/EditGrid.js index 2f51f678d3..add52d689e 100644 --- a/src/components/editgrid/EditGrid.js +++ b/src/components/editgrid/EditGrid.js @@ -1409,16 +1409,6 @@ export default class EditGridComponent extends NestedArrayComponent { this.setNestedValue(component, editRow.data, flags); }); } - - emptyRows() { - this.editRows.forEach((editRow, index) => this.destroyComponents(false, index)); - this.editRows = []; - } - - resetValue() { - super.resetValue(); - // this.emptyRows(); - } } EditGridComponent.prototype.hasChanged = Component.prototype.hasChanged; diff --git a/src/components/selectboxes/SelectBoxes.js b/src/components/selectboxes/SelectBoxes.js index 6919738b63..85a8198259 100644 --- a/src/components/selectboxes/SelectBoxes.js +++ b/src/components/selectboxes/SelectBoxes.js @@ -307,7 +307,7 @@ export default class SelectBoxesComponent extends RadioComponent { return super.setCustomValidity(_.filter(messages, (message) => message.ruleName !=='invalidValueProperty'), dirty, external); } else { return super.setCustomValidity(messages, dirty, external); - }; + } } validateValueAvailability(setting, value) {