diff --git a/src/components/editor/dynamic-panel-editor.vue b/src/components/editor/dynamic-panel-editor.vue new file mode 100644 index 000000000..cb05ba90f --- /dev/null +++ b/src/components/editor/dynamic-panel-editor.vue @@ -0,0 +1,377 @@ + + + + + diff --git a/src/components/editor/index.js b/src/components/editor/index.js index 2b2a453da..94e39bd1f 100644 --- a/src/components/editor/index.js +++ b/src/components/editor/index.js @@ -1,2 +1,3 @@ export { default as Loop } from "./loop.vue"; export { default as MultiColumn } from "./multi-column.vue"; +export { default as DynamicPanel } from "./dynamic-panel-editor.vue"; diff --git a/src/components/index.js b/src/components/index.js index 5740296da..23dbe1753 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -8,8 +8,10 @@ import * as inspector from "./inspector"; import FormBuilderControls from "../form-builder-controls"; import Task from "./task.vue"; import Loop from "./editor/loop.vue"; +import DynamicPanel from "./editor/dynamic-panel-editor.vue"; import MultiColumn from "./editor/multi-column.vue"; import FormLoop from "./renderer/form-loop.vue"; +import FormDynamicPanel from "./renderer/form-dynamic-panel.vue"; import NewFormMultiColumn from "./renderer/new-form-multi-column.vue"; import FormNestedScreen from "./renderer/form-nested-screen.vue"; import ScreenRenderer from "./screen-renderer.vue"; @@ -144,10 +146,12 @@ export default { Vue.component("FormImage", FormImage); Vue.component("FormAvatar", FormAvatar); Vue.component("FormLoop", FormLoop); + Vue.component("FormDynamicPanel", FormDynamicPanel); Vue.component("FormMultiColumn", FormMultiColumn); Vue.component("FormNestedScreen", FormNestedScreen); Vue.component("FormRecordList", FormRecordList); Vue.component("Loop", Loop); + Vue.component("DynamicPanel", DynamicPanel); Vue.component("MultiColumn", MultiColumn); Vue.component("NewFormMultiColumn", NewFormMultiColumn); Vue.component("ScreenRenderer", ScreenRenderer); diff --git a/src/components/inspector/dynamic-panel.vue b/src/components/inspector/dynamic-panel.vue new file mode 100644 index 000000000..d5f6e8656 --- /dev/null +++ b/src/components/inspector/dynamic-panel.vue @@ -0,0 +1,61 @@ + + + + diff --git a/src/components/inspector/index.js b/src/components/inspector/index.js index 3df8d995a..210bfd7c9 100644 --- a/src/components/inspector/index.js +++ b/src/components/inspector/index.js @@ -18,6 +18,7 @@ export { default as ImageUpload } from "./image-upload.vue"; export { default as ImageVariable } from "./image-variable.vue"; export { default as InputVariable } from "./input-variable.vue"; export { default as LoopInspector } from "./loop.vue"; +export { default as DynamicPanelInspector } from "./dynamic-panel.vue"; export { default as MustacheHelper } from "./mustache-helper.vue"; export { default as OptionsList } from "./options-list.vue"; export { default as OutboundConfig } from "./outbound-config.vue"; diff --git a/src/components/renderer/form-dynamic-panel.vue b/src/components/renderer/form-dynamic-panel.vue new file mode 100644 index 000000000..a2a394567 --- /dev/null +++ b/src/components/renderer/form-dynamic-panel.vue @@ -0,0 +1,43 @@ + + + + + diff --git a/src/components/renderer/index.js b/src/components/renderer/index.js index dc0a61768..fc8c7cba9 100755 --- a/src/components/renderer/index.js +++ b/src/components/renderer/index.js @@ -21,3 +21,4 @@ export { default as FormTasks } from "./form-tasks.vue"; export { default as LinkButton } from "./link-button.vue"; export { default as FormCollectionRecordControl } from "./form-collection-record-control.vue"; export { default as FormCollectionViewControl } from "./form-collection-view-control.vue"; +export { default as FormDynamicPanel } from "./form-dynamic-panel.vue"; \ No newline at end of file diff --git a/src/components/vue-form-builder.vue b/src/components/vue-form-builder.vue index 60702854f..a94fcefc8 100644 --- a/src/components/vue-form-builder.vue +++ b/src/components/vue-form-builder.vue @@ -1521,7 +1521,8 @@ export default { if ( _.findIndex(control.inspector, keyNamePropertyToFind) !== -1 || - control.component === "FormLoop" + control.component === "FormLoop" || + control.component === "FormDynamicPanel" ) { [this.variables, copy.config.name] = this.generator.generate( this.config, diff --git a/src/form-builder-controls.js b/src/form-builder-controls.js index 0943f6c3a..5dd1a3e8d 100755 --- a/src/form-builder-controls.js +++ b/src/form-builder-controls.js @@ -3,6 +3,7 @@ import FormAvatar from './components/renderer/form-avatar'; import FormButton from './components/renderer/form-button'; import FormMultiColumn from './components/renderer/form-multi-column'; import FormLoop from './components/renderer/form-loop'; +import FormDynamicPanel from './components/renderer/form-dynamic-panel.vue'; import FormRecordList from './components/renderer/form-record-list'; import FormImage from './components/renderer/form-image'; import FormMaskedInput from './components/renderer/form-masked-input'; @@ -461,6 +462,42 @@ export default [ ], }, }, + { + editorComponent: FormDynamicPanel, + editorBinding: 'FormDynamicPanel', + rendererComponent: FormDynamicPanel, + rendererBinding: 'FormDynamicPanel', + control: { + popoverContent: "Add a dynamic panel component", + order: 6.0, + group: 'Content Fields', + label: 'Dynamic Panel', + component: 'FormDynamicPanel', + 'editor-component': 'DynamicPanel', + 'editor-control': 'DynamicPanel', + container: true, + // Default items container + items: [], + config: { + name: '', + icon: 'fas fa-th-large', + settings: { + type: 'new', + varname: 'dynamic_panel', + indexName: '', + add: false, + }, + }, + inspector: [ + { + type: 'DynamicPanelInspector', + field: 'settings', + config: { + }, + }, + ], + }, + }, { editorComponent: FormText, editorBinding: 'FormText', diff --git a/src/mixins/extensions/FormDynamicPanel.js b/src/mixins/extensions/FormDynamicPanel.js new file mode 100644 index 000000000..a940fbf6f --- /dev/null +++ b/src/mixins/extensions/FormDynamicPanel.js @@ -0,0 +1,62 @@ +export default { + props: { + configRef: null, + loopContext: null + }, + data() { + return { + }; + }, + methods: { + loadFormDynamicPanelProperties({ properties, element }) { + const variableName = element.config.settings.varname; + const index = element.config.settings.indexName; + + // Add itemData to the properties of FormDynamicPanel + properties[':itemData'] = `${variableName} && ${variableName}[${index}]`; + this.registerVariable(element.config.settings.varname, element); + }, + loadFormDynamicPanelItems({ element, node, definition }) { + const nested = { + config: [ + { + items: element.items, + } + ], + watchers: [], + isMobile: false + }; + + + const variableName = element.config.settings.varname; + const index = element.config.settings.indexName; + + // Add nested component inside dynamic panel + const child = this.createComponent("ScreenRenderer", { + ":definition": this.byRef(nested), + ":value": `${variableName} && ${variableName}[${index}]`, + ":loop-context": `'${variableName} && ${variableName}[${index}]'`, + ":_parent": "getValidationData()", + ":components": this.byRef(this.components), + ":config-ref": this.byRef(this.configRef || definition.config), + "@submit": "submitForm" + }); + node.appendChild(child); + } + }, + mounted() { + // Convert the FormDynamicPanel to a div + this.extensions.push({ + onloadproperties(params) { + if (params.element.container && params.componentName === "FormDynamicPanel") { + this.loadFormDynamicPanelProperties(params); + } + }, + onloaditems(params) { + if (params.element.container && params.componentName === "FormDynamicPanel") { + this.loadFormDynamicPanelItems(params); + } + } + }); + } +}; diff --git a/tests/e2e/fixtures/dynamic_panels_screen.json b/tests/e2e/fixtures/dynamic_panels_screen.json new file mode 100644 index 000000000..6960ec6de --- /dev/null +++ b/tests/e2e/fixtures/dynamic_panels_screen.json @@ -0,0 +1,1198 @@ +{ + "screens": [ + { + "id": "dynamic-panels-test", + "config": [ + { + "name": "Default", + "items": [ + { + "uuid": "b3cbc4c3-234d-4a56-b880-f9a84617a673", + "config": { + "icon": "far fa-square", + "label": "Index Number", + "name": "index", + "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": "2ac890c4-6515-420a-9932-bf3ec8c1d1bc", + "config": { + "icon": "fas fa-table", + "options": [ + { "value": "1", "content": "6" }, + { "value": "2", "content": "6" } + ], + "label": "" + }, + "inspector": [ + { + "type": "ContainerColumns", + "field": "options", + "config": { + "label": "Column Width", + "validation": "columns-adds-to-12", + "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": "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": "FormMultiColumn", + "editor-component": "MultiColumn", + "editor-control": "FormMultiColumn", + "label": "Multicolumn / Table", + "items": [ + [ + { + "uuid": "80fa28e6-6c3b-418d-8bd9-32aed7dbb497", + "config": { + "name": "loop_1", + "icon": "fas fa-redo", + "settings": { + "type": "new", + "varname": "loop_1", + "times": "3", + "add": false + }, + "label": "" + }, + "inspector": [ + { + "type": "LoopInspector", + "field": "settings", + "config": {} + }, + { + "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": "1eba26ad-a6c8-4295-b096-9de8c34c5851", + "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" + } + ], + "container": true + } + ], + [ + { + "uuid": "ac634f39-d75e-461e-ac8c-0719a3991910", + "config": { + "name": "loop_1", + "icon": "fas fa-th-large", + "settings": { + "type": "new", + "varname": "loop_1", + "indexOf": "index", + "add": false, + "indexName": "index" + }, + "label": "" + }, + "inspector": [ + { + "type": "DynamicPanelInspector", + "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": "FormDynamicPanel", + "editor-component": "DynamicPanel", + "editor-control": "DynamicPanel", + "label": "Dynamic Panel", + "items": [ + { + "uuid": "4447b61f-b12c-49ee-8648-51f2c7ddff83", + "config": { + "icon": "far fa-square", + "label": "Selected 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" + } + ], + "container": true + } + ] + ], + "container": true + }, + { + "uuid": "48606936-107b-4bcf-ac86-a726cd717c57", + "config": { + "icon": "fas fa-share-square", + "label": "New Submit", + "variant": "primary", + "event": "submit", + "loading": false, + "loadingLabel": "Loading...", + "defaultSubmit": true, + "name": null, + "fieldValue": null, + "tooltip": {}, + "handler": "" + }, + "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" + } + ], + "order": 1 + } + ] + } + ] +} \ No newline at end of file diff --git a/tests/e2e/specs/DynamicPanelsSimplified.spec.js b/tests/e2e/specs/DynamicPanelsSimplified.spec.js new file mode 100644 index 000000000..5a2ed715b --- /dev/null +++ b/tests/e2e/specs/DynamicPanelsSimplified.spec.js @@ -0,0 +1,128 @@ +describe("Dynamic Panels - Simplified", () => { + beforeEach(() => { + cy.visit("/"); + // Load the screen configuration from fixture + cy.loadFromJson("dynamic_panels_screen.json", 0, "form"); + }); + + it("should render the screen with dynamic panels correctly", () => { + // Switch to preview mode + cy.get("[data-cy=mode-preview]").click(); + + // Verify the Index Number input is present + cy.get("[data-cy=preview-content] [name=index]").should("be.visible"); + cy.get("[data-cy=preview-content] label").contains("Index Number").should("be.visible"); + + // Verify the multicolumn structure is present + cy.get("[data-cy=preview-content] .col-sm-6").should("have.length", 2); + + // Verify the loop inputs are present (should be 3 based on the configuration) + cy.get("[data-cy=preview-content] [name=name]").should("have.length", 3); + + // Verify the dynamic panel is present + cy.get("[data-cy=preview-content] [name=name]").should("be.visible"); + + // Verify the submit button is present + cy.get("[data-cy=preview-content] button").contains("New Submit").should("be.visible"); + }); + + it("should test dynamic panel functionality with loop data", () => { + // Switch to preview mode + cy.get("[data-cy=mode-preview]").click(); + + // Set preview data with loop items + const testData = { + index: "0", + loop_1: [ + { name: "John Doe" }, + { name: "Jane Smith" }, + { name: "Bob Johnson" } + ] + }; + + cy.setPreviewDataInput(testData); + + // Verify the Index Number field has the correct value + cy.get("[data-cy=preview-content] [name=index]").should("have.value", "0"); + + // Verify the loop inputs are populated correctly + cy.get("[data-cy=preview-content] [name=name]").eq(0).should("have.value", "John Doe"); + cy.get("[data-cy=preview-content] [name=name]").eq(1).should("have.value", "Jane Smith"); + cy.get("[data-cy=preview-content] [name=name]").eq(2).should("have.value", "Bob Johnson"); + + // Test dynamic panel functionality by changing the index + cy.get("[data-cy=preview-content] [name=index]").clear().type("1"); + + // Find the input field that has the "Selected Name" label and check its value + // The dynamic panel should show the selected name based on the index + cy.get("[data-cy=preview-content] label").contains("Selected Name").parent().find("input").should("have.value", "Jane Smith"); + + // Test with index 0 + cy.get("[data-cy=preview-content] [name=index]").clear().type("0"); + cy.get("[data-cy=preview-content] label").contains("Selected Name").parent().find("input").should("have.value", "John Doe"); + + // Test with index 2 + cy.get("[data-cy=preview-content] [name=index]").clear().type("2"); + cy.get("[data-cy=preview-content] label").contains("Selected Name").parent().find("input").should("have.value", "Bob Johnson"); + + // Test with invalid index - the dynamic panel should not exist or be hidden + cy.get("[data-cy=preview-content] [name=index]").clear().type("5"); + cy.get("[data-cy=preview-content] label").contains("Selected Name").should("not.exist"); + + // Test with negative index - the dynamic panel should not exist + cy.get("[data-cy=preview-content] [name=index]").clear().type("-1"); + cy.get("[data-cy=preview-content] label").contains("Selected Name").should("not.exist"); + + // Test with non-numeric index - the dynamic panel should not exist + cy.get("[data-cy=preview-content] [name=index]").clear().type("abc"); + cy.get("[data-cy=preview-content] label").contains("Selected Name").should("not.exist"); + }); + + // test update loop items and see if the dynamic panel updates + it("should test update loop items and see if the dynamic panel updates", () => { + // Switch to preview mode + cy.get("[data-cy=mode-preview]").click(); + cy.get("[data-cy=preview-content] [name=index]").clear().type("1"); + + // Update the loop items + cy.get("[data-cy=preview-content] [name=name]").eq(0).clear().type("Alice Cooper"); + cy.get("[data-cy=preview-content] [name=name]").eq(1).clear().type("Bob Dylan"); + cy.get("[data-cy=preview-content] [name=name]").eq(2).clear().type("Charlie Brown"); + + // Verify the dynamic panel shows the correct selected name + cy.get("[data-cy=preview-content] label").contains("Selected Name").parent().find("input").should("have.value", "Bob Dylan"); + + // Update Bob Dylan + cy.get("[data-cy=preview-content] [name=name]").eq(1).clear().type("Bob Dylan Updated"); + cy.get("[data-cy=preview-content] label").contains("Selected Name").parent().find("input").should("have.value", "Bob Dylan Updated"); + + }); + + it("should test form submission with dynamic panels", () => { + // Switch to preview mode + cy.get("[data-cy=mode-preview]").click(); + + // Fill in the form data + cy.get("[data-cy=preview-content] [name=index]").clear().type("1"); + cy.get("[data-cy=preview-content] [name=name]").eq(0).clear().type("Alice Cooper"); + cy.get("[data-cy=preview-content] [name=name]").eq(1).clear().type("Bob Dylan"); + cy.get("[data-cy=preview-content] [name=name]").eq(2).clear().type("Charlie Brown"); + + // Verify the dynamic panel shows the correct selected name + cy.get("[data-cy=preview-content] label").contains("Selected Name").parent().find("input").should("have.value", "Bob Dylan"); + + // Wait for the submit button to be visible and click it + cy.get('[data-cy=preview-content]').should('be.visible'); + cy.get('[data-cy=preview-content] button').contains('New Submit').click(); + + // Verify the final data structure + cy.assertPreviewData({ + index: "1", + loop_1: [ + { name: "Alice Cooper" }, + { name: "Bob Dylan" }, + { name: "Charlie Brown" } + ] + }); + }); +}); \ No newline at end of file