From b42949ccafd81d73e5581d3c588a94423921c0ad Mon Sep 17 00:00:00 2001 From: John Teague <164385719+johnformio@users.noreply.github.com> Date: Wed, 26 Jun 2024 08:51:15 -0500 Subject: [PATCH] Fio-8316 invalid data submitted in nested form (#94) * skipping dataObject model types when filtering * only skipping dataObject model types * integrated istanbul code coverage testing with current thresholds * removed console statements * using functional version of lodash set so original submit object doesn't get mutated during filter post process * verified dataGrid submissions are valid --------- Co-authored-by: John Teague --- .DS_Store | Bin 6148 -> 0 bytes .gitignore | 4 + package.json | 23 +- src/process/__tests__/process.test.ts | 5295 +++++++++-------- src/process/filter/index.ts | 132 +- src/process/process.ts | 249 +- .../validation/rules/validateMultiple.ts | 169 +- src/utils/formUtil.ts | 166 +- 8 files changed, 3153 insertions(+), 2885 deletions(-) delete mode 100644 .DS_Store diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 6f251e0f0822db5dbd9f50ff8885474618665206..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHK%}T>S5Z<-brW7Fug&r5Y7OYmQ;w8lT0!H+pQWH}&7_%iw&7l->))(?gd>&_Z zH()Vm5jz9B-~8@oKgj+t#<)8R_ZYJoV-_?-j!KQ7yEe3Dk`XzMkxheG24H;zQxp5^ zfZyI=8B5tiP<;RXILh*V|C6sZn(G@at7UbpJMT#rUhe0!%=Ob7v@WHLgG%>Baateo5q;rjrBPN;VBv@D7Su&8wFtu}mJpQ)O3Kgv0 Q<$!b%P=ruN4EzEEUnE9JkN^Mx diff --git a/.gitignore b/.gitignore index 585a5657..ce02f237 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,10 @@ +.DS_Store .vscode node_modules docs lib dist .dccache +# ignore nyc output +.nyc_output +coverage diff --git a/package.json b/package.json index 0fb5a099..e1a44c75 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "./dist/formio.core.min.js": "./dist/formio.core.min.js" }, "scripts": { - "test": "TEST=1 mocha -r ts-node/register -r tsconfig-paths/register -r mock-local-storage -r jsdom-global/register -b -t 0 'src/**/__tests__/*.test.ts'", + "test": "TEST=1 nyc --reporter=lcov --reporter=text --reporter=text-summary mocha -r ts-node/register -r tsconfig-paths/register -r mock-local-storage -r jsdom-global/register -t 0 'src/**/__tests__/*.test.ts'", "lib": "tsc --project tsconfig.json && tsc-alias -p tsconfig.json", "replace": "node -r tsconfig-paths/register -r ts-node/register ./lib/base/array/ArrayComponent.js", "test:debug": "mocha -r ts-node/register -r tsconfig-paths/register -r mock-local-storage -r jsdom-global/register --debug-brk --inspect '**/*.spec.ts'", @@ -72,6 +72,7 @@ "mocha": "^10.0.0", "mocha-jsdom": "^2.0.0", "mock-local-storage": "^1.1.20", + "nyc": "^15.1.0", "power-assert": "^1.6.1", "sinon": "^17.0.1", "ts-loader": "^9.5.0", @@ -97,5 +98,23 @@ "json-logic-js": "^2.0.2", "lodash": "^4.17.21", "moment": "^2.29.4" + }, + "nyc": { + "check-coverage": true, + "statements": 62, + "branches": 52, + "functions": 57, + "lines": 60, + "include": [ + "src/**/*.ts", + "src/**/*.js" + ], + "exclude": [ + "src/**/*.test.ts", + "src/**/__tests__/", + "src/experimental/**/*.ts", + "src/types/**/*.ts" + ], + "all": true } -} +} \ No newline at end of file diff --git a/src/process/__tests__/process.test.ts b/src/process/__tests__/process.test.ts index 103c37df..527eb6b7 100644 --- a/src/process/__tests__/process.test.ts +++ b/src/process/__tests__/process.test.ts @@ -1,5 +1,6 @@ -import { expect } from "chai"; -import { processSync, ProcessTargets } from "../index"; +import { expect } from 'chai'; +import { processSync, ProcessTargets } from '../index'; +import { ValidationScope } from 'types'; const assert = require('assert'); const form1 = require('./fixtures/form1.json'); const data1a = require('./fixtures/data1a.json'); @@ -67,2707 +68,2865 @@ describe('Process Tests', () => { */ describe('Process Tests', () => { - it('Should submit data within a nested form.', async () => { - const form = { - _id: { + it('Should submit data within a nested form.', async () => { + const form = { + _id: {}, + title: 'parent-fio-8023', + name: 'parentFio8023', + path: 'parentfio8023', + type: 'form', + display: 'wizard', + tags: [], + deleted: null, + access: [ + { + type: 'read_all', + roles: [{}, {}, {}], + }, + ], + submissionAccess: [], + owner: {}, + components: [ + { + title: 'Nested Wizard', + breadcrumbClickable: true, + buttonSettings: { + previous: true, + cancel: true, + next: true, + }, + collapsible: false, + key: 'page1', + type: 'panel', + label: 'Nested Wizard', + tableView: false, + components: [ + { + label: 'HTML', + attrs: [ + { + attr: '', + value: '', + }, + ], + content: + '- Nested Wizard inside Parent Form\n
- Nested Wizard pages should follow the Parent Wizard page\n
- No Nested Wizard pages should display in a Parent Wizard page\n
- Should not be able to submit the Parent Wizard until all the Child and Parent field validation is satisified', + refreshOnChange: false, + key: 'html', + type: 'htmlelement', + tableView: false, + input: false, }, - title: "parent-fio-8023", - name: "parentFio8023", - path: "parentfio8023", - type: "form", - display: "wizard", - tags: [ - ], - deleted: null, - access: [ - { - type: "read_all", - roles: [ + { + label: 'Parent Text', + tableView: true, + validate: { + required: true, + }, + key: 'parentText', + type: 'textfield', + input: true, + }, + { + label: 'Parent Number', + mask: false, + spellcheck: true, + tableView: false, + delimiter: false, + requireDecimal: false, + inputFormat: 'plain', + validate: { + required: true, + }, + key: 'parentNumber', + type: 'number', + input: true, + }, + { + label: 'Signature', + tableView: false, + validate: { + required: true, + }, + key: 'signature', + type: 'signature', + input: true, + }, + ], + input: false, + }, + { + title: 'Nested EditGrid/DataGrid', + breadcrumbClickable: true, + buttonSettings: { + previous: true, + cancel: true, + next: true, + }, + collapsible: false, + tableView: false, + key: 'page2', + type: 'panel', + label: 'Page 2', + input: false, + components: [ + { + label: 'Form', + tableView: true, + form: '65ea3662705068f84a93c781', + useOriginalRevision: false, + key: 'form1', + type: 'form', + revision: '1', + input: true, + components: [ + { + title: 'Basic Components', + theme: 'info', + breadcrumbClickable: true, + buttonSettings: { + previous: true, + cancel: true, + next: true, + }, + collapsible: false, + key: 'page1', + type: 'panel', + label: 'Basic Components', + tableView: false, + components: [ + { + label: 'Number', + mask: false, + spellcheck: true, + tableView: false, + delimiter: false, + requireDecimal: false, + inputFormat: 'plain', + validate: { + required: true, + }, + key: 'number', + type: 'number', + input: true, + }, + { + label: 'Checkbox', + tableView: false, + validate: { + required: true, + }, + key: 'checkbox', + type: 'checkbox', + input: true, + defaultValue: false, + }, + { + label: 'Select Boxes', + optionsLabelPosition: 'right', + tableView: false, + values: [ { + label: 'SB - A', + value: 'sbA', + shortcut: '', }, { + label: 'SB - B', + value: 'sbB', + shortcut: '', }, { + label: 'SB - C', + value: 'sbC', + shortcut: '', }, - ], - }, - ], - submissionAccess: [ - ], - owner: { - }, - components: [ - { - title: "Nested Wizard", - breadcrumbClickable: true, - buttonSettings: { - previous: true, - cancel: true, - next: true, - }, - collapsible: false, - key: "page1", - type: "panel", - label: "Nested Wizard", - tableView: false, - components: [ { - label: "HTML", - attrs: [ - { - attr: "", - value: "", - }, - ], - content: "- Nested Wizard inside Parent Form\n
- Nested Wizard pages should follow the Parent Wizard page\n
- No Nested Wizard pages should display in a Parent Wizard page\n
- Should not be able to submit the Parent Wizard until all the Child and Parent field validation is satisified", - refreshOnChange: false, - key: "html", - type: "htmlelement", - tableView: false, - input: false, + label: 'SB - D', + value: 'sbD', + shortcut: '', }, + ], + validate: { + required: true, + }, + key: 'selectBoxes1', + type: 'selectboxes', + input: true, + inputType: 'checkbox', + defaultValue: { + '': false, + sbA: false, + sbB: false, + sbC: false, + sbD: false, + }, + }, + { + label: 'Select', + widget: 'choicesjs', + tableView: true, + data: { + values: [ + { + label: 'SA', + value: 'sa', + }, + { + label: 'Sb', + value: 'sb', + }, + { + label: 'SC', + value: 'sc', + }, + ], + }, + validate: { + required: true, + }, + key: 'select1', + type: 'select', + input: true, + }, + { + label: 'Select - URL', + widget: 'choicesjs', + tableView: true, + dataSrc: 'url', + data: { + url: 'https://cdn.rawgit.com/mshafrir/2646763/raw/states_titlecase.json', + headers: [ + { + key: '', + value: '', + }, + ], + }, + template: '{{ item.name }}', + validate: { + required: true, + }, + key: 'selectUrl', + type: 'select', + input: true, + disableLimit: false, + }, + { + label: 'Radio', + optionsLabelPosition: 'right', + inline: false, + tableView: false, + values: [ { - label: "Parent Text", - tableView: true, - validate: { - required: true, - }, - key: "parentText", - type: "textfield", - input: true, + label: 'Ra', + value: 'ra', + shortcut: '', }, { - label: "Parent Number", - mask: false, - spellcheck: true, - tableView: false, - delimiter: false, - requireDecimal: false, - inputFormat: "plain", - validate: { - required: true, - }, - key: "parentNumber", - type: "number", - input: true, + label: 'Rb', + value: 'rb', + shortcut: '', }, { - label: "Signature", - tableView: false, - validate: { - required: true, - }, - key: "signature", - type: "signature", - input: true, + label: 'Rc', + value: 'rc', + shortcut: '', }, - ], - input: false, + ], + validate: { + required: true, + }, + key: 'radio1', + type: 'radio', + input: true, + }, + ], + input: false, }, { - title: "Nested EditGrid/DataGrid", - breadcrumbClickable: true, - buttonSettings: { - previous: true, - cancel: true, - next: true, + title: 'Advanced', + theme: 'primary', + breadcrumbClickable: true, + buttonSettings: { + previous: true, + cancel: true, + next: true, + }, + collapsible: false, + key: 'page2', + type: 'panel', + label: 'Advanced', + tableView: false, + input: false, + components: [ + { + label: 'Email', + tableView: true, + validate: { + required: true, + }, + key: 'email', + type: 'email', + input: true, + }, + { + label: 'Url', + tableView: true, + validate: { + required: true, + }, + key: 'url', + type: 'url', + input: true, + }, + { + label: 'Phone Number', + tableView: true, + validate: { + required: true, + }, + key: 'phoneNumber', + type: 'phoneNumber', + input: true, + }, + { + label: 'Tags', + tableView: false, + validate: { + required: true, + }, + key: 'tags', + type: 'tags', + input: true, }, - collapsible: false, - tableView: false, - key: "page2", - type: "panel", - label: "Page 2", - input: false, - components: [ + { + label: 'Address', + tableView: false, + provider: 'google', + validate: { + required: true, + }, + key: 'address', + type: 'address', + providerOptions: { + params: { + key: '', + region: '', + autocompleteOptions: {}, + }, + }, + input: true, + components: [ { - label: "Form", - tableView: true, - form: "65ea3662705068f84a93c781", - useOriginalRevision: false, - key: "form1", - type: "form", - revision: "1", - input: true, - components: [ - { - title: "Basic Components", - theme: "info", - breadcrumbClickable: true, - buttonSettings: { - previous: true, - cancel: true, - next: true, - }, - collapsible: false, - key: "page1", - type: "panel", - label: "Basic Components", - tableView: false, - components: [ - { - label: "Number", - mask: false, - spellcheck: true, - tableView: false, - delimiter: false, - requireDecimal: false, - inputFormat: "plain", - validate: { - required: true, - }, - key: "number", - type: "number", - input: true, - }, - { - label: "Checkbox", - tableView: false, - validate: { - required: true, - }, - key: "checkbox", - type: "checkbox", - input: true, - defaultValue: false, - }, - { - label: "Select Boxes", - optionsLabelPosition: "right", - tableView: false, - values: [ - { - label: "SB - A", - value: "sbA", - shortcut: "", - }, - { - label: "SB - B", - value: "sbB", - shortcut: "", - }, - { - label: "SB - C", - value: "sbC", - shortcut: "", - }, - { - label: "SB - D", - value: "sbD", - shortcut: "", - }, - ], - validate: { - required: true, - }, - key: "selectBoxes1", - type: "selectboxes", - input: true, - inputType: "checkbox", - defaultValue: { - "": false, - sbA: false, - sbB: false, - sbC: false, - sbD: false, - }, - }, - { - label: "Select", - widget: "choicesjs", - tableView: true, - data: { - values: [ - { - label: "SA", - value: "sa", - }, - { - label: "Sb", - value: "sb", - }, - { - label: "SC", - value: "sc", - }, - ], - }, - validate: { - required: true, - }, - key: "select1", - type: "select", - input: true, - }, - { - label: "Select - URL", - widget: "choicesjs", - tableView: true, - dataSrc: "url", - data: { - url: "https://cdn.rawgit.com/mshafrir/2646763/raw/states_titlecase.json", - headers: [ - { - key: "", - value: "", - }, - ], - }, - template: "{{ item.name }}", - validate: { - required: true, - }, - key: "selectUrl", - type: "select", - input: true, - disableLimit: false, - }, - { - label: "Radio", - optionsLabelPosition: "right", - inline: false, - tableView: false, - values: [ - { - label: "Ra", - value: "ra", - shortcut: "", - }, - { - label: "Rb", - value: "rb", - shortcut: "", - }, - { - label: "Rc", - value: "rc", - shortcut: "", - }, - ], - validate: { - required: true, - }, - key: "radio1", - type: "radio", - input: true, - }, - ], - input: false, - }, - { - title: "Advanced", - theme: "primary", - breadcrumbClickable: true, - buttonSettings: { - previous: true, - cancel: true, - next: true, - }, - collapsible: false, - key: "page2", - type: "panel", - label: "Advanced", - tableView: false, - input: false, - components: [ - { - label: "Email", - tableView: true, - validate: { - required: true, - }, - key: "email", - type: "email", - input: true, - }, - { - label: "Url", - tableView: true, - validate: { - required: true, - }, - key: "url", - type: "url", - input: true, - }, - { - label: "Phone Number", - tableView: true, - validate: { - required: true, - }, - key: "phoneNumber", - type: "phoneNumber", - input: true, - }, - { - label: "Tags", - tableView: false, - validate: { - required: true, - }, - key: "tags", - type: "tags", - input: true, - }, - { - label: "Address", - tableView: false, - provider: "google", - validate: { - required: true, - }, - key: "address", - type: "address", - providerOptions: { - params: { - key: "", - region: "", - autocompleteOptions: { - }, - }, - }, - input: true, - components: [ - { - label: "Address 1", - tableView: false, - key: "address1", - type: "textfield", - input: true, - customConditional: "show = _.get(instance, 'parent.manualMode', false);", - }, - { - label: "Address 2", - tableView: false, - key: "address2", - type: "textfield", - input: true, - customConditional: "show = _.get(instance, 'parent.manualMode', false);", - }, - { - label: "City", - tableView: false, - key: "city", - type: "textfield", - input: true, - customConditional: "show = _.get(instance, 'parent.manualMode', false);", - }, - { - label: "State", - tableView: false, - key: "state", - type: "textfield", - input: true, - customConditional: "show = _.get(instance, 'parent.manualMode', false);", - }, - { - label: "Country", - tableView: false, - key: "country", - type: "textfield", - input: true, - customConditional: "show = _.get(instance, 'parent.manualMode', false);", - }, - { - label: "Zip Code", - tableView: false, - key: "zip", - type: "textfield", - input: true, - customConditional: "show = _.get(instance, 'parent.manualMode', false);", - }, - ], - }, - { - label: "Date / Time", - tableView: false, - enableMinDateInput: false, - datePicker: { - disableWeekends: false, - disableWeekdays: false, - }, - enableMaxDateInput: false, - validate: { - required: true, - }, - key: "dateTime", - type: "datetime", - input: true, - widget: { - type: "calendar", - displayInTimezone: "viewer", - locale: "en", - useLocaleSettings: false, - allowInput: true, - mode: "single", - enableTime: true, - noCalendar: false, - format: "yyyy-MM-dd hh:mm a", - hourIncrement: 1, - minuteIncrement: 1, - time_24hr: false, - minDate: null, - disableWeekends: false, - disableWeekdays: false, - maxDate: null, - }, - }, - { - label: "Day", - hideInputLabels: false, - inputsLabelPosition: "top", - useLocaleSettings: false, - tableView: false, - fields: { - day: { - hide: false, - required: true, - }, - month: { - hide: false, - required: true, - }, - year: { - hide: false, - required: true, - }, - }, - key: "day", - type: "day", - input: true, - defaultValue: "00/00/0000", - }, - { - label: "Time", - tableView: true, - dataFormat: "HH:mm:ss a", - validate: { - required: true, - }, - key: "time", - type: "time", - input: true, - inputMask: "99:99", - }, - { - label: "Currency", - mask: false, - spellcheck: true, - tableView: false, - currency: "USD", - inputFormat: "plain", - validate: { - required: true, - }, - key: "currency", - type: "currency", - input: true, - delimiter: true, - }, - { - label: "Signature", - tableView: false, - validate: { - required: true, - }, - key: "signature", - type: "signature", - input: true, - }, - ], - }, - { - title: "DataGrid / EditGrid", - theme: "danger", - breadcrumbClickable: true, - buttonSettings: { - previous: true, - cancel: true, - next: true, - }, - collapsible: false, - key: "page3", - type: "panel", - label: "DataGrid / EditGrid", - tableView: false, - input: false, - components: [ - { - label: "Data Grid", - reorder: false, - addAnotherPosition: "bottom", - defaultOpen: false, - layoutFixed: false, - enableRowGroups: false, - tableView: false, - defaultValue: [ - { - }, - ], - key: "dataGrid", - type: "datagrid", - input: true, - components: [ - { - label: "Checkbox", - tableView: false, - validate: { - required: true, - }, - key: "checkbox", - type: "checkbox", - input: true, - defaultValue: false, - }, - { - label: "Select", - widget: "choicesjs", - tableView: true, - dataSrc: "resource", - data: { - resource: "65e9eb1aee138974f569d619", - }, - valueProperty: "data.text", - template: "{{ item.data.text }}", - validate: { - select: false, - }, - key: "select", - type: "select", - searchField: "data.text__regex", - input: true, - addResource: false, - reference: false, - }, - { - label: "Radio", - optionsLabelPosition: "right", - inline: false, - tableView: false, - values: [ - { - label: "Ra", - value: "ra", - shortcut: "", - }, - { - label: "Rb", - value: "rb", - shortcut: "", - }, - { - label: "Rc", - value: "rc", - shortcut: "", - }, - ], - validate: { - required: true, - }, - key: "radio1", - type: "radio", - input: true, - }, - ], - }, - { - label: "Edit Grid", - tableView: true, - rowDrafts: false, - key: "editGrid", - type: "editgrid", - input: true, - components: [ - { - label: "Date / Time", - tableView: true, - enableMinDateInput: false, - datePicker: { - disableWeekends: false, - disableWeekdays: false, - }, - enableMaxDateInput: false, - validate: { - required: true, - }, - key: "dateTime", - type: "datetime", - input: true, - widget: { - type: "calendar", - displayInTimezone: "viewer", - locale: "en", - useLocaleSettings: false, - allowInput: true, - mode: "single", - enableTime: true, - noCalendar: false, - format: "yyyy-MM-dd hh:mm a", - hourIncrement: 1, - minuteIncrement: 1, - time_24hr: false, - minDate: null, - disableWeekends: false, - disableWeekdays: false, - maxDate: null, - }, - }, - { - label: "Address", - tableView: true, - provider: "google", - key: "address", - type: "address", - providerOptions: { - params: { - key: "", - region: "", - autocompleteOptions: { - }, - }, - }, - input: true, - components: [ - { - label: "Address 1", - tableView: false, - key: "address1", - type: "textfield", - input: true, - customConditional: "show = _.get(instance, 'parent.manualMode', false);", - }, - { - label: "Address 2", - tableView: false, - key: "address2", - type: "textfield", - input: true, - customConditional: "show = _.get(instance, 'parent.manualMode', false);", - }, - { - label: "City", - tableView: false, - key: "city", - type: "textfield", - input: true, - customConditional: "show = _.get(instance, 'parent.manualMode', false);", - }, - { - label: "State", - tableView: false, - key: "state", - type: "textfield", - input: true, - customConditional: "show = _.get(instance, 'parent.manualMode', false);", - }, - { - label: "Country", - tableView: false, - key: "country", - type: "textfield", - input: true, - customConditional: "show = _.get(instance, 'parent.manualMode', false);", - }, - { - label: "Zip Code", - tableView: false, - key: "zip", - type: "textfield", - input: true, - customConditional: "show = _.get(instance, 'parent.manualMode', false);", - }, - ], - }, - ], - }, - ], - }, - ], + label: 'Address 1', + tableView: false, + key: 'address1', + type: 'textfield', + input: true, + customConditional: + "show = _.get(instance, 'parent.manualMode', false);", }, - ], - }, - ], - settings: { - share: { - theme: "", - showHeader: true, - }, - }, - properties: { - }, - project: { - }, - controller: "", - revisions: "", - submissionRevisions: "", - _vid: 0, - created: "2024-03-07T21:50:03.872Z", - modified: "2024-03-07T21:50:03.879Z", - machineName: "authoring-oaomxjpqpoigtqg:parentFio8023", - __v: 0, - }; - const submission = { - data: { - parentText: "test", - signature: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABgAAAACWCAYAAAAYAUMPAAAAAXNSR0IArs4c6QAAFkZJREFUeF7t3SFsHGcWB/Cv2CAo8MoTnQrCksiVysyuMFLCgwod4KXngBgWpTigsGHLTurKV3YgqlRyJK5UYrz4Tp97u9rsOWvv8+zOm29+Q6Ik+82893sf2v/OzBfz+eV/ioMAAQIECBAgQIAAAQIECBAgQIAAAQIECBBoSuALAUBT89QMAQIECBAgQIAAAQIECBAgQIAAAQIECBC4EhAA2AgECBAgQIAAAQIECBAgQIAAAQIECBAgQKBBAQFAg0PVEgECBAgQIECAAAECBAgQIECAAAECBAgQEADYAwQIECBAgAABAgQIECBAgAABAgQIECBAoEEBAUCDQ9USAQIECBAgQIAAAQIECBAgQIAAAQIECBAQANgDBAgQIECAAAECBAgQIECAAAECBAgQIECgQQEBQIND1RIBAgQIECBAgAABAgQIECBAgAABAgQIEBAA2AMECBAgQIAAAQIECBAgQIAAAQIECBAgQKBBAQFAg0PVEgECBAgQIECAAAECBAgQIECAAAECBAgQEADYAwQIECBAgAABAgQIECBAgAABAgQIECBAoEEBAUCDQ9USAQIECBAgQIAAAQIECBAgQIAAAQIECBAQANgDBAgQIECAAAECBAgQIECAAAECBAgQIECgQQEBQIND1RIBAgQIECBAgAABAgQIECBAgAABAgQIEBAA2AMECBAgQIAAAQIECBAgQIAAAQIECBAgQKBBAQFAg0PVEgECBAgQIECAAAECBAgQIECAAAECBAgQEADYAwQIECBAgAABAgQIECBAgAABAgQIECBAoEEBAUCDQ9USAQIECBAgQIAAAQIECBAgQIAAAQIECBAQANgDBAgQIECAAAECBAgQIECAAAECBAgQIECgQQEBQIND1RIBAgQIECBAgAABAgQIECBAgAABAgQIEBAA2AMECBAgQIAAAQIECBAgQIAAAQIECBAgQKBBAQFAg0PVEgECBAgQIECAAAECBAgQIECAAAECBAgQEADYA6kFjo6+LYeHT8pk8ip1nYojQIAAAQIECBAgQIAAAQIECBAgQIBANgEBQLaJqGcpMJudlz8DgKdlOv2JDAECBAgQIECAAAECBAgQIECAAAECBAhsISAA2ALLR/crsAgA6lXn88v9XtzVCBAgQIAAAQIECBAgQIAAAQIECBAgMHABAcDAB9hy+QKAlqerNwIECBAgQIAAAQIECBAgQIAAAQIEdi0gANi1sPOHBQQAYToLCRAgQIAAAQIECBAgQIAAAQIECBAgUAQANkFagdUAoL4DoL4LwEGAAAECBAgQIECAAAECBAgQIECAAAECtxMQANzOyad6EBAA9IDukgQIECBAgAABAgQIECBAgAABAgQINCMgAGhmlG02cnBw/6qxk5PjMpm8arNJXREgQIAAAQIECBAgQIAAAQIECBAgQGAHAgKAHaA6ZXcCAoDuLJ2JAAECBAgQIECAAAECBAgQIECAAIFxCQgAxjXvwXUrABjcyBRMgAABAgQIECBAgAABAgQIECBAgEASAQFAkkEo43qBRQBQXwBcXwTsIECAAAECBAgQIECAAAECBAgQIECAAIHbCQgAbufkUz0JCAB6gndZAgQIECBAgAABAgQIECBAgAABAgQGLyAAGPwI225gEQDULufzy7ab1R0BAgQIECBAgAABAgQIECBAgAABAgQ6FBAAdIjpVN0LCAC6N3VGAgQIECBAgAABAgQIECBAgAABAgTGISAAGMecb+xyNjsv9Tn72Y7VAKC+AyBjjdnM1EOAAAECBAgQIECAAAECBAgQIECAAIEqIAAY8T6oX/qfnp6V+ufiODk5Ll9//TTNF+1HR98u6xMAjHizap0AAQIECBAgQIAAAQIECBAgQIAAga0FBABbkw1/wenpm/L69dnGRp4/f1YuLi7KdPq+14ZXA4AaTkwmr3qtx8UJECBAgAABAgQIECBAgAABAgQIECAwFAEBwFAm1WGdL19+V969+/FWZ/zqq7+WN2/+3tsdAQKAW43JhwgQIECAAAECBAgQIECAAAECBAgQIPB/AgKAEW6Kx4+/KR8+/PpJ5/XZ+quPAlr9z/p/k8lxLyGAAGCEG1TLBAgQIECAAAECBAgQIECAAAECBAh0IiAA6IRxWCdZfbFu/YX/L7/8Y9nApscD9fEM/tV6ahBRa3AQIECAAAECBAgQIECAAAECBAgQIECAwM0CAoCbjZr6RP2Vf/1V/eL43Jf6n3tM0L6fwy8AaGr7aYYAAQIECBAgQIAAAQIECBAgQIAAgT0KCAD2iJ3lUqt3AGz6Vf/n7gbYZwggAMiya9RBgAABAgQIECBAgAABAgQIECBAgMDQBAQAQ5tYB/WuBgA3fZnfdwggAOhg4E5BgAABAgQIECBAgAABAgQIECBAgMAoBQQAIxz7p1+qPynT6fuNCn2+F0AAMMINqmUCBAgQIECAAAECBAgQIECAAAECBDoREAB0wjisk6w+3//evXvljz/+fWMD9d0Bp6dnpf65fsznlzeuj36gvq9gcU0vAY4qWkeAAAECBAgQIECAAAECBAgQIECAwBgFBAAjnPpdflV/3d0Au/xiXgAwwg2qZQIECBAgQIAAAQIECBAgQIAAAQIEOhEQAHTCOKyTrH+Jv+0v+PcZAggAhrW3VEuAAAECBAgQIECAAAECBAgQIECAQB4BAUCeWeytkrvcAbAo8roQ4KYXCkcaFABE1KwhQIAAAQIECBAgQIAAAQIECBAgQIBAKQKAEe6Cu94BsCkEmE5/KvWRQF0dAoCuJJ2HAAECBAgQIECAAAECBAgQIECAAIGxCQgAxjbxUkpXAUClWz/Xl1/+pfz22786U10NAHZxh0FnhToRAQIECBAgQIAAAQIECBAgQIAAAQIEkgkIAJINZB/ldBkA1HpXv6Svf3/x4ll5+/b7TloRAHTC6CQECBAgQIAAAQIECBAgQIAAAQIECIxQQAAwwqF38Q6AdbYHDx6Vi4vfl//c1a/1Dw7uL8/Z9eOFRjh6LRMgQIAAAQIECBAgQIAAAQIECBAgMCIBAcCIhr1o9eXL78q7dz8uO5/PL++sMJudX90JsHp0EQI8fPiofPz4Z7DQxfnu3KgTECBAgAABAgQIECBAgAABAgQIECBAYCACAoCBDKrLMrt+BNCitl2EAKt3Fjx//qz88EM3jxbq0tO5CBAgQIAAAQIECBAgQIAAAQIECBAgkFFAAJBxKjuuaf2Z/V3cAbApBLjLo3u8A2DHm8HpCRAgQIAAAQIECBAgQIAAAQIECBBoVkAA0OxoP9/YLgOAetX1Owzqv0VDgNVaDw+fXp3HQYAAAQIECBAgQIAAAQIECBAgQIAAAQI3CwgAbjZq7hPrAUD0y/lNMF2FAO4AaG77aYgAAQIECBAgQIAAAQIECBAgQIAAgT0JCAD2BJ3pMvsIAGq/XYQAAoBMO0ctBAgQIECAAAECBAgQIECAAAECBAgMSUAAMKRpdVTrrh8BtFrmdSHANu8cEAB0NHSnIUCAAAECBAgQIECAAAECBAgQIEBgdAICgNGNvJR9BgCVdz0E2OZZ/gcH95cTOjk5LpPJqxFOTMsECBAgQIAAAQIECBAgQIAAAQIECBDYXkAAsL3Z4Fc8fPiofPz4+7KPXbwDYB0pGgKsBgD7qHPww9UAAQIECBAgQIAAAQIECBAgQIAAAQIE/icgABjhVnjw4FG5uNhvAFCZ10OAFy+elbdvv984AQHACDeolgkQIECAAAECBAgQIECAAAECBAgQ6ERAANAJ47BOsvqleq18n4/Wmc3Orx5BtDg2PQ5o/bPuABjWPlMtAQIECBAgQIAAAQIECBAgQIAAAQL9CggA+vXv5ep9BgC14etCgMnkuNQwYPUQAPSyPVyUAAECBAgQIECAAAECBAgQIECAAIFGBAQAjQxymzb6DgAWIcDp6dlVGLA4Dg+flOn0/fLv648McgfANlP2WQIECBAgQIAAAQIECBAgQIAAAQIExi4gABjhDsgQACzY6+OAPg0Bnpb6RX891uuczy9HOC0tEyBAgAABAgQIECBAgAABAgQIECBAICYgAIi5DXrV+hfrff+y/vHjb8qHD78uTes7Cerx+vXZ8t82vStg0MNQPAECBAgQIECAAAECBAgQIECAAAECBHYkIADYEWzm064+WifLF+vrIcC6X98hReZ5qo0AAQIECBAgQIAAAQIECBAgQIAAAQLXCQgARrovFo/dWX/xbp8c63cmLGrJElL0aePaBAgQIECAAAECBAgQIECAAAECBAgQ2FZAALCtmM/vTKCGEvWdAOuHX//vjNyJCRAgQIAAAQIECBAgQIAAAQIECBBoWEAA0PBwh9ra0dHfymz2z6vy/fp/qFNUNwECBAgQIECAAAECBAgQIECAAAECfQsIAPqegOtfK1DvBvj55/MymbwiRIAAAQIECBAgQIAAAQIECBAgQIAAAQIBAQFAAM0SAgQIECBAgAABAgQIECBAgAABAgQIECCQXUAAkH1C6iNAgAABAgQIECBAgAABAgQIECBAgAABAgEBAUAAzRICBAgQIECAAAECBAgQIECAAAECBAgQIJBdQACQfULqI0CAAAECBAgQIECAAAECBAgQIECAAAECAQEBQADNEgIECBAgQIAAAQIECBAgQIAAAQIECBAgkF1AAJB9QuojQIAAAQIECBAgQIAAAQIECBAgQIAAAQIBAQFAAM0SAgQIECBAgAABAgQIECBAgAABAgQIECCQXUAAkH1C6iNAgAABAgQIECBAgAABAgQIECBAgAABAgEBAUAAzRICBAgQIECAAAECBAgQIECAAAECBAgQIJBdQACQfULqI0CAAAECBAgQIECAAAECBAgQIECAAAECAQEBQADNEgIECBAgQIAAAQIECBAgQIAAAQIECBAgkF1AAJB9QuojQIAAAQIECBAgQIAAAQIECBAgQIAAAQIBAQFAAM0SAgQIECBAgAABAgQIECBAgAABAgQIECCQXUAAkH1C6iNAgAABAgQIECBAgAABAgQIECBAgAABAgEBAUAAzRICBAgQIECAAAECBAgQIECAAAECBAgQIJBdQACQfULqI0CAAAECBAgQIECAAAECBAgQIECAAAECAQEBQADNEgIECBAgQIAAAQIECBAgQIAAAQIECBAgkF1AAJB9QuojQIAAAQIECBAgQIAAAQIECBAgQIAAAQIBAQFAAM0SAgQIECBAgAABAgQIECBAgAABAgQIECCQXUAAkH1C6iNAgAABAgQIECBAgAABAgQIECBAgAABAgEBAUAAzRICBAgQIECAAAECBAgQIECAAAECBAgQIJBdQACQfULqI0CAAAECBAgQIECAAAECBAgQIECAAAECAQEBQADNEgIECBAgQIAAAQIECBAgQIAAAQIECBAgkF1AAJB9QuojQIAAAQIECBAgQIAAAQIECBAgQIAAAQIBAQFAAM0SAgQIECBAgAABAgQIECBAgAABAgQIECCQXUAAkH1C6iNAgAABAgQIECBAgAABAgQIECBAgAABAgEBAUAAzRICBAgQIECAAAECBAgQIECAAAECBAgQIJBdQACQfULqI0CAAAECBAgQIECAAAECBAgQIECAAAECAQEBQADNEgIECBAgQIAAAQIECBAgQIAAAQIECBAgkF1AAJB9QuojQIAAAQIECBAgQIAAAQIECBAgQIAAAQIBAQFAAM0SAgQIECBAgAABAgQIECBAgAABAgQIECCQXUAAkH1C6iNAgAABAgQIECBAgAABAgQIECBAgAABAgEBAUAAzRICBAgQIECAAAECBAgQIECAAAECBAgQIJBdQACQfULqI0CAAAECBAgQIECAAAECBAgQIECAAAECAQEBQADNEgIECBAgQIAAAQIECBAgQIAAAQIECBAgkF1AAJB9QuojQIAAAQIECBAgQIAAAQIECBAgQIAAAQIBAQFAAM0SAgQIECBAgAABAgQIECBAgAABAgQIECCQXUAAkH1C6iNAgAABAgQIECBAgAABAgQIECBAgAABAgEBAUAAzRICBAgQIECAAAECBAgQIECAAAECBAgQIJBdQACQfULqI0CAAAECBAgQIECAAAECBAgQIECAAAECAQEBQADNEgIECBAgQIAAAQIECBAgQIAAAQIECBAgkF1AAJB9QuojQIAAAQIECBAgQIAAAQIECBAgQIAAAQIBAQFAAM0SAgQIECBAgAABAgQIECBAgAABAgQIECCQXUAAkH1C6iNAgAABAgQIECBAgAABAgQIECBAgAABAgEBAUAAzRICBAgQIECAAAECBAgQIECAAAECBAgQIJBdQACQfULqI0CAAAECBAgQIECAAAECBAgQIECAAAECAQEBQADNEgIECBAgQIAAAQIECBAgQIAAAQIECBAgkF1AAJB9QuojQIAAAQIECBAgQIAAAQIECBAgQIAAAQIBAQFAAM0SAgQIECBAgAABAgQIECBAgAABAgQIECCQXUAAkH1C6iNAgAABAgQIECBAgAABAgQIECBAgAABAgEBAUAAzRICBAgQIECAAAECBAgQIECAAAECBAgQIJBdQACQfULqI0CAAAECBAgQIECAAAECBAgQIECAAAECAQEBQADNEgIECBAgQIAAAQIECBAgQIAAAQIECBAgkF1AAJB9QuojQIAAAQIECBAgQIAAAQIECBAgQIAAAQIBAQFAAM0SAgQIECBAgAABAgQIECBAgAABAgQIECCQXUAAkH1C6iNAgAABAgQIECBAgAABAgQIECBAgAABAgEBAUAAzRICBAgQIECAAAECBAgQIECAAAECBAgQIJBdQACQfULqI0CAAAECBAgQIECAAAECBAgQIECAAAECAQEBQADNEgIECBAgQIAAAQIECBAgQIAAAQIECBAgkF1AAJB9QuojQIAAAQIECBAgQIAAAQIECBAgQIAAAQIBAQFAAM0SAgQIECBAgAABAgQIECBAgAABAgQIECCQXUAAkH1C6iNAgAABAgQIECBAgAABAgQIECBAgAABAgEBAUAAzRICBAgQIECAAAECBAgQIECAAAECBAgQIJBdQACQfULqI0CAAAECBAgQIECAAAECBAgQIECAAAECAQEBQADNEgIECBAgQIAAAQIECBAgQIAAAQIECBAgkF1AAJB9QuojQIAAAQIECBAgQIAAAQIECBAgQIAAAQIBAQFAAM0SAgQIECBAgAABAgQIECBAgAABAgQIECCQXUAAkH1C6iNAgAABAgQIECBAgAABAgQIECBAgAABAgEBAUAAzRICBAgQIECAAAECBAgQIECAAAECBAgQIJBdQACQfULqI0CAAAECBAgQIECAAAECBAgQIECAAAECAQEBQADNEgIECBAgQIAAAQIECBAgQIAAAQIECBAgkF1AAJB9QuojQIAAAQIECBAgQIAAAQIECBAgQIAAAQIBAQFAAM0SAgQIECBAgAABAgQIECBAgAABAgQIECCQXUAAkH1C6iNAgAABAgQIECBAgAABAgQIECBAgAABAgEBAUAAzRICBAgQIECAAAECBAgQIECAAAECBAgQIJBdQACQfULqI0CAAAECBAgQIECAAAECBAgQIECAAAECAYH/AuX7sgv4DBgrAAAAAElFTkSuQmCC", - form1: { - form: "65ea3662705068f84a93c781", - owner: "65ea3601c3792e416cabcb2a", - roles: [ - ], - access: [ - ], - metadata: { - selectData: { - form1: { - data: { - select1: { - label: "Sb", - }, - }, - }, + { + label: 'Address 2', + tableView: false, + key: 'address2', + type: 'textfield', + input: true, + customConditional: + "show = _.get(instance, 'parent.manualMode', false);", + }, + { + label: 'City', + tableView: false, + key: 'city', + type: 'textfield', + input: true, + customConditional: + "show = _.get(instance, 'parent.manualMode', false);", + }, + { + label: 'State', + tableView: false, + key: 'state', + type: 'textfield', + input: true, + customConditional: + "show = _.get(instance, 'parent.manualMode', false);", }, - timezone: "America/Chicago", - offset: -360, - origin: "http://localhost:3000", - referrer: "", - browserName: "Netscape", - userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36", - pathName: "/", - onLine: true, - headers: { - host: "localhost:3000", - connection: "keep-alive", - "content-length": "9020", - pragma: "no-cache", - "cache-control": "no-cache", - "sec-ch-ua": "\"Chromium\";v=\"122\", \"Not(A:Brand\";v=\"24\", \"Brave\";v=\"122\"", - accept: "application/json", - "content-type": "application/json", - "sec-ch-ua-mobile": "?0", - "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36", - "sec-ch-ua-platform": "\"macOS\"", - "sec-gpc": "1", - "accept-language": "en-US,en", - origin: "http://localhost:3000", - "sec-fetch-site": "same-origin", - "sec-fetch-mode": "cors", - "sec-fetch-dest": "empty", - referer: "http://localhost:3000/", - "accept-encoding": "gzip, deflate, br", + { + label: 'Country', + tableView: false, + key: 'country', + type: 'textfield', + input: true, + customConditional: + "show = _.get(instance, 'parent.manualMode', false);", + }, + { + label: 'Zip Code', + tableView: false, + key: 'zip', + type: 'textfield', + input: true, + customConditional: + "show = _.get(instance, 'parent.manualMode', false);", }, + ], + }, + { + label: 'Date / Time', + tableView: false, + enableMinDateInput: false, + datePicker: { + disableWeekends: false, + disableWeekdays: false, + }, + enableMaxDateInput: false, + validate: { + required: true, + }, + key: 'dateTime', + type: 'datetime', + input: true, + widget: { + type: 'calendar', + displayInTimezone: 'viewer', + locale: 'en', + useLocaleSettings: false, + allowInput: true, + mode: 'single', + enableTime: true, + noCalendar: false, + format: 'yyyy-MM-dd hh:mm a', + hourIncrement: 1, + minuteIncrement: 1, + time_24hr: false, + minDate: null, + disableWeekends: false, + disableWeekdays: false, + maxDate: null, + }, }, - data: { - number: 23, - checkbox: true, - selectBoxes1: { - sbA: false, - sbB: true, - sbC: false, - sbD: false, + { + label: 'Day', + hideInputLabels: false, + inputsLabelPosition: 'top', + useLocaleSettings: false, + tableView: false, + fields: { + day: { + hide: false, + required: true, }, - select1: "sb", - selectUrl: { - name: "Alaska", - abbreviation: "AK", + month: { + hide: false, + required: true, }, - radio1: "ra", - email: "travis@form.io", - url: "google.com", - phoneNumber: "(234) 234-2342", - tags: "test", - address: { - address_components: [ - { - long_name: "12342", - short_name: "12342", - types: [ - "street_number", - ], - }, - { - long_name: "Coit Road", - short_name: "Coit Rd", - types: [ - "route", - ], - }, - { - long_name: "North Dallas", - short_name: "North Dallas", - types: [ - "neighborhood", - "political", - ], - }, - { - long_name: "Dallas", - short_name: "Dallas", - types: [ - "locality", - "political", - ], - }, - { - long_name: "Dallas County", - short_name: "Dallas County", - types: [ - "administrative_area_level_2", - "political", - ], - }, - { - long_name: "Texas", - short_name: "TX", - types: [ - "administrative_area_level_1", - "political", - ], - }, - { - long_name: "United States", - short_name: "US", - types: [ - "country", - "political", - ], - }, - { - long_name: "75243", - short_name: "75243", - types: [ - "postal_code", - ], - }, - { - long_name: "2308", - short_name: "2308", - types: [ - "postal_code_suffix", - ], - }, - ], - formatted_address: "12342 Coit Rd, Dallas, TX 75243, USA", - geometry: { - location: { - lat: 32.9165814, - lng: -96.76889729999999, - }, - viewport: { - south: 32.9151396697085, - west: -96.7703730302915, - north: 32.9178376302915, - east: -96.76767506970849, - }, - }, - place_id: "ChIJrbdWEhUgTIYRl5rVJe8Zl6A", - plus_code: { - compound_code: "W68J+JC Dallas, TX, USA", - global_code: "8645W68J+JC", - }, - types: [ - "street_address", - ], - formattedPlace: "12342 Coit Rd, Dallas, TX 75243, USA", + year: { + hide: false, + required: true, }, - dateTime: "2024-03-14T17:00:00.000Z", - day: "03/23/2029", - time: "15:30:00 pm", - currency: 2, - signature: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABgAAAACWCAYAAAAYAUMPAAAAAXNSR0IArs4c6QAAFBBJREFUeF7t3bFuE1kUBuChTk2J8h4gU6ekXInnIAWu7YLUKXbrFJRs5xqLlFugFS9BRZF6V9cro6yJsePjMWfOfCOhFHjunPOdW/n3zDy5u/v6T+cgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIESgk8EQCUmqdmCBAgQIAAAQIECBAgQIAAAQIECBAgQIDASkAAYCMQIECAAAECBAgQIECAAAECBAgQIECAAIGCAgKAgkPVEgECBAgQIECAAAECBAgQIECAAAECBAgQEADYAwQIECBAgAABAgQIECBAgAABAgQIECBAoKCAAKDgULVEgAABAgQIECBAgAABAgQIECBAgAABAgQEAPYAAQIECBAgQIAAAQIECBAgQIAAAQIECBAoKCAAKDhULREgQIAAAQIECBAgQIAAAQIECBAgQIAAAQGAPUCAAAECBAgQIECAAAECBAgQIECAAAECBAoKCAAKDlVLBAgQIECAAAECBAgQIECAAAECBAgQIEBAAGAPECBAgAABAgQIECBAgAABAgQIECBAgACBggICgIJD1RIBAgQIECBAgAABAgQIECBAgAABAgQIEBAA2AMECBAgQIAAAQIECBAgQIAAAQIECBAgQKCggACg4FC1RIAAAQIECBAgQIAAAQIECBAgQIAAAQIEBAD2AAECBAgQIECAAAECBAgQIECAAAECBAgQKCggACg4VC0RIECAAAECBAgQIECAAAECBAgQIECAAAEBgD1AgAABAgQIECBAgAABAgQIECBAgAABAgQKCggACg5VSwQIECBAgAABAgQIECBAgAABAgQIECBAQABgDxAgQIAAAQIECBAgQIAAAQIECBAgQIAAgYICAoCCQ9USAQIECBAgQIAAAQIECBAgQIAAAQIECBAQANgDBAgQIECAAAECBAgQIECAAAECBAgQIECgoIAAoOBQtUSAAAECBAgQIECAAAECBAgQIECAAAECBAQA9gABAgQIECBAgAABAgQIECBAgAABAgQIECgoIAAoOFQtESBAgAABAgQIECBAgAABAgQIECBAgAABAYA9QIAAAQIECBAgQIAAAQIECBAgQIAAAQIECgoIAAoOVUsECBAgQIAAAQIECBAgQIAAAQIECBAgQEAAYA8QIECAAAECBAgQIECAAAECBAgQIECAAIGCAgKAgkPVEgECBAgQIECAAAECBAgQIECAAAECBAgQEADYAwQIECBAgAABAgQIECBAgAABAgQIECBAoKCAAKDgULVEgAABAgQIECBAgAABAgQIECBAgAABAgQEAPYAAQIECBAgQIAAAQIECBAgQIAAAQIECBAoKCAAKDhULREgQIAAAQIECBAgQIAAAQIECBAgQIAAAQGAPUCAAAECBAgQIECAAAECBAgQIECAAAECBAoKCAAKDlVLBAgQIECAAAECBAgQIECAAAECBAgQIEBAAGAPECBAgAABAgQIECBAgAABAgQIECBAgACBggICgIJD1RIBAgQIECBAgAABAgQIECBAgAABAgQIEBAA2AMECBAgQIAAAQIECBAgQIAAAQIECBAgQKCggACg4FC1RIAAAQIECBAgQIAAAQIECBAgQIAAAQIEBAD2AAECBAgQIECAAAECBAgQIECAAAECBAgQKCggACg4VC0RIECAAAECBAgQIECAAAECBAgQIECAAAEBgD1AgAABAgQIECBAgAABAgQIECBAgAABAgQKCggACg5VSwQIECBAgAABAgQIECBAgAABAgQIECBAQABgDxAgQIAAAQIECBAgQIAAAQIECBAgQIAAgYICAoCCQ9USAQIECBAgQIAAAQIECBAgQIAAAQIECBAQANgDBAgQIECAAAECBAgQIECAAAECBAgQIECgoIAAoOBQtUSAAAECBAgQIECAAAECBAgQIECAAAECBAQA9gABAgQIECBAgAABAgQIECBAgAABAgQIECgoIAAoOFQtESBAgAABAgQIECBAgAABAgQIECBAgAABAYA9QIAAAQIECBAgQIAAAQIECBAgQIAAAQIECgoIAAoOVUsECBAgQIAAAQIECBAgQIAAAQIECBAgQEAAYA8QIECAAAECBAgQIECAAAECBAgQIECAAIGCAgKAgkPVEgECBAgQIECAAAECBAgQIECAAAECBAgQEADYAwQIECBAgAABAgQIECBAgAABAgQIECBAoKCAAKDgULVEgAABAgQIECBAgAABAgQIECBAgAABAgQEAPYAAQIECBAgQIAAAQIECBAgQIAAAQIECBAoKCAAKDhULREgQIAAAQIECBAgQIAAAQIECBAgQIAAAQGAPUCAAAECBAgQIECAAAECBAgQIECAAAECBAoKCAAKDlVLBAgQIECAAAECBAgQIECAAAECBAgQIEBAAGAPECBAgAABAgQIECBAgAABAgQIECBAgACBggICgIJD1RIBAgQIECBAgAABAgQIECBAgAABAgQIEBAA2AMECBAgQIAAAQIECBAgQIAAAQIECBAgQKCggACg4FC1RIAAAQIECBAgQIAAAQIECBAgQIAAAQIEBAD2AAECBAgQIECAAAECBAgQIECAAAECBAgQKCggACg4VC0RIECAAAECBAgQIECAAAECBAgQIECAAAEBgD1AgAABAgQIECBAgAABAgQIECBAgAABAgQKCggACg5VSwQIECBAgAABAgQIECBAgAABAgQIECBAQABgD5xMYLn81H38+Kl7+fLF6m87lsvb1d/J5Pnq73R6ebJ6XIgAAQIECBAgQIAAAQIECBAgQIAAAQKVBQQAlaebqLfZ7F03n1/tVdFk8mIVCAgD9uLyIQIECBAgQIAAAQIECBAgQIAAAQIECDwoIACwMU4icHb29KDrrIOAFgo4CBAgQIAAAQIECBAgQIAAAQIECBAgQGB/AQHA/lY+eaBAe/TPxcWrA8/+77QWAEynb1Z/HQQIECBAgAABAgQIECBAgAABAgQIECCwW0AAsNvIJ44g8JhHAP3scoKAIwzDEgQIECBAgAABAgQIECBAgAABAgQIjEJAADCKMedosoUA66O9CHjzaC8Gbi8FbncM7DpaELBYfNj1Mf9PgAABAgQIECBAgAABAgQIECBAgACB0QoIAEY7+tyNt7Dg+vqP7tu3b1sLFQLknqHqCBAgQIAAAQIECBAgQIAAAQIECBD4tQICgF/r7+o7BNrdALPZ1da7As7Pn3VfvvzFkQABAgQIECBAgAABAgQIECBAgAABAgQ2BAQAtsQgBHYFAe1xQF4QPIhRKpIAAQIECBAgQIAAAQIECBAgQIAAgRMJCABOBO0yxxPY9kJhLwg+nrGVCBAgQIAAAQIECBAgQIAAAQIECBAYvoAAYPgzHGUH19e/dzc377vPn//+of+3b9900+nlKF00TYAAAQIECBAgQIAAAQIECBAgQIAAgbWAAMBeGLSAuwEGPT7FEyBAgAABAgQIECBAgAABAgQIECDQo4AAoEdcS59G4GfvB3A3wGlm4CoECBAgQIAAAQIECBAgQIAAAQIECOQTEADkm4mKDhRwN8CBcE4jQIAAAQIECBAgQIAAAQIECBAgQKCkgACg5FjH3dTFxauu3RWweXhJ8Lj3he4JECBAgAABAgQIECBAgAABAgQIjE1AADC2iY+kX48FGsmgtUmAAAECBAgQIECAAAECBAgQIECAwFYBAYDNUVogw2OBWg3L5W23WHwoba05AgQIECBAgAABAgQIECBAgAABAgRyCQgAcs1DNT0JbHssUN8vCW53IrRrt6Pva/VEZ1kCBAgQIECAAAECBAgQIECAAAECBAYqIAAY6OCU/XiBbY8F6vPdAPcDgHYddwE8fm7OIECAAAECBAgQIECAAAECBAgQIEDgMAEBwGFuzhqwwLYgoK9f6J+dPf2udXf3dcBySidAgAABAgQIECBAgAABAgQIECBAYEgCAoAhTUutRxV4KAjo426A+wFAuwOgXcNBgAABAgQIECBAgAABAgQIECBAgACBvgUEAH0LWz+9wENBwDHvBhAApN8CCiRAgAABAgQIECBAgAABAgQIECBQUkAAUHKsmjpEYDZ7183nV99PnUyed4vFn4cs9b9z7r+A+JjBQrgwCxAgQIAAAQIECBAgQIAAAQIECBAgUFpAAFB6vJo7RGAzCIh+ae8OgEOm4BwCBAgQIECAAAECBAgQIECAAAECBKICAoCooPPLCjwUBLRmp9PLvXveXMNLgPem80ECBAgQIECAAAECBAgQIECAAAECBIICAoAgoNNrC2x+gb/u9vz8Wff69W87w4D7v/5vL/9tLwF2ECBAgAABAgQIECBAgAABAgQIECBA4BQCAoBTKLvG4AW2BQHt8UDt2LwroL1Y+Obm/erf+mhf/rcQwEGAAAECBAgQIECAAAECBAgQIECAAIFTCAgATqHsGmUEWhDQvtxfLm9/6Gn95X77/83Dr//LbAGNECBAgAABAgQIECBAgAABAgQIEBiMgABgMKNSaCaBFgS0Yz6/2qssv/7fi8mHCBAgQIAAAQIECBAgQIAAAQIECBA4ooAA4IiYlhqfwD5BQHtM0GNeHDw+RR0TIECAAAECBAgQIECAAAECBAgQINCHgACgD1VrjlJg/egfz/kf5fg1TYAAAQIECBAgQIAAAQIECBAgQCCdgAAg3UgURIAAAQIECBAgQIAAAQIECBAgQIAAAQIE4gICgLihFQgQIECAAAECBAgQIECAAAECBAgQIECAQDoBAUC6kSiIAAECBAgQIECAAAECBAgQIECAAAECBAjEBQQAcUMrECBAgAABAgQIECBAgAABAgQIECBAgACBdAICgHQjURABAgQIECBAgAABAgQIECBAgAABAgQIEIgLCADihlYgQIAAAQIECBAgQIAAAQIECBAgQIAAAQLpBAQA6UaiIAIECBAgQIAAAQIECBAgQIAAAQIECBAgEBcQAMQNrUCAAAECBAgQIECAAAECBAgQIECAAAECBNIJCADSjURBBAgQIECAAAECBAgQIECAAAECBAgQIEAgLiAAiBtagQABAgQIECBAgAABAgQIECBAgAABAgQIpBMQAKQbiYIIECBAgAABAgQIECBAgAABAgQIECBAgEBcQAAQN7QCAQIECBAgQIAAAQIECBAgQIAAAQIECBBIJyAASDcSBREgQIAAAQIECBAgQIAAAQIECBAgQIAAgbiAACBuaAUCBAgQIECAAAECBAgQIECAAAECBAgQIJBOQACQbiQKIkCAAAECBAgQIECAAAECBAgQIECAAAECcQEBQNzQCgQIECBAgAABAgQIECBAgAABAgQIECBAIJ2AACDdSBREgAABAgQIECBAgAABAgQIECBAgAABAgTiAgKAuKEVCBAgQIAAAQIECBAgQIAAAQIECBAgQIBAOgEBQLqRKIgAAQIECBAgQIAAAQIECBAgQIAAAQIECMQFBABxQysQIECAAAECBAgQIECAAAECBAgQIECAAIF0AgKAdCNREAECBAgQIECAAAECBAgQIECAAAECBAgQiAsIAOKGViBAgAABAgQIECBAgAABAgQIECBAgAABAukEBADpRqIgAgQIECBAgAABAgQIECBAgAABAgQIECAQFxAAxA2tQIAAAQIECBAgQIAAAQIECBAgQIAAAQIE0gkIANKNREEECBAgQIAAAQIECBAgQIAAAQIECBAgQCAuIACIG1qBAAECBAgQIECAAAECBAgQIECAAAECBAikExAApBuJgggQIECAAAECBAgQIECAAAECBAgQIECAQFxAABA3tAIBAgQIECBAgAABAgQIECBAgAABAgQIEEgnIABINxIFESBAgAABAgQIECBAgAABAgQIECBAgACBuIAAIG5oBQIECBAgQIAAAQIECBAgQIAAAQIECBAgkE5AAJBuJAoiQIAAAQIECBAgQIAAAQIECBAgQIAAAQJxAQFA3NAKBAgQIECAAAECBAgQIECAAAECBAgQIEAgnYAAIN1IFESAAAECBAgQIECAAAECBAgQIECAAAECBOICAoC4oRUIECBAgAABAgQIECBAgAABAgQIECBAgEA6AQFAupEoiAABAgQIECBAgAABAgQIECBAgAABAgQIxAUEAHFDKxAgQIAAAQIECBAgQIAAAQIECBAgQIAAgXQCAoB0I1EQAQIECBAgQIAAAQIECBAgQIAAAQIECBCICwgA4oZWIECAAAECBAgQIECAAAECBAgQIECAAAEC6QQEAOlGoiACBAgQIECAAAECBAgQIECAAAECBAgQIBAXEADEDa1AgAABAgQIECBAgAABAgQIECBAgAABAgTSCQgA0o1EQQQIECBAgAABAgQIECBAgAABAgQIECBAIC4gAIgbWoEAAQIECBAgQIAAAQIECBAgQIAAAQIECKQTEACkG4mCCBAgQIAAAQIECBAgQIAAAQIECBAgQIBAXEAAEDe0AgECBAgQIECAAAECBAgQIECAAAECBAgQSCcgAEg3EgURIECAAAECBAgQIECAAAECBAgQIECAAIG4gAAgbmgFAgQIECBAgAABAgQIECBAgAABAgQIECCQTkAAkG4kCiJAgAABAgQIECBAgAABAgQIECBAgAABAnEBAUDc0AoECBAgQIAAAQIECBAgQIAAAQIECBAgQCCdgAAg3UgURIAAAQIECBAgQIAAAQIECBAgQIAAAQIE4gICgLihFQgQIECAAAECBAgQIECAAAECBAgQIECAQDoBAUC6kSiIAAECBAgQIECAAAECBAgQIECAAAECBAjEBQQAcUMrECBAgAABAgQIECBAgAABAgQIECBAgACBdAICgHQjURABAgQIECBAgAABAgQIECBAgAABAgQIEIgL/AseLFj8GyK16AAAAABJRU5ErkJggg==", - dataGrid: [ - { - checkbox: true, - select: "", - radio1: "rb", - }, - ], + }, + key: 'day', + type: 'day', + input: true, + defaultValue: '00/00/0000', }, - _id: "65ea36dd705068f84a93c9c3", - _fvid: 1, - project: "65ea3620705068f84a93c694", - state: "submitted", - externalIds: [ - ], - created: "2024-03-07T21:51:25.110Z", - modified: "2024-03-07T21:51:25.110Z", - }, - parentNumber: 234, - }, - owner: "65ea3601c3792e416cabcb2a", - access: [ - ], - metadata: { - timezone: "America/Chicago", - offset: -360, - origin: "http://localhost:3000", - referrer: "", - browserName: "Netscape", - userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36", - pathName: "/", - onLine: true, - headers: { - "accept-language": "en-US,en", - "cache-control": "no-cache", - connection: "keep-alive", - origin: "http://localhost:3000", - pragma: "no-cache", - referer: "http://localhost:3000/", - "sec-fetch-dest": "empty", - "sec-fetch-mode": "cors", - "sec-fetch-site": "same-origin", - "sec-gpc": "1", - "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36", - accept: "application/json", - "content-type": "application/json", - "sec-ch-ua": "\"Chromium\";v=\"122\", \"Not(A:Brand\";v=\"24\", \"Brave\";v=\"122\"", - "sec-ch-ua-mobile": "?0", - "sec-ch-ua-platform": "\"macOS\"", - host: "localhost:3000", - "accept-encoding": "gzip, deflate, br", - "content-length": "18055", - }, - }, - _vnote: "", - state: "submitted", - form: "65ea368b705068f84a93c87a", - }; - - const errors: any = []; - const context = { - form, - submission, - data: submission.data, - components: form.components, - processors: ProcessTargets.submission, - scope: { errors }, - config: { - server: true - } - }; - processSync(context); - submission.data = context.data; - context.processors = ProcessTargets.evaluator; - processSync(context); - assert.equal(context.scope.errors.length, 0); - }); - - it('Should process nested form data correctly', async () => { - const submission = { - data: { - submit: true, - child: { - data: { - input: "4", - output: 200, + { + label: 'Time', + tableView: true, + dataFormat: 'HH:mm:ss a', + validate: { + required: true, + }, + key: 'time', + type: 'time', + input: true, + inputMask: '99:99', }, - }, - }, - owner: "65df88d8a98df60a25008300", - access: [ - ], - metadata: { - headers: { - "accept-language": "en-US,en", - "cache-control": "no-cache", - connection: "keep-alive", - origin: "http://localhost:3000", - pragma: "no-cache", - referer: "http://localhost:3000/", - "sec-fetch-dest": "empty", - "sec-fetch-mode": "cors", - "sec-fetch-site": "same-origin", - "sec-gpc": "1", - "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36", - accept: "application/json", - "content-type": "application/json", - "sec-ch-ua": "\"Chromium\";v=\"122\", \"Not(A:Brand\";v=\"24\", \"Brave\";v=\"122\"", - "sec-ch-ua-mobile": "?0", - "sec-ch-ua-platform": "\"macOS\"", - host: "localhost:3000", - "accept-encoding": "gzip, deflate, br", - "content-length": "172", - }, - }, - form: "65e74c65ef4451c9ede341e3", - }; - const form = { - title: "Parent Form", - name: "parentForm", - path: "parentform", - type: "form", - display: "form", - tags: [ - ], - deleted: null, - access: [ - { - type: "create_own", - roles: [ - ], - }, - { - type: "create_all", - roles: [ - ], - }, - { - type: "read_own", - roles: [ - ], + { + label: 'Currency', + mask: false, + spellcheck: true, + tableView: false, + currency: 'USD', + inputFormat: 'plain', + validate: { + required: true, + }, + key: 'currency', + type: 'currency', + input: true, + delimiter: true, + }, + { + label: 'Signature', + tableView: false, + validate: { + required: true, + }, + key: 'signature', + type: 'signature', + input: true, + }, + ], }, { - type: "read_all", - roles: [ + title: 'DataGrid / EditGrid', + theme: 'danger', + breadcrumbClickable: true, + buttonSettings: { + previous: true, + cancel: true, + next: true, + }, + collapsible: false, + key: 'page3', + type: 'panel', + label: 'DataGrid / EditGrid', + tableView: false, + input: false, + components: [ + { + label: 'Data Grid', + reorder: false, + addAnotherPosition: 'bottom', + defaultOpen: false, + layoutFixed: false, + enableRowGroups: false, + tableView: false, + defaultValue: [{}], + key: 'dataGrid', + type: 'datagrid', + input: true, + components: [ { + label: 'Checkbox', + tableView: false, + validate: { + required: true, + }, + key: 'checkbox', + type: 'checkbox', + input: true, + defaultValue: false, }, { + label: 'Select', + widget: 'choicesjs', + tableView: true, + dataSrc: 'resource', + data: { + resource: '65e9eb1aee138974f569d619', + }, + valueProperty: 'data.text', + template: '{{ item.data.text }}', + validate: { + select: false, + }, + key: 'select', + type: 'select', + searchField: 'data.text__regex', + input: true, + addResource: false, + reference: false, }, { + label: 'Radio', + optionsLabelPosition: 'right', + inline: false, + tableView: false, + values: [ + { + label: 'Ra', + value: 'ra', + shortcut: '', + }, + { + label: 'Rb', + value: 'rb', + shortcut: '', + }, + { + label: 'Rc', + value: 'rc', + shortcut: '', + }, + ], + validate: { + required: true, + }, + key: 'radio1', + type: 'radio', + input: true, }, - ], - }, - { - type: "update_own", - roles: [ - ], - }, - { - type: "update_all", - roles: [ - ], - }, - { - type: "delete_own", - roles: [ - ], - }, - { - type: "delete_all", - roles: [ - ], - }, - { - type: "team_read", - roles: [ - ], - }, - { - type: "team_write", - roles: [ - ], + ], + }, + { + label: 'Edit Grid', + tableView: true, + rowDrafts: false, + key: 'editGrid', + type: 'editgrid', + input: true, + components: [ + { + label: 'Date / Time', + tableView: true, + enableMinDateInput: false, + datePicker: { + disableWeekends: false, + disableWeekdays: false, + }, + enableMaxDateInput: false, + validate: { + required: true, + }, + key: 'dateTime', + type: 'datetime', + input: true, + widget: { + type: 'calendar', + displayInTimezone: 'viewer', + locale: 'en', + useLocaleSettings: false, + allowInput: true, + mode: 'single', + enableTime: true, + noCalendar: false, + format: 'yyyy-MM-dd hh:mm a', + hourIncrement: 1, + minuteIncrement: 1, + time_24hr: false, + minDate: null, + disableWeekends: false, + disableWeekdays: false, + maxDate: null, + }, + }, + { + label: 'Address', + tableView: true, + provider: 'google', + key: 'address', + type: 'address', + providerOptions: { + params: { + key: '', + region: '', + autocompleteOptions: {}, + }, + }, + input: true, + components: [ + { + label: 'Address 1', + tableView: false, + key: 'address1', + type: 'textfield', + input: true, + customConditional: + "show = _.get(instance, 'parent.manualMode', false);", + }, + { + label: 'Address 2', + tableView: false, + key: 'address2', + type: 'textfield', + input: true, + customConditional: + "show = _.get(instance, 'parent.manualMode', false);", + }, + { + label: 'City', + tableView: false, + key: 'city', + type: 'textfield', + input: true, + customConditional: + "show = _.get(instance, 'parent.manualMode', false);", + }, + { + label: 'State', + tableView: false, + key: 'state', + type: 'textfield', + input: true, + customConditional: + "show = _.get(instance, 'parent.manualMode', false);", + }, + { + label: 'Country', + tableView: false, + key: 'country', + type: 'textfield', + input: true, + customConditional: + "show = _.get(instance, 'parent.manualMode', false);", + }, + { + label: 'Zip Code', + tableView: false, + key: 'zip', + type: 'textfield', + input: true, + customConditional: + "show = _.get(instance, 'parent.manualMode', false);", + }, + ], + }, + ], + }, + ], }, - { - type: "team_admin", - roles: [ - ], + ], + }, + ], + }, + ], + settings: { + share: { + theme: '', + showHeader: true, + }, + }, + properties: {}, + project: {}, + controller: '', + revisions: '', + submissionRevisions: '', + _vid: 0, + created: '2024-03-07T21:50:03.872Z', + modified: '2024-03-07T21:50:03.879Z', + machineName: 'authoring-oaomxjpqpoigtqg:parentFio8023', + __v: 0, + }; + const submission = { + data: { + parentText: 'test', + signature: + 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABgAAAACWCAYAAAAYAUMPAAAAAXNSR0IArs4c6QAAFkZJREFUeF7t3SFsHGcWB/Cv2CAo8MoTnQrCksiVysyuMFLCgwod4KXngBgWpTigsGHLTurKV3YgqlRyJK5UYrz4Tp97u9rsOWvv8+zOm29+Q6Ik+82893sf2v/OzBfz+eV/ioMAAQIECBAgQIAAAQIECBAgQIAAAQIECBBoSuALAUBT89QMAQIECBAgQIAAAQIECBAgQIAAAQIECBC4EhAA2AgECBAgQIAAAQIECBAgQIAAAQIECBAgQKBBAQFAg0PVEgECBAgQIECAAAECBAgQIECAAAECBAgQEADYAwQIECBAgAABAgQIECBAgAABAgQIECBAoEEBAUCDQ9USAQIECBAgQIAAAQIECBAgQIAAAQIECBAQANgDBAgQIECAAAECBAgQIECAAAECBAgQIECgQQEBQIND1RIBAgQIECBAgAABAgQIECBAgAABAgQIEBAA2AMECBAgQIAAAQIECBAgQIAAAQIECBAgQKBBAQFAg0PVEgECBAgQIECAAAECBAgQIECAAAECBAgQEADYAwQIECBAgAABAgQIECBAgAABAgQIECBAoEEBAUCDQ9USAQIECBAgQIAAAQIECBAgQIAAAQIECBAQANgDBAgQIECAAAECBAgQIECAAAECBAgQIECgQQEBQIND1RIBAgQIECBAgAABAgQIECBAgAABAgQIEBAA2AMECBAgQIAAAQIECBAgQIAAAQIECBAgQKBBAQFAg0PVEgECBAgQIECAAAECBAgQIECAAAECBAgQEADYAwQIECBAgAABAgQIECBAgAABAgQIECBAoEEBAUCDQ9USAQIECBAgQIAAAQIECBAgQIAAAQIECBAQANgDBAgQIECAAAECBAgQIECAAAECBAgQIECgQQEBQIND1RIBAgQIECBAgAABAgQIECBAgAABAgQIEBAA2AMECBAgQIAAAQIECBAgQIAAAQIECBAgQKBBAQFAg0PVEgECBAgQIECAAAECBAgQIECAAAECBAgQEADYA6kFjo6+LYeHT8pk8ip1nYojQIAAAQIECBAgQIAAAQIECBAgQIBANgEBQLaJqGcpMJudlz8DgKdlOv2JDAECBAgQIECAAAECBAgQIECAAAECBAhsISAA2ALLR/crsAgA6lXn88v9XtzVCBAgQIAAAQIECBAgQIAAAQIECBAgMHABAcDAB9hy+QKAlqerNwIECBAgQIAAAQIECBAgQIAAAQIEdi0gANi1sPOHBQQAYToLCRAgQIAAAQIECBAgQIAAAQIECBAgUAQANkFagdUAoL4DoL4LwEGAAAECBAgQIECAAAECBAgQIECAAAECtxMQANzOyad6EBAA9IDukgQIECBAgAABAgQIECBAgAABAgQINCMgAGhmlG02cnBw/6qxk5PjMpm8arNJXREgQIAAAQIECBAgQIAAAQIECBAgQGAHAgKAHaA6ZXcCAoDuLJ2JAAECBAgQIECAAAECBAgQIECAAIFxCQgAxjXvwXUrABjcyBRMgAABAgQIECBAgAABAgQIECBAgEASAQFAkkEo43qBRQBQXwBcXwTsIECAAAECBAgQIECAAAECBAgQIECAAIHbCQgAbufkUz0JCAB6gndZAgQIECBAgAABAgQIECBAgAABAgQGLyAAGPwI225gEQDULufzy7ab1R0BAgQIECBAgAABAgQIECBAgAABAgQ6FBAAdIjpVN0LCAC6N3VGAgQIECBAgAABAgQIECBAgAABAgTGISAAGMecb+xyNjsv9Tn72Y7VAKC+AyBjjdnM1EOAAAECBAgQIECAAAECBAgQIECAAIEqIAAY8T6oX/qfnp6V+ufiODk5Ll9//TTNF+1HR98u6xMAjHizap0AAQIECBAgQIAAAQIECBAgQIAAga0FBABbkw1/wenpm/L69dnGRp4/f1YuLi7KdPq+14ZXA4AaTkwmr3qtx8UJECBAgAABAgQIECBAgAABAgQIECAwFAEBwFAm1WGdL19+V969+/FWZ/zqq7+WN2/+3tsdAQKAW43JhwgQIECAAAECBAgQIECAAAECBAgQIPB/AgKAEW6Kx4+/KR8+/PpJ5/XZ+quPAlr9z/p/k8lxLyGAAGCEG1TLBAgQIECAAAECBAgQIECAAAECBAh0IiAA6IRxWCdZfbFu/YX/L7/8Y9nApscD9fEM/tV6ahBRa3AQIECAAAECBAgQIECAAAECBAgQIECAwM0CAoCbjZr6RP2Vf/1V/eL43Jf6n3tM0L6fwy8AaGr7aYYAAQIECBAgQIAAAQIECBAgQIAAgT0KCAD2iJ3lUqt3AGz6Vf/n7gbYZwggAMiya9RBgAABAgQIECBAgAABAgQIECBAgMDQBAQAQ5tYB/WuBgA3fZnfdwggAOhg4E5BgAABAgQIECBAgAABAgQIECBAgMAoBQQAIxz7p1+qPynT6fuNCn2+F0AAMMINqmUCBAgQIECAAAECBAgQIECAAAECBDoREAB0wjisk6w+3//evXvljz/+fWMD9d0Bp6dnpf65fsznlzeuj36gvq9gcU0vAY4qWkeAAAECBAgQIECAAAECBAgQIECAwBgFBAAjnPpdflV/3d0Au/xiXgAwwg2qZQIECBAgQIAAAQIECBAgQIAAAQIEOhEQAHTCOKyTrH+Jv+0v+PcZAggAhrW3VEuAAAECBAgQIECAAAECBAgQIECAQB4BAUCeWeytkrvcAbAo8roQ4KYXCkcaFABE1KwhQIAAAQIECBAgQIAAAQIECBAgQIBAKQKAEe6Cu94BsCkEmE5/KvWRQF0dAoCuJJ2HAAECBAgQIECAAAECBAgQIECAAIGxCQgAxjbxUkpXAUClWz/Xl1/+pfz22786U10NAHZxh0FnhToRAQIECBAgQIAAAQIECBAgQIAAAQIEkgkIAJINZB/ldBkA1HpXv6Svf3/x4ll5+/b7TloRAHTC6CQECBAgQIAAAQIECBAgQIAAAQIECIxQQAAwwqF38Q6AdbYHDx6Vi4vfl//c1a/1Dw7uL8/Z9eOFRjh6LRMgQIAAAQIECBAgQIAAAQIECBAgMCIBAcCIhr1o9eXL78q7dz8uO5/PL++sMJudX90JsHp0EQI8fPiofPz4Z7DQxfnu3KgTECBAgAABAgQIECBAgAABAgQIECBAYCACAoCBDKrLMrt+BNCitl2EAKt3Fjx//qz88EM3jxbq0tO5CBAgQIAAAQIECBAgQIAAAQIECBAgkFFAAJBxKjuuaf2Z/V3cAbApBLjLo3u8A2DHm8HpCRAgQIAAAQIECBAgQIAAAQIECBBoVkAA0OxoP9/YLgOAetX1Owzqv0VDgNVaDw+fXp3HQYAAAQIECBAgQIAAAQIECBAgQIAAAQI3CwgAbjZq7hPrAUD0y/lNMF2FAO4AaG77aYgAAQIECBAgQIAAAQIECBAgQIAAgT0JCAD2BJ3pMvsIAGq/XYQAAoBMO0ctBAgQIECAAAECBAgQIECAAAECBAgMSUAAMKRpdVTrrh8BtFrmdSHANu8cEAB0NHSnIUCAAAECBAgQIECAAAECBAgQIEBgdAICgNGNvJR9BgCVdz0E2OZZ/gcH95cTOjk5LpPJqxFOTMsECBAgQIAAAQIECBAgQIAAAQIECBDYXkAAsL3Z4Fc8fPiofPz4+7KPXbwDYB0pGgKsBgD7qHPww9UAAQIECBAgQIAAAQIECBAgQIAAAQIE/icgABjhVnjw4FG5uNhvAFCZ10OAFy+elbdvv984AQHACDeolgkQIECAAAECBAgQIECAAAECBAgQ6ERAANAJ47BOsvqleq18n4/Wmc3Orx5BtDg2PQ5o/bPuABjWPlMtAQIECBAgQIAAAQIECBAgQIAAAQL9CggA+vXv5ep9BgC14etCgMnkuNQwYPUQAPSyPVyUAAECBAgQIECAAAECBAgQIECAAIFGBAQAjQxymzb6DgAWIcDp6dlVGLA4Dg+flOn0/fLv648McgfANlP2WQIECBAgQIAAAQIECBAgQIAAAQIExi4gABjhDsgQACzY6+OAPg0Bnpb6RX891uuczy9HOC0tEyBAgAABAgQIECBAgAABAgQIECBAICYgAIi5DXrV+hfrff+y/vHjb8qHD78uTes7Cerx+vXZ8t82vStg0MNQPAECBAgQIECAAAECBAgQIECAAAECBHYkIADYEWzm064+WifLF+vrIcC6X98hReZ5qo0AAQIECBAgQIAAAQIECBAgQIAAAQLXCQgARrovFo/dWX/xbp8c63cmLGrJElL0aePaBAgQIECAAAECBAgQIECAAAECBAgQ2FZAALCtmM/vTKCGEvWdAOuHX//vjNyJCRAgQIAAAQIECBAgQIAAAQIECBBoWEAA0PBwh9ra0dHfymz2z6vy/fp/qFNUNwECBAgQIECAAAECBAgQIECAAAECfQsIAPqegOtfK1DvBvj55/MymbwiRIAAAQIECBAgQIAAAQIECBAgQIAAAQIBAQFAAM0SAgQIECBAgAABAgQIECBAgAABAgQIECCQXUAAkH1C6iNAgAABAgQIECBAgAABAgQIECBAgAABAgEBAUAAzRICBAgQIECAAAECBAgQIECAAAECBAgQIJBdQACQfULqI0CAAAECBAgQIECAAAECBAgQIECAAAECAQEBQADNEgIECBAgQIAAAQIECBAgQIAAAQIECBAgkF1AAJB9QuojQIAAAQIECBAgQIAAAQIECBAgQIAAAQIBAQFAAM0SAgQIECBAgAABAgQIECBAgAABAgQIECCQXUAAkH1C6iNAgAABAgQIECBAgAABAgQIECBAgAABAgEBAUAAzRICBAgQIECAAAECBAgQIECAAAECBAgQIJBdQACQfULqI0CAAAECBAgQIECAAAECBAgQIECAAAECAQEBQADNEgIECBAgQIAAAQIECBAgQIAAAQIECBAgkF1AAJB9QuojQIAAAQIECBAgQIAAAQIECBAgQIAAAQIBAQFAAM0SAgQIECBAgAABAgQIECBAgAABAgQIECCQXUAAkH1C6iNAgAABAgQIECBAgAABAgQIECBAgAABAgEBAUAAzRICBAgQIECAAAECBAgQIECAAAECBAgQIJBdQACQfULqI0CAAAECBAgQIECAAAECBAgQIECAAAECAQEBQADNEgIECBAgQIAAAQIECBAgQIAAAQIECBAgkF1AAJB9QuojQIAAAQIECBAgQIAAAQIECBAgQIAAAQIBAQFAAM0SAgQIECBAgAABAgQIECBAgAABAgQIECCQXUAAkH1C6iNAgAABAgQIECBAgAABAgQIECBAgAABAgEBAUAAzRICBAgQIECAAAECBAgQIECAAAECBAgQIJBdQACQfULqI0CAAAECBAgQIECAAAECBAgQIECAAAECAQEBQADNEgIECBAgQIAAAQIECBAgQIAAAQIECBAgkF1AAJB9QuojQIAAAQIECBAgQIAAAQIECBAgQIAAAQIBAQFAAM0SAgQIECBAgAABAgQIECBAgAABAgQIECCQXUAAkH1C6iNAgAABAgQIECBAgAABAgQIECBAgAABAgEBAUAAzRICBAgQIECAAAECBAgQIECAAAECBAgQIJBdQACQfULqI0CAAAECBAgQIECAAAECBAgQIECAAAECAQEBQADNEgIECBAgQIAAAQIECBAgQIAAAQIECBAgkF1AAJB9QuojQIAAAQIECBAgQIAAAQIECBAgQIAAAQIBAQFAAM0SAgQIECBAgAABAgQIECBAgAABAgQIECCQXUAAkH1C6iNAgAABAgQIECBAgAABAgQIECBAgAABAgEBAUAAzRICBAgQIECAAAECBAgQIECAAAECBAgQIJBdQACQfULqI0CAAAECBAgQIECAAAECBAgQIECAAAECAQEBQADNEgIECBAgQIAAAQIECBAgQIAAAQIECBAgkF1AAJB9QuojQIAAAQIECBAgQIAAAQIECBAgQIAAAQIBAQFAAM0SAgQIECBAgAABAgQIECBAgAABAgQIECCQXUAAkH1C6iNAgAABAgQIECBAgAABAgQIECBAgAABAgEBAUAAzRICBAgQIECAAAECBAgQIECAAAECBAgQIJBdQACQfULqI0CAAAECBAgQIECAAAECBAgQIECAAAECAQEBQADNEgIECBAgQIAAAQIECBAgQIAAAQIECBAgkF1AAJB9QuojQIAAAQIECBAgQIAAAQIECBAgQIAAAQIBAQFAAM0SAgQIECBAgAABAgQIECBAgAABAgQIECCQXUAAkH1C6iNAgAABAgQIECBAgAABAgQIECBAgAABAgEBAUAAzRICBAgQIECAAAECBAgQIECAAAECBAgQIJBdQACQfULqI0CAAAECBAgQIECAAAECBAgQIECAAAECAQEBQADNEgIECBAgQIAAAQIECBAgQIAAAQIECBAgkF1AAJB9QuojQIAAAQIECBAgQIAAAQIECBAgQIAAAQIBAQFAAM0SAgQIECBAgAABAgQIECBAgAABAgQIECCQXUAAkH1C6iNAgAABAgQIECBAgAABAgQIECBAgAABAgEBAUAAzRICBAgQIECAAAECBAgQIECAAAECBAgQIJBdQACQfULqI0CAAAECBAgQIECAAAECBAgQIECAAAECAQEBQADNEgIECBAgQIAAAQIECBAgQIAAAQIECBAgkF1AAJB9QuojQIAAAQIECBAgQIAAAQIECBAgQIAAAQIBAQFAAM0SAgQIECBAgAABAgQIECBAgAABAgQIECCQXUAAkH1C6iNAgAABAgQIECBAgAABAgQIECBAgAABAgEBAUAAzRICBAgQIECAAAECBAgQIECAAAECBAgQIJBdQACQfULqI0CAAAECBAgQIECAAAECBAgQIECAAAECAQEBQADNEgIECBAgQIAAAQIECBAgQIAAAQIECBAgkF1AAJB9QuojQIAAAQIECBAgQIAAAQIECBAgQIAAAQIBAQFAAM0SAgQIECBAgAABAgQIECBAgAABAgQIECCQXUAAkH1C6iNAgAABAgQIECBAgAABAgQIECBAgAABAgEBAUAAzRICBAgQIECAAAECBAgQIECAAAECBAgQIJBdQACQfULqI0CAAAECBAgQIECAAAECBAgQIECAAAECAQEBQADNEgIECBAgQIAAAQIECBAgQIAAAQIECBAgkF1AAJB9QuojQIAAAQIECBAgQIAAAQIECBAgQIAAAQIBAQFAAM0SAgQIECBAgAABAgQIECBAgAABAgQIECCQXUAAkH1C6iNAgAABAgQIECBAgAABAgQIECBAgAABAgEBAUAAzRICBAgQIECAAAECBAgQIECAAAECBAgQIJBdQACQfULqI0CAAAECBAgQIECAAAECBAgQIECAAAECAQEBQADNEgIECBAgQIAAAQIECBAgQIAAAQIECBAgkF1AAJB9QuojQIAAAQIECBAgQIAAAQIECBAgQIAAAQIBAQFAAM0SAgQIECBAgAABAgQIECBAgAABAgQIECCQXUAAkH1C6iNAgAABAgQIECBAgAABAgQIECBAgAABAgEBAUAAzRICBAgQIECAAAECBAgQIECAAAECBAgQIJBdQACQfULqI0CAAAECBAgQIECAAAECBAgQIECAAAECAYH/AuX7sgv4DBgrAAAAAElFTkSuQmCC', + form1: { + form: '65ea3662705068f84a93c781', + owner: '65ea3601c3792e416cabcb2a', + roles: [], + access: [], + metadata: { + selectData: { + form1: { + data: { + select1: { + label: 'Sb', + }, }, - ], - submissionAccess: [ + }, + }, + timezone: 'America/Chicago', + offset: -360, + origin: 'http://localhost:3000', + referrer: '', + browserName: 'Netscape', + userAgent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36', + pathName: '/', + onLine: true, + headers: { + host: 'localhost:3000', + connection: 'keep-alive', + 'content-length': '9020', + pragma: 'no-cache', + 'cache-control': 'no-cache', + 'sec-ch-ua': + '"Chromium";v="122", "Not(A:Brand";v="24", "Brave";v="122"', + accept: 'application/json', + 'content-type': 'application/json', + 'sec-ch-ua-mobile': '?0', + 'user-agent': + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36', + 'sec-ch-ua-platform': '"macOS"', + 'sec-gpc': '1', + 'accept-language': 'en-US,en', + origin: 'http://localhost:3000', + 'sec-fetch-site': 'same-origin', + 'sec-fetch-mode': 'cors', + 'sec-fetch-dest': 'empty', + referer: 'http://localhost:3000/', + 'accept-encoding': 'gzip, deflate, br', + }, + }, + data: { + number: 23, + checkbox: true, + selectBoxes1: { + sbA: false, + sbB: true, + sbC: false, + sbD: false, + }, + select1: 'sb', + selectUrl: { + name: 'Alaska', + abbreviation: 'AK', + }, + radio1: 'ra', + email: 'travis@form.io', + url: 'google.com', + phoneNumber: '(234) 234-2342', + tags: 'test', + address: { + address_components: [ { - type: "create_own", - roles: [ - ], + long_name: '12342', + short_name: '12342', + types: ['street_number'], }, { - type: "create_all", - roles: [ - ], + long_name: 'Coit Road', + short_name: 'Coit Rd', + types: ['route'], }, { - type: "read_own", - roles: [ - ], + long_name: 'North Dallas', + short_name: 'North Dallas', + types: ['neighborhood', 'political'], }, { - type: "read_all", - roles: [ - ], + long_name: 'Dallas', + short_name: 'Dallas', + types: ['locality', 'political'], }, { - type: "update_own", - roles: [ - ], + long_name: 'Dallas County', + short_name: 'Dallas County', + types: ['administrative_area_level_2', 'political'], }, { - type: "update_all", - roles: [ - ], + long_name: 'Texas', + short_name: 'TX', + types: ['administrative_area_level_1', 'political'], }, { - type: "delete_own", - roles: [ - ], + long_name: 'United States', + short_name: 'US', + types: ['country', 'political'], }, { - type: "delete_all", - roles: [ - ], + long_name: '75243', + short_name: '75243', + types: ['postal_code'], }, { - type: "team_read", - roles: [ - ], + long_name: '2308', + short_name: '2308', + types: ['postal_code_suffix'], }, - { - type: "team_write", - roles: [ - ], + ], + formatted_address: '12342 Coit Rd, Dallas, TX 75243, USA', + geometry: { + location: { + lat: 32.9165814, + lng: -96.76889729999999, }, - { - type: "team_admin", - roles: [ - ], + viewport: { + south: 32.9151396697085, + west: -96.7703730302915, + north: 32.9178376302915, + east: -96.76767506970849, }, - ], - owner: { + }, + place_id: 'ChIJrbdWEhUgTIYRl5rVJe8Zl6A', + plus_code: { + compound_code: 'W68J+JC Dallas, TX, USA', + global_code: '8645W68J+JC', + }, + types: ['street_address'], + formattedPlace: '12342 Coit Rd, Dallas, TX 75243, USA', }, - components: [ - { - label: "Child", - tableView: true, - form: "65e74c2aef4451c9ede34105", - useOriginalRevision: false, - reference: false, - key: "child", - type: "form", - input: true, - components: [ - { - label: "Input", - applyMaskOn: "change", - tableView: true, - key: "input", - type: "textfield", - input: true, - }, - { - label: "Output", - applyMaskOn: "change", - disabled: true, - tableView: true, - calculateValue: "value = parseInt(data.input) * 5;", - calculateServer: true, - key: "output", - type: "textfield", - input: true, - }, - { - type: "button", - label: "Submit", - key: "submit", - disableOnInvalid: true, - input: true, - tableView: false, - }, - ], - }, - { - type: "button", - label: "Submit", - key: "submit", - disableOnInvalid: true, - input: true, - tableView: false, - }, + dateTime: '2024-03-14T17:00:00.000Z', + day: '03/23/2029', + time: '15:30:00 pm', + currency: 2, + signature: + 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABgAAAACWCAYAAAAYAUMPAAAAAXNSR0IArs4c6QAAFBBJREFUeF7t3bFuE1kUBuChTk2J8h4gU6ekXInnIAWu7YLUKXbrFJRs5xqLlFugFS9BRZF6V9cro6yJsePjMWfOfCOhFHjunPOdW/n3zDy5u/v6T+cgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIESgk8EQCUmqdmCBAgQIAAAQIECBAgQIAAAQIECBAgQIDASkAAYCMQIECAAAECBAgQIECAAAECBAgQIECAAIGCAgKAgkPVEgECBAgQIECAAAECBAgQIECAAAECBAgQEADYAwQIECBAgAABAgQIECBAgAABAgQIECBAoKCAAKDgULVEgAABAgQIECBAgAABAgQIECBAgAABAgQEAPYAAQIECBAgQIAAAQIECBAgQIAAAQIECBAoKCAAKDhULREgQIAAAQIECBAgQIAAAQIECBAgQIAAAQGAPUCAAAECBAgQIECAAAECBAgQIECAAAECBAoKCAAKDlVLBAgQIECAAAECBAgQIECAAAECBAgQIEBAAGAPECBAgAABAgQIECBAgAABAgQIECBAgACBggICgIJD1RIBAgQIECBAgAABAgQIECBAgAABAgQIEBAA2AMECBAgQIAAAQIECBAgQIAAAQIECBAgQKCggACg4FC1RIAAAQIECBAgQIAAAQIECBAgQIAAAQIEBAD2AAECBAgQIECAAAECBAgQIECAAAECBAgQKCggACg4VC0RIECAAAECBAgQIECAAAECBAgQIECAAAEBgD1AgAABAgQIECBAgAABAgQIECBAgAABAgQKCggACg5VSwQIECBAgAABAgQIECBAgAABAgQIECBAQABgDxAgQIAAAQIECBAgQIAAAQIECBAgQIAAgYICAoCCQ9USAQIECBAgQIAAAQIECBAgQIAAAQIECBAQANgDBAgQIECAAAECBAgQIECAAAECBAgQIECgoIAAoOBQtUSAAAECBAgQIECAAAECBAgQIECAAAECBAQA9gABAgQIECBAgAABAgQIECBAgAABAgQIECgoIAAoOFQtESBAgAABAgQIECBAgAABAgQIECBAgAABAYA9QIAAAQIECBAgQIAAAQIECBAgQIAAAQIECgoIAAoOVUsECBAgQIAAAQIECBAgQIAAAQIECBAgQEAAYA8QIECAAAECBAgQIECAAAECBAgQIECAAIGCAgKAgkPVEgECBAgQIECAAAECBAgQIECAAAECBAgQEADYAwQIECBAgAABAgQIECBAgAABAgQIECBAoKCAAKDgULVEgAABAgQIECBAgAABAgQIECBAgAABAgQEAPYAAQIECBAgQIAAAQIECBAgQIAAAQIECBAoKCAAKDhULREgQIAAAQIECBAgQIAAAQIECBAgQIAAAQGAPUCAAAECBAgQIECAAAECBAgQIECAAAECBAoKCAAKDlVLBAgQIECAAAECBAgQIECAAAECBAgQIEBAAGAPECBAgAABAgQIECBAgAABAgQIECBAgACBggICgIJD1RIBAgQIECBAgAABAgQIECBAgAABAgQIEBAA2AMECBAgQIAAAQIECBAgQIAAAQIECBAgQKCggACg4FC1RIAAAQIECBAgQIAAAQIECBAgQIAAAQIEBAD2AAECBAgQIECAAAECBAgQIECAAAECBAgQKCggACg4VC0RIECAAAECBAgQIECAAAECBAgQIECAAAEBgD1AgAABAgQIECBAgAABAgQIECBAgAABAgQKCggACg5VSwQIECBAgAABAgQIECBAgAABAgQIECBAQABgDxAgQIAAAQIECBAgQIAAAQIECBAgQIAAgYICAoCCQ9USAQIECBAgQIAAAQIECBAgQIAAAQIECBAQANgDBAgQIECAAAECBAgQIECAAAECBAgQIECgoIAAoOBQtUSAAAECBAgQIECAAAECBAgQIECAAAECBAQA9gABAgQIECBAgAABAgQIECBAgAABAgQIECgoIAAoOFQtESBAgAABAgQIECBAgAABAgQIECBAgAABAYA9QIAAAQIECBAgQIAAAQIECBAgQIAAAQIECgoIAAoOVUsECBAgQIAAAQIECBAgQIAAAQIECBAgQEAAYA8QIECAAAECBAgQIECAAAECBAgQIECAAIGCAgKAgkPVEgECBAgQIECAAAECBAgQIECAAAECBAgQEADYAwQIECBAgAABAgQIECBAgAABAgQIECBAoKCAAKDgULVEgAABAgQIECBAgAABAgQIECBAgAABAgQEAPYAAQIECBAgQIAAAQIECBAgQIAAAQIECBAoKCAAKDhULREgQIAAAQIECBAgQIAAAQIECBAgQIAAAQGAPUCAAAECBAgQIECAAAECBAgQIECAAAECBAoKCAAKDlVLBAgQIECAAAECBAgQIECAAAECBAgQIEBAAGAPECBAgAABAgQIECBAgAABAgQIECBAgACBggICgIJD1RIBAgQIECBAgAABAgQIECBAgAABAgQIEBAA2AMECBAgQIAAAQIECBAgQIAAAQIECBAgQKCggACg4FC1RIAAAQIECBAgQIAAAQIECBAgQIAAAQIEBAD2AAECBAgQIECAAAECBAgQIECAAAECBAgQKCggACg4VC0RIECAAAECBAgQIECAAAECBAgQIECAAAEBgD1AgAABAgQIECBAgAABAgQIECBAgAABAgQKCggACg5VSwQIECBAgAABAgQIECBAgAABAgQIECBAQABgD5xMYLn81H38+Kl7+fLF6m87lsvb1d/J5Pnq73R6ebJ6XIgAAQIECBAgQIAAAQIECBAgQIAAAQKVBQQAlaebqLfZ7F03n1/tVdFk8mIVCAgD9uLyIQIECBAgQIAAAQIECBAgQIAAAQIECDwoIACwMU4icHb29KDrrIOAFgo4CBAgQIAAAQIECBAgQIAAAQIECBAgQGB/AQHA/lY+eaBAe/TPxcWrA8/+77QWAEynb1Z/HQQIECBAgAABAgQIECBAgAABAgQIECCwW0AAsNvIJ44g8JhHAP3scoKAIwzDEgQIECBAgAABAgQIECBAgAABAgQIjEJAADCKMedosoUA66O9CHjzaC8Gbi8FbncM7DpaELBYfNj1Mf9PgAABAgQIECBAgAABAgQIECBAgACB0QoIAEY7+tyNt7Dg+vqP7tu3b1sLFQLknqHqCBAgQIAAAQIECBAgQIAAAQIECBD4tQICgF/r7+o7BNrdALPZ1da7As7Pn3VfvvzFkQABAgQIECBAgAABAgQIECBAgAABAgQ2BAQAtsQgBHYFAe1xQF4QPIhRKpIAAQIECBAgQIAAAQIECBAgQIAAgRMJCABOBO0yxxPY9kJhLwg+nrGVCBAgQIAAAQIECBAgQIAAAQIECBAYvoAAYPgzHGUH19e/dzc377vPn//+of+3b9900+nlKF00TYAAAQIECBAgQIAAAQIECBAgQIAAgbWAAMBeGLSAuwEGPT7FEyBAgAABAgQIECBAgAABAgQIECDQo4AAoEdcS59G4GfvB3A3wGlm4CoECBAgQIAAAQIECBAgQIAAAQIECOQTEADkm4mKDhRwN8CBcE4jQIAAAQIECBAgQIAAAQIECBAgQKCkgACg5FjH3dTFxauu3RWweXhJ8Lj3he4JECBAgAABAgQIECBAgAABAgQIjE1AADC2iY+kX48FGsmgtUmAAAECBAgQIECAAAECBAgQIECAwFYBAYDNUVogw2OBWg3L5W23WHwoba05AgQIECBAgAABAgQIECBAgAABAgRyCQgAcs1DNT0JbHssUN8vCW53IrRrt6Pva/VEZ1kCBAgQIECAAAECBAgQIECAAAECBAYqIAAY6OCU/XiBbY8F6vPdAPcDgHYddwE8fm7OIECAAAECBAgQIECAAAECBAgQIEDgMAEBwGFuzhqwwLYgoK9f6J+dPf2udXf3dcBySidAgAABAgQIECBAgAABAgQIECBAYEgCAoAhTUutRxV4KAjo426A+wFAuwOgXcNBgAABAgQIECBAgAABAgQIECBAgACBvgUEAH0LWz+9wENBwDHvBhAApN8CCiRAgAABAgQIECBAgAABAgQIECBQUkAAUHKsmjpEYDZ7183nV99PnUyed4vFn4cs9b9z7r+A+JjBQrgwCxAgQIAAAQIECBAgQIAAAQIECBAgUFpAAFB6vJo7RGAzCIh+ae8OgEOm4BwCBAgQIECAAAECBAgQIECAAAECBKICAoCooPPLCjwUBLRmp9PLvXveXMNLgPem80ECBAgQIECAAAECBAgQIECAAAECBIICAoAgoNNrC2x+gb/u9vz8Wff69W87w4D7v/5vL/9tLwF2ECBAgAABAgQIECBAgAABAgQIECBA4BQCAoBTKLvG4AW2BQHt8UDt2LwroL1Y+Obm/erf+mhf/rcQwEGAAAECBAgQIECAAAECBAgQIECAAIFTCAgATqHsGmUEWhDQvtxfLm9/6Gn95X77/83Dr//LbAGNECBAgAABAgQIECBAgAABAgQIEBiMgABgMKNSaCaBFgS0Yz6/2qssv/7fi8mHCBAgQIAAAQIECBAgQIAAAQIECBA4ooAA4IiYlhqfwD5BQHtM0GNeHDw+RR0TIECAAAECBAgQIECAAAECBAgQINCHgACgD1VrjlJg/egfz/kf5fg1TYAAAQIECBAgQIAAAQIECBAgQCCdgAAg3UgURIAAAQIECBAgQIAAAQIECBAgQIAAAQIE4gICgLihFQgQIECAAAECBAgQIECAAAECBAgQIECAQDoBAUC6kSiIAAECBAgQIECAAAECBAgQIECAAAECBAjEBQQAcUMrECBAgAABAgQIECBAgAABAgQIECBAgACBdAICgHQjURABAgQIECBAgAABAgQIECBAgAABAgQIEIgLCADihlYgQIAAAQIECBAgQIAAAQIECBAgQIAAAQLpBAQA6UaiIAIECBAgQIAAAQIECBAgQIAAAQIECBAgEBcQAMQNrUCAAAECBAgQIECAAAECBAgQIECAAAECBNIJCADSjURBBAgQIECAAAECBAgQIECAAAECBAgQIEAgLiAAiBtagQABAgQIECBAgAABAgQIECBAgAABAgQIpBMQAKQbiYIIECBAgAABAgQIECBAgAABAgQIECBAgEBcQAAQN7QCAQIECBAgQIAAAQIECBAgQIAAAQIECBBIJyAASDcSBREgQIAAAQIECBAgQIAAAQIECBAgQIAAgbiAACBuaAUCBAgQIECAAAECBAgQIECAAAECBAgQIJBOQACQbiQKIkCAAAECBAgQIECAAAECBAgQIECAAAECcQEBQNzQCgQIECBAgAABAgQIECBAgAABAgQIECBAIJ2AACDdSBREgAABAgQIECBAgAABAgQIECBAgAABAgTiAgKAuKEVCBAgQIAAAQIECBAgQIAAAQIECBAgQIBAOgEBQLqRKIgAAQIECBAgQIAAAQIECBAgQIAAAQIECMQFBABxQysQIECAAAECBAgQIECAAAECBAgQIECAAIF0AgKAdCNREAECBAgQIECAAAECBAgQIECAAAECBAgQiAsIAOKGViBAgAABAgQIECBAgAABAgQIECBAgAABAukEBADpRqIgAgQIECBAgAABAgQIECBAgAABAgQIECAQFxAAxA2tQIAAAQIECBAgQIAAAQIECBAgQIAAAQIE0gkIANKNREEECBAgQIAAAQIECBAgQIAAAQIECBAgQCAuIACIG1qBAAECBAgQIECAAAECBAgQIECAAAECBAikExAApBuJgggQIECAAAECBAgQIECAAAECBAgQIECAQFxAABA3tAIBAgQIECBAgAABAgQIECBAgAABAgQIEEgnIABINxIFESBAgAABAgQIECBAgAABAgQIECBAgACBuIAAIG5oBQIECBAgQIAAAQIECBAgQIAAAQIECBAgkE5AAJBuJAoiQIAAAQIECBAgQIAAAQIECBAgQIAAAQJxAQFA3NAKBAgQIECAAAECBAgQIECAAAECBAgQIEAgnYAAIN1IFESAAAECBAgQIECAAAECBAgQIECAAAECBOICAoC4oRUIECBAgAABAgQIECBAgAABAgQIECBAgEA6AQFAupEoiAABAgQIECBAgAABAgQIECBAgAABAgQIxAUEAHFDKxAgQIAAAQIECBAgQIAAAQIECBAgQIAAgXQCAoB0I1EQAQIECBAgQIAAAQIECBAgQIAAAQIECBCICwgA4oZWIECAAAECBAgQIECAAAECBAgQIECAAAEC6QQEAOlGoiACBAgQIECAAAECBAgQIECAAAECBAgQIBAXEADEDa1AgAABAgQIECBAgAABAgQIECBAgAABAgTSCQgA0o1EQQQIECBAgAABAgQIECBAgAABAgQIECBAIC4gAIgbWoEAAQIECBAgQIAAAQIECBAgQIAAAQIECKQTEACkG4mCCBAgQIAAAQIECBAgQIAAAQIECBAgQIBAXEAAEDe0AgECBAgQIECAAAECBAgQIECAAAECBAgQSCcgAEg3EgURIECAAAECBAgQIECAAAECBAgQIECAAIG4gAAgbmgFAgQIECBAgAABAgQIECBAgAABAgQIECCQTkAAkG4kCiJAgAABAgQIECBAgAABAgQIECBAgAABAnEBAUDc0AoECBAgQIAAAQIECBAgQIAAAQIECBAgQCCdgAAg3UgURIAAAQIECBAgQIAAAQIECBAgQIAAAQIE4gICgLihFQgQIECAAAECBAgQIECAAAECBAgQIECAQDoBAUC6kSiIAAECBAgQIECAAAECBAgQIECAAAECBAjEBQQAcUMrECBAgAABAgQIECBAgAABAgQIECBAgACBdAICgHQjURABAgQIECBAgAABAgQIECBAgAABAgQIEIgL/AseLFj8GyK16AAAAABJRU5ErkJggg==', + dataGrid: [ + { + checkbox: true, + select: '', + radio1: 'rb', + }, ], - settings: { + }, + _id: '65ea36dd705068f84a93c9c3', + _fvid: 1, + project: '65ea3620705068f84a93c694', + state: 'submitted', + externalIds: [], + created: '2024-03-07T21:51:25.110Z', + modified: '2024-03-07T21:51:25.110Z', + }, + parentNumber: 234, + }, + owner: '65ea3601c3792e416cabcb2a', + access: [], + metadata: { + timezone: 'America/Chicago', + offset: -360, + origin: 'http://localhost:3000', + referrer: '', + browserName: 'Netscape', + userAgent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36', + pathName: '/', + onLine: true, + headers: { + 'accept-language': 'en-US,en', + 'cache-control': 'no-cache', + connection: 'keep-alive', + origin: 'http://localhost:3000', + pragma: 'no-cache', + referer: 'http://localhost:3000/', + 'sec-fetch-dest': 'empty', + 'sec-fetch-mode': 'cors', + 'sec-fetch-site': 'same-origin', + 'sec-gpc': '1', + 'user-agent': + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36', + accept: 'application/json', + 'content-type': 'application/json', + 'sec-ch-ua': + '"Chromium";v="122", "Not(A:Brand";v="24", "Brave";v="122"', + 'sec-ch-ua-mobile': '?0', + 'sec-ch-ua-platform': '"macOS"', + host: 'localhost:3000', + 'accept-encoding': 'gzip, deflate, br', + 'content-length': '18055', + }, + }, + _vnote: '', + state: 'submitted', + form: '65ea368b705068f84a93c87a', + }; + + const errors: any = []; + const context = { + form, + submission, + data: submission.data, + components: form.components, + processors: ProcessTargets.submission, + scope: { errors }, + config: { + server: true, + }, + }; + processSync(context); + submission.data = context.data; + context.processors = ProcessTargets.evaluator; + processSync(context); + assert.equal(context.scope.errors.length, 0); + }); + it('should remove submission data not in a nested form definition', async function () { + const form = { + _id: {}, + title: 'parent', + name: 'parent', + type: 'form', + components: [ + { + type: 'checkbox', + label: 'A', + key: 'A', + input: true, + }, + { + type: 'checkbox', + label: 'B', + key: 'B', + input: true, + }, + { + key: 'child', + label: 'child', + form: 'child form', + type: 'form', + input: true, + reference: false, + clearOnHide: false, + components: [ + { + label: 'Input', + key: 'input', + type: 'textfield', + input: true, }, - properties: { + ], + }, + ], + }; + const submission = { + data: { + A: true, + B: true, + child: { + _id: 'submission id', + data: { + input: 'test', + invalid: 'invalid submission data', + }, + }, + }, + }; + const context = { + form, + submission, + data: submission.data, + components: form.components, + processors: ProcessTargets.submission, + scope: {}, + config: { + server: true, + }, + }; + processSync(context); + expect(context.data).to.deep.include({ + child: { _id: 'submission id', data: { input: 'test' } }, + }); + expect(context.data.child.data).to.not.have.property('invalid'); + }); + it('Should process nested form data correctly', async () => { + const submission = { + data: { + submit: true, + child: { + data: { + input: '4', + output: 200, + }, + }, + }, + owner: '65df88d8a98df60a25008300', + access: [], + metadata: { + headers: { + 'accept-language': 'en-US,en', + 'cache-control': 'no-cache', + connection: 'keep-alive', + origin: 'http://localhost:3000', + pragma: 'no-cache', + referer: 'http://localhost:3000/', + 'sec-fetch-dest': 'empty', + 'sec-fetch-mode': 'cors', + 'sec-fetch-site': 'same-origin', + 'sec-gpc': '1', + 'user-agent': + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36', + accept: 'application/json', + 'content-type': 'application/json', + 'sec-ch-ua': + '"Chromium";v="122", "Not(A:Brand";v="24", "Brave";v="122"', + 'sec-ch-ua-mobile': '?0', + 'sec-ch-ua-platform': '"macOS"', + host: 'localhost:3000', + 'accept-encoding': 'gzip, deflate, br', + 'content-length': '172', + }, + }, + form: '65e74c65ef4451c9ede341e3', + }; + const form = { + title: 'Parent Form', + name: 'parentForm', + path: 'parentform', + type: 'form', + display: 'form', + tags: [], + deleted: null, + access: [ + { + type: 'create_own', + roles: [], + }, + { + type: 'create_all', + roles: [], + }, + { + type: 'read_own', + roles: [], + }, + { + type: 'read_all', + roles: [{}, {}, {}], + }, + { + type: 'update_own', + roles: [], + }, + { + type: 'update_all', + roles: [], + }, + { + type: 'delete_own', + roles: [], + }, + { + type: 'delete_all', + roles: [], + }, + { + type: 'team_read', + roles: [], + }, + { + type: 'team_write', + roles: [], + }, + { + type: 'team_admin', + roles: [], + }, + ], + submissionAccess: [ + { + type: 'create_own', + roles: [], + }, + { + type: 'create_all', + roles: [], + }, + { + type: 'read_own', + roles: [], + }, + { + type: 'read_all', + roles: [], + }, + { + type: 'update_own', + roles: [], + }, + { + type: 'update_all', + roles: [], + }, + { + type: 'delete_own', + roles: [], + }, + { + type: 'delete_all', + roles: [], + }, + { + type: 'team_read', + roles: [], + }, + { + type: 'team_write', + roles: [], + }, + { + type: 'team_admin', + roles: [], + }, + ], + owner: {}, + components: [ + { + label: 'Child', + tableView: true, + form: '65e74c2aef4451c9ede34105', + useOriginalRevision: false, + reference: false, + key: 'child', + type: 'form', + input: true, + components: [ + { + label: 'Input', + applyMaskOn: 'change', + tableView: true, + key: 'input', + type: 'textfield', + input: true, }, - project: { + { + label: 'Output', + applyMaskOn: 'change', + disabled: true, + tableView: true, + calculateValue: 'value = parseInt(data.input) * 5;', + calculateServer: true, + key: 'output', + type: 'textfield', + input: true, }, - controller: "", - revisions: "", - submissionRevisions: "", - _vid: 0, - created: "2024-03-05T16:46:29.859Z", - modified: "2024-03-05T18:50:08.638Z", - machineName: "tzcuqutdtlpgicr:parentForm", - __v: 1, - config: { - appUrl: "http://localhost:3000/tzcuqutdtlpgicr", + { + type: 'button', + label: 'Submit', + key: 'submit', + disableOnInvalid: true, + input: true, + tableView: false, }, - }; - const context = { - form, - submission, - data: submission.data, - components: form.components, - processors: ProcessTargets.evaluator, - scope: {}, - config: { - server: true - } - }; - processSync(context); - expect(context.data.child.data.output).to.equal(20); - }); + ], + }, + { + type: 'button', + label: 'Submit', + key: 'submit', + disableOnInvalid: true, + input: true, + tableView: false, + }, + ], + settings: {}, + properties: {}, + project: {}, + controller: '', + revisions: '', + submissionRevisions: '', + _vid: 0, + created: '2024-03-05T16:46:29.859Z', + modified: '2024-03-05T18:50:08.638Z', + machineName: 'tzcuqutdtlpgicr:parentForm', + __v: 1, + config: { + appUrl: 'http://localhost:3000/tzcuqutdtlpgicr', + }, + }; + const context = { + form, + submission, + data: submission.data, + components: form.components, + processors: ProcessTargets.evaluator, + scope: {}, + config: { + server: true, + }, + }; + processSync(context); + expect(context.data.child.data.output).to.equal(20); + }); - it('Should process nested data correctly.', async () => { - const form = { - _id: { + it('Should process nested data correctly.', async () => { + const form = { + _id: {}, + title: 'parent', + name: 'parent', + path: 'parent', + type: 'form', + display: 'form', + tags: [], + deleted: null, + access: [ + { + type: 'read_all', + roles: [{}, {}, {}], + }, + ], + submissionAccess: [], + owner: {}, + components: [ + { + type: 'checkbox', + label: 'Show A', + key: 'showA', + input: true, + }, + { + type: 'checkbox', + label: 'Show B', + key: 'showB', + input: true, + }, + { + type: 'checkbox', + label: 'Show C', + key: 'showC', + input: true, + }, + { + type: 'form', + form: '65e8786fc5dacf667eef12d2', + label: 'Child A', + key: 'childA', + input: true, + conditional: { + show: true, + when: 'showA', + eq: true, + }, + components: [ + { + type: 'textfield', + label: 'A', + key: 'a', + validate: { + required: true, + }, + input: true, }, - title: "parent", - name: "parent", - path: "parent", - type: "form", - display: "form", - tags: [ - ], - deleted: null, - access: [ - { - type: "read_all", - roles: [ - { - }, - { - }, - { - }, - ], - }, - ], - submissionAccess: [ - ], - owner: { + { + type: 'textfield', + label: 'B', + key: 'b', + input: true, }, - components: [ - { - type: "checkbox", - label: "Show A", - key: "showA", - input: true, - }, - { - type: "checkbox", - label: "Show B", - key: "showB", - input: true, - }, - { - type: "checkbox", - label: "Show C", - key: "showC", - input: true, - }, - { - type: "form", - form: "65e8786fc5dacf667eef12d2", - label: "Child A", - key: "childA", - input: true, - conditional: { - show: true, - when: "showA", - eq: true, - }, - components: [ - { - type: "textfield", - label: "A", - key: "a", - validate: { - required: true, - }, - input: true, - }, - { - type: "textfield", - label: "B", - key: "b", - input: true, - }, - ], - }, - { - type: "form", - form: "65e8786fc5dacf667eef12e0", - label: "Child B", - key: "childB", - input: true, - conditional: { - show: true, - when: "showB", - eq: true, - }, - components: [ - { - type: "textfield", - label: "C", - key: "c", - input: true, - validate: { - required: true, - }, - }, - { - type: "textfield", - label: "D", - key: "d", - input: true, - }, - ], - }, - { - type: "form", - form: "65e8786fc5dacf667eef12ee", - label: "Child C", - key: "childC", - conditional: { - show: true, - when: "showC", - eq: true, - }, - input: true, - components: [ - { - type: "textfield", - label: "E", - key: "e", - input: true, - validate: { - required: true, - }, - }, - { - type: "textfield", - label: "F", - key: "f", - input: true, - }, - ], - }, - ], - created: "2024-03-06T14:06:39.724Z", - modified: "2024-03-06T14:06:39.726Z", - machineName: "parent", - __v: 0, - }; - const submission = { - data: { - showA: true, - showB: true, - showC: true, - childA: { - data: { - a: "One", - b: "Two", - }, - }, - childB: { - data: { - c: "Three", - d: "Four", - }, - }, - childC: { - data: { - e: "Five", - f: "Six", - }, - }, + ], + }, + { + type: 'form', + form: '65e8786fc5dacf667eef12e0', + label: 'Child B', + key: 'childB', + input: true, + conditional: { + show: true, + when: 'showB', + eq: true, + }, + components: [ + { + type: 'textfield', + label: 'C', + key: 'c', + input: true, + validate: { + required: true, + }, }, - owner: "65e87843c5dacf667eeeecc1", - access: [ - ], - metadata: { - headers: { - host: "127.0.0.1:64851", - "accept-encoding": "gzip, deflate", - "content-type": "application/json", - "content-length": "173", - connection: "close", - }, + { + type: 'textfield', + label: 'D', + key: 'd', + input: true, }, - form: "65e8786fc5dacf667eef12fc", - }; - - const errors: any = []; - const context = { - form, - submission, - data: submission.data, - components: form.components, - processors: ProcessTargets.submission, - scope: { errors }, - config: { - server: true - } - }; - processSync(context); - submission.data = context.data; - context.processors = ProcessTargets.evaluator; - processSync(context); - assert.equal(context.scope.errors.length, 0); - assert.equal(context.data.showA, true); - assert.equal(context.data.showB, true); - assert.equal(context.data.showC, true); - assert.deepEqual(context.data.childA.data, { + ], + }, + { + type: 'form', + form: '65e8786fc5dacf667eef12ee', + label: 'Child C', + key: 'childC', + conditional: { + show: true, + when: 'showC', + eq: true, + }, + input: true, + components: [ + { + type: 'textfield', + label: 'E', + key: 'e', + input: true, + validate: { + required: true, + }, + }, + { + type: 'textfield', + label: 'F', + key: 'f', + input: true, + }, + ], + }, + ], + created: '2024-03-06T14:06:39.724Z', + modified: '2024-03-06T14:06:39.726Z', + machineName: 'parent', + __v: 0, + }; + const submission = { + data: { + showA: true, + showB: true, + showC: true, + childA: { + data: { a: 'One', - b: 'Two' - }); - assert.deepEqual(context.data.childB.data, { + b: 'Two', + }, + }, + childB: { + data: { c: 'Three', - d: 'Four' - }); - assert.deepEqual(context.data.childC.data, { + d: 'Four', + }, + }, + childC: { + data: { e: 'Five', - f: 'Six' - }); + f: 'Six', + }, + }, + }, + owner: '65e87843c5dacf667eeeecc1', + access: [], + metadata: { + headers: { + host: '127.0.0.1:64851', + 'accept-encoding': 'gzip, deflate', + 'content-type': 'application/json', + 'content-length': '173', + connection: 'close', + }, + }, + form: '65e8786fc5dacf667eef12fc', + }; + + const errors: any = []; + const context = { + form, + submission, + data: submission.data, + components: form.components, + processors: ProcessTargets.submission, + scope: { errors }, + config: { + server: true, + }, + }; + processSync(context); + submission.data = context.data; + context.processors = ProcessTargets.evaluator; + processSync(context); + assert.equal(context.scope.errors.length, 0); + assert.equal(context.data.showA, true); + assert.equal(context.data.showB, true); + assert.equal(context.data.showC, true); + assert.deepEqual(context.data.childA.data, { + a: 'One', + b: 'Two', + }); + assert.deepEqual(context.data.childB.data, { + c: 'Three', + d: 'Four', }); + assert.deepEqual(context.data.childC.data, { + e: 'Five', + f: 'Six', + }); + }); - it('Should allow the submission to go through if the subform is conditionally hidden', async () => { - const form = { - _id: { + it('Should allow the submission to go through if the subform is conditionally hidden', async () => { + const form = { + _id: {}, + title: 'parent', + name: 'parent', + path: 'parent', + type: 'form', + display: 'form', + tags: [], + deleted: null, + access: [ + { + type: 'read_all', + roles: [{}, {}, {}], + }, + ], + submissionAccess: [], + owner: {}, + components: [ + { + type: 'checkbox', + label: 'Show A', + key: 'showA', + input: true, + }, + { + type: 'checkbox', + label: 'Show B', + key: 'showB', + input: true, + }, + { + type: 'checkbox', + label: 'Show C', + key: 'showC', + input: true, + }, + { + type: 'form', + form: '65e8786fc5dacf667eef12d2', + label: 'Child A', + key: 'childA', + input: true, + conditional: { + show: true, + when: 'showA', + eq: true, + }, + components: [ + { + type: 'textfield', + label: 'A', + key: 'a', + validate: { + required: true, + }, + input: true, }, - title: "parent", - name: "parent", - path: "parent", - type: "form", - display: "form", - tags: [ - ], - deleted: null, - access: [ - { - type: "read_all", - roles: [ - { - }, - { - }, - { - }, - ], - }, - ], - submissionAccess: [ - ], - owner: { + { + type: 'textfield', + label: 'B', + key: 'b', + input: true, }, - components: [ - { - type: "checkbox", - label: "Show A", - key: "showA", - input: true, - }, - { - type: "checkbox", - label: "Show B", - key: "showB", - input: true, - }, - { - type: "checkbox", - label: "Show C", - key: "showC", - input: true, - }, - { - type: "form", - form: "65e8786fc5dacf667eef12d2", - label: "Child A", - key: "childA", - input: true, - conditional: { - show: true, - when: "showA", - eq: true, - }, - components: [ - { - type: "textfield", - label: "A", - key: "a", - validate: { - required: true, - }, - input: true, - }, - { - type: "textfield", - label: "B", - key: "b", - input: true, - }, - ], - }, - { - type: "form", - form: "65e8786fc5dacf667eef12e0", - label: "Child B", - key: "childB", - input: true, - conditional: { - show: true, - when: "showB", - eq: true, - }, - components: [ - { - type: "textfield", - label: "C", - key: "c", - input: true, - validate: { - required: true, - }, - }, - { - type: "textfield", - label: "D", - key: "d", - input: true, - }, - ], - }, - { - type: "form", - form: "65e8786fc5dacf667eef12ee", - label: "Child C", - key: "childC", - conditional: { - show: true, - when: "showC", - eq: true, - }, - input: true, - components: [ - { - type: "textfield", - label: "E", - key: "e", - input: true, - validate: { - required: true, - }, - }, - { - type: "textfield", - label: "F", - key: "f", - input: true, - }, - ], - }, - ], - created: "2024-03-06T14:06:39.724Z", - modified: "2024-03-06T14:06:39.726Z", - machineName: "parent", - __v: 0, - }; - const submission = { - data: { - showA: false, - showB: true, - showC: true, - childB: { - data: { - c: 'Three', - d: 'Four' - } - }, - childC: { - data: { - e: 'Five', - f: 'Six' - } - } + ], + }, + { + type: 'form', + form: '65e8786fc5dacf667eef12e0', + label: 'Child B', + key: 'childB', + input: true, + conditional: { + show: true, + when: 'showB', + eq: true, + }, + components: [ + { + type: 'textfield', + label: 'C', + key: 'c', + input: true, + validate: { + required: true, + }, }, - owner: "65e87843c5dacf667eeeecc1", - access: [ - ], - metadata: { - headers: { - host: "127.0.0.1:64851", - "accept-encoding": "gzip, deflate", - "content-type": "application/json", - "content-length": "173", - connection: "close", - }, + { + type: 'textfield', + label: 'D', + key: 'd', + input: true, }, - form: "65e8786fc5dacf667eef12fc", - }; - - const errors: any = []; - const context = { - form, - submission, - data: submission.data, - components: form.components, - processors: ProcessTargets.submission, - scope: { errors }, - config: { - server: true - } - }; - processSync(context); - submission.data = context.data; - context.processors = ProcessTargets.evaluator; - processSync(context); - assert.equal(context.scope.errors.length, 0); - assert.equal(context.data.showA, false); - assert.equal(context.data.showB, true); - assert.equal(context.data.showC, true); - assert(!context.data.hasOwnProperty('childA'), 'The childA form should not be present.'); - assert.deepEqual(context.data.childB.data, { + ], + }, + { + type: 'form', + form: '65e8786fc5dacf667eef12ee', + label: 'Child C', + key: 'childC', + conditional: { + show: true, + when: 'showC', + eq: true, + }, + input: true, + components: [ + { + type: 'textfield', + label: 'E', + key: 'e', + input: true, + validate: { + required: true, + }, + }, + { + type: 'textfield', + label: 'F', + key: 'f', + input: true, + }, + ], + }, + ], + created: '2024-03-06T14:06:39.724Z', + modified: '2024-03-06T14:06:39.726Z', + machineName: 'parent', + __v: 0, + }; + const submission = { + data: { + showA: false, + showB: true, + showC: true, + childB: { + data: { c: 'Three', - d: 'Four' - }); - assert.deepEqual(context.data.childC.data, { + d: 'Four', + }, + }, + childC: { + data: { e: 'Five', - f: 'Six' - }); - }); + f: 'Six', + }, + }, + }, + owner: '65e87843c5dacf667eeeecc1', + access: [], + metadata: { + headers: { + host: '127.0.0.1:64851', + 'accept-encoding': 'gzip, deflate', + 'content-type': 'application/json', + 'content-length': '173', + connection: 'close', + }, + }, + form: '65e8786fc5dacf667eef12fc', + }; - it('Should process data within a fieldset properly.', async () => { - const submission = { - data: { - firstName: 'Joe', - lastName: 'Smith' - } - }; - const form = { - components: [ - { - type: 'fieldset', - key: 'name', - input: false, - components: [ - { - type: 'textfield', - key: 'firstName', - input: true - }, - { - type: 'textfield', - key: 'lastName', - input: true - } - ] - } - ] - }; - const context = { - form, - submission, - data: submission.data, - components: form.components, - processors: ProcessTargets.submission, - scope: {}, - config: { - server: true - } - }; - processSync(context); - expect(context.data.firstName).to.equal('Joe'); - expect(context.data.lastName).to.equal('Smith'); + const errors: any = []; + const context = { + form, + submission, + data: submission.data, + components: form.components, + processors: ProcessTargets.submission, + scope: { errors }, + config: { + server: true, + }, + }; + processSync(context); + submission.data = context.data; + context.processors = ProcessTargets.evaluator; + processSync(context); + assert.equal(context.scope.errors.length, 0); + assert.equal(context.data.showA, false); + assert.equal(context.data.showB, true); + assert.equal(context.data.showC, true); + assert( + !context.data.hasOwnProperty('childA'), + 'The childA form should not be present.' + ); + assert.deepEqual(context.data.childB.data, { + c: 'Three', + d: 'Four', + }); + assert.deepEqual(context.data.childC.data, { + e: 'Five', + f: 'Six', }); + }); - it('Requires a conditionally visible field', async () => { - const form = { - components: [ - { - "input": true, - "tableView": true, - "inputType": "radio", - "label": "Selector", - "key": "selector", - "values": [ - { - "value": "one", - "label": "One" - }, - { - "value": "two", - "label": "Two" - } - ], - "defaultValue": "", - "protected": false, - "persistent": true, - "validate": { - "required": false, - "custom": "", - "customPrivate": false - }, - "type": "radio", - "conditional": { - "show": "", - "when": null, - "eq": "" - } - }, - { - "input": true, - "tableView": true, - "inputType": "text", - "inputMask": "", - "label": "Required Field", - "key": "requiredField", - "placeholder": "", - "prefix": "", - "suffix": "", - "multiple": false, - "defaultValue": "", - "protected": false, - "unique": false, - "persistent": true, - "validate": { - "required": true, - "minLength": "", - "maxLength": "", - "pattern": "", - "custom": "", - "customPrivate": false - }, - "conditional": { - "show": "true", - "when": "selector", - "eq": "two" - }, - "type": "textfield" - } - ] - } + it('Should process data within a fieldset properly.', async () => { + const submission = { + data: { + firstName: 'Joe', + lastName: 'Smith', + }, + }; + const form = { + components: [ + { + type: 'fieldset', + key: 'name', + input: false, + components: [ + { + type: 'textfield', + key: 'firstName', + input: true, + }, + { + type: 'textfield', + key: 'lastName', + input: true, + }, + ], + }, + ], + }; + const context = { + form, + submission, + data: submission.data, + components: form.components, + processors: ProcessTargets.submission, + scope: {}, + config: { + server: true, + }, + }; + processSync(context); + expect(context.data.firstName).to.equal('Joe'); + expect(context.data.lastName).to.equal('Smith'); + }); - const submission = { data: { selector: 'two' } } - const errors: any = []; - const context = { - form, - submission, - data: submission.data, - components: form.components, - processors: ProcessTargets.evaluator, - scope: { errors }, - config: { - server: true - } - }; - processSync(context); - assert.equal(context.scope.errors.length, 1); - assert.equal(context.scope.errors[0].errorKeyOrMessage, 'required'); - assert.equal(context.scope.errors[0].context.path, 'requiredField'); - }); + it('Requires a conditionally visible field', async () => { + const form = { + components: [ + { + input: true, + tableView: true, + inputType: 'radio', + label: 'Selector', + key: 'selector', + values: [ + { + value: 'one', + label: 'One', + }, + { + value: 'two', + label: 'Two', + }, + ], + defaultValue: '', + protected: false, + persistent: true, + validate: { + required: false, + custom: '', + customPrivate: false, + }, + type: 'radio', + conditional: { + show: '', + when: null, + eq: '', + }, + }, + { + input: true, + tableView: true, + inputType: 'text', + inputMask: '', + label: 'Required Field', + key: 'requiredField', + placeholder: '', + prefix: '', + suffix: '', + multiple: false, + defaultValue: '', + protected: false, + unique: false, + persistent: true, + validate: { + required: true, + minLength: '', + maxLength: '', + pattern: '', + custom: '', + customPrivate: false, + }, + conditional: { + show: 'true', + when: 'selector', + eq: 'two', + }, + type: 'textfield', + }, + ], + }; - it('Doesn\'t require a conditionally hidden field', async () => { - const form = { - components: [ - { - "input": true, - "tableView": true, - "inputType": "radio", - "label": "Selector", - "key": "selector", - "values": [ - { - "value": "one", - "label": "One" - }, - { - "value": "two", - "label": "Two" - } - ], - "defaultValue": "", - "protected": false, - "persistent": true, - "validate": { - "required": false, - "custom": "", - "customPrivate": false - }, - "type": "radio", - "conditional": { - "show": "", - "when": null, - "eq": "" - } - }, - { - "input": true, - "tableView": true, - "inputType": "text", - "inputMask": "", - "label": "Required Field", - "key": "requiredField", - "placeholder": "", - "prefix": "", - "suffix": "", - "multiple": false, - "defaultValue": "", - "protected": false, - "unique": false, - "persistent": true, - "validate": { - "required": true, - "minLength": "", - "maxLength": "", - "pattern": "", - "custom": "", - "customPrivate": false - }, - "conditional": { - "show": "true", - "when": "selector", - "eq": "two" - }, - "type": "textfield" - } - ] - }; + const submission = { data: { selector: 'two' } }; + const errors: any = []; + const context = { + form, + submission, + data: submission.data, + components: form.components, + processors: ProcessTargets.evaluator, + scope: { errors }, + config: { + server: true, + }, + }; + processSync(context); + assert.equal(context.scope.errors.length, 1); + assert.equal(context.scope.errors[0].errorKeyOrMessage, 'required'); + assert.equal(context.scope.errors[0].context.path, 'requiredField'); + }); - const submission = { - data: { - selector: 'one' - } - }; + it("Doesn't require a conditionally hidden field", async () => { + const form = { + components: [ + { + input: true, + tableView: true, + inputType: 'radio', + label: 'Selector', + key: 'selector', + values: [ + { + value: 'one', + label: 'One', + }, + { + value: 'two', + label: 'Two', + }, + ], + defaultValue: '', + protected: false, + persistent: true, + validate: { + required: false, + custom: '', + customPrivate: false, + }, + type: 'radio', + conditional: { + show: '', + when: null, + eq: '', + }, + }, + { + input: true, + tableView: true, + inputType: 'text', + inputMask: '', + label: 'Required Field', + key: 'requiredField', + placeholder: '', + prefix: '', + suffix: '', + multiple: false, + defaultValue: '', + protected: false, + unique: false, + persistent: true, + validate: { + required: true, + minLength: '', + maxLength: '', + pattern: '', + custom: '', + customPrivate: false, + }, + conditional: { + show: 'true', + when: 'selector', + eq: 'two', + }, + type: 'textfield', + }, + ], + }; - const errors: any = []; - const context = { - form, - submission, - data: submission.data, - components: form.components, - processors: ProcessTargets.evaluator, - scope: { errors }, - config: { - server: true - } - }; - processSync(context); - assert.equal(context.scope.errors.length, 0); - }); + const submission = { + data: { + selector: 'one', + }, + }; - it('Allows a conditionally required field', async () => { - const form = { - components: [ - { - "input": true, - "tableView": true, - "inputType": "radio", - "label": "Selector", - "key": "selector", - "values": [ - { - "value": "one", - "label": "One" - }, - { - "value": "two", - "label": "Two" - } - ], - "defaultValue": "", - "protected": false, - "persistent": true, - "validate": { - "required": false, - "custom": "", - "customPrivate": false - }, - "type": "radio", - "conditional": { - "show": "", - "when": null, - "eq": "" - } - }, - { - "input": true, - "tableView": true, - "inputType": "text", - "inputMask": "", - "label": "Required Field", - "key": "requiredField", - "placeholder": "", - "prefix": "", - "suffix": "", - "multiple": false, - "defaultValue": "", - "protected": false, - "unique": false, - "persistent": true, - "validate": { - "required": true, - "minLength": "", - "maxLength": "", - "pattern": "", - "custom": "", - "customPrivate": false - }, - "conditional": { - "show": "true", - "when": "selector", - "eq": "two" - }, - "type": "textfield" - } - ] - }; + const errors: any = []; + const context = { + form, + submission, + data: submission.data, + components: form.components, + processors: ProcessTargets.evaluator, + scope: { errors }, + config: { + server: true, + }, + }; + processSync(context); + assert.equal(context.scope.errors.length, 0); + }); - const submission = { - data: { - selector: 'two', - requiredField: 'Has a value' - } - }; + it('Allows a conditionally required field', async () => { + const form = { + components: [ + { + input: true, + tableView: true, + inputType: 'radio', + label: 'Selector', + key: 'selector', + values: [ + { + value: 'one', + label: 'One', + }, + { + value: 'two', + label: 'Two', + }, + ], + defaultValue: '', + protected: false, + persistent: true, + validate: { + required: false, + custom: '', + customPrivate: false, + }, + type: 'radio', + conditional: { + show: '', + when: null, + eq: '', + }, + }, + { + input: true, + tableView: true, + inputType: 'text', + inputMask: '', + label: 'Required Field', + key: 'requiredField', + placeholder: '', + prefix: '', + suffix: '', + multiple: false, + defaultValue: '', + protected: false, + unique: false, + persistent: true, + validate: { + required: true, + minLength: '', + maxLength: '', + pattern: '', + custom: '', + customPrivate: false, + }, + conditional: { + show: 'true', + when: 'selector', + eq: 'two', + }, + type: 'textfield', + }, + ], + }; - const errors: any = []; - const context = { - form, - submission, - data: submission.data, - components: form.components, - processors: ProcessTargets.evaluator, - scope: { errors }, - config: { - server: true - } - }; - processSync(context); - assert.equal(context.scope.errors.length, 0); - }); + const submission = { + data: { + selector: 'two', + requiredField: 'Has a value', + }, + }; - it('Ignores conditionally hidden fields', async () => { - const form = { - components: [ - { - "input": true, - "tableView": true, - "inputType": "radio", - "label": "Selector", - "key": "selector", - "values": [ - { - "value": "one", - "label": "One" - }, - { - "value": "two", - "label": "Two" - } - ], - "defaultValue": "", - "protected": false, - "persistent": true, - "validate": { - "required": false, - "custom": "", - "customPrivate": false - }, - "type": "radio", - "conditional": { - "show": "", - "when": null, - "eq": "" - } - }, - { - "input": true, - "tableView": true, - "inputType": "text", - "inputMask": "", - "label": "Required Field", - "key": "requiredField", - "placeholder": "", - "prefix": "", - "suffix": "", - "multiple": false, - "defaultValue": "", - "protected": false, - "unique": false, - "persistent": true, - "validate": { - "required": true, - "minLength": "", - "maxLength": "", - "pattern": "", - "custom": "", - "customPrivate": false - }, - "conditional": { - "show": "true", - "when": "selector", - "eq": "two" - }, - "type": "textfield" - } - ] - }; + const errors: any = []; + const context = { + form, + submission, + data: submission.data, + components: form.components, + processors: ProcessTargets.evaluator, + scope: { errors }, + config: { + server: true, + }, + }; + processSync(context); + assert.equal(context.scope.errors.length, 0); + }); - const submission = { - data: { - selector: 'one', - requiredField: 'Has a value' - } - }; + it('Ignores conditionally hidden fields', async () => { + const form = { + components: [ + { + input: true, + tableView: true, + inputType: 'radio', + label: 'Selector', + key: 'selector', + values: [ + { + value: 'one', + label: 'One', + }, + { + value: 'two', + label: 'Two', + }, + ], + defaultValue: '', + protected: false, + persistent: true, + validate: { + required: false, + custom: '', + customPrivate: false, + }, + type: 'radio', + conditional: { + show: '', + when: null, + eq: '', + }, + }, + { + input: true, + tableView: true, + inputType: 'text', + inputMask: '', + label: 'Required Field', + key: 'requiredField', + placeholder: '', + prefix: '', + suffix: '', + multiple: false, + defaultValue: '', + protected: false, + unique: false, + persistent: true, + validate: { + required: true, + minLength: '', + maxLength: '', + pattern: '', + custom: '', + customPrivate: false, + }, + conditional: { + show: 'true', + when: 'selector', + eq: 'two', + }, + type: 'textfield', + }, + ], + }; - const errors: any = []; - const context = { - form, - submission, - data: submission.data, - components: form.components, - processors: ProcessTargets.evaluator, - scope: { errors }, - config: { - server: true - } - }; - processSync(context); - assert.deepEqual(context.data, { selector: 'one' }); - assert.equal(context.scope.errors.length, 0); - }); + const submission = { + data: { + selector: 'one', + requiredField: 'Has a value', + }, + }; - it('Requires a conditionally visible field in a panel', async () => { - const form = { - components: [ - { - "input": true, - "tableView": true, - "inputType": "radio", - "label": "Selector", - "key": "selector", - "values": [ - { - "value": "one", - "label": "One" - }, - { - "value": "two", - "label": "Two" - } - ], - "defaultValue": "", - "protected": false, - "persistent": true, - "validate": { - "required": false, - "custom": "", - "customPrivate": false - }, - "type": "radio", - "conditional": { - "show": "", - "when": null, - "eq": "" - } - }, - { - "input": false, - "title": "Panel", - "theme": "default", - "components": [ - { - "input": true, - "tableView": true, - "inputType": "text", - "inputMask": "", - "label": "Required Field", - "key": "requiredField", - "placeholder": "", - "prefix": "", - "suffix": "", - "multiple": false, - "defaultValue": "", - "protected": false, - "unique": false, - "persistent": true, - "validate": { - "required": true, - "minLength": "", - "maxLength": "", - "pattern": "", - "custom": "", - "customPrivate": false - }, - "conditional": { - "show": null, - "when": null, - "eq": "" - }, - "type": "textfield" - } - ], - "type": "panel", - "key": "panel", - "conditional": { - "show": "true", - "when": "selector", - "eq": "two" - } - } - ] - }; + const errors: any = []; + const context = { + form, + submission, + data: submission.data, + components: form.components, + processors: ProcessTargets.evaluator, + scope: { errors }, + config: { + server: true, + }, + }; + processSync(context); + assert.deepEqual(context.data, { selector: 'one' }); + assert.equal(context.scope.errors.length, 0); + }); - const submission = { - data: { - selector: 'two' - } - }; + it('Requires a conditionally visible field in a panel', async () => { + const form = { + components: [ + { + input: true, + tableView: true, + inputType: 'radio', + label: 'Selector', + key: 'selector', + values: [ + { + value: 'one', + label: 'One', + }, + { + value: 'two', + label: 'Two', + }, + ], + defaultValue: '', + protected: false, + persistent: true, + validate: { + required: false, + custom: '', + customPrivate: false, + }, + type: 'radio', + conditional: { + show: '', + when: null, + eq: '', + }, + }, + { + input: false, + title: 'Panel', + theme: 'default', + components: [ + { + input: true, + tableView: true, + inputType: 'text', + inputMask: '', + label: 'Required Field', + key: 'requiredField', + placeholder: '', + prefix: '', + suffix: '', + multiple: false, + defaultValue: '', + protected: false, + unique: false, + persistent: true, + validate: { + required: true, + minLength: '', + maxLength: '', + pattern: '', + custom: '', + customPrivate: false, + }, + conditional: { + show: null, + when: null, + eq: '', + }, + type: 'textfield', + }, + ], + type: 'panel', + key: 'panel', + conditional: { + show: 'true', + when: 'selector', + eq: 'two', + }, + }, + ], + }; - const errors: any = []; - const context = { - form, - submission, - data: submission.data, - components: form.components, - processors: ProcessTargets.evaluator, - scope: { errors }, - config: { - server: true - } - }; - processSync(context); - assert.equal(context.scope.errors.length, 1); - assert.equal(context.scope.errors[0].errorKeyOrMessage, 'required'); - assert.equal(context.scope.errors[0].context.path, 'requiredField'); - }); + const submission = { + data: { + selector: 'two', + }, + }; - it('Doesn\'t require a conditionally hidden field in a panel', async () => { - const form = { - components: [ - { - "input": true, - "tableView": true, - "inputType": "radio", - "label": "Selector", - "key": "selector", - "values": [ - { - "value": "one", - "label": "One" - }, - { - "value": "two", - "label": "Two" - } - ], - "defaultValue": "", - "protected": false, - "persistent": true, - "validate": { - "required": false, - "custom": "", - "customPrivate": false - }, - "type": "radio", - "conditional": { - "show": "", - "when": null, - "eq": "" - } - }, - { - "input": false, - "title": "Panel", - "theme": "default", - "components": [ - { - "input": true, - "tableView": true, - "inputType": "text", - "inputMask": "", - "label": "Required Field", - "key": "requiredField", - "placeholder": "", - "prefix": "", - "suffix": "", - "multiple": false, - "defaultValue": "", - "protected": false, - "unique": false, - "persistent": true, - "validate": { - "required": true, - "minLength": "", - "maxLength": "", - "pattern": "", - "custom": "", - "customPrivate": false - }, - "conditional": { - "show": null, - "when": null, - "eq": "" - }, - "type": "textfield" - } - ], - "type": "panel", - "key": "panel", - "conditional": { - "show": "true", - "when": "selector", - "eq": "two" - } - } - ] - }; + const errors: any = []; + const context = { + form, + submission, + data: submission.data, + components: form.components, + processors: ProcessTargets.evaluator, + scope: { errors }, + config: { + server: true, + }, + }; + processSync(context); + assert.equal(context.scope.errors.length, 1); + assert.equal(context.scope.errors[0].errorKeyOrMessage, 'required'); + assert.equal(context.scope.errors[0].context.path, 'requiredField'); + }); - const submission = { - data: { - selector: 'one' - } - }; + it("Doesn't require a conditionally hidden field in a panel", async () => { + const form = { + components: [ + { + input: true, + tableView: true, + inputType: 'radio', + label: 'Selector', + key: 'selector', + values: [ + { + value: 'one', + label: 'One', + }, + { + value: 'two', + label: 'Two', + }, + ], + defaultValue: '', + protected: false, + persistent: true, + validate: { + required: false, + custom: '', + customPrivate: false, + }, + type: 'radio', + conditional: { + show: '', + when: null, + eq: '', + }, + }, + { + input: false, + title: 'Panel', + theme: 'default', + components: [ + { + input: true, + tableView: true, + inputType: 'text', + inputMask: '', + label: 'Required Field', + key: 'requiredField', + placeholder: '', + prefix: '', + suffix: '', + multiple: false, + defaultValue: '', + protected: false, + unique: false, + persistent: true, + validate: { + required: true, + minLength: '', + maxLength: '', + pattern: '', + custom: '', + customPrivate: false, + }, + conditional: { + show: null, + when: null, + eq: '', + }, + type: 'textfield', + }, + ], + type: 'panel', + key: 'panel', + conditional: { + show: 'true', + when: 'selector', + eq: 'two', + }, + }, + ], + }; - const errors: any = []; - const context = { - form, - submission, - data: submission.data, - components: form.components, - processors: ProcessTargets.evaluator, - scope: { errors }, - config: { - server: true - } - }; - processSync(context); - assert.equal(context.scope.errors.length, 0); + const submission = { + data: { + selector: 'one', + }, + }; - }); + const errors: any = []; + const context = { + form, + submission, + data: submission.data, + components: form.components, + processors: ProcessTargets.evaluator, + scope: { errors }, + config: { + server: true, + }, + }; + processSync(context); + assert.equal(context.scope.errors.length, 0); + }); - it('Allows a conditionally required field in a panel', async () => { - const form = { - components: [ - { - "input": true, - "tableView": true, - "inputType": "radio", - "label": "Selector", - "key": "selector", - "values": [ - { - "value": "one", - "label": "One" - }, - { - "value": "two", - "label": "Two" - } - ], - "defaultValue": "", - "protected": false, - "persistent": true, - "validate": { - "required": false, - "custom": "", - "customPrivate": false - }, - "type": "radio", - "conditional": { - "show": "", - "when": null, - "eq": "" - } - }, - { - "input": false, - "title": "Panel", - "theme": "default", - "components": [ - { - "input": true, - "tableView": true, - "inputType": "text", - "inputMask": "", - "label": "Required Field", - "key": "requiredField", - "placeholder": "", - "prefix": "", - "suffix": "", - "multiple": false, - "defaultValue": "", - "protected": false, - "unique": false, - "persistent": true, - "validate": { - "required": true, - "minLength": "", - "maxLength": "", - "pattern": "", - "custom": "", - "customPrivate": false - }, - "conditional": { - "show": null, - "when": null, - "eq": "" - }, - "type": "textfield" - } - ], - "type": "panel", - "key": "panel", - "conditional": { - "show": "true", - "when": "selector", - "eq": "two" - } - } - ] - }; + it('Allows a conditionally required field in a panel', async () => { + const form = { + components: [ + { + input: true, + tableView: true, + inputType: 'radio', + label: 'Selector', + key: 'selector', + values: [ + { + value: 'one', + label: 'One', + }, + { + value: 'two', + label: 'Two', + }, + ], + defaultValue: '', + protected: false, + persistent: true, + validate: { + required: false, + custom: '', + customPrivate: false, + }, + type: 'radio', + conditional: { + show: '', + when: null, + eq: '', + }, + }, + { + input: false, + title: 'Panel', + theme: 'default', + components: [ + { + input: true, + tableView: true, + inputType: 'text', + inputMask: '', + label: 'Required Field', + key: 'requiredField', + placeholder: '', + prefix: '', + suffix: '', + multiple: false, + defaultValue: '', + protected: false, + unique: false, + persistent: true, + validate: { + required: true, + minLength: '', + maxLength: '', + pattern: '', + custom: '', + customPrivate: false, + }, + conditional: { + show: null, + when: null, + eq: '', + }, + type: 'textfield', + }, + ], + type: 'panel', + key: 'panel', + conditional: { + show: 'true', + when: 'selector', + eq: 'two', + }, + }, + ], + }; - const submission = { - data: { - selector: 'two', - requiredField: 'Has a value' - } - }; + const submission = { + data: { + selector: 'two', + requiredField: 'Has a value', + }, + }; - const errors: any = []; - const context = { - form, - submission, - data: submission.data, - components: form.components, - processors: ProcessTargets.evaluator, - scope: { errors }, - config: { - server: true - } - }; - processSync(context); - assert.equal(context.scope.errors.length, 0); + const errors: any = []; + const context = { + form, + submission, + data: submission.data, + components: form.components, + processors: ProcessTargets.evaluator, + scope: { errors }, + config: { + server: true, + }, + }; + processSync(context); + assert.equal(context.scope.errors.length, 0); + }); - }); + it('Ignores conditionally hidden fields in a panel', async () => { + const form = { + components: [ + { + input: true, + tableView: true, + inputType: 'radio', + label: 'Selector', + key: 'selector', + values: [ + { + value: 'one', + label: 'One', + }, + { + value: 'two', + label: 'Two', + }, + ], + defaultValue: '', + protected: false, + persistent: true, + validate: { + required: false, + custom: '', + customPrivate: false, + }, + type: 'radio', + conditional: { + show: '', + when: null, + eq: '', + }, + }, + { + input: false, + title: 'Panel', + theme: 'default', + components: [ + { + input: true, + tableView: true, + inputType: 'text', + inputMask: '', + label: 'Required Field', + key: 'requiredField', + placeholder: '', + prefix: '', + suffix: '', + multiple: false, + defaultValue: '', + protected: false, + unique: false, + persistent: true, + validate: { + required: true, + minLength: '', + maxLength: '', + pattern: '', + custom: '', + customPrivate: false, + }, + conditional: { + show: null, + when: null, + eq: '', + }, + type: 'textfield', + }, + ], + type: 'panel', + key: 'panel', + conditional: { + show: 'true', + when: 'selector', + eq: 'two', + }, + }, + ], + }; - it('Ignores conditionally hidden fields in a panel', async () => { - const form = { - components: [ - { - "input": true, - "tableView": true, - "inputType": "radio", - "label": "Selector", - "key": "selector", - "values": [ - { - "value": "one", - "label": "One" - }, - { - "value": "two", - "label": "Two" - } - ], - "defaultValue": "", - "protected": false, - "persistent": true, - "validate": { - "required": false, - "custom": "", - "customPrivate": false + const submission = { + data: { + selector: 'one', + requiredField: 'Has a value', + }, + }; + + const errors: any = []; + const context = { + form, + submission, + data: submission.data, + components: form.components, + processors: ProcessTargets.evaluator, + scope: { errors }, + config: { + server: true, + }, + }; + processSync(context); + assert.deepEqual(context.data, { selector: 'one' }); + assert.equal(context.scope.errors.length, 0); + }); + + it('Should not include submission data for conditionally hidden fields', async () => { + const form = { + display: 'form', + components: [ + { + type: 'textfield', + key: 'textField', + label: 'Text Field', + input: true, + }, + { + type: 'textarea', + key: 'textArea', + label: 'Text Area', + input: true, + conditional: { + show: false, + conjunction: 'all', + conditions: [ + { + component: 'textField', + operator: 'isEmpty', + }, + ], + }, + }, + ], + }; + + const submission = { + data: { + textField: '', + textArea: 'should not be in submission', + }, + }; + + const context = { + form, + submission, + data: submission.data, + components: form.components, + processors: ProcessTargets.evaluator, + scope: {}, + config: { + server: true, + }, + }; + processSync(context); + expect(context.data).to.deep.equal({ textField: '' }); + }); + + it('Should not include submission data for logically hidden fields', async () => { + const form = { + display: 'form', + components: [ + { + type: 'textfield', + key: 'textField', + label: 'Text Field', + input: true, + }, + { + type: 'textarea', + key: 'textArea', + label: 'Text Area', + input: true, + logic: [ + { + name: 'Hide When Empty', + trigger: { + type: 'simple' as const, + simple: { + show: true, + conjunction: 'all', + conditions: [ + { + component: 'textField', + operator: 'isEmpty', }, - "type": "radio", - "conditional": { - "show": "", - "when": null, - "eq": "" - } + ], }, - { - "input": false, - "title": "Panel", - "theme": "default", - "components": [ - { - "input": true, - "tableView": true, - "inputType": "text", - "inputMask": "", - "label": "Required Field", - "key": "requiredField", - "placeholder": "", - "prefix": "", - "suffix": "", - "multiple": false, - "defaultValue": "", - "protected": false, - "unique": false, - "persistent": true, - "validate": { - "required": true, - "minLength": "", - "maxLength": "", - "pattern": "", - "custom": "", - "customPrivate": false - }, - "conditional": { - "show": null, - "when": null, - "eq": "" - }, - "type": "textfield" - } - ], - "type": "panel", - "key": "panel", - "conditional": { - "show": "true", - "when": "selector", - "eq": "two" - } - } - ] - }; + }, + actions: [ + { + name: 'Hide', + type: 'property' as const, + property: { + label: 'Hidden', + value: 'hidden', + type: 'boolean' as const, + }, + state: true, + }, + ], + }, + ], + }, + ], + }; - const submission = { - data: { - selector: 'one', - requiredField: 'Has a value' - } - }; + const submission = { + data: { + textField: '', + textArea: 'should not be in submission', + }, + }; - const errors: any = []; - const context = { - form, - submission, - data: submission.data, - components: form.components, - processors: ProcessTargets.evaluator, - scope: { errors }, - config: { - server: true - } - }; - processSync(context); - assert.deepEqual(context.data, { selector: 'one' }); - assert.equal(context.scope.errors.length, 0); + const context = { + form, + submission, + data: submission.data, + components: form.components, + processors: ProcessTargets.evaluator, + scope: {}, + config: { + server: true, + }, + }; + processSync(context); + expect(context.data).to.deep.equal({ textField: '' }); + }); + + it('Should allow conditionally hidden text fields within DataGrid and EditGrids', async () => { + const form = { + display: 'form', + components: [ + { + label: 'Edit Grid', + tableView: false, + rowDrafts: false, + key: 'editGrid', + type: 'editgrid', + displayAsTable: false, + input: true, + components: [ + { + label: 'Select', + widget: 'choicesjs', + tableView: true, + data: { + values: [ + { + label: 'Action1', + value: 'action1', + }, + { + label: 'Custom', + value: 'custom', + }, + ], + }, + key: 'select', + type: 'select', + input: true, + }, + { + label: 'Text Field', + applyMaskOn: 'change', + tableView: true, + key: 'textField', + conditional: { + show: true, + conjunction: 'all', + conditions: [ + { + component: 'editGrid.select', + operator: 'isEqual', + value: 'custom', + }, + ], + }, + type: 'textfield', + input: true, + }, + ], + }, + { + type: 'button', + label: 'Submit', + key: 'submit', + disableOnInvalid: true, + input: true, + tableView: false, + }, + ], + }; + + const submission = { + data: { + editGrid: [ + { + select: 'action1', + }, + { + select: 'custom', + textField: 'TEST', + }, + ], + }, + }; + + const context = { + form, + submission, + data: submission.data, + components: form.components, + processors: ProcessTargets.evaluator, + scope: {}, + config: { + server: true, + }, + }; + processSync(context); + expect(context.data).to.deep.equal({ + editGrid: [ + { + select: 'action1', + }, + { + select: 'custom', + textField: 'TEST', + }, + ], }); + }); - it('Should not include submission data for conditionally hidden fields', async () => { - const form = { - display: 'form', - components: [ - { - type: 'textfield', - key: 'textField', - label: 'Text Field', - input: true, - }, - { - type: 'textarea', - key: 'textArea', - label: 'Text Area', - input: true, - conditional: { - show: false, - conjunction: 'all', - conditions: [ - { - component: 'textField', - operator: 'isEmpty' - } - ] - }, - } - ] - }; + describe('Required component validation in nested form in DataGrid/EditGrid', () => { + const nestedForm = { + key: 'form', + type: 'form', + input: true, + components: [ + { + key: 'textField', + type: 'textfield', + validate: { + required: true, + }, + input: true, + }, + ], + }; + describe('For DataGrid:', () => { + const components = [ + { + key: 'dataGrid', + type: 'datagrid', + input: true, + components: [nestedForm], + }, + ]; + it('Should validate required component when it is filled out', async () => { const submission = { - data: { - textField: '', - textArea: 'should not be in submission' - } + data: { + dataGrid: [ + { + form: { + data: { + textField: 'test', + invalidField: 'bad', + }, + }, + }, + { + invalidDataGridField: 'wrong', + }, + ], + }, }; const context = { - form, - submission, - data: submission.data, - components: form.components, - processors: ProcessTargets.evaluator, - scope: {}, - config: { - server: true - } + form: { components }, + submission, + data: submission.data, + components, + processors: ProcessTargets.submission, + scope: {}, + config: { + server: true, + }, }; processSync(context); - expect(context.data).to.deep.equal({ textField: '' }); - }); - - it('Should not include submission data for logically hidden fields', async () => { - const form = { - display: 'form', - components: [ - { - type: 'textfield', - key: 'textField', - label: 'Text Field', - input: true, - }, - { - type: 'textarea', - key: 'textArea', - label: 'Text Area', - input: true, - logic: [ - { - name: 'Hide When Empty', - trigger: { - type: 'simple' as const, - simple: { - show: true, - conjunction: 'all', - conditions: [ - { - component: 'textField', - operator: 'isEmpty', - }, - ], - }, - }, - actions: [ - { - name: 'Hide', - type: 'property' as const, - property: { - label: 'Hidden', - value: 'hidden', - type: 'boolean' as const, - }, - state: true, - }, - ], - }, - ] - } - ] - }; - + context.processors = ProcessTargets.evaluator; + processSync(context); + const errors = (context.scope as ValidationScope).errors; + expect(context.data).to.deep.equal({ + dataGrid: [{ form: { data: { textField: 'test' } } }], + }); + expect((context.scope as ValidationScope).errors).to.have.length(0); + expect; + }); + it('Should not validate required component when it is not filled out', async () => { const submission = { - data: { - textField: '', - textArea: 'should not be in submission' - } + data: { + dataGrid: [ + { + form: { + data: { + textField: '', + }, + }, + }, + ], + }, }; const context = { - form, - submission, - data: submission.data, - components: form.components, - processors: ProcessTargets.evaluator, - scope: {}, - config: { - server: true - } + form: { components }, + submission, + data: submission.data, + components, + processors: ProcessTargets.submission, + scope: {}, + config: { + server: true, + }, }; processSync(context); - expect(context.data).to.deep.equal({ textField: '' }); + context.processors = ProcessTargets.evaluator; + processSync(context); + expect((context.scope as ValidationScope).errors).to.have.length(1); + }); }); - - it('Should allow conditionally hidden text fields within DataGrid and EditGrids', async () => { - const form = { - display: 'form', - components: [ - { - "label": "Edit Grid", - "tableView": false, - "rowDrafts": false, - "key": "editGrid", - "type": "editgrid", - "displayAsTable": false, - "input": true, - "components": [ - { - "label": "Select", - "widget": "choicesjs", - "tableView": true, - "data": { - "values": [ - { - "label": "Action1", - "value": "action1" - }, - { - "label": "Custom", - "value": "custom" - } - ] - }, - "key": "select", - "type": "select", - "input": true - }, - { - "label": "Text Field", - "applyMaskOn": "change", - "tableView": true, - "key": "textField", - "conditional": { - "show": true, - "conjunction": "all", - "conditions": [ - { - "component": "editGrid.select", - "operator": "isEqual", - "value": "custom" - } - ] - }, - "type": "textfield", - "input": true - } - ] + describe('For EditGrid:', () => { + const components = [ + { + key: 'editGrid', + type: 'editgrid', + input: true, + components: [nestedForm], + }, + ]; + it('Should validate required component when it is filled out', async () => { + const submission = { + data: { + editGrid: [ + { + form: { + data: { + textField: 'test', + }, }, - { - "type": "button", - "label": "Submit", - "key": "submit", - "disableOnInvalid": true, - "input": true, - "tableView": false - } - ] + }, + ], + }, }; + const context = { + form: { components }, + submission, + data: submission.data, + components, + processors: ProcessTargets.submission, + scope: {}, + config: { + server: true, + }, + }; + processSync(context); + context.processors = ProcessTargets.evaluator; + processSync(context); + expect((context.scope as ValidationScope).errors).to.have.length(0); + }); + it('Should not validate required component when it is not filled out', async () => { const submission = { - data: { - editGrid: [ - { - "select": "action1" - }, - { - "select": "custom", - "textField": "TEST" - } - ] - } + data: { + editGrid: [ + { + form: { + data: { + textField: '', + }, + }, + }, + ], + }, }; const context = { - form, - submission, - data: submission.data, - components: form.components, - processors: ProcessTargets.evaluator, - scope: {}, - config: { - server: true - } + form: { components }, + submission, + data: submission.data, + components, + processors: ProcessTargets.submission, + scope: {}, + config: { + server: true, + }, }; processSync(context); - expect(context.data).to.deep.equal({ - "editGrid": [ - { - "select": "action1" - }, - { - "select": "custom", - "textField": "TEST" - } - ] - }); + context.processors = ProcessTargets.evaluator; + processSync(context); + expect((context.scope as ValidationScope).errors).to.have.length(1); + }); }); - /* + }); + /* it('Should not clearOnHide when set to false', async () => { var components = [ { diff --git a/src/process/filter/index.ts b/src/process/filter/index.ts index e8e489cf..29552076 100644 --- a/src/process/filter/index.ts +++ b/src/process/filter/index.ts @@ -1,61 +1,93 @@ -import { FilterContext, FilterScope, ProcessorFn, ProcessorFnSync, ProcessorInfo } from "types"; -import set from 'lodash/set'; -import { Utils } from "utils"; -import { get, isObject } from "lodash"; -import { getComponentAbsolutePath } from "utils/formUtil"; -export const filterProcessSync: ProcessorFnSync = (context: FilterContext) => { - const { scope, component } = context; - let { value } = context; - const absolutePath = getComponentAbsolutePath(component); - if (!scope.filter) scope.filter = {}; - if (value !== undefined) { - const modelType = Utils.getModelType(component); - switch (modelType) { - case 'dataObject': - scope.filter[absolutePath] = {data: {}}; - break; - case 'array': - scope.filter[absolutePath] = true; - break; - case 'object': - if (component.type !== 'container') { - scope.filter[absolutePath] = true; - } - break; - default: - scope.filter[absolutePath] = true; - break; +import { + FilterContext, + FilterScope, + ProcessorFn, + ProcessorFnSync, + ProcessorInfo, +} from 'types'; +import set from 'lodash/fp/set'; +import { Utils } from 'utils'; +import { get } from 'lodash'; +import { getComponentAbsolutePath } from 'utils/formUtil'; +export const filterProcessSync: ProcessorFnSync = ( + context: FilterContext +) => { + const { scope, component } = context; + let { value } = context; + const absolutePath = getComponentAbsolutePath(component); + + if (!scope.filter) scope.filter = {}; + if (value !== undefined) { + const modelType = Utils.getModelType(component); + switch (modelType) { + case 'dataObject': + scope.filter[absolutePath] = { + compModelType: modelType, + include: true, + value: { data: {} }, + }; + break; + case 'array': + scope.filter[absolutePath] = { + compModelType: modelType, + include: true, + }; + break; + case 'object': + if (component.type !== 'container') { + scope.filter[absolutePath] = { + compModelType: modelType, + include: true, + }; } + break; + default: + scope.filter[absolutePath] = { + compModelType: modelType, + include: true, + }; + break; } + } }; -export const filterProcess: ProcessorFn = async (context: FilterContext) => { - return filterProcessSync(context); +export const filterProcess: ProcessorFn = async ( + context: FilterContext +) => { + return filterProcessSync(context); }; -export const filterPostProcess: ProcessorFnSync = (context: FilterContext) => { - const { scope, submission } = context; - const filtered = {}; - for (const path in scope.filter) { - if (scope.filter[path]) { - let value = get(submission?.data, path); - if (isObject(value) && isObject(scope.filter[path])) { - if ((value as any).data) { - value = {...value, ...scope.filter[path], data: (value as any)?.data} - } else { - value = {...value, ...scope.filter[path]} - } - } - set(filtered, path, value); - } +export const filterPostProcess: ProcessorFnSync = ( + context: FilterContext +) => { + const { scope, component, submission } = context; + let filtered = {}; + for (const path in scope.filter) { + const pathFilter = scope.filter[path]; + if (pathFilter.compModelType === 'array') { + continue; + } + if (pathFilter) { + let value = get(submission?.data, path) as any; + + // when it's a dataModel Object, don't set values directly on the data object, let child fields do that. + // it can have extra data on updates, so pass all other values except data + // standard lodash set function will mutate original value, using the functional version so it doesn't + if (pathFilter.compModelType === 'dataObject') { + const { data, ...rest } = value; + filtered = set(path, rest)(filtered); + } else { + filtered = set(path, value)(filtered); + } } - context.data = filtered; + } + context.data = filtered; }; export const filterProcessInfo: ProcessorInfo = { - name: 'filter', - process: filterProcess, - processSync: filterProcessSync, - postProcess: filterPostProcess, - shouldProcess: (context: FilterContext) => true, + name: 'filter', + process: filterProcess, + processSync: filterProcessSync, + postProcess: filterPostProcess, + shouldProcess: (context: FilterContext) => true, }; diff --git a/src/process/process.ts b/src/process/process.ts index ea5af3cc..1eab42cc 100644 --- a/src/process/process.ts +++ b/src/process/process.ts @@ -1,120 +1,155 @@ -import get from "lodash/get"; -import set from "lodash/set"; -import { ProcessContext, ProcessTarget, ProcessorInfo, ProcessorScope } from "types"; -import { eachComponentData, eachComponentDataAsync } from "utils/formUtil"; +import get from 'lodash/get'; +import set from 'lodash/set'; +import { + ProcessContext, + ProcessTarget, + ProcessorInfo, + ProcessorScope, +} from 'types'; +import { eachComponentData, eachComponentDataAsync } from 'utils/formUtil'; import { processOne, processOneSync } from './processOne'; -import { defaultValueProcessInfo, serverDefaultValueProcessInfo, customDefaultValueProcessInfo } from "./defaultValue"; -import { fetchProcessInfo } from "./fetch"; -import { calculateProcessInfo } from "./calculation"; -import { logicProcessInfo } from "./logic"; -import { conditionProcessInfo, customConditionProcessInfo, simpleConditionProcessInfo } from "./conditions"; -import { validateCustomProcessInfo, validateProcessInfo, validateServerProcessInfo } from "./validation"; -import { filterProcessInfo } from "./filter"; -import { normalizeProcessInfo } from "./normalize"; -import { dereferenceProcessInfo } from "./dereference"; -import { clearHiddenProcessInfo } from "./clearHidden"; +import { + defaultValueProcessInfo, + serverDefaultValueProcessInfo, + customDefaultValueProcessInfo, +} from './defaultValue'; +import { fetchProcessInfo } from './fetch'; +import { calculateProcessInfo } from './calculation'; +import { logicProcessInfo } from './logic'; +import { + conditionProcessInfo, + customConditionProcessInfo, + simpleConditionProcessInfo, +} from './conditions'; +import { + validateCustomProcessInfo, + validateProcessInfo, + validateServerProcessInfo, +} from './validation'; +import { filterProcessInfo } from './filter'; +import { normalizeProcessInfo } from './normalize'; +import { dereferenceProcessInfo } from './dereference'; +import { clearHiddenProcessInfo } from './clearHidden'; -export async function process(context: ProcessContext): Promise { - const { instances, components, data, scope, flat, processors } = context; - await eachComponentDataAsync(components, data, async (component, compData, row, path, components, index) => { - // Skip processing if row is null or undefined - if (!row) { - return; - } - await processOne({...context, ...{ - data: compData, - component, - components, - path, - row, - index, - instance: instances ? instances[path] : undefined - }}); - if (flat) { - return true; - } - if ((scope as ProcessorScope).noRecurse) { - (scope as ProcessorScope).noRecurse = false; - return true; - } - }); - for (let i = 0; i < processors?.length; i++) { - const processor = processors[i]; - if (processor.postProcess) { - processor.postProcess(context); - } +export async function process( + context: ProcessContext +): Promise { + const { instances, components, data, scope, flat, processors } = context; + + await eachComponentDataAsync( + components, + data, + async (component, compData, row, path, components, index) => { + // Skip processing if row is null or undefined + if (!row) { + return; + } + await processOne({ + ...context, + ...{ + data: compData, + component, + components, + path, + row, + index, + instance: instances ? instances[path] : undefined, + }, + }); + if (flat) { + return true; + } + if ((scope as ProcessorScope).noRecurse) { + (scope as ProcessorScope).noRecurse = false; + return true; + } + } + ); + for (let i = 0; i < processors?.length; i++) { + const processor = processors[i]; + if (processor.postProcess) { + processor.postProcess(context); } - return scope; + } + return scope; } -export function processSync(context: ProcessContext): ProcessScope { - const { instances, components, data, scope, flat, processors } = context; - eachComponentData(components, data, (component, compData, row, path, components, index) => { - // Skip processing if row is null or undefined - if (!row) { - return; - } - processOneSync({...context, - data: compData, - component, - components, - path, - row, - index, - instance: instances ? instances[path] : undefined - }); - if (flat) { - return true; - } - if ((scope as ProcessorScope).noRecurse) { - (scope as ProcessorScope).noRecurse = false; - return true; - } - }); - for (let i = 0; i < processors?.length; i++) { - const processor = processors[i]; - if (processor.postProcess) { - processor.postProcess(context); - } +export function processSync( + context: ProcessContext +): ProcessScope { + const { instances, components, data, scope, flat, processors } = context; + + eachComponentData( + components, + data, + (component, compData, row, path, components, index) => { + // Skip processing if row is null or undefined + if (!row) { + return; + } + processOneSync({ + ...context, + data: compData, + component, + components, + path, + row, + index, + instance: instances ? instances[path] : undefined, + }); + if (flat) { + return true; + } + if ((scope as ProcessorScope).noRecurse) { + (scope as ProcessorScope).noRecurse = false; + return true; + } + } + ); + for (let i = 0; i < processors?.length; i++) { + const processor = processors[i]; + if (processor.postProcess) { + processor.postProcess(context); } - return scope; + } + return scope; } export const ProcessorMap: Record> = { - filter: filterProcessInfo, - defaultValue: defaultValueProcessInfo, - serverDefaultValue: serverDefaultValueProcessInfo, - customDefaultValue: customDefaultValueProcessInfo, - calculate: calculateProcessInfo, - conditions: conditionProcessInfo, - customConditions: customConditionProcessInfo, - simpleConditions: simpleConditionProcessInfo, - normalize: normalizeProcessInfo, - dereference: dereferenceProcessInfo, - clearHidden: clearHiddenProcessInfo, - fetch: fetchProcessInfo, - logic: logicProcessInfo, - validate: validateProcessInfo, - validateCustom: validateCustomProcessInfo, - validateServer: validateServerProcessInfo + filter: filterProcessInfo, + defaultValue: defaultValueProcessInfo, + serverDefaultValue: serverDefaultValueProcessInfo, + customDefaultValue: customDefaultValueProcessInfo, + calculate: calculateProcessInfo, + conditions: conditionProcessInfo, + customConditions: customConditionProcessInfo, + simpleConditions: simpleConditionProcessInfo, + normalize: normalizeProcessInfo, + dereference: dereferenceProcessInfo, + clearHidden: clearHiddenProcessInfo, + fetch: fetchProcessInfo, + logic: logicProcessInfo, + validate: validateProcessInfo, + validateCustom: validateCustomProcessInfo, + validateServer: validateServerProcessInfo, }; export const ProcessTargets: ProcessTarget = { - submission: [ - filterProcessInfo, - serverDefaultValueProcessInfo, - normalizeProcessInfo, - dereferenceProcessInfo, - fetchProcessInfo, - simpleConditionProcessInfo, - validateServerProcessInfo - ], - evaluator: [ - customDefaultValueProcessInfo, - calculateProcessInfo, - logicProcessInfo, - conditionProcessInfo, - clearHiddenProcessInfo, - validateProcessInfo - ] + submission: [ + filterProcessInfo, + serverDefaultValueProcessInfo, + normalizeProcessInfo, + dereferenceProcessInfo, + fetchProcessInfo, + simpleConditionProcessInfo, + validateServerProcessInfo, + ], + evaluator: [ + customDefaultValueProcessInfo, + calculateProcessInfo, + logicProcessInfo, + conditionProcessInfo, + clearHiddenProcessInfo, + validateProcessInfo, + ], }; diff --git a/src/process/validation/rules/validateMultiple.ts b/src/process/validation/rules/validateMultiple.ts index 8fc64db2..5ab343fa 100644 --- a/src/process/validation/rules/validateMultiple.ts +++ b/src/process/validation/rules/validateMultiple.ts @@ -1,95 +1,114 @@ import { isNil } from 'lodash'; import { FieldError } from 'error'; -import { Component, TextAreaComponent, RuleFn, TagsComponent, RuleFnSync, ValidationContext } from 'types'; +import { + Component, + TextAreaComponent, + RuleFn, + TagsComponent, + RuleFnSync, + ValidationContext, +} from 'types'; import { ProcessorInfo } from 'types/process/ProcessorInfo'; export const isEligible = (component: Component) => { - // TODO: would be nice if this was type safe - switch (component.type) { - case 'hidden': - case 'address': - if (!component.multiple) { - return false; - } - return true; - case 'textArea': - if (!(component as TextAreaComponent).as || (component as TextAreaComponent).as !== 'json') { - return false; - } - return true; - // TODO: For backwards compatibility, skip multiple validation for select components until we can investigate - // how this validation might break existing forms - case 'select': - return false; - default: - return true; - } -} + // TODO: would be nice if this was type safe + switch (component.type) { + case 'hidden': + case 'address': + if (!component.multiple) { + return false; + } + return true; + case 'textarea': + if ( + !(component as TextAreaComponent).as || + (component as TextAreaComponent).as !== 'json' + ) { + return false; + } + return true; + // TODO: For backwards compatibility, skip multiple validation for select components until we can investigate + // how this validation might break existing forms + case 'select': + return false; + default: + return true; + } +}; export const emptyValueIsArray = (component: Component) => { - // TODO: How do we infer the data model of the compoennt given only its JSON? For now, we have to hardcode component types - switch (component.type) { - case 'datagrid': - case 'editgrid': - case 'tagpad': - case 'sketchpad': - case 'datatable': - case 'dynamicWizard': - case 'file': - return true; - case 'select': - return !!component.multiple; - case 'tags': - return (component as TagsComponent).storeas !== 'string'; - default: - return false; - } -} + // TODO: How do we infer the data model of the compoennt given only its JSON? For now, we have to hardcode component types + switch (component.type) { + case 'datagrid': + case 'editgrid': + case 'tagpad': + case 'sketchpad': + case 'datatable': + case 'dynamicWizard': + case 'file': + return true; + case 'select': + return !!component.multiple; + case 'tags': + return (component as TagsComponent).storeas !== 'string'; + default: + return false; + } +}; export const shouldValidate = (context: ValidationContext) => { - const { component } = context; - if (!isEligible(component)) { - return false; - } - return true; + const { component } = context; + if (!isEligible(component)) { + return false; + } + return true; }; export const validateMultiple: RuleFn = async (context: ValidationContext) => { - return validateMultipleSync(context); + return validateMultipleSync(context); }; -export const validateMultipleSync: RuleFnSync = (context: ValidationContext) => { - const { component, value } = context; - // Skip multiple validation if the component tells us to - if (!isEligible(component)) { - return null; - } +export const validateMultipleSync: RuleFnSync = ( + context: ValidationContext +) => { + const { component, value } = context; + // Skip multiple validation if the component tells us to + if (!isEligible(component)) { + return null; + } - const shouldBeArray = !!component.multiple; - const isRequired = !!component.validate?.required; - const isArray = Array.isArray(value); + const shouldBeArray = !!component.multiple; + const isRequired = !!component.validate?.required; + const isArray = Array.isArray(value); - if (shouldBeArray) { - if (isArray) { - return isRequired ? value.length > 0 ? null : new FieldError('array_nonempty', {...context, setting: true}): null; - } else { - const error = new FieldError('array', {...context, setting: true}); - // Null/undefined is ok if this value isn't required; anything else should fail - return isNil(value) ? isRequired ? error : null : error; - } + if (shouldBeArray) { + if (isArray) { + return isRequired + ? value.length > 0 + ? null + : new FieldError('array_nonempty', { ...context, setting: true }) + : null; } else { - const canBeArray = emptyValueIsArray(component); - if (!canBeArray && isArray) { - return new FieldError('nonarray', {...context, setting: false}); - } - return null; + const error = new FieldError('array', { ...context, setting: true }); + // Null/undefined is ok if this value isn't required; anything else should fail + return isNil(value) ? (isRequired ? error : null) : error; } -} + } else { + const canBeArray = emptyValueIsArray(component); + if (!canBeArray && isArray) { + return new FieldError('nonarray', { ...context, setting: false }); + } + return null; + } +}; -export const validateMultipleInfo: ProcessorInfo = { - name: 'validateMultiple', - process: validateMultiple, - fullValue: true, - processSync: validateMultipleSync, - shouldProcess: shouldValidate, +export const validateMultipleInfo: ProcessorInfo< + ValidationContext, + FieldError | null +> = { + name: 'validateMultiple', + process: validateMultiple, + fullValue: true, + processSync: validateMultipleSync, + shouldProcess: shouldValidate, }; diff --git a/src/utils/formUtil.ts b/src/utils/formUtil.ts index 7ccf4eac..75a392ce 100644 --- a/src/utils/formUtil.ts +++ b/src/utils/formUtil.ts @@ -579,13 +579,13 @@ export function getComponentActualValue(component: Component, compPath: string, let value = null; if (row) { - value = get(row, compPath); + value = get(row, compPath); } if (data && isNil(value)) { - value = get(data, compPath); + value = get(data, compPath); } if (isNil(value) || (isObject(value) && isEmpty(value))) { - value = ''; + value = ''; } return value; } @@ -945,20 +945,20 @@ export function generateFormChange(type: any, data: any) { export function applyFormChanges(form: any, changes: any) { const failed: any = []; - changes.forEach(function(change: any) { + changes.forEach(function (change: any) { var found = false; switch (change.op) { case 'add': var newComponent = change.component; // Find the container to set the component in. - findComponent(form.components, change.container, null, function(parent: any) { + findComponent(form.components, change.container, null, function (parent: any) { if (!change.container) { parent = form; } // A move will first run an add so remove any existing components with matching key before inserting. - findComponent(form.components, change.key, null, function(component: any, path: any) { + findComponent(form.components, change.key, null, function (component: any, path: any) { // If found, use the existing component. (If someone else edited it, the changes would be here) newComponent = component; removeComponent(form.components, path); @@ -970,7 +970,7 @@ export function applyFormChanges(form: any, changes: any) { }); break; case 'remove': - findComponent(form.components, change.key, null, function(component: any, path: any) { + findComponent(form.components, change.key, null, function (component: any, path: any) { found = true; const oldComponent = get(form.components, path); if (oldComponent.key !== component.key) { @@ -980,7 +980,7 @@ export function applyFormChanges(form: any, changes: any) { }); break; case 'edit': - findComponent(form.components, change.key, null, function(component: any, path: any) { + findComponent(form.components, change.key, null, function (component: any, path: any) { found = true; try { const oldComponent = get(form.components, path); @@ -1011,18 +1011,18 @@ export function applyFormChanges(form: any, changes: any) { }; } - /** - * This function will find a component in a form and return the component AND THE PATH to the component in the form. - * Path to the component is stored as an array of nested components and their indexes.The Path is being filled recursively - * when you iterating through the nested structure. - * If the component is not found the callback won't be called and function won't return anything. - * - * @param components - * @param key - * @param fn - * @param path - * @returns {*} - */ +/** +* This function will find a component in a form and return the component AND THE PATH to the component in the form. +* Path to the component is stored as an array of nested components and their indexes.The Path is being filled recursively +* when you iterating through the nested structure. +* If the component is not found the callback won't be called and function won't return anything. +* +* @param components +* @param key +* @param fn +* @param path +* @returns {*} +*/ export function findComponent(components: any, key: any, path: any, fn: any) { if (!components) return; path = path || []; @@ -1031,7 +1031,7 @@ export function findComponent(components: any, key: any, path: any, fn: any) { return fn(components); } - components.forEach(function(component: any, index: any) { + components.forEach(function (component: any, index: any) { var newPath = path.slice(); // Add an index of the component it iterates through in nested structure newPath.push(index); @@ -1039,7 +1039,7 @@ export function findComponent(components: any, key: any, path: any, fn: any) { if (component.hasOwnProperty('columns') && Array.isArray(component.columns)) { newPath.push('columns'); - component.columns.forEach(function(column: any, index: any) { + component.columns.forEach(function (column: any, index: any) { var colPath = newPath.slice(); colPath.push(index); colPath.push('components'); @@ -1049,10 +1049,10 @@ export function findComponent(components: any, key: any, path: any, fn: any) { if (component.hasOwnProperty('rows') && Array.isArray(component.rows)) { newPath.push('rows'); - component.rows.forEach(function(row: any, index: any) { + component.rows.forEach(function (row: any, index: any) { var rowPath = newPath.slice(); rowPath.push(index); - row.forEach(function(column: any, index: any) { + row.forEach(function (column: any, index: any) { var colPath = rowPath.slice(); colPath.push(index); colPath.push('components'); @@ -1084,20 +1084,20 @@ const isTextAreaComponent = (component: Component): component is TextAreaCompone const isTextFieldComponent = (component: Component): component is TextFieldComponent => component.type === 'textfield'; export function getEmptyValue(component: Component) { - switch (component.type) { - case 'textarea': - case 'textfield': - case 'time': - case 'datetime': - case 'day': - return ''; - case 'datagrid': - case 'editgrid': - return []; - - default: - return null; - } + switch (component.type) { + case 'textarea': + case 'textfield': + case 'time': + case 'datetime': + case 'day': + return ''; + case 'datagrid': + case 'editgrid': + return []; + + default: + return null; + } } const replaceBlanks = (value: unknown) => { @@ -1109,17 +1109,17 @@ const replaceBlanks = (value: unknown) => { }; function trimBlanks(value: unknown) { - if (!value) { - return value; - } - - if (Array.isArray(value)) { - value = value.map((val: any) => replaceBlanks(val)); - } - else { - value = replaceBlanks(value); - } + if (!value) { return value; + } + + if (Array.isArray(value)) { + value = value.map((val: any) => replaceBlanks(val)); + } + else { + value = replaceBlanks(value); + } + return value; } function isValueEmpty(component: Component, value: any) { @@ -1128,42 +1128,42 @@ function isValueEmpty(component: Component, value: any) { } export function isComponentDataEmpty(component: Component, data: any, path: string): boolean { - const value = get(data, path); - if (isCheckboxComponent(component)) { - return isValueEmpty(component, value) || value === false; - } else if (isDataGridComponent(component) || isEditGridComponent(component) || isDataTableComponent(component) || hasChildComponents(component)) { - if (component.components?.length) { - let childrenEmpty = true; - // wrap component in an array to let eachComponentData handle introspection to child components (e.g. this will be different - // for data grids versus nested forms, etc.) - eachComponentData([component], data, (thisComponent, data, row, path, components, index) => { - if (component.key === thisComponent.key) return; - if (!isComponentDataEmpty(thisComponent, data, path)) { - childrenEmpty = false; - } - }); - return isValueEmpty(component, value) || childrenEmpty; - } - return isValueEmpty(component, value); - } else if (isDateTimeComponent(component)) { - return isValueEmpty(component, value) || value.toString() === 'Invalid date'; - } else if (isSelectBoxesComponent(component)) { - let selectBoxEmpty = true; - for (const key in value) { - if (value[key]) { - selectBoxEmpty = false; - break; - } + const value = get(data, path); + if (isCheckboxComponent(component)) { + return isValueEmpty(component, value) || value === false; + } else if (isDataGridComponent(component) || isEditGridComponent(component) || isDataTableComponent(component) || hasChildComponents(component)) { + if (component.components?.length) { + let childrenEmpty = true; + // wrap component in an array to let eachComponentData handle introspection to child components (e.g. this will be different + // for data grids versus nested forms, etc.) + eachComponentData([component], data, (thisComponent, data, row, path, components, index) => { + if (component.key === thisComponent.key) return; + if (!isComponentDataEmpty(thisComponent, data, path)) { + childrenEmpty = false; } - return isValueEmpty(component, value) || selectBoxEmpty; - } else if (isTextAreaComponent(component)) { - const isPlain = !component.wysiwyg && !component.editor; - return isPlain ? typeof value === 'string' ? isValueEmpty(component, value.trim()) : isValueEmpty(component, value) : isValueEmpty(component, trimBlanks(value)); - } else if (isTextFieldComponent(component)) { - if (component.allowMultipleMasks && !!component.inputMasks && !!component.inputMasks.length) { - return isValueEmpty(component, value) || (component.multiple ? value.length === 0 : (!value.maskName || !value.value)); - } - return isValueEmpty(component, value?.toString().trim()); + }); + return isValueEmpty(component, value) || childrenEmpty; } return isValueEmpty(component, value); + } else if (isDateTimeComponent(component)) { + return isValueEmpty(component, value) || value.toString() === 'Invalid date'; + } else if (isSelectBoxesComponent(component)) { + let selectBoxEmpty = true; + for (const key in value) { + if (value[key]) { + selectBoxEmpty = false; + break; + } + } + return isValueEmpty(component, value) || selectBoxEmpty; + } else if (isTextAreaComponent(component)) { + const isPlain = !component.wysiwyg && !component.editor; + return isPlain ? typeof value === 'string' ? isValueEmpty(component, value.trim()) : isValueEmpty(component, value) : isValueEmpty(component, trimBlanks(value)); + } else if (isTextFieldComponent(component)) { + if (component.allowMultipleMasks && !!component.inputMasks && !!component.inputMasks.length) { + return isValueEmpty(component, value) || (component.multiple ? value.length === 0 : (!value.maskName || !value.value)); + } + return isValueEmpty(component, value?.toString().trim()); + } + return isValueEmpty(component, value); }