diff --git a/package-lock.json b/package-lock.json
index 93c06ac9..b8ea654b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11,6 +11,7 @@
"@chantouchsek/validatorjs": "1.2.3",
"@storybook/addon-docs": "^7.6.13",
"axios-extensions": "^3.1.6",
+ "flatted": "^3.3.3",
"lodash": "^4.17.21",
"lru-cache": "^10.0.1",
"moment": "^2.30.1",
@@ -13580,10 +13581,9 @@
}
},
"node_modules/flatted": {
- "version": "3.2.9",
- "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz",
- "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==",
- "dev": true
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz",
+ "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="
},
"node_modules/flow-parser": {
"version": "0.228.0",
diff --git a/package.json b/package.json
index 5413fa7d..3959ad7f 100644
--- a/package.json
+++ b/package.json
@@ -34,6 +34,7 @@
"@chantouchsek/validatorjs": "1.2.3",
"@storybook/addon-docs": "^7.6.13",
"axios-extensions": "^3.1.6",
+ "flatted": "^3.3.3",
"lodash": "^4.17.21",
"lru-cache": "^10.0.1",
"moment": "^2.30.1",
diff --git a/src/components/renderer/form-button.vue b/src/components/renderer/form-button.vue
index 45e3a7cd..9d09e717 100644
--- a/src/components/renderer/form-button.vue
+++ b/src/components/renderer/form-button.vue
@@ -20,6 +20,8 @@ import Mustache from 'mustache';
import { mapActions, mapState } from "vuex";
import { getValidPath } from '@/mixins';
import Worker from "@/workers/worker.js?worker&inline";
+import { findRootScreen } from "@/mixins/DataReference";
+import { stringify } from 'flatted';
export default {
mixins: [getValidPath],
@@ -112,45 +114,50 @@ export default {
});
return;
}
+ if (this.event === 'pageNavigate') {
+ // Run handler for page navigate
+ await this.runHandler();
+ }
this.$emit(this.event, this.eventData);
if (this.event === 'pageNavigate') {
this.$emit('page-navigate', this.eventData);
}
},
- async runHandler() {
+ runHandler() {
if (this.handler) {
- try {
- const data = this.getScreenDataReference(
- null,
- (screen, name, value) => {
- // Enable the data reference to be updated by the handler
- screen.$set(screen.vdata, name, value);
- }
- );
+ return new Promise((resolve, reject) => {
+ try {
+ const rootScreen = findRootScreen(this);
+ const data = rootScreen.vdata;
+ const scope = this.transientData;
- const rawData = data[Symbol.for("__v_raw")];
+ const worker = new Worker();
+ // Send the handler code to the worker
+ worker.postMessage({
+ fn: this.handler,
+ dataRefs: stringify({data, scope}),
+ });
- const worker = new Worker();
- // Send the handler code to the worker
- worker.postMessage({
- fn: this.handler,
- data: rawData,
- });
-
- // Listen for the result from the worker
- worker.onmessage = (e) => {
- if (e.data.error) {
- console.error("Worker error:", e.data.error);
- } else if (e.data.result) {
- // Update the data with the result
- Object.keys(e.data.result).forEach(key => {
- rawData[key] = e.data.result[key];
- });
- }
- };
- } catch (error) {
- console.error("❌ There is an error in the button handler", error);
- }
+ // Listen for the result from the worker
+ worker.onmessage = (e) => {
+ if (e.data.error) {
+ reject(e.data.error);
+ } else if (e.data.result) {
+ // Update the data with the result
+ Object.keys(e.data.result).forEach(key => {
+ if (key === '_root') {
+ Object.assign(data, e.data.result[key]);
+ } else {
+ scope[key] = e.data.result[key];
+ }
+ });
+ resolve();
+ }
+ };
+ } catch (error) {
+ console.error("❌ There is an error in the button handler", error);
+ }
+ });
}
}
},
diff --git a/src/workers/worker.js b/src/workers/worker.js
index ab1eb3fd..df13a0e9 100644
--- a/src/workers/worker.js
+++ b/src/workers/worker.js
@@ -1,6 +1,9 @@
// worker.js
+import { parse } from 'flatted';
+
self.onmessage = async function (e) {
- const { fn, data } = e.data;
+ const { fn, dataRefs } = e.data;
+ const { data, scope, parent } = parse(dataRefs);
try {
// Validate inputs
@@ -18,15 +21,15 @@ self.onmessage = async function (e) {
// Use Function constructor with explicit parameter and body
// eslint-disable-next-line no-new-func
- const userFunc = new Function('data', functionBody);
- const result = isAsync ? await userFunc(data) : userFunc(data);
+ const userFunc = new Function('data', 'parent', functionBody);
+ const result = isAsync ? await userFunc.apply(scope, [data, parent]) : userFunc.apply(scope, [data, parent]);
self.postMessage({ result });
} catch (error) {
- console.error('Error executing handler:', error);
+ console.error('❌ Error executing handler:', error);
self.postMessage({
- error: error.message,
+ error: error.message || error.toString(),
stack: error.stack
});
}
diff --git a/tests/e2e/fixtures/button_click_handler_worker.json b/tests/e2e/fixtures/button_click_handler_worker.json
new file mode 100644
index 00000000..0f806228
--- /dev/null
+++ b/tests/e2e/fixtures/button_click_handler_worker.json
@@ -0,0 +1,2626 @@
+{
+ "type": "screen_package",
+ "version": "2",
+ "screens": [
+ {
+ "id": 1,
+ "screen_category_id": "1",
+ "title": "ClickHandler",
+ "description": "ClickHandler",
+ "type": "FORM",
+ "config": [
+ {
+ "name": "Default",
+ "items": [
+ {
+ "uuid": "305baccb-7167-4930-acaf-e296100dff7b",
+ "config": {
+ "name": "form_record_list_1",
+ "icon": "fas fa-th-list",
+ "label": "New Record List",
+ "editable": true,
+ "fields": {
+ "dataSource": "provideData",
+ "jsonData": "[]",
+ "optionsList": [],
+ "showOptionCard": false,
+ "showRemoveWarning": false,
+ "showJsonEditor": false,
+ "editIndex": null,
+ "removeIndex": null
+ },
+ "form": "1",
+ "source": {
+ "collectionFields": [],
+ "collectionFieldsColumns": [],
+ "pmql": null,
+ "sourceOptions": "Variable",
+ "variableStore": null,
+ "dataSelectionOptions": "no-selection",
+ "singleField": null
+ }
+ },
+ "inspector": [
+ {
+ "type": "FormInput",
+ "field": "name",
+ "config": {
+ "label": "Variable Name",
+ "name": "Variable Name",
+ "validation": "dot_notation|required|not_in:null,break,case,catch,continue,debugger,default,delete,do,else,finally,for,function,if,in,instanceof,new,return,switch,this,throw,try,typeof,var,void,while,with,class,const,enum,export,extends,import,super,true,false",
+ "helper": "A variable name is a symbolic name to reference information."
+ }
+ },
+ {
+ "type": "FormInput",
+ "field": "label",
+ "config": {
+ "label": "List Label",
+ "helper": "The label describes this record list"
+ }
+ },
+ {
+ "type": "collectionDataSource",
+ "field": "source",
+ "config": {
+ "label": "Source of Record List",
+ "helper": "A record list can display the data of a defined variable or a collection"
+ }
+ },
+ {
+ "type": "FormCheckbox",
+ "field": "editable",
+ "config": {
+ "label": "Editable?",
+ "helper": "Should records be editable/removable and can new records be added"
+ },
+ "if": "hideControl"
+ },
+ {
+ "type": "ColumnSetup",
+ "field": "fields",
+ "config": {
+ "label": "Columns",
+ "helper": "List of columns to display in the record list"
+ }
+ },
+ {
+ "type": "FormMultiselect",
+ "field": "paginationOption",
+ "config": {
+ "icon": "fas",
+ "label": "Pagination",
+ "options": [
+ {
+ "content": "No Pagination (show all)",
+ "value": 0
+ },
+ {
+ "content": "5 items per page",
+ "value": 5
+ },
+ {
+ "content": "10 items per page",
+ "value": 10
+ },
+ {
+ "content": "15 items per page",
+ "value": 15
+ },
+ {
+ "content": "25 items per page",
+ "value": 25
+ },
+ {
+ "content": "50 items per page",
+ "value": 50
+ }
+ ],
+ "helper": ""
+ }
+ },
+ {
+ "type": "PageSelect",
+ "field": "form",
+ "config": {
+ "label": "Record Form",
+ "helper": "The form to use for adding/editing records"
+ },
+ "if": "hideControl"
+ },
+ {
+ "type": "collectionDesignerMode",
+ "field": "designerMode",
+ "config": {
+ "label": "Table Style",
+ "helper": ""
+ }
+ },
+ {
+ "type": "ColorSelectRecord",
+ "field": "color",
+ "config": {
+ "label": "Text Color",
+ "helper": "Set the element's text color",
+ "options": [
+ {
+ "value": "text-primary",
+ "content": "primary"
+ },
+ {
+ "value": "text-secondary",
+ "content": "secondary"
+ },
+ {
+ "value": "text-success",
+ "content": "success"
+ },
+ {
+ "value": "text-danger",
+ "content": "danger"
+ },
+ {
+ "value": "text-warning",
+ "content": "warning"
+ },
+ {
+ "value": "text-info",
+ "content": "info"
+ },
+ {
+ "value": "text-light",
+ "content": "light"
+ },
+ {
+ "value": "text-dark",
+ "content": "dark"
+ }
+ ]
+ }
+ },
+ {
+ "type": "ColorSelectRecord",
+ "field": "bgcolor",
+ "config": {
+ "label": "Background Color",
+ "helper": "Set the element's background color",
+ "options": [
+ {
+ "value": "alert alert-primary",
+ "content": "primary"
+ },
+ {
+ "value": "alert alert-secondary",
+ "content": "secondary"
+ },
+ {
+ "value": "alert alert-success",
+ "content": "success"
+ },
+ {
+ "value": "alert alert-danger",
+ "content": "danger"
+ },
+ {
+ "value": "alert alert-warning",
+ "content": "warning"
+ },
+ {
+ "value": "alert alert-info",
+ "content": "info"
+ },
+ {
+ "value": "alert alert-light",
+ "content": "light"
+ },
+ {
+ "value": "alert alert-dark",
+ "content": "dark"
+ }
+ ]
+ }
+ },
+ {
+ "type": "ColorSelectModern",
+ "field": "bgcolormodern",
+ "config": {
+ "label": "",
+ "helper": "",
+ "options": [
+ {
+ "value": "alert alert-primary",
+ "content": "primary"
+ },
+ {
+ "value": "alert alert-success",
+ "content": "success"
+ },
+ {
+ "value": "alert alert-warning",
+ "content": "warning"
+ },
+ {
+ "value": "alert alert-secondary",
+ "content": "secondary"
+ }
+ ]
+ }
+ },
+ {
+ "type": "FormInput",
+ "field": "conditionalHide",
+ "config": {
+ "label": "Visibility Rule",
+ "helper": "This control is hidden until this expression is true"
+ }
+ },
+ {
+ "type": "DeviceVisibility",
+ "field": "deviceVisibility",
+ "config": {
+ "label": "Device Visibility",
+ "helper": "This control is hidden until this expression is true"
+ }
+ },
+ {
+ "type": "FormInput",
+ "field": "customFormatter",
+ "config": {
+ "label": "Custom Format String",
+ "helper": "Use the Mask Pattern format
Date ##/##/####
SSN ###-##-####
Phone (###) ###-####",
+ "validation": ""
+ }
+ },
+ {
+ "type": "FormInput",
+ "field": "customCssSelector",
+ "config": {
+ "label": "CSS Selector Name",
+ "helper": "Use this in your custom css rules",
+ "validation": "regex: [-?[_a-zA-Z]+[_-a-zA-Z0-9]*]"
+ }
+ },
+ {
+ "type": "FormInput",
+ "field": "ariaLabel",
+ "config": {
+ "label": "Aria Label",
+ "helper": "Attribute designed to help assistive technology (e.g. screen readers) attach a label"
+ }
+ },
+ {
+ "type": "FormInput",
+ "field": "tabindex",
+ "config": {
+ "label": "Tab Order",
+ "helper": "Order in which a user will move focus from one control to another by pressing the Tab key",
+ "validation": "regex: [0-9]*"
+ }
+ },
+ {
+ "type": "EncryptedConfig",
+ "field": "encryptedConfig",
+ "config": {
+ "label": "Encrypted",
+ "helper": ""
+ }
+ }
+ ],
+ "component": "FormRecordList",
+ "editor-component": "FormText",
+ "editor-control": "FormText",
+ "label": "Record List"
+ },
+ {
+ "uuid": "e4447496-0239-4d6f-9d83-26c3053d54ec",
+ "config": {
+ "name": "form_record_list_1",
+ "icon": "fas fa-redo",
+ "settings": {
+ "type": "existing",
+ "varname": "form_record_list_1",
+ "times": "3",
+ "add": true
+ },
+ "label": ""
+ },
+ "inspector": [
+ {
+ "type": "LoopInspector",
+ "field": "settings",
+ "config": {
+ "label": "",
+ "helper": ""
+ }
+ },
+ {
+ "type": "FormInput",
+ "field": "conditionalHide",
+ "config": {
+ "label": "Visibility Rule",
+ "helper": "This control is hidden until this expression is true"
+ }
+ },
+ {
+ "type": "DeviceVisibility",
+ "field": "deviceVisibility",
+ "config": {
+ "label": "Device Visibility",
+ "helper": "This control is hidden until this expression is true"
+ }
+ },
+ {
+ "type": "FormInput",
+ "field": "customFormatter",
+ "config": {
+ "label": "Custom Format String",
+ "helper": "Use the Mask Pattern format
Date ##/##/####
SSN ###-##-####
Phone (###) ###-####",
+ "validation": ""
+ }
+ },
+ {
+ "type": "FormInput",
+ "field": "customCssSelector",
+ "config": {
+ "label": "CSS Selector Name",
+ "helper": "Use this in your custom css rules",
+ "validation": "regex: [-?[_a-zA-Z]+[_-a-zA-Z0-9]*]"
+ }
+ },
+ {
+ "type": "FormInput",
+ "field": "ariaLabel",
+ "config": {
+ "label": "Aria Label",
+ "helper": "Attribute designed to help assistive technology (e.g. screen readers) attach a label"
+ }
+ },
+ {
+ "type": "FormInput",
+ "field": "tabindex",
+ "config": {
+ "label": "Tab Order",
+ "helper": "Order in which a user will move focus from one control to another by pressing the Tab key",
+ "validation": "regex: [0-9]*"
+ }
+ },
+ {
+ "type": "EncryptedConfig",
+ "field": "encryptedConfig",
+ "config": {
+ "label": "Encrypted",
+ "helper": ""
+ }
+ }
+ ],
+ "component": "FormLoop",
+ "editor-component": "Loop",
+ "editor-control": "Loop",
+ "label": "Loop",
+ "items": [
+ {
+ "uuid": "7e7670f3-12e2-4a9e-b683-fa4c2abe2b84",
+ "config": {
+ "icon": "fas fa-share-square",
+ "label": "x Delete row",
+ "variant": "danger",
+ "event": "submit",
+ "loading": false,
+ "loadingLabel": "Loading...",
+ "defaultSubmit": true,
+ "name": "delete_row",
+ "fieldValue": null,
+ "tooltip": {},
+ "handler": "const form_record_list_1 = data.form_record_list_1;\nconst index = form_record_list_1.indexOf(this);\nform_record_list_1.splice(index, 1);\n\nreturn {\n name: \"deleted\",\n _root: {\n form_record_list_1\n }\n}"
+ },
+ "inspector": [
+ {
+ "type": "FormInput",
+ "field": "label",
+ "config": {
+ "label": "Label",
+ "helper": "The label describes the button's text"
+ }
+ },
+ {
+ "type": "FormInput",
+ "field": "name",
+ "config": {
+ "label": "Variable Name",
+ "name": "Variable Name",
+ "helper": "A variable name is a symbolic name to reference information.",
+ "validation": "regex:/^(?:[A-Za-z])(?:[0-9A-Z_.a-z])*(? Date ##/##/####
SSN ###-##-####
Phone (###) ###-####",
+ "validation": ""
+ }
+ },
+ {
+ "type": "FormInput",
+ "field": "customCssSelector",
+ "config": {
+ "label": "CSS Selector Name",
+ "helper": "Use this in your custom css rules",
+ "validation": "regex: [-?[_a-zA-Z]+[_-a-zA-Z0-9]*]"
+ }
+ },
+ {
+ "type": "FormInput",
+ "field": "ariaLabel",
+ "config": {
+ "label": "Aria Label",
+ "helper": "Attribute designed to help assistive technology (e.g. screen readers) attach a label"
+ }
+ },
+ {
+ "type": "FormInput",
+ "field": "tabindex",
+ "config": {
+ "label": "Tab Order",
+ "helper": "Order in which a user will move focus from one control to another by pressing the Tab key",
+ "validation": "regex: [0-9]*"
+ }
+ },
+ {
+ "type": "EncryptedConfig",
+ "field": "encryptedConfig",
+ "config": {
+ "label": "Encrypted",
+ "helper": ""
+ }
+ }
+ ],
+ "component": "FormButton",
+ "editor-component": "FormButton",
+ "editor-control": "FormSubmit",
+ "label": "Submit Button"
+ },
+ {
+ "uuid": "693b4380-e319-4c5f-a1a5-18eea510d067",
+ "config": {
+ "icon": "fas fa-share-square",
+ "label": "Error",
+ "variant": "warning",
+ "event": "submit",
+ "loading": false,
+ "loadingLabel": "Loading...",
+ "defaultSubmit": true,
+ "name": "handle_error",
+ "fieldValue": null,
+ "tooltip": {
+ "variant": "primary"
+ },
+ "handler": "throw new Error(\"Testing error\")"
+ },
+ "inspector": [
+ {
+ "type": "FormInput",
+ "field": "label",
+ "config": {
+ "label": "Label",
+ "helper": "The label describes the button's text"
+ }
+ },
+ {
+ "type": "FormInput",
+ "field": "name",
+ "config": {
+ "label": "Variable Name",
+ "name": "Variable Name",
+ "helper": "A variable name is a symbolic name to reference information.",
+ "validation": "regex:/^(?:[A-Za-z])(?:[0-9A-Z_.a-z])*(? Date ##/##/####
SSN ###-##-####
Phone (###) ###-####",
+ "validation": ""
+ }
+ },
+ {
+ "type": "FormInput",
+ "field": "customCssSelector",
+ "config": {
+ "label": "CSS Selector Name",
+ "helper": "Use this in your custom css rules",
+ "validation": "regex: [-?[_a-zA-Z]+[_-a-zA-Z0-9]*]"
+ }
+ },
+ {
+ "type": "FormInput",
+ "field": "ariaLabel",
+ "config": {
+ "label": "Aria Label",
+ "helper": "Attribute designed to help assistive technology (e.g. screen readers) attach a label"
+ }
+ },
+ {
+ "type": "FormInput",
+ "field": "tabindex",
+ "config": {
+ "label": "Tab Order",
+ "helper": "Order in which a user will move focus from one control to another by pressing the Tab key",
+ "validation": "regex: [0-9]*"
+ }
+ },
+ {
+ "type": "EncryptedConfig",
+ "field": "encryptedConfig",
+ "config": {
+ "label": "Encrypted",
+ "helper": ""
+ }
+ },
+ {
+ "type": "FormInput",
+ "field": "conditionalHide",
+ "config": {
+ "label": "Visibility Rule",
+ "helper": "This control is hidden until this expression is true"
+ }
+ },
+ {
+ "type": "DeviceVisibility",
+ "field": "deviceVisibility",
+ "config": {
+ "label": "Device Visibility",
+ "helper": "This control is hidden until this expression is true"
+ }
+ },
+ {
+ "type": "FormInput",
+ "field": "customFormatter",
+ "config": {
+ "label": "Custom Format String",
+ "helper": "Use the Mask Pattern format
Date ##/##/####
SSN ###-##-####
Phone (###) ###-####",
+ "validation": ""
+ }
+ },
+ {
+ "type": "FormInput",
+ "field": "customCssSelector",
+ "config": {
+ "label": "CSS Selector Name",
+ "helper": "Use this in your custom css rules",
+ "validation": "regex: [-?[_a-zA-Z]+[_-a-zA-Z0-9]*]"
+ }
+ },
+ {
+ "type": "FormInput",
+ "field": "ariaLabel",
+ "config": {
+ "label": "Aria Label",
+ "helper": "Attribute designed to help assistive technology (e.g. screen readers) attach a label"
+ }
+ },
+ {
+ "type": "FormInput",
+ "field": "tabindex",
+ "config": {
+ "label": "Tab Order",
+ "helper": "Order in which a user will move focus from one control to another by pressing the Tab key",
+ "validation": "regex: [0-9]*"
+ }
+ },
+ {
+ "type": "EncryptedConfig",
+ "field": "encryptedConfig",
+ "config": {
+ "label": "Encrypted",
+ "helper": ""
+ }
+ },
+ {
+ "type": "FormInput",
+ "field": "conditionalHide",
+ "config": {
+ "label": "Visibility Rule",
+ "helper": "This control is hidden until this expression is true"
+ }
+ },
+ {
+ "type": "DeviceVisibility",
+ "field": "deviceVisibility",
+ "config": {
+ "label": "Device Visibility",
+ "helper": "This control is hidden until this expression is true"
+ }
+ },
+ {
+ "type": "FormInput",
+ "field": "customFormatter",
+ "config": {
+ "label": "Custom Format String",
+ "helper": "Use the Mask Pattern format
Date ##/##/####
SSN ###-##-####
Phone (###) ###-####",
+ "validation": ""
+ }
+ },
+ {
+ "type": "FormInput",
+ "field": "customCssSelector",
+ "config": {
+ "label": "CSS Selector Name",
+ "helper": "Use this in your custom css rules",
+ "validation": "regex: [-?[_a-zA-Z]+[_-a-zA-Z0-9]*]"
+ }
+ },
+ {
+ "type": "FormInput",
+ "field": "ariaLabel",
+ "config": {
+ "label": "Aria Label",
+ "helper": "Attribute designed to help assistive technology (e.g. screen readers) attach a label"
+ }
+ },
+ {
+ "type": "FormInput",
+ "field": "tabindex",
+ "config": {
+ "label": "Tab Order",
+ "helper": "Order in which a user will move focus from one control to another by pressing the Tab key",
+ "validation": "regex: [0-9]*"
+ }
+ },
+ {
+ "type": "EncryptedConfig",
+ "field": "encryptedConfig",
+ "config": {
+ "label": "Encrypted",
+ "helper": ""
+ }
+ }
+ ],
+ "component": "FormButton",
+ "editor-component": "FormButton",
+ "editor-control": "FormSubmit",
+ "label": "Submit Button"
+ },
+ {
+ "uuid": "3a02685d-598b-4aad-a0e6-cd6919d09cab",
+ "config": {
+ "icon": "far fa-square",
+ "label": "name",
+ "name": "name",
+ "placeholder": "",
+ "validation": [],
+ "helper": null,
+ "type": "text",
+ "dataFormat": "string",
+ "readonly": false
+ },
+ "inspector": [
+ {
+ "type": "FormInput",
+ "field": "name",
+ "config": {
+ "label": "Variable Name",
+ "name": "Variable Name",
+ "validation": "dot_notation|required|not_in:null,break,case,catch,continue,debugger,default,delete,do,else,finally,for,function,if,in,instanceof,new,return,switch,this,throw,try,typeof,var,void,while,with,class,const,enum,export,extends,import,super,true,false",
+ "helper": "A variable name is a symbolic name to reference information."
+ }
+ },
+ {
+ "type": "FormInput",
+ "field": "label",
+ "config": {
+ "label": "Label",
+ "helper": "The label describes the field's name"
+ }
+ },
+ {
+ "type": "FormMultiselect",
+ "field": "dataFormat",
+ "config": {
+ "label": "Data Type",
+ "name": "Data Type",
+ "helper": "The data type specifies what kind of data is stored in the variable.",
+ "validation": "required",
+ "options": [
+ {
+ "value": "string",
+ "content": "Text"
+ },
+ {
+ "value": "int",
+ "content": "Integer"
+ },
+ {
+ "value": "currency",
+ "content": "Currency"
+ },
+ {
+ "value": "percentage",
+ "content": "Percentage"
+ },
+ {
+ "value": "float",
+ "content": "Decimal"
+ },
+ {
+ "value": "datetime",
+ "content": "Datetime"
+ },
+ {
+ "value": "date",
+ "content": "Date"
+ },
+ {
+ "value": "password",
+ "content": "Password"
+ }
+ ]
+ }
+ },
+ {
+ "type": "SelectDataTypeMask",
+ "field": "dataMask",
+ "config": {
+ "label": "Data Format",
+ "name": "Data Format",
+ "helper": "The data format for the selected type."
+ }
+ },
+ {
+ "type": "ValidationSelect",
+ "field": "validation",
+ "config": {
+ "label": "Validation Rules",
+ "helper": "The validation rules needed for this field"
+ }
+ },
+ {
+ "type": "FormInput",
+ "field": "placeholder",
+ "config": {
+ "label": "Placeholder Text",
+ "helper": "The placeholder is what is shown in the field when no value is provided yet"
+ }
+ },
+ {
+ "type": "FormInput",
+ "field": "helper",
+ "config": {
+ "label": "Helper Text",
+ "helper": "Help text is meant to provide additional guidance on the field's value"
+ }
+ },
+ {
+ "type": "FormCheckbox",
+ "field": "readonly",
+ "config": {
+ "label": "Read Only",
+ "helper": ""
+ }
+ },
+ {
+ "type": "ColorSelect",
+ "field": "color",
+ "config": {
+ "label": "Text Color",
+ "helper": "Set the element's text color",
+ "options": [
+ {
+ "value": "text-primary",
+ "content": "primary"
+ },
+ {
+ "value": "text-secondary",
+ "content": "secondary"
+ },
+ {
+ "value": "text-success",
+ "content": "success"
+ },
+ {
+ "value": "text-danger",
+ "content": "danger"
+ },
+ {
+ "value": "text-warning",
+ "content": "warning"
+ },
+ {
+ "value": "text-info",
+ "content": "info"
+ },
+ {
+ "value": "text-light",
+ "content": "light"
+ },
+ {
+ "value": "text-dark",
+ "content": "dark"
+ }
+ ]
+ }
+ },
+ {
+ "type": "ColorSelect",
+ "field": "bgcolor",
+ "config": {
+ "label": "Background Color",
+ "helper": "Set the element's background color",
+ "options": [
+ {
+ "value": "alert alert-primary",
+ "content": "primary"
+ },
+ {
+ "value": "alert alert-secondary",
+ "content": "secondary"
+ },
+ {
+ "value": "alert alert-success",
+ "content": "success"
+ },
+ {
+ "value": "alert alert-danger",
+ "content": "danger"
+ },
+ {
+ "value": "alert alert-warning",
+ "content": "warning"
+ },
+ {
+ "value": "alert alert-info",
+ "content": "info"
+ },
+ {
+ "value": "alert alert-light",
+ "content": "light"
+ },
+ {
+ "value": "alert alert-dark",
+ "content": "dark"
+ }
+ ]
+ }
+ },
+ {
+ "type": "default-value-editor",
+ "field": "defaultValue",
+ "config": {
+ "label": "Default Value",
+ "helper": "The default value is pre populated using the existing request data. This feature will allow you to modify the value displayed on screen load if needed."
+ }
+ },
+ {
+ "type": "FormInput",
+ "field": "conditionalHide",
+ "config": {
+ "label": "Visibility Rule",
+ "helper": "This control is hidden until this expression is true"
+ }
+ },
+ {
+ "type": "DeviceVisibility",
+ "field": "deviceVisibility",
+ "config": {
+ "label": "Device Visibility",
+ "helper": "This control is hidden until this expression is true"
+ }
+ },
+ {
+ "type": "FormInput",
+ "field": "customFormatter",
+ "config": {
+ "label": "Custom Format String",
+ "helper": "Use the Mask Pattern format
Date ##/##/####
SSN ###-##-####
Phone (###) ###-####",
+ "validation": ""
+ }
+ },
+ {
+ "type": "FormInput",
+ "field": "customCssSelector",
+ "config": {
+ "label": "CSS Selector Name",
+ "helper": "Use this in your custom css rules",
+ "validation": "regex: [-?[_a-zA-Z]+[_-a-zA-Z0-9]*]"
+ }
+ },
+ {
+ "type": "FormInput",
+ "field": "ariaLabel",
+ "config": {
+ "label": "Aria Label",
+ "helper": "Attribute designed to help assistive technology (e.g. screen readers) attach a label"
+ }
+ },
+ {
+ "type": "FormInput",
+ "field": "tabindex",
+ "config": {
+ "label": "Tab Order",
+ "helper": "Order in which a user will move focus from one control to another by pressing the Tab key",
+ "validation": "regex: [0-9]*"
+ }
+ },
+ {
+ "type": "EncryptedConfig",
+ "field": "encryptedConfig",
+ "config": {
+ "label": "Encrypted",
+ "helper": ""
+ }
+ }
+ ],
+ "component": "FormInput",
+ "editor-component": "FormInput",
+ "editor-control": "FormInput",
+ "label": "Line Input"
+ },
+ {
+ "uuid": "fa288d55-b472-4c23-a228-c4342cb6002d",
+ "config": {
+ "icon": "fas fa-share-square",
+ "label": "change and submit",
+ "variant": "primary",
+ "event": "submit",
+ "loading": false,
+ "loadingLabel": "Loading...",
+ "defaultSubmit": true,
+ "name": "change_and_submit",
+ "fieldValue": null,
+ "tooltip": {},
+ "handler": "return {\n name: \"last change and submit\"\n}"
+ },
+ "inspector": [
+ {
+ "type": "FormInput",
+ "field": "label",
+ "config": {
+ "label": "Label",
+ "helper": "The label describes the button's text"
+ }
+ },
+ {
+ "type": "FormInput",
+ "field": "name",
+ "config": {
+ "label": "Variable Name",
+ "name": "Variable Name",
+ "helper": "A variable name is a symbolic name to reference information.",
+ "validation": "regex:/^(?:[A-Za-z])(?:[0-9A-Z_.a-z])*(? Date ##/##/####
SSN ###-##-####
Phone (###) ###-####",
+ "validation": ""
+ }
+ },
+ {
+ "type": "FormInput",
+ "field": "customCssSelector",
+ "config": {
+ "label": "CSS Selector Name",
+ "helper": "Use this in your custom css rules",
+ "validation": "regex: [-?[_a-zA-Z]+[_-a-zA-Z0-9]*]"
+ }
+ },
+ {
+ "type": "FormInput",
+ "field": "ariaLabel",
+ "config": {
+ "label": "Aria Label",
+ "helper": "Attribute designed to help assistive technology (e.g. screen readers) attach a label"
+ }
+ },
+ {
+ "type": "FormInput",
+ "field": "tabindex",
+ "config": {
+ "label": "Tab Order",
+ "helper": "Order in which a user will move focus from one control to another by pressing the Tab key",
+ "validation": "regex: [0-9]*"
+ }
+ },
+ {
+ "type": "EncryptedConfig",
+ "field": "encryptedConfig",
+ "config": {
+ "label": "Encrypted",
+ "helper": ""
+ }
+ }
+ ],
+ "component": "FormButton",
+ "editor-component": "FormButton",
+ "editor-control": "FormSubmit",
+ "label": "Submit Button"
+ }
+ ],
+ "container": true
+ }
+ ],
+ "order": 1
+ },
+ {
+ "name": "test",
+ "order": 2,
+ "items": [
+ {
+ "uuid": "f539505a-65cc-40d2-8257-666bf44970cc",
+ "config": {
+ "icon": "far fa-square",
+ "label": "name",
+ "name": "name",
+ "placeholder": "",
+ "validation": [],
+ "helper": null,
+ "type": "text",
+ "dataFormat": "string",
+ "readonly": false
+ },
+ "inspector": [
+ {
+ "type": "FormInput",
+ "field": "name",
+ "config": {
+ "label": "Variable Name",
+ "name": "Variable Name",
+ "validation": "dot_notation|required|not_in:null,break,case,catch,continue,debugger,default,delete,do,else,finally,for,function,if,in,instanceof,new,return,switch,this,throw,try,typeof,var,void,while,with,class,const,enum,export,extends,import,super,true,false",
+ "helper": "A variable name is a symbolic name to reference information."
+ }
+ },
+ {
+ "type": "FormInput",
+ "field": "label",
+ "config": {
+ "label": "Label",
+ "helper": "The label describes the field's name"
+ }
+ },
+ {
+ "type": "FormMultiselect",
+ "field": "dataFormat",
+ "config": {
+ "label": "Data Type",
+ "name": "Data Type",
+ "helper": "The data type specifies what kind of data is stored in the variable.",
+ "validation": "required",
+ "options": [
+ {
+ "value": "string",
+ "content": "Text"
+ },
+ {
+ "value": "int",
+ "content": "Integer"
+ },
+ {
+ "value": "currency",
+ "content": "Currency"
+ },
+ {
+ "value": "percentage",
+ "content": "Percentage"
+ },
+ {
+ "value": "float",
+ "content": "Decimal"
+ },
+ {
+ "value": "datetime",
+ "content": "Datetime"
+ },
+ {
+ "value": "date",
+ "content": "Date"
+ },
+ {
+ "value": "password",
+ "content": "Password"
+ }
+ ]
+ }
+ },
+ {
+ "type": "SelectDataTypeMask",
+ "field": "dataMask",
+ "config": {
+ "label": "Data Format",
+ "name": "Data Format",
+ "helper": "The data format for the selected type."
+ }
+ },
+ {
+ "type": "ValidationSelect",
+ "field": "validation",
+ "config": {
+ "label": "Validation Rules",
+ "helper": "The validation rules needed for this field"
+ }
+ },
+ {
+ "type": "FormInput",
+ "field": "placeholder",
+ "config": {
+ "label": "Placeholder Text",
+ "helper": "The placeholder is what is shown in the field when no value is provided yet"
+ }
+ },
+ {
+ "type": "FormInput",
+ "field": "helper",
+ "config": {
+ "label": "Helper Text",
+ "helper": "Help text is meant to provide additional guidance on the field's value"
+ }
+ },
+ {
+ "type": "FormCheckbox",
+ "field": "readonly",
+ "config": {
+ "label": "Read Only",
+ "helper": ""
+ }
+ },
+ {
+ "type": "ColorSelect",
+ "field": "color",
+ "config": {
+ "label": "Text Color",
+ "helper": "Set the element's text color",
+ "options": [
+ {
+ "value": "text-primary",
+ "content": "primary"
+ },
+ {
+ "value": "text-secondary",
+ "content": "secondary"
+ },
+ {
+ "value": "text-success",
+ "content": "success"
+ },
+ {
+ "value": "text-danger",
+ "content": "danger"
+ },
+ {
+ "value": "text-warning",
+ "content": "warning"
+ },
+ {
+ "value": "text-info",
+ "content": "info"
+ },
+ {
+ "value": "text-light",
+ "content": "light"
+ },
+ {
+ "value": "text-dark",
+ "content": "dark"
+ }
+ ]
+ }
+ },
+ {
+ "type": "ColorSelect",
+ "field": "bgcolor",
+ "config": {
+ "label": "Background Color",
+ "helper": "Set the element's background color",
+ "options": [
+ {
+ "value": "alert alert-primary",
+ "content": "primary"
+ },
+ {
+ "value": "alert alert-secondary",
+ "content": "secondary"
+ },
+ {
+ "value": "alert alert-success",
+ "content": "success"
+ },
+ {
+ "value": "alert alert-danger",
+ "content": "danger"
+ },
+ {
+ "value": "alert alert-warning",
+ "content": "warning"
+ },
+ {
+ "value": "alert alert-info",
+ "content": "info"
+ },
+ {
+ "value": "alert alert-light",
+ "content": "light"
+ },
+ {
+ "value": "alert alert-dark",
+ "content": "dark"
+ }
+ ]
+ }
+ },
+ {
+ "type": "default-value-editor",
+ "field": "defaultValue",
+ "config": {
+ "label": "Default Value",
+ "helper": "The default value is pre populated using the existing request data. This feature will allow you to modify the value displayed on screen load if needed."
+ }
+ },
+ {
+ "type": "FormInput",
+ "field": "conditionalHide",
+ "config": {
+ "label": "Visibility Rule",
+ "helper": "This control is hidden until this expression is true"
+ }
+ },
+ {
+ "type": "DeviceVisibility",
+ "field": "deviceVisibility",
+ "config": {
+ "label": "Device Visibility",
+ "helper": "This control is hidden until this expression is true"
+ }
+ },
+ {
+ "type": "FormInput",
+ "field": "customFormatter",
+ "config": {
+ "label": "Custom Format String",
+ "helper": "Use the Mask Pattern format
Date ##/##/####
SSN ###-##-####
Phone (###) ###-####",
+ "validation": ""
+ }
+ },
+ {
+ "type": "FormInput",
+ "field": "customCssSelector",
+ "config": {
+ "label": "CSS Selector Name",
+ "helper": "Use this in your custom css rules",
+ "validation": "regex: [-?[_a-zA-Z]+[_-a-zA-Z0-9]*]"
+ }
+ },
+ {
+ "type": "FormInput",
+ "field": "ariaLabel",
+ "config": {
+ "label": "Aria Label",
+ "helper": "Attribute designed to help assistive technology (e.g. screen readers) attach a label"
+ }
+ },
+ {
+ "type": "FormInput",
+ "field": "tabindex",
+ "config": {
+ "label": "Tab Order",
+ "helper": "Order in which a user will move focus from one control to another by pressing the Tab key",
+ "validation": "regex: [0-9]*"
+ }
+ },
+ {
+ "type": "EncryptedConfig",
+ "field": "encryptedConfig",
+ "config": {
+ "label": "Encrypted",
+ "helper": ""
+ }
+ }
+ ],
+ "component": "FormInput",
+ "editor-component": "FormInput",
+ "editor-control": "FormInput",
+ "label": "Line Input"
+ },
+ {
+ "uuid": "a50d6f9b-5798-49ab-8d0e-103930cdf000",
+ "config": {
+ "icon": "fas fa-share-square",
+ "label": "New Submit",
+ "variant": "secondary",
+ "event": "submit",
+ "loading": false,
+ "loadingLabel": "Loading...",
+ "defaultSubmit": true,
+ "name": "record_list_button_with_handler",
+ "fieldValue": null,
+ "tooltip": {},
+ "handler": "return {\n name: \"value changed by handler\"\n}"
+ },
+ "inspector": [
+ {
+ "type": "FormInput",
+ "field": "label",
+ "config": {
+ "label": "Label",
+ "helper": "The label describes the button's text"
+ }
+ },
+ {
+ "type": "FormInput",
+ "field": "name",
+ "config": {
+ "label": "Variable Name",
+ "name": "Variable Name",
+ "helper": "A variable name is a symbolic name to reference information.",
+ "validation": "regex:/^(?:[A-Za-z])(?:[0-9A-Z_.a-z])*(? Date ##/##/####
SSN ###-##-####
Phone (###) ###-####",
+ "validation": ""
+ }
+ },
+ {
+ "type": "FormInput",
+ "field": "customCssSelector",
+ "config": {
+ "label": "CSS Selector Name",
+ "helper": "Use this in your custom css rules",
+ "validation": "regex: [-?[_a-zA-Z]+[_-a-zA-Z0-9]*]"
+ }
+ },
+ {
+ "type": "FormInput",
+ "field": "ariaLabel",
+ "config": {
+ "label": "Aria Label",
+ "helper": "Attribute designed to help assistive technology (e.g. screen readers) attach a label"
+ }
+ },
+ {
+ "type": "FormInput",
+ "field": "tabindex",
+ "config": {
+ "label": "Tab Order",
+ "helper": "Order in which a user will move focus from one control to another by pressing the Tab key",
+ "validation": "regex: [0-9]*"
+ }
+ },
+ {
+ "type": "EncryptedConfig",
+ "field": "encryptedConfig",
+ "config": {
+ "label": "Encrypted",
+ "helper": ""
+ }
+ }
+ ],
+ "component": "FormButton",
+ "editor-component": "FormButton",
+ "editor-control": "FormSubmit",
+ "label": "Submit Button"
+ }
+ ]
+ }
+ ],
+ "computed": [
+ {
+ "id": 1,
+ "name": "padre is a circular reference",
+ "type": "javascript",
+ "formula": "return this._parent;",
+ "property": "padre"
+ }
+ ],
+ "custom_css": null,
+ "created_at": "2020-06-17T22:30:48+00:00",
+ "updated_at": "2020-08-03T21:11:40+00:00",
+ "status": "ACTIVE",
+ "key": null,
+ "watchers": [],
+ "categories": [
+ {
+ "id": 1,
+ "name": "Uncategorized",
+ "status": "ACTIVE",
+ "is_system": 0,
+ "created_at": "2020-07-01T20:06:18+00:00",
+ "updated_at": "2020-07-01T20:06:18+00:00",
+ "pivot": {
+ "assignable_id": 5,
+ "category_id": 1,
+ "category_type": "ProcessMaker\\Models\\ScreenCategory"
+ }
+ }
+ ]
+ }
+ ],
+ "screen_categories": [],
+ "scripts": []
+}
\ No newline at end of file
diff --git a/tests/e2e/specs/ButtonClickHandlerWorker.spec.js b/tests/e2e/specs/ButtonClickHandlerWorker.spec.js
new file mode 100644
index 00000000..a91009f7
--- /dev/null
+++ b/tests/e2e/specs/ButtonClickHandlerWorker.spec.js
@@ -0,0 +1,62 @@
+describe("Button click handler", () => {
+
+ before(() => {
+ cy.visit("/", {
+ onBeforeLoad(win) {
+ // stub console.error
+ cy.stub(win.console, 'error').as('consoleError')
+ }
+ });
+ });
+
+ it.only("Test circular reference and click handlers", () => {
+ cy.loadFromJson("button_click_handler_worker.json", 0);
+ cy.wait(1000);
+
+ cy.get("[data-cy=mode-preview]").click();
+ // Add new row in record list (data-cy="add-row")
+ cy.get("[data-cy=preview-content] [data-cy=add-row]").click();
+ // Fill the first input
+ cy.get("[data-cy=preview-content] [data-cy=screen-field-form_record_list_1] [data-cy=modal-add] [name=name]").clear().type("12345678");
+ cy.get("[data-cy=preview-content] [data-cy=screen-field-form_record_list_1] [data-cy=modal-add] [name=name]").should(
+ "have.value",
+ "12345678"
+ );
+ // Click on the first button
+ cy.get("[data-cy=preview-content] [data-cy=screen-field-form_record_list_1] [data-cy=modal-add] [name=record_list_button_with_handler]").click();
+ cy.get("[data-cy=preview-content] [data-cy=screen-field-form_record_list_1] [data-cy=modal-add] [name=name]").should(
+ "have.value",
+ "value changed by handler"
+ );
+ // Click on OK
+ cy.get("[data-cy=preview-content] [data-cy=screen-field-form_record_list_1] [data-cy=modal-add] button.btn-primary").click();
+
+ // Add new row (data-cy="loop-form_record_list_1-add")
+ cy.get("[data-cy=preview-content] [data-cy=loop-form_record_list_1-add]").click();
+
+ // Click on first dete row
+ cy.get("[data-cy=preview-content] [name=delete_row]").eq(0).click();
+
+ // Click on first handle error
+ cy.get("[data-cy=preview-content] [name=handle_error]").eq(0).click();
+ // Check the error message in console
+ cy.get('@consoleError')
+ .should('have.been.called')
+ .and('have.been.calledWith', 'Testing error');
+
+ // Click on first change and submit
+ cy.get("[data-cy=preview-content] [name=change_and_submit]").eq(0).click();
+
+ // Check the data of the screen
+ cy.assertPreviewData({
+ "form_record_list_1": [
+ {
+ "delete_row": null,
+ "handle_error": null,
+ "name": "last change and submit",
+ "change_and_submit": null
+ }
+ ]
+ });
+ });
+});