Merge pull request #11383 from appsmithorg/feat/4182-form-detect-changes

feat: Internal property to detect changes in a form
This commit is contained in:
Tolulope Adetula 2022-04-05 06:35:20 +01:00 committed by GitHub
commit e05e75758d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
48 changed files with 1373 additions and 16 deletions

View File

@ -19,6 +19,22 @@
"dynamicBindingPathList": [],
"leftColumn": 0,
"children": [
{
"isVisible": true,
"text": "Label",
"textAlign": "LEFT",
"widgetName": "Text1",
"type": "TEXT_WIDGET",
"isLoading": false,
"parentColumnSpace": 14.015625,
"parentRowSpace": 10,
"leftColumn": 1,
"rightColumn": 7,
"topRow": 1,
"bottomRow": 4,
"parentId": "bxekwxgc1i",
"widgetId": "9xcfqahpw2"
},
{
"isMirrored": true,
"widgetName": "Camera1",

View File

@ -59,6 +59,22 @@
"renderMode": "CANVAS",
"isLoading": false,
"isInline": true
},
{
"isVisible": true,
"text": "Label",
"textAlign": "LEFT",
"widgetName": "Text1",
"type": "TEXT_WIDGET",
"isLoading": false,
"parentColumnSpace": 18.66171875,
"parentRowSpace": 10,
"leftColumn": 3,
"rightColumn": 10,
"topRow": 5,
"bottomRow": 9,
"parentId": "bxekwxgc1i",
"widgetId": "9xcfqahpw2"
}
]
}

View File

@ -122,6 +122,22 @@
"renderMode":"CANVAS",
"isLoading":false,
"allowClear":false
},
{
"isVisible": true,
"text": "Label",
"textAlign": "LEFT",
"widgetName": "Text1",
"type": "TEXT_WIDGET",
"isLoading": false,
"parentColumnSpace": 15.974999999999998,
"parentRowSpace": 10,
"leftColumn": 3,
"rightColumn": 15,
"topRow": 10,
"bottomRow": 12,
"parentId": "bxekwxgc1i",
"widgetId": "9xcfqahpw2"
}
]
}

View File

@ -0,0 +1,481 @@
{
"dsl": {
"widgetName": "MainContainer",
"backgroundColor": "none",
"rightColumn": 909,
"snapColumns": 64,
"detachFromLayout": true,
"widgetId": "0",
"topRow": 0,
"bottomRow": 1290,
"containerStyle": "none",
"snapRows": 125,
"parentRowSpace": 1,
"type": "CANVAS_WIDGET",
"canExtend": true,
"version": 52,
"minHeight": 690,
"parentColumnSpace": 1,
"dynamicBindingPathList": [],
"leftColumn": 0,
"children": [
{
"widgetName": "Form1",
"backgroundColor": "white",
"rightColumn": 62,
"isCanvas": true,
"displayName": "Form",
"iconSVG": "/static/media/icon.ea3e08d1.svg",
"widgetId": "ui8kenblnq",
"topRow": 5,
"bottomRow": 62,
"parentRowSpace": 10,
"isVisible": true,
"type": "FORM_WIDGET",
"hideCard": false,
"parentId": "0",
"renderMode": "CANVAS",
"isLoading": false,
"animateLoading": true,
"parentColumnSpace": 14.015625,
"leftColumn": 2,
"children": [
{
"widgetName": "Canvas1",
"rightColumn": 336.375,
"detachFromLayout": true,
"displayName": "Canvas",
"widgetId": "rgnqy2hpcb",
"containerStyle": "none",
"topRow": 0,
"bottomRow": 570,
"parentRowSpace": 1,
"isVisible": true,
"type": "CANVAS_WIDGET",
"canExtend": false,
"version": 1,
"hideCard": true,
"parentId": "ui8kenblnq",
"minHeight": 400,
"renderMode": "CANVAS",
"isLoading": false,
"parentColumnSpace": 1,
"leftColumn": 0,
"children": [
{
"widgetName": "Text1",
"displayName": "Text",
"iconSVG": "/static/media/icon.97c59b52.svg",
"topRow": 1,
"bottomRow": 5,
"type": "TEXT_WIDGET",
"hideCard": false,
"animateLoading": true,
"dynamicTriggerPathList": [],
"leftColumn": 1.5,
"dynamicBindingPathList": [],
"shouldTruncate": false,
"truncateButtonColor": "#FFC13D",
"text": "TopLevelForm",
"key": "r71qxtucag",
"rightColumn": 25.5,
"textAlign": "LEFT",
"widgetId": "az2x2gnx8i",
"isVisible": true,
"fontStyle": "BOLD",
"textColor": "#231F20",
"shouldScroll": false,
"version": 1,
"parentId": "rgnqy2hpcb",
"renderMode": "CANVAS",
"isLoading": false,
"fontSize": "HEADING1"
},
{
"boxShadow": "NONE",
"widgetName": "Container1",
"borderColor": "transparent",
"isCanvas": true,
"displayName": "Container",
"iconSVG": "/static/media/icon.1977dca3.svg",
"topRow": 7,
"bottomRow": 55,
"parentRowSpace": 10,
"type": "CONTAINER_WIDGET",
"hideCard": false,
"animateLoading": true,
"parentColumnSpace": 12.8271484375,
"leftColumn": 1,
"children": [
{
"widgetName": "Canvas2",
"rightColumn": 307.8515625,
"detachFromLayout": true,
"displayName": "Canvas",
"widgetId": "o16xur9huq",
"containerStyle": "none",
"topRow": 0,
"bottomRow": 490,
"parentRowSpace": 1,
"isVisible": true,
"type": "CANVAS_WIDGET",
"canExtend": false,
"version": 1,
"hideCard": true,
"parentId": "c3ib5l2wce",
"minHeight": 400,
"renderMode": "CANVAS",
"isLoading": false,
"parentColumnSpace": 1,
"leftColumn": 0,
"children": [
{
"widgetName": "Form2",
"backgroundColor": "white",
"rightColumn": 64,
"isCanvas": true,
"displayName": "Form",
"iconSVG": "/static/media/icon.ea3e08d1.svg",
"widgetId": "r0vj63apg4",
"topRow": 1,
"bottomRow": 47,
"parentRowSpace": 10,
"isVisible": true,
"type": "FORM_WIDGET",
"hideCard": false,
"parentId": "o16xur9huq",
"renderMode": "CANVAS",
"isLoading": false,
"animateLoading": true,
"parentColumnSpace": 12.314224243164062,
"leftColumn": 0,
"children": [
{
"widgetName": "Canvas3",
"rightColumn": 295.5413818359375,
"detachFromLayout": true,
"displayName": "Canvas",
"widgetId": "hyxstzafwr",
"containerStyle": "none",
"topRow": 0,
"bottomRow": 460,
"parentRowSpace": 1,
"isVisible": true,
"type": "CANVAS_WIDGET",
"canExtend": false,
"version": 1,
"hideCard": true,
"parentId": "r0vj63apg4",
"minHeight": 400,
"renderMode": "CANVAS",
"isLoading": false,
"parentColumnSpace": 1,
"leftColumn": 0,
"children": [
{
"widgetName": "Text2",
"displayName": "Text",
"iconSVG": "/static/media/icon.97c59b52.svg",
"topRow": 1,
"bottomRow": 5,
"type": "TEXT_WIDGET",
"hideCard": false,
"animateLoading": true,
"dynamicTriggerPathList": [],
"leftColumn": 1.5,
"dynamicBindingPathList": [],
"shouldTruncate": false,
"truncateButtonColor": "#FFC13D",
"text": "NestedForm1",
"key": "r71qxtucag",
"rightColumn": 25.5,
"textAlign": "LEFT",
"widgetId": "9x1tljetyn",
"isVisible": true,
"fontStyle": "BOLD",
"textColor": "#231F20",
"shouldScroll": false,
"version": 1,
"parentId": "hyxstzafwr",
"renderMode": "CANVAS",
"isLoading": false,
"fontSize": "HEADING1"
},
{
"boxShadow": "NONE",
"widgetName": "Container2",
"borderColor": "transparent",
"isCanvas": true,
"displayName": "Container",
"iconSVG": "/static/media/icon.1977dca3.svg",
"topRow": 6,
"bottomRow": 44,
"parentRowSpace": 10,
"type": "CONTAINER_WIDGET",
"hideCard": false,
"animateLoading": true,
"parentColumnSpace": 12.001724243164062,
"leftColumn": 0,
"children": [
{
"widgetName": "Canvas4",
"rightColumn": 288.0413818359375,
"detachFromLayout": true,
"displayName": "Canvas",
"widgetId": "bezg0vuivd",
"containerStyle": "none",
"topRow": 0,
"bottomRow": 390,
"parentRowSpace": 1,
"isVisible": true,
"type": "CANVAS_WIDGET",
"canExtend": false,
"version": 1,
"hideCard": true,
"parentId": "d3bbrwipbo",
"minHeight": 400,
"renderMode": "CANVAS",
"isLoading": false,
"parentColumnSpace": 1,
"leftColumn": 0,
"children": [
{
"widgetName": "Form3",
"backgroundColor": "white",
"rightColumn": 62,
"isCanvas": true,
"displayName": "Form",
"iconSVG": "/static/media/icon.ea3e08d1.svg",
"widgetId": "e9qj527b6g",
"topRow": 0,
"bottomRow": 36,
"parentRowSpace": 10,
"isVisible": true,
"type": "FORM_WIDGET",
"hideCard": false,
"parentId": "bezg0vuivd",
"renderMode": "CANVAS",
"isLoading": false,
"animateLoading": true,
"parentColumnSpace": 12.001724243164062,
"leftColumn": 0,
"children": [
{
"widgetName": "Canvas5",
"rightColumn": 288.0413818359375,
"detachFromLayout": true,
"displayName": "Canvas",
"widgetId": "ju0erkgavw",
"containerStyle": "none",
"topRow": 0,
"bottomRow": 390,
"parentRowSpace": 1,
"isVisible": true,
"type": "CANVAS_WIDGET",
"canExtend": false,
"version": 1,
"hideCard": true,
"parentId": "e9qj527b6g",
"minHeight": 400,
"renderMode": "CANVAS",
"isLoading": false,
"parentColumnSpace": 1,
"leftColumn": 0,
"children": [
{
"widgetName": "Text3",
"displayName": "Text",
"iconSVG": "/static/media/icon.97c59b52.svg",
"topRow": 1,
"bottomRow": 5,
"type": "TEXT_WIDGET",
"hideCard": false,
"animateLoading": true,
"dynamicTriggerPathList": [],
"leftColumn": 1.5,
"dynamicBindingPathList": [],
"shouldTruncate": false,
"truncateButtonColor": "#FFC13D",
"text": "NestedForm2",
"key": "r71qxtucag",
"rightColumn": 25.5,
"textAlign": "LEFT",
"widgetId": "kx3kje0esv",
"isVisible": true,
"fontStyle": "BOLD",
"textColor": "#231F20",
"shouldScroll": false,
"version": 1,
"parentId": "ju0erkgavw",
"renderMode": "CANVAS",
"isLoading": false,
"fontSize": "HEADING1"
},
{
"resetFormOnClick": true,
"widgetName": "FormButton1",
"buttonColor": "#03B365",
"displayName": "FormButton",
"iconSVG": "/static/media/icon.c8f649ed.svg",
"topRow": 29,
"bottomRow": 33,
"type": "FORM_BUTTON_WIDGET",
"hideCard": true,
"animateLoading": true,
"leftColumn": 47,
"text": "Submit",
"key": "6rckarmxa8",
"rightColumn": 63,
"isDefaultClickDisabled": true,
"widgetId": "71jwl9rqyl",
"isVisible": true,
"recaptchaType": "V3",
"version": 1,
"parentId": "ju0erkgavw",
"renderMode": "CANVAS",
"isLoading": false,
"disabledWhenInvalid": true,
"buttonVariant": "PRIMARY"
},
{
"resetFormOnClick": true,
"widgetName": "FormButton2",
"buttonColor": "#03B365",
"displayName": "FormButton",
"iconSVG": "/static/media/icon.c8f649ed.svg",
"topRow": 29,
"bottomRow": 33,
"type": "FORM_BUTTON_WIDGET",
"hideCard": true,
"animateLoading": true,
"leftColumn": 30,
"text": "Reset",
"key": "6rckarmxa8",
"rightColumn": 46,
"isDefaultClickDisabled": true,
"widgetId": "385vwj1sak",
"isVisible": true,
"recaptchaType": "V3",
"version": 1,
"parentId": "ju0erkgavw",
"renderMode": "CANVAS",
"isLoading": false,
"disabledWhenInvalid": false,
"buttonVariant": "SECONDARY"
},
{
"widgetName": "Checkbox1",
"displayName": "Checkbox",
"iconSVG": "/static/media/icon.aaab032b.svg",
"topRow": 7,
"bottomRow": 11,
"parentRowSpace": 10,
"type": "CHECKBOX_WIDGET",
"alignWidget": "LEFT",
"hideCard": false,
"animateLoading": true,
"parentColumnSpace": 11.011435985565186,
"leftColumn": 1,
"isDisabled": false,
"key": "kn37pa6063",
"isRequired": false,
"rightColumn": 21,
"widgetId": "xcs2djteir",
"isVisible": true,
"label": "Label",
"version": 1,
"parentId": "ju0erkgavw",
"renderMode": "CANVAS",
"isLoading": false,
"defaultCheckedState": true
}
],
"key": "invgby4psp"
}
],
"key": "1yxjunjfwb"
}
],
"key": "invgby4psp"
}
],
"borderWidth": "0",
"key": "tpj0tcwdhg",
"backgroundColor": "#FFFFFF",
"rightColumn": 64,
"widgetId": "d3bbrwipbo",
"containerStyle": "card",
"isVisible": true,
"version": 1,
"parentId": "hyxstzafwr",
"renderMode": "CANVAS",
"isLoading": false,
"borderRadius": "0"
}
],
"key": "invgby4psp"
}
],
"key": "1yxjunjfwb"
}
],
"key": "invgby4psp"
}
],
"borderWidth": "0",
"key": "tpj0tcwdhg",
"backgroundColor": "#FFFFFF",
"rightColumn": 64,
"widgetId": "c3ib5l2wce",
"containerStyle": "card",
"isVisible": true,
"version": 1,
"parentId": "rgnqy2hpcb",
"renderMode": "CANVAS",
"isLoading": false,
"borderRadius": "0"
},
{
"widgetName": "Text4",
"displayName": "Text",
"iconSVG": "/static/media/icon.97c59b52.svg",
"topRow": 1,
"bottomRow": 5,
"parentRowSpace": 10,
"type": "TEXT_WIDGET",
"hideCard": false,
"animateLoading": true,
"parentColumnSpace": 12.8271484375,
"dynamicTriggerPathList": [],
"leftColumn": 47,
"dynamicBindingPathList": [
{
"key": "text"
}
],
"shouldTruncate": false,
"truncateButtonColor": "#FFC13D",
"text": "{{Form1.hasChanges}}",
"key": "r71qxtucag",
"rightColumn": 63,
"textAlign": "LEFT",
"widgetId": "6lwl9odtd7",
"isVisible": true,
"fontStyle": "BOLD",
"textColor": "#231F20",
"shouldScroll": false,
"version": 1,
"parentId": "rgnqy2hpcb",
"renderMode": "CANVAS",
"isLoading": false,
"fontSize": "PARAGRAPH"
}
],
"key": "invgby4psp"
}
],
"key": "1yxjunjfwb"
}
]
}
}

View File

@ -105,6 +105,22 @@
"bottomRow": 7,
"parentId": "7tkpo9s22m",
"widgetId": "6h8j08u7ea"
},
{
"isVisible": true,
"text": "Label",
"textAlign": "LEFT",
"widgetName": "Text1",
"type": "TEXT_WIDGET",
"isLoading": false,
"parentColumnSpace": 71.75,
"parentRowSpace": 38,
"leftColumn": 3,
"rightColumn": 5,
"topRow": 10,
"bottomRow": 12,
"parentId": "bxekwxgc1i",
"widgetId": "9xcfqahpw2"
}
],
"widgetId": "7tkpo9s22m",

View File

@ -61,6 +61,25 @@
"leftColumn": 0,
"dynamicBindingPathList": [],
"children": [
{
"widgetName": "Text1",
"rightColumn": 10,
"widgetId": "4d8d2eh4xg",
"dynamicPropertyPathList": [],
"topRow": 0,
"bottomRow": 3,
"parentRowSpace": 38,
"isVisible": true,
"type": "TEXT_WIDGET",
"dynamicBindingPathList": [],
"shouldScroll": true,
"parentId": "hjiybwqair",
"isLoading": false,
"parentColumnSpace": 71.75,
"leftColumn": 1,
"text": "Test text",
"textStyle": "HEADING"
},
{
"isRequired": false,
"widgetName": "Input1",

View File

@ -52,6 +52,25 @@
"snapColumns": 16,
"orientation": "VERTICAL",
"children": [
{
"widgetName": "Text1",
"rightColumn": 3,
"widgetId": "4d8d2eh4xg",
"dynamicPropertyPathList": [],
"topRow": 0,
"bottomRow": 1,
"parentRowSpace": 38,
"isVisible": true,
"type": "TEXT_WIDGET",
"dynamicBindingPathList": [],
"shouldScroll": true,
"parentId": "hjiybwqair",
"isLoading": false,
"parentColumnSpace": 71.75,
"leftColumn": 1,
"text": "Test text",
"textStyle": "HEADING"
},
{
"isVisible": true,
"inputType": "TEXT",

View File

@ -59,9 +59,10 @@ describe("Entity explorer Drag and Drop widgets testcases", function() {
cy.hoverAndClickParticularIndex(1);
cy.selectAction("Show Bindings");
cy.get(apiwidget.propertyList).then(function($lis) {
expect($lis).to.have.length(2);
expect($lis).to.have.length(3);
expect($lis.eq(0)).to.contain("{{FormTest.isVisible}}");
expect($lis.eq(1)).to.contain("{{FormTest.data}}");
expect($lis.eq(2)).to.contain("{{FormTest.hasChanges}}");
});
});
});

View File

@ -35,9 +35,10 @@ describe("Test Suite to validate copy/delete/undo functionalites", function() {
cy.hoverAndClickParticularIndex(1);
cy.selectAction("Show Bindings");
cy.get(apiwidget.propertyList).then(function($lis) {
expect($lis).to.have.length(2);
expect($lis).to.have.length(3);
expect($lis.eq(0)).to.contain("{{FormTest.isVisible}}");
expect($lis.eq(1)).to.contain("{{FormTest.data}}");
expect($lis.eq(2)).to.contain("{{FormTest.hasChanges}}");
});
cy.get(".t--entity-name")
.contains("FormTest")
@ -68,9 +69,10 @@ describe("Test Suite to validate copy/delete/undo functionalites", function() {
cy.hoverAndClickParticularIndex(1);
cy.selectAction("Show Bindings");
cy.get(apiwidget.propertyList).then(function($lis) {
expect($lis).to.have.length(2);
expect($lis).to.have.length(3);
expect($lis.eq(0)).to.contain("{{FormTest.isVisible}}");
expect($lis.eq(1)).to.contain("{{FormTest.data}}");
expect($lis.eq(2)).to.contain("{{FormTest.hasChanges}}");
});
});
});

View File

@ -46,9 +46,10 @@ describe("Test Suite to validate copy/delete/undo functionalites", function() {
ee.expandCollapseEntity("FormTest");
ee.ActionContextMenuByEntityName("FormTestCopy", "Show Bindings");
cy.get(apiwidget.propertyList).then(function($lis) {
expect($lis).to.have.length(2);
expect($lis).to.have.length(3);
expect($lis.eq(0)).to.contain("{{FormTestCopy.isVisible}}");
expect($lis.eq(1)).to.contain("{{FormTestCopy.data}}");
expect($lis.eq(2)).to.contain("{{FormTestCopy.hasChanges}}");
cy.contains("FormTestCopy");
cy.get($lis.eq(1))
.contains("{{FormTestCopy.data}}")

View File

@ -0,0 +1,40 @@
const explorer = require("../../../../locators/explorerlocators.json");
const widgetName = "audiorecorderwidget";
describe("AudioRecorder Widget", () => {
it("Drag & drop AudioRecorder and Text widgets", () => {
cy.get(explorer.addWidget).click();
cy.dragAndDropToCanvas(widgetName, { x: 300, y: 300 });
cy.get(`.t--widget-${widgetName}`).should("exist");
cy.dragAndDropToCanvas("textwidget", { x: 300, y: 500 });
cy.openPropertyPane("textwidget");
cy.updateCodeInput(
".t--property-control-text",
`{{AudioRecorder1.isDirty}}`,
);
});
it("Check isDirty meta property", () => {
// Check if isDirty is false for the first time
cy.get(".t--widget-textwidget").should("contain", "false");
// Interact with UI
cy.get(`.t--widget-${widgetName} button`)
.first()
.click();
cy.get(`.t--widget-${widgetName} .status`)
.should("have.text", "Press to start recording")
.should("exist");
// Start recording and recorder for 3 seconds
cy.get(`.t--widget-${widgetName} button`)
.first()
.click();
cy.wait(3000);
// Stop recording
cy.get(`.t--widget-${widgetName} button span.bp3-icon-symbol-square`)
.first()
.click();
// Check if isDirty is set to true
cy.get(".t--widget-textwidget").should("contain", "true");
});
});

View File

@ -14,12 +14,16 @@ describe("Camera Widget", () => {
cy.goToEditFromPublish();
});
it("Property: onImageSave, show modal", () => {
it("Check isDirty, onImageSave", () => {
const modalName = `modal`;
const mainControlSelector =
"//div[contains(@class, 't--widget-camerawidget')]//button";
cy.createModal(modalName);
cy.openPropertyPane("textwidget");
cy.updateCodeInput(".t--property-control-text", "{{Camera1.isDirty}}");
// Initial value of isDirty should be false
cy.get(".t--widget-textwidget").should("contain", "false");
// Take photo
cy.xpath(mainControlSelector)
.eq(2)
@ -32,5 +36,7 @@ describe("Camera Widget", () => {
// Assert: should trigger onImageSave action - modal popup
cy.get(modalWidgetPage.modelTextField).should("have.text", modalName);
// Check if isDirty is set to true
cy.get(".t--widget-textwidget").should("contain", "true");
});
});

View File

@ -5,6 +5,7 @@ const publish = require("../../../../locators/publishWidgetspage.json");
const dsl = require("../../../../fixtures/newFormDsl.json");
const formWidgetDsl = require("../../../../fixtures/formWidgetdsl.json");
const pages = require("../../../../locators/Pages.json");
const explorer = require("../../../../locators/explorerlocators.json");
describe("Checkbox Widget Functionality", function() {
before(() => {
@ -72,6 +73,26 @@ describe("Checkbox Widget Functionality", function() {
cy.get(publish.checkboxWidget + " " + "input").should("be.checked");
cy.get(publish.backToEditor).click();
});
it("Check isDirty meta property", function() {
cy.openPropertyPane("textwidget");
cy.updateCodeInput(".t--property-control-text", `{{checker.isDirty}}`);
// Check if initial value of isDirty is false
cy.get(".t--widget-textwidget").should("contain", "false");
// Interact with UI
cy.get(`${formWidgetsPage.checkboxWidget} label`)
.first()
.click();
// Check if isDirty is set to true
cy.get(".t--widget-textwidget").should("contain", "true");
// Change defaultCheckedState property
cy.openPropertyPane("checkboxwidget");
cy.get(".t--property-control-defaultselected label")
.last()
.click();
// Check if isDirty is reset to false
cy.get(".t--widget-textwidget").should("contain", "false");
});
});
afterEach(() => {

View File

@ -1,6 +1,7 @@
const commonlocators = require("../../../../locators/commonlocators.json");
const formWidgetsPage = require("../../../../locators/FormWidgets.json");
const publish = require("../../../../locators/publishWidgetspage.json");
const explorer = require("../../../../locators/explorerlocators.json");
const dsl = require("../../../../fixtures/checkboxgroupDsl.json");
describe("Checkbox Group Widget Functionality", function() {
@ -129,6 +130,27 @@ describe("Checkbox Group Widget Functionality", function() {
".t--draggable-checkboxgroupwidget div[data-cy^='checkbox-group-container']",
).should("have.css", "justify-content", "flex-start");
});
it("Check isDirty meta property", function() {
cy.get(explorer.addWidget).click();
cy.dragAndDropToCanvas("textwidget", { x: 300, y: 500 });
cy.openPropertyPane("textwidget");
cy.updateCodeInput(
".t--property-control-text",
`{{checkboxgrouptest.isDirty}}`,
);
// Change defaultSelectedValues
cy.openPropertyPane("checkboxgroupwidget");
cy.updateCodeInput(".t--property-control-defaultselectedvalues", "GREEN");
// Check if isDirty is reset to false
cy.get(".t--widget-textwidget").should("contain", "false");
// Interact with UI
cy.get(formWidgetsPage.labelCheckboxGroup)
.first()
.click();
// Check if isDirty is set to true
cy.get(".t--widget-textwidget").should("contain", "true");
});
});
afterEach(() => {
// put your clean up code if any

View File

@ -248,4 +248,28 @@ describe("Currency widget - ", () => {
enterAndTest(d[0], d[1]);
});
});
it("Check isDirty meta property", function() {
cy.openPropertyPane("textwidget");
cy.updateCodeInput(
".t--property-control-text",
`{{CurrencyInput1.isDirty}}`,
);
// Init isDirty
cy.openPropertyPane(widgetName);
cy.updateCodeInput(".t--property-control-defaulttext", "1");
cy.closePropertyPane();
// Check if initial value of isDirty is false
cy.get(".t--widget-textwidget").should("contain", "false");
// Interact with UI
cy.get(widgetInput).clear();
cy.wait(300);
// Check if isDirty is set to true
cy.get(".t--widget-textwidget").should("contain", "true");
// Change defaultText
cy.openPropertyPane(widgetName);
cy.updateCodeInput(".t--property-control-defaulttext", "5");
// Check if isDirty is reset to false
cy.get(".t--widget-textwidget").should("contain", "false");
});
});

View File

@ -140,6 +140,49 @@ describe("DatePicker Widget Property pane tests with js bindings", function() {
.should("have.text", "May 4, 2021 6:25 AM");
});
it("Check isDirty meta property", function() {
cy.openPropertyPane("textwidget");
cy.updateCodeInput(".t--property-control-text", `{{DatePicker1.isDirty}}`);
// Init isDirty
cy.openPropertyPane("datepickerwidget2");
cy.get(formWidgetsPage.toggleJsDefaultDate).click();
cy.get(".t--property-control-defaultdate .bp3-input").clear();
cy.get(formWidgetsPage.toggleJsDefaultDate).click();
cy.testJsontext(
"defaultdate",
'{{moment("04/05/2021 05:25", "DD/MM/YYYY HH:mm").toISOString()}}',
);
cy.closePropertyPane();
// Check if initial value of isDirty is false
cy.get(".t--widget-textwidget")
.first()
.should("contain", "false");
// Interact with UI
cy.get(".t--draggable-datepickerwidget2 .bp3-input")
.clear({
force: true,
})
.type("04/05/2021 06:25");
cy.wait("@updateLayout");
// Check if isDirty is set to true
cy.get(".t--widget-textwidget")
.first()
.should("contain", "true");
// Change defaultDate
cy.openPropertyPane("datepickerwidget2");
cy.get(formWidgetsPage.toggleJsDefaultDate).click();
cy.get(".t--property-control-defaultdate .bp3-input").clear();
cy.get(formWidgetsPage.toggleJsDefaultDate).click();
cy.testJsontext(
"defaultdate",
'{{moment("07/05/2021 05:25", "DD/MM/YYYY HH:mm").toISOString()}}',
);
// Check if isDirty is reset to false
cy.get(".t--widget-textwidget")
.first()
.should("contain", "false");
});
it("Datepicker default date validation with js binding", function() {
cy.PublishtheApp();
// eslint-disable-next-line cypress/no-unnecessary-waiting

View File

@ -0,0 +1,30 @@
const explorer = require("../../../../locators/explorerlocators.json");
const commonlocators = require("../../../../locators/commonlocators.json");
const widgetName = "filepickerwidgetv2";
describe("File picker widget v2", () => {
it("1. Drag & drop FilePicker/Text widgets", () => {
cy.get(explorer.addWidget).click();
cy.dragAndDropToCanvas(widgetName, { x: 300, y: 300 });
cy.get(`.t--widget-${widgetName}`).should("exist");
cy.dragAndDropToCanvas("textwidget", { x: 300, y: 500 });
cy.openPropertyPane("textwidget");
cy.updateCodeInput(".t--property-control-text", `{{FilePicker1.isDirty}}`);
});
it("Check isDirty meta property", function() {
// Check if initial value of isDirty is false
cy.get(".t--widget-textwidget").should("contain", "false");
// Upload a new file
cy.get(`.t--widget-${widgetName}`).click();
cy.get(commonlocators.filePickerInput)
.first()
.attachFile("testFile.mov");
cy.get(commonlocators.filePickerUploadButton).click();
//eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(500);
// Check if isDirty is set to true
cy.get(".t--widget-textwidget").should("contain", "true");
});
});

View File

@ -0,0 +1,19 @@
const dsl = require("../../../../fixtures/formHasChangesDsl.json");
describe("Form Widget", () => {
before(() => {
cy.addDsl(dsl);
});
it("Check hasChanges meta property", () => {
// Check if isDirty is false for the first time
cy.contains(".t--widget-textwidget", "false").should("exist");
// Interact with UI
cy.get(`.t--widget-checkboxwidget label`)
.first()
.click();
// Check if isDirty is set to true
cy.contains(".t--widget-textwidget", "false").should("not.exist");
cy.contains(".t--widget-textwidget", "true").should("exist");
});
});

View File

@ -422,6 +422,29 @@ describe("Input widget V2 - ", () => {
cy.get(".t--widget-textwidget").should("contain", "1.0001:1.0001:true");
});
it("Check isDirty meta property", function() {
cy.openPropertyPane("textwidget");
cy.updateCodeInput(".t--property-control-text", `{{Input1.isDirty}}`);
// Init isDirty
cy.openPropertyPane(widgetName);
cy.selectDropdownValue(".t--property-control-datatype", "Text");
cy.updateCodeInput(".t--property-control-defaulttext", "a");
// Check if initial value of isDirty is false
cy.get(".t--widget-textwidget").should("contain", "false");
// Interact with UI
cy.get(widgetInput).clear();
cy.wait(300);
cy.get(widgetInput).type("b");
cy.wait(300);
// Check if isDirty is set to true
cy.get(".t--widget-textwidget").should("contain", "true");
// Change defaultText
cy.openPropertyPane(widgetName);
cy.updateCodeInput(".t--property-control-defaulttext", "c");
// Check if isDirty is reset to false
cy.get(".t--widget-textwidget").should("contain", "false");
});
function enterAndTest(text, expected) {
cy.get(`.t--widget-${widgetName} input`).clear();
cy.wait(300);

View File

@ -90,6 +90,30 @@ describe("MultiSelect Widget Functionality", function() {
.eq(1)
.should("have.text", "Option 2");
});
it("Check isDirty meta property", function() {
cy.openPropertyPane("textwidget");
cy.updateCodeInput(".t--property-control-text", `{{MultiSelect2.isDirty}}`);
// Init isDirty by changing defaultOptionValue
cy.openPropertyPane("multiselectwidgetv2");
cy.updateCodeInput(
".t--property-control-defaultvalue",
'[\n {\n "label": "Option 1",\n "value": "1"\n }\n]',
);
cy.get(".t--widget-textwidget").should("contain", "false");
// Interact with UI
cy.get(".rc-select-selector").click({ force: true });
cy.dropdownMultiSelectDynamic("Option 2");
// Check if isDirty is set to true
cy.get(".t--widget-textwidget").should("contain", "true");
// Reset isDirty by changing defaultOptionValue
cy.updateCodeInput(
".t--property-control-defaultvalue",
'[\n {\n "label": "Option 2",\n "value": "2"\n }\n]',
);
// Check if isDirty is set to false
cy.get(".t--widget-textwidget").should("contain", "false");
});
});
afterEach(() => {
// put your clean up code if any

View File

@ -2,6 +2,7 @@ const dsl = require("../../../../fixtures/TreeSelectDsl.json");
const formWidgetsPage = require("../../../../locators/FormWidgets.json");
const publish = require("../../../../locators/publishWidgetspage.json");
const commonlocators = require("../../../../locators/commonlocators.json");
const explorer = require("../../../../locators/explorerlocators.json");
describe("MultiSelectTree Widget Functionality", function() {
before(() => {
@ -41,6 +42,32 @@ describe("MultiSelectTree Widget Functionality", function() {
).should("be.visible");
cy.get(publish.backToEditor).click();
});
it("Check isDirty meta property", function() {
cy.get(explorer.addWidget).click();
cy.dragAndDropToCanvas("textwidget", { x: 300, y: 500 });
cy.openPropertyPane("textwidget");
cy.updateCodeInput(
".t--property-control-text",
`{{MultiSelectTree1.isDirty}}`,
);
// Change defaultValue
cy.openPropertyPane("multiselecttreewidget");
cy.testJsontext("defaultvalue", "GREEN\n");
// Check if isDirty is set to false
cy.get(".t--widget-textwidget").should("contain", "false");
// Interact with UI
cy.get(formWidgetsPage.treeSelectInput)
.first()
.click({ force: true });
cy.treeMultiSelectDropdown("Red");
// Check if isDirty is set to true
cy.get(".t--widget-textwidget").should("contain", "true");
// Reset isDirty by changing defaultValue
cy.testJsontext("defaultvalue", "BLUE\n");
// Check if isDirty is set to false
cy.get(".t--widget-textwidget").should("contain", "false");
});
});
afterEach(() => {
// put your clean up code if any

View File

@ -105,4 +105,27 @@ describe("Phone input widget - ", () => {
cy.get(widgetInput).should("contain.value", "");
cy.get(".t--widget-textwidget").should("contain", ":undefined");
});
it("Check isDirty meta property", function() {
cy.openPropertyPane("textwidget");
cy.updateCodeInput(".t--property-control-text", `{{PhoneInput1.isDirty}}`);
// Change defaultText
cy.openPropertyPane(widgetName);
cy.updateCodeInput(".t--property-control-defaulttext", "1");
cy.closePropertyPane();
// Check if isDirty is set to false
cy.get(".t--widget-textwidget").should("contain", "false");
// Interact with UI
cy.get(widgetInput).clear();
cy.wait(300);
cy.get(widgetInput).type("2");
cy.wait(300);
// Check if isDirty is set to true
cy.get(".t--widget-textwidget").should("contain", "true");
// Reset isDirty by changing defaultText
cy.openPropertyPane(widgetName);
cy.updateCodeInput(".t--property-control-defaulttext", "3");
// Check if isDirty is set to false
cy.get(".t--widget-textwidget").should("contain", "false");
});
});

View File

@ -0,0 +1,30 @@
const explorer = require("../../../../locators/explorerlocators.json");
const widgetName = "radiogroupwidget";
describe("Radio Group Widget", () => {
it("Drag & drop Radio group & Text widgets", () => {
cy.get(explorer.addWidget).click();
cy.dragAndDropToCanvas(widgetName, { x: 300, y: 300 });
cy.get(`.t--widget-${widgetName}`).should("exist");
cy.dragAndDropToCanvas("textwidget", { x: 300, y: 500 });
cy.openPropertyPane("textwidget");
cy.updateCodeInput(".t--property-control-text", `{{RadioGroup1.isDirty}}`);
});
it("Check isDirty meta property", function() {
// Check if initial value of isDirty is false
cy.get(".t--widget-textwidget").should("contain", "false");
// Interact with UI
cy.get(".t--widget-radiogroupwidget .bp3-radio")
.last()
.click();
// Check if isDirty is set to true
cy.get(".t--widget-textwidget").should("contain", "true");
// Change defaultOptionValue
cy.openPropertyPane(widgetName);
cy.updateCodeInput(".t--property-control-defaultselectedvalue", "N");
// Check if isDirty is reset to false
cy.get(".t--widget-textwidget").should("contain", "false");
});
});

View File

@ -128,6 +128,28 @@ describe("RichTextEditor Widget Functionality", function() {
);
});
it("Check isDirty meta property", function() {
cy.openPropertyPane("textwidget");
cy.updateCodeInput(
".t--property-control-text",
`{{RichtextEditor.isDirty}}`,
);
// Change defaultText
cy.openPropertyPane("richtexteditorwidget");
cy.testJsontext("defaulttext", "a");
// Check if isDirty has been changed into false
cy.get(".t--widget-textwidget").should("contain", "false");
// Interact with UI
cy.setTinyMceContent("rte-6h8j08u7ea", "abc");
// Check if isDirty is set to true
cy.get(".t--widget-textwidget").should("contain", "true");
// Change defaultText
cy.openPropertyPane("richtexteditorwidget");
cy.testJsontext("defaulttext", "b");
// Check if isDirty is reset to false
cy.get(".t--widget-textwidget").should("contain", "false");
});
afterEach(() => {
cy.goToEditFromPublish();
});

View File

@ -0,0 +1,34 @@
const explorer = require("../../../../locators/explorerlocators.json");
const commonlocators = require("../../../../locators/commonlocators.json");
const formWidgetsPage = require("../../../../locators/FormWidgets.json");
const widgetLocators = require("../../../../locators/Widgets.json");
const widgetName = "selectwidget";
describe("Select widget", () => {
it("1. Drag and drop Select/Text widgets", () => {
cy.get(explorer.addWidget).click();
cy.dragAndDropToCanvas(widgetName, { x: 300, y: 300 });
cy.get(`.t--widget-${widgetName}`).should("exist");
cy.dragAndDropToCanvas("textwidget", { x: 300, y: 500 });
});
it("2. Check isDirty meta property", () => {
cy.openPropertyPane("textwidget");
cy.updateCodeInput(".t--property-control-text", `{{Select1.isDirty}}`);
// Check if initial value of isDirty is false
cy.get(".t--widget-textwidget").should("contain", "false");
// Interact with UI
cy.get(formWidgetsPage.selectWidget)
.find(widgetLocators.dropdownSingleSelect)
.click({ force: true });
cy.get(commonlocators.singleSelectWidgetMenuItem)
.contains("Blue")
.click({ force: true });
// Check if isDirty is set to true
cy.get(".t--widget-textwidget").should("contain", "true");
// Change defaultOptionValue property
cy.updateCodeInput(".t--property-control-defaultvalue", "RED");
// Check if isDirty is reset to false
cy.get(".t--widget-textwidget").should("contain", "false");
});
});

View File

@ -41,6 +41,35 @@ describe("Single Select Widget Functionality", function() {
).should("be.visible");
cy.get(publish.backToEditor).click();
});
it("Check isDirty meta property", function() {
cy.openPropertyPane("textwidget");
cy.updateCodeInput(
".t--property-control-text",
`{{SingleSelectTree1.isDirty}}`,
);
// Change defaultText
cy.openPropertyPane("singleselecttreewidget");
cy.updateCodeInput(".t--property-control-defaultvalue", "GREEN");
cy.closePropertyPane();
// Check if isDirty is reset to false
cy.get(".t--widget-textwidget").should("contain", "false");
// Interact with UI
cy.get(formWidgetsPage.treeSelectInput)
.last()
.click({ force: true });
cy.get(formWidgetsPage.treeSelectFilterInput)
.click()
.type("light");
cy.treeSelectDropdown("Light Blue");
// Check if isDirty is set to true
cy.get(".t--widget-textwidget").should("contain", "true");
// Change defaultText
cy.openPropertyPane("singleselecttreewidget");
cy.updateCodeInput(".t--property-control-defaultvalue", "RED");
// Check if isDirty is reset to false
cy.get(".t--widget-textwidget").should("contain", "false");
});
});
afterEach(() => {
// put your clean up code if any

View File

@ -100,4 +100,34 @@ describe("Switch Group Widget Functionality", function() {
this.data.ModalName,
);
});
it("Check isDirty meta property", function() {
cy.openPropertyPane("textwidget");
cy.updateCodeInput(
".t--property-control-text",
`{{switchgrouptest.isDirty}}`,
);
// Change defaultSelectedValues
cy.openPropertyPane("switchgroupwidget");
cy.updateCodeInput(
".t--property-control-defaultselectedvalues",
`[\n"BLUE"\n]`,
);
// Check if isDirty is reset to false
cy.get(".t--widget-textwidget").should("contain", "false");
// Interact with UI
cy.get(formWidgetsPage.labelSwitchGroup)
.first()
.click();
// Check if isDirty is set to true
cy.get(".t--widget-textwidget").should("contain", "true");
// Change defaultSelectedValues
cy.openPropertyPane("switchgroupwidget");
cy.updateCodeInput(
".t--property-control-defaultselectedvalues",
`[\n"GREEN"\n]`,
);
// Check if isDirty is reset to false
cy.get(".t--widget-textwidget").should("contain", "false");
});
});

View File

@ -86,6 +86,31 @@ describe("Switch Widget Functionality", function() {
cy.get(publish.switchwidget + " " + ".bp3-align-left").should("not.exist");
cy.get(publish.backToEditor).click();
});
it("Check isDirty meta property", function() {
cy.openPropertyPane("textwidget");
cy.updateCodeInput(".t--property-control-text", `{{Toggler.isDirty}}`);
// Change defaultSwitchState property
cy.openPropertyPane("switchwidget");
cy.get(".t--property-control-defaultselected label")
.last()
.click();
// Check if isDirty is reset to false
cy.get(".t--widget-textwidget").should("contain", "false");
// Interact with UI
cy.get(`${formWidgetsPage.switchWidget} label`)
.first()
.click();
// Check if isDirty is set to true
cy.get(".t--widget-textwidget").should("contain", "true");
// Change defaultSwitchState property
cy.openPropertyPane("switchwidget");
cy.get(".t--property-control-defaultselected label")
.last()
.click();
// Check if isDirty is reset to false
cy.get(".t--widget-textwidget").should("contain", "false");
});
});
afterEach(() => {
// put your clean up code if any

View File

@ -311,6 +311,7 @@ export const entityDefinitions: Record<string, unknown> = {
"!url": "https://docs.appsmith.com/widget-reference/form",
isVisible: isVisible,
data: generateTypeDef(widget.data),
hasChanges: "bool",
}),
FORM_BUTTON_WIDGET: {
"!doc":

View File

@ -17,6 +17,7 @@ export interface AudioRecorderWidgetProps extends WidgetProps {
onRecordingStart?: string;
onRecordingComplete?: string;
blobURL?: string;
isDirty: boolean;
}
class AudioRecorderWidget extends BaseWidget<
@ -117,6 +118,7 @@ class AudioRecorderWidget extends BaseWidget<
blobURL: undefined,
dataURL: undefined,
rawBinary: undefined,
isDirty: false,
};
}
@ -125,6 +127,10 @@ class AudioRecorderWidget extends BaseWidget<
}
handleRecordingStart = () => {
if (!this.props.isDirty) {
this.props.updateWidgetMetaProperty("isDirty", true);
}
if (this.props.blobURL) {
URL.revokeObjectURL(this.props.blobURL);
}

View File

@ -906,7 +906,9 @@ function CameraComponent(props: CameraComponentProps) {
}, [isAudioMuted, isVideoMuted]);
useEffect(() => {
setIsReadyPlayerTimer(false);
// Clean up
resetMedia();
if (mode === CameraModeTypes.CAMERA) {
setMediaCaptureStatus(MediaCaptureStatusTypes.IMAGE_DEFAULT);
return;

View File

@ -160,10 +160,10 @@ class CameraWidget extends BaseWidget<CameraWidgetProps, WidgetState> {
imageDataURL: undefined,
imageRawBinary: undefined,
mediaCaptureStatus: MediaCaptureStatusTypes.IMAGE_DEFAULT,
timer: undefined,
videoBlobURL: undefined,
videoDataURL: undefined,
videoRawBinary: undefined,
isDirty: false,
};
}
@ -215,6 +215,11 @@ class CameraWidget extends BaseWidget<CameraWidgetProps, WidgetState> {
this.props.updateWidgetMetaProperty("imageRawBinary", undefined);
return;
}
// Set isDirty to true when an image is caputured
if (!this.props.isDirty) {
this.props.updateWidgetMetaProperty("isDirty", true);
}
const base64Data = image.split(",")[1];
const imageBlob = base64ToBlob(base64Data, "image/webp");
const blobURL = URL.createObjectURL(imageBlob);
@ -251,6 +256,10 @@ class CameraWidget extends BaseWidget<CameraWidgetProps, WidgetState> {
};
handleRecordingStart = () => {
if (!this.props.isDirty) {
this.props.updateWidgetMetaProperty("isDirty", true);
}
if (this.props.onRecordingStart) {
super.executeAction({
triggerPropertyName: "onRecordingStart",
@ -319,6 +328,7 @@ export interface CameraWidgetProps extends WidgetProps {
onRecordingStop?: string;
onVideoSave?: string;
videoBlobURL?: string;
isDirty: boolean;
}
export default CameraWidget;

View File

@ -1,5 +1,5 @@
import React from "react";
import { compact } from "lodash";
import { compact, xor } from "lodash";
import {
ValidationResponse,
@ -267,6 +267,7 @@ class CheckboxGroupWidget extends BaseWidget<
static getMetaPropertiesMap(): Record<string, any> {
return {
selectedValues: undefined,
isDirty: false,
};
}
@ -308,6 +309,14 @@ class CheckboxGroupWidget extends BaseWidget<
},
});
}
// Reset isDirty to false whenever defaultSelectedValues changes
if (
xor(this.props.defaultSelectedValues, prevProps.defaultSelectedValues)
.length > 0 &&
this.props.isDirty
) {
this.props.updateWidgetMetaProperty("isDirty", false);
}
}
getPageView() {
@ -346,6 +355,11 @@ class CheckboxGroupWidget extends BaseWidget<
);
}
// Update isDirty to true whenever value changes
if (!this.props.isDirty) {
this.props.updateWidgetMetaProperty("isDirty", true);
}
this.props.updateWidgetMetaProperty("selectedValues", selectedValues, {
triggerPropertyName: "onSelectionChange",
dynamicString: this.props.onSelectionChange,
@ -370,6 +384,10 @@ class CheckboxGroupWidget extends BaseWidget<
break;
}
if (!this.props.isDirty) {
this.props.updateWidgetMetaProperty("isDirty", true);
}
this.props.updateWidgetMetaProperty("selectedValues", selectedValues, {
triggerPropertyName: "onSelectionChange",
dynamicString: this.props.onSelectionChange,

View File

@ -127,9 +127,19 @@ class CheckboxWidget extends BaseWidget<CheckboxWidgetProps, WidgetState> {
static getMetaPropertiesMap(): Record<string, any> {
return {
isChecked: undefined,
isDirty: false,
};
}
componentDidUpdate(prevProps: CheckboxWidgetProps) {
if (
this.props.defaultCheckedState !== prevProps.defaultCheckedState &&
this.props.isDirty
) {
this.props.updateWidgetMetaProperty("isDirty", false);
}
}
getPageView() {
return (
<CheckboxComponent
@ -148,6 +158,10 @@ class CheckboxWidget extends BaseWidget<CheckboxWidgetProps, WidgetState> {
}
onCheckChange = (isChecked: boolean) => {
if (!this.props.isDirty) {
this.props.updateWidgetMetaProperty("isDirty", true);
}
this.props.updateWidgetMetaProperty("isChecked", isChecked, {
triggerPropertyName: "onCheckChange",
dynamicString: this.props.onCheckChange,

View File

@ -204,6 +204,13 @@ class CurrencyInputWidget extends BaseInputWidget<
) {
this.formatText();
}
// If defaultText property has changed, reset isDirty to false
if (
this.props.defaultText !== prevProps.defaultText &&
this.props.isDirty
) {
this.props.updateWidgetMetaProperty("isDirty", false);
}
}
formatText() {
@ -303,6 +310,10 @@ class CurrencyInputWidget extends BaseInputWidget<
this.props.decimals,
String(value),
);
if (!this.props.isDirty) {
this.props.updateWidgetMetaProperty("isDirty", true);
}
this.props.updateWidgetMetaProperty("text", String(formattedValue), {
triggerPropertyName: "onTextChanged",
dynamicString: this.props.onTextChanged,

View File

@ -230,9 +230,19 @@ class DatePickerWidget extends BaseWidget<DatePickerWidget2Props, WidgetState> {
static getMetaPropertiesMap(): Record<string, any> {
return {
value: undefined,
isDirty: false,
};
}
componentDidUpdate(prevProps: DatePickerWidget2Props): void {
if (
this.props.defaultDate !== prevProps.defaultDate &&
this.props.isDirty
) {
this.props.updateWidgetMetaProperty("isDirty", false);
}
}
getPageView() {
return (
<DatePickerComponent
@ -255,6 +265,10 @@ class DatePickerWidget extends BaseWidget<DatePickerWidget2Props, WidgetState> {
}
onDateSelected = (selectedDate: string) => {
if (!this.props.isDirty) {
this.props.updateWidgetMetaProperty("isDirty", true);
}
this.props.updateWidgetMetaProperty("value", selectedDate, {
triggerPropertyName: "onDateSelected",
dynamicString: this.props.onDateSelected,

View File

@ -223,6 +223,7 @@ class FilePickerWidget extends BaseWidget<
return {
selectedFiles: [],
uploadedFileData: {},
isDirty: false,
};
}
@ -383,6 +384,10 @@ class FilePickerWidget extends BaseWidget<
});
Promise.all(fileReaderPromises).then((files) => {
if (!this.props.isDirty) {
this.props.updateWidgetMetaProperty("isDirty", true);
}
this.props.updateWidgetMetaProperty(
"selectedFiles",
dslFiles.concat(files),

View File

@ -27,11 +27,45 @@ class FormWidget extends ContainerWidget {
componentDidMount() {
super.componentDidMount();
this.updateFormData();
// Check if the form is dirty
const hasChanges = this.checkFormValueChanges(
get(this.props, "children[0]"),
);
if (hasChanges !== this.props.hasChanges) {
this.props.updateWidgetMetaProperty("hasChanges", hasChanges);
}
}
componentDidUpdate(prevProps: ContainerWidgetProps<any>) {
super.componentDidUpdate(prevProps);
this.updateFormData();
// Check if the form is dirty
const hasChanges = this.checkFormValueChanges(
get(this.props, "children[0]"),
);
if (hasChanges !== this.props.hasChanges) {
this.props.updateWidgetMetaProperty("hasChanges", hasChanges);
}
}
checkFormValueChanges(
containerWidget: ContainerWidgetProps<WidgetProps>,
): boolean {
const childWidgets = containerWidget.children || [];
const hasChanges = childWidgets.some((child) => child.isDirty);
if (!hasChanges) {
return childWidgets.some(
(child) =>
child.children &&
this.checkFormValueChanges(get(child, "children[0]")),
);
}
return hasChanges;
}
updateFormData() {
@ -75,6 +109,7 @@ class FormWidget extends ContainerWidget {
export interface FormWidgetProps extends ContainerComponentProps {
name: string;
data: Record<string, unknown>;
hasChanges: boolean;
}
export default FormWidget;

View File

@ -379,6 +379,13 @@ class InputWidget extends BaseInputWidget<InputWidgetProps, WidgetState> {
getParsedText(this.props.inputText, this.props.inputType),
);
}
// If defaultText property has changed, reset isDirty to false
if (
this.props.defaultText !== prevProps.defaultText &&
this.props.isDirty
) {
this.props.updateWidgetMetaProperty("isDirty", false);
}
};
onValueChange = (value: string) => {

View File

@ -2,7 +2,7 @@ import React, { ReactNode } from "react";
import BaseWidget, { WidgetProps, WidgetState } from "widgets/BaseWidget";
import { TextSize, WidgetType } from "constants/WidgetConstants";
import { EventType } from "constants/AppsmithActionConstants/ActionConstants";
import { isArray, findIndex } from "lodash";
import { isArray, findIndex, xor } from "lodash";
import {
ValidationResponse,
ValidationTypes,
@ -354,8 +354,20 @@ class MultiSelectTreeWidget extends BaseWidget<
return {
selectedOptionValueArr: undefined,
selectedLabel: [],
isDirty: false,
};
}
componentDidUpdate(prevProps: MultiSelectTreeWidgetProps): void {
if (
xor(this.props.defaultOptionValue, prevProps.defaultOptionValue).length >
0 &&
this.props.isDirty
) {
this.props.updateWidgetMetaProperty("isDirty", false);
}
}
getPageView() {
const options =
isArray(this.props.options) &&
@ -407,6 +419,9 @@ class MultiSelectTreeWidget extends BaseWidget<
}
onOptionChange = (value?: DefaultValueType, labelList?: ReactNode[]) => {
if (!this.props.isDirty) {
this.props.updateWidgetMetaProperty("isDirty", true);
}
this.props.updateWidgetMetaProperty("selectedOptionValueArr", value);
this.props.updateWidgetMetaProperty("selectedLabel", labelList, {
triggerPropertyName: "onOptionChange",
@ -472,7 +487,7 @@ export interface MultiSelectTreeWidgetProps extends WidgetProps {
labelTextColor?: string;
labelTextSize?: TextSize;
labelStyle?: string;
isDirty?: boolean;
isDirty: boolean;
}
export default MultiSelectTreeWidget;

View File

@ -2,7 +2,15 @@ import React from "react";
import BaseWidget, { WidgetProps, WidgetState } from "widgets/BaseWidget";
import { WidgetType } from "constants/WidgetConstants";
import { EventType } from "constants/AppsmithActionConstants/ActionConstants";
import { isArray, isString, isNumber, LoDashStatic } from "lodash";
import {
isArray,
isEqual,
isFinite,
isString,
isNumber,
LoDashStatic,
xorWith,
} from "lodash";
import {
ValidationResponse,
ValidationTypes,
@ -208,7 +216,7 @@ class MultiSelectWidget extends BaseWidget<
fn: defaultOptionValueValidation,
expected: {
type: "Array of values",
example: `['option1', 'option2'] | [{ "label": "label1", "value": "value1" }]`,
example: ` "option1, option2" | ['option1', 'option2'] | [{ "label": "label1", "value": "value1" }]`,
autocompleteDataType: AutocompleteDataType.ARRAY,
},
},
@ -431,9 +439,38 @@ class MultiSelectWidget extends BaseWidget<
return {
selectedOptions: undefined,
filterText: "",
isDirty: false,
};
}
componentDidUpdate(prevProps: MultiSelectWidgetProps): void {
// Check if defaultOptionValue is string
let isStringArray = false;
if (
this.props.defaultOptionValue.some(
(value: any) => isString(value) || isFinite(value),
)
) {
isStringArray = true;
}
const hasChanges = isStringArray
? xorWith(
this.props.defaultOptionValue as string[],
prevProps.defaultOptionValue as string[],
isEqual,
).length > 0
: xorWith(
this.props.defaultOptionValue as OptionValue[],
prevProps.defaultOptionValue as OptionValue[],
isEqual,
).length > 0;
if (hasChanges && this.props.isDirty) {
this.props.updateWidgetMetaProperty("isDirty", false);
}
}
getPageView() {
const options = isArray(this.props.options) ? this.props.options : [];
const dropDownWidth = MinimumPopupRows * this.props.parentColumnSpace;
@ -527,7 +564,7 @@ export interface MultiSelectWidgetProps extends WidgetProps {
options?: DropdownOption[];
onOptionChange: string;
onFilterChange: string;
defaultOptionValue: string | string[] | OptionValue[];
defaultOptionValue: string[] | OptionValue[];
isRequired: boolean;
isLoading: boolean;
selectedOptions: LabelValueType[];

View File

@ -206,6 +206,13 @@ class PhoneInputWidget extends BaseInputWidget<
);
this.props.updateWidgetMetaProperty("text", formattedValue);
}
// If defaultText property has changed, reset isDirty to false
if (this.props.defaultText !== prevProps.defaultText) {
if (this.props.isDirty) {
this.props.updateWidgetMetaProperty("isDirty", false);
}
}
}
onISDCodeChange = (dialCode?: string) => {

View File

@ -272,9 +272,19 @@ class RadioGroupWidget extends BaseWidget<RadioGroupWidgetProps, WidgetState> {
static getMetaPropertiesMap(): Record<string, any> {
return {
selectedOptionValue: undefined,
isDirty: false,
};
}
componentDidUpdate(prevProps: RadioGroupWidgetProps): void {
if (
this.props.defaultOptionValue !== prevProps.defaultOptionValue &&
this.props.isDirty
) {
this.props.updateWidgetMetaProperty("isDirty", false);
}
}
getPageView() {
return (
<RadioGroupComponent
@ -297,6 +307,11 @@ class RadioGroupWidget extends BaseWidget<RadioGroupWidgetProps, WidgetState> {
} else {
newVal = updatedValue;
}
// Set isDirty to true when the selection changes
if (!this.props.isDirty) {
this.props.updateWidgetMetaProperty("isDirty", true);
}
this.props.updateWidgetMetaProperty("selectedOptionValue", newVal, {
triggerPropertyName: "onSelectionChange",
dynamicString: this.props.onSelectionChange,
@ -318,6 +333,7 @@ export interface RadioGroupWidgetProps extends WidgetProps {
onSelectionChange: string;
defaultOptionValue: string;
isRequired?: boolean;
isDirty: boolean;
}
export default RadioGroupWidget;

View File

@ -130,6 +130,8 @@ class RichTextEditorWidget extends BaseWidget<
static getMetaPropertiesMap(): Record<string, any> {
return {
text: undefined,
shouldReset: false,
isDirty: false,
};
}
@ -146,7 +148,30 @@ class RichTextEditorWidget extends BaseWidget<
};
}
componentDidMount(): void {
if (this.props.defaultText) {
this.props.updateWidgetMetaProperty("shouldReset", true);
}
}
componentDidUpdate(prevProps: RichTextEditorWidgetProps): void {
if (this.props.defaultText !== prevProps.defaultText) {
if (this.props.isDirty) {
this.props.updateWidgetMetaProperty("isDirty", false);
}
if (this.props.defaultText) {
this.props.updateWidgetMetaProperty("shouldReset", true);
}
}
}
onValueChange = (text: string) => {
if (this.props.shouldReset) {
this.props.updateWidgetMetaProperty("shouldReset", false);
} else if (!this.props.isDirty) {
this.props.updateWidgetMetaProperty("isDirty", true);
}
this.props.updateWidgetMetaProperty("text", text, {
triggerPropertyName: "onTextChange",
dynamicString: this.props.onTextChange,
@ -196,6 +221,7 @@ export interface RichTextEditorWidgetProps extends WidgetProps {
isVisible?: boolean;
isRequired?: boolean;
isToolbarHidden?: boolean;
isDirty: boolean;
}
export default RichTextEditorWidget;

View File

@ -11,7 +11,14 @@ import {
import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory";
import { MinimumPopupRows, GRID_DENSITY_MIGRATION_V1 } from "widgets/constants";
import { AutocompleteDataType } from "utils/autocomplete/TernServer";
import { findIndex, isArray, isNumber, isString, LoDashStatic } from "lodash";
import {
findIndex,
isArray,
isEqual,
isNumber,
isString,
LoDashStatic,
} from "lodash";
export function defaultOptionValueValidation(
value: unknown,
@ -335,6 +342,7 @@ class SelectWidget extends BaseWidget<SelectWidgetProps, WidgetState> {
value: undefined,
label: undefined,
filterText: "",
isDirty: false,
};
}
@ -351,6 +359,16 @@ class SelectWidget extends BaseWidget<SelectWidgetProps, WidgetState> {
this.changeSelectedOption();
}
componentDidUpdate(prevProps: SelectWidgetProps): void {
// Reset isDirty to false if defaultOptionValue changes
if (
!isEqual(this.props.defaultOptionValue, prevProps.defaultOptionValue) &&
this.props.isDirty
) {
this.props.updateWidgetMetaProperty("isDirty", false);
}
}
isStringOrNumber = (value: any): value is string | number =>
isString(value) || isNumber(value);
@ -408,6 +426,10 @@ class SelectWidget extends BaseWidget<SelectWidgetProps, WidgetState> {
isChanged = !(this.props.selectedOptionValue === selectedOption.value);
}
if (isChanged) {
if (!this.props.isDirty) {
this.props.updateWidgetMetaProperty("isDirty", true);
}
this.props.updateWidgetMetaProperty("label", selectedOption.label ?? "");
this.props.updateWidgetMetaProperty("value", selectedOption.value ?? "", {

View File

@ -319,9 +319,19 @@ class SingleSelectTreeWidget extends BaseWidget<
selectedOption: undefined,
selectedOptionValueArr: undefined,
selectedLabel: [],
isDirty: false,
};
}
componentDidUpdate(prevProps: SingleSelectTreeWidgetProps): void {
if (
this.props.defaultOptionValue !== prevProps.defaultOptionValue &&
this.props.isDirty
) {
this.props.updateWidgetMetaProperty("isDirty", false);
}
}
getPageView() {
const options =
isArray(this.props.options) &&
@ -372,6 +382,10 @@ class SingleSelectTreeWidget extends BaseWidget<
}
onOptionChange = (value?: DefaultValueType, labelList?: ReactNode[]) => {
if (!this.props.isDirty) {
this.props.updateWidgetMetaProperty("isDirty", true);
}
this.props.updateWidgetMetaProperty("selectedOption", value);
this.props.updateWidgetMetaProperty("selectedLabel", labelList, {
triggerPropertyName: "onOptionChange",

View File

@ -1,5 +1,6 @@
import React from "react";
import { Alignment } from "@blueprintjs/core";
import { xor } from "lodash";
import BaseWidget, { WidgetProps, WidgetState } from "widgets/BaseWidget";
import { DerivedPropertiesMap } from "utils/WidgetFactory";
@ -178,6 +179,7 @@ class SwitchGroupWidget extends BaseWidget<
static getMetaPropertiesMap(): Record<string, any> {
return {
selectedValuesArray: undefined,
isDirty: false,
};
}
@ -196,6 +198,22 @@ class SwitchGroupWidget extends BaseWidget<
return "SWITCH_GROUP_WIDGET";
}
componentDidUpdate(prevProps: SwitchGroupWidgetProps): void {
if (
xor(this.props.defaultSelectedValues, prevProps.defaultSelectedValues)
.length > 0
) {
if (this.props.isDirty) {
this.props.updateWidgetMetaProperty("isDirty", false);
}
this.props.updateWidgetMetaProperty(
"selectedValuesArray",
this.props.defaultSelectedValues,
);
}
}
getPageView() {
const {
alignment,
@ -205,7 +223,7 @@ class SwitchGroupWidget extends BaseWidget<
isValid,
options,
parentRowSpace,
selectedValues,
selectedValuesArray,
} = this.props;
return (
@ -217,7 +235,7 @@ class SwitchGroupWidget extends BaseWidget<
options={options}
required={isRequired}
rowSpace={parentRowSpace}
selected={selectedValues}
selected={selectedValuesArray}
valid={isValid}
/>
);
@ -235,6 +253,10 @@ class SwitchGroupWidget extends BaseWidget<
);
}
if (!this.props.isDirty) {
this.props.updateWidgetMetaProperty("isDirty", true);
}
this.props.updateWidgetMetaProperty(
"selectedValuesArray",
selectedValuesArray,
@ -253,6 +275,7 @@ class SwitchGroupWidget extends BaseWidget<
export interface SwitchGroupWidgetProps extends WidgetProps {
options: OptionProps[];
defaultSelectedValues: string[];
selectedValuesArray: string[];
isInline: boolean;
isRequired?: boolean;
isValid?: boolean;

View File

@ -131,6 +131,7 @@ class SwitchWidget extends BaseWidget<SwitchWidgetProps, WidgetState> {
static getMetaPropertiesMap(): Record<string, any> {
return {
isSwitchedOn: undefined,
isDirty: false,
};
}
@ -140,7 +141,20 @@ class SwitchWidget extends BaseWidget<SwitchWidgetProps, WidgetState> {
};
}
componentDidUpdate(prevProps: SwitchWidgetProps): void {
if (
this.props.defaultSwitchState !== prevProps.defaultSwitchState &&
this.props.isDirty
) {
this.props.updateWidgetMetaProperty("isDirty", false);
}
}
onChange = (isSwitchedOn: boolean) => {
if (!this.props.isDirty) {
this.props.updateWidgetMetaProperty("isDirty", true);
}
this.props.updateWidgetMetaProperty("isSwitchedOn", isSwitchedOn, {
triggerPropertyName: "onChange",
dynamicString: this.props.onChange,
@ -156,6 +170,7 @@ export interface SwitchWidgetProps extends WidgetProps {
defaultSwitchState: boolean;
alignWidget: AlignWidget;
label: string;
isDirty: boolean;
}
export default SwitchWidget;