Skip to content

Commit

Permalink
Changing the path strategy to not require stateful objects on compone…
Browse files Browse the repository at this point in the history
…nt object.
  • Loading branch information
travist committed Nov 15, 2024
1 parent df110d2 commit 53e9daf
Show file tree
Hide file tree
Showing 20 changed files with 171 additions and 294 deletions.
39 changes: 17 additions & 22 deletions src/Webform.js
Original file line number Diff line number Diff line change
Expand Up @@ -660,6 +660,7 @@ export default class Webform extends NestedDataComponent {
try {
// Do not set the form again if it has been already set
if (isFormAlreadySet && JSON.stringify(this._form) === JSON.stringify(form)) {
this.formReadyResolve();
return Promise.resolve();
}

Expand All @@ -671,11 +672,13 @@ export default class Webform extends NestedDataComponent {
}

if (this.parent?.component?.modalEdit) {
this.formReadyResolve();
return Promise.resolve();
}
} catch (err) {
console.warn(err);
// If provided form is not a valid JSON object, do not set it too
this.formReadyReject(err);
return Promise.resolve();
}

Expand Down Expand Up @@ -1274,32 +1277,24 @@ export default class Webform extends NestedDataComponent {
}

// Mark any components as invalid if in a custom message.
const componentErrors = {};
errors.forEach((err) => {
const { components = [] } = err;
if (err.component) {
components.push(err.component);
const path = err.path || err.context?.path || err.component?.key;
if (!componentErrors[path]) {
componentErrors[path] = [];
}
componentErrors[path].push(err);
});

if (err.path) {
components.push(err.path);
// Iterate through all of our component errors and apply them to the components.
for (let path in componentErrors) {
const component = this.getComponent(path);
const errors = componentErrors[path];
if (component) {
component.serverErrors = errors.filter((err) => err.fromServer);
component.setCustomValidity(errors, true)
}

components.forEach((path) => {
const originalPath = getStringFromComponentPath(path);
const component = this.getComponent(path, _.identity, originalPath);

if (err.fromServer) {
if (component.serverErrors) {
component.serverErrors.push(err);
} else {
component.serverErrors = [err];
}
}
const components = _.compact(Array.isArray(component) ? component : [component]);

components.forEach((component) => component.setCustomValidity(err.message, true));
});
});
}

const displayedErrors = [];
if (errors.length) {
Expand Down
2 changes: 1 addition & 1 deletion src/Wizard.js
Original file line number Diff line number Diff line change
Expand Up @@ -1080,7 +1080,7 @@ export default class Wizard extends Webform {
errors = [...errors, ...subWizard.errors]
}
})
};
}
return super.showErrors(errors, triggerEvent)
}

Expand Down
38 changes: 18 additions & 20 deletions src/components/_classes/component/Component.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
/* globals Quill, ClassicEditor, CKEDITOR */
import { conformToMask } from '@formio/vanilla-text-mask';
import { Utils } from '@formio/core/utils';
const { componentPath, COMPONENT_PATH, setComponentScope, setDefaultComponentPaths, setParentReference } = Utils;
import tippy from 'tippy.js';
import _ from 'lodash';
import isMobile from 'ismobilejs';
Expand All @@ -10,7 +8,7 @@ import { processOne, processOneSync, validateProcessInfo } from '@formio/core/pr
import { Formio } from '../../../Formio';
import * as FormioUtils from '../../../utils/utils';
import {
fastCloneDeep, boolValue, getComponentPath, isInsideScopingComponent, currentTimezone, getScriptPlugin
fastCloneDeep, boolValue, isInsideScopingComponent, currentTimezone, getScriptPlugin
} from '../../../utils/utils';
import Element from '../../../Element';
import ComponentModal from '../componentModal/ComponentModal';
Expand Down Expand Up @@ -361,11 +359,14 @@ export default class Component extends Element {
*/
this.parent = this.options.parent;

// Set the component paths for this component.
setParentReference(component, this.parent?.component);
setDefaultComponentPaths(component);
setComponentScope(component, 'dataPath', componentPath(component, COMPONENT_PATH.DATA));
setComponentScope(component, 'localDataPath', componentPath(component, COMPONENT_PATH.LOCAL_DATA));
/**
* The component paths for this component.
* @type {import('@formio/core').ComponentPaths} - The component paths.
*/
this.paths = FormioUtils.getComponentPaths(this.component, this.parent?.component, {
...this.parent?.paths,
dataIndex: this.options.rowIndex === undefined ? this.parent?.paths?.dataIndex : this.options.rowIndex
});
this.options.name = this.options.name || 'data';

this._path = '';
Expand Down Expand Up @@ -485,12 +486,7 @@ export default class Component extends Element {
/* eslint-enable max-statements */

get componentsMap() {
if (this.localRoot?.childComponentsMap) {
return this.localRoot.childComponentsMap;
}
const localMap = {};
localMap[this.path] = this;
return localMap;
return this.root.childComponentsMap;
}

get data() {
Expand Down Expand Up @@ -561,9 +557,10 @@ export default class Component extends Element {
* @returns {void}
*/
set rowIndex(value) {
setComponentScope(this.component, 'dataIndex', value);
setComponentScope(this.component, 'dataPath', componentPath(this.component, COMPONENT_PATH.DATA));
setComponentScope(this.component, 'localDataPath', componentPath(this.component, COMPONENT_PATH.LOCAL_DATA));
this.paths = FormioUtils.getComponentPaths(this.component, this.parent?.component, {
...(this.parent?.paths || {}),
...{ dataIndex: value }
});
this._rowIndex = value;
}

Expand Down Expand Up @@ -650,7 +647,7 @@ export default class Component extends Element {
}

get path() {
return this.component.scope?.dataPath;
return this.paths.dataPath;
}

set path(path) {
Expand Down Expand Up @@ -1491,7 +1488,7 @@ export default class Component extends Element {
this.refresh(this.data, changed, flags);
}
else if (
(changePath && getComponentPath(changed.instance) === refreshData) && changed && changed.instance &&
(changePath && (changed.instance?.paths?.localPath === refreshData)) && changed && changed.instance &&
// Make sure the changed component is not in a different "context". Solves issues where refreshOn being set
// in fields inside EditGrids could alter their state from other rows (which is bad).
this.inContext(changed.instance)
Expand Down Expand Up @@ -3422,7 +3419,7 @@ export default class Component extends Element {
if (flags.silentCheck) {
return [];
}
let isDirty = this.dirty || flags.dirty;
let isDirty = (flags.dirty === false) ? false : (this.dirty || flags.dirty);
if (this.options.alwaysDirty) {
isDirty = true;
}
Expand All @@ -3449,6 +3446,7 @@ export default class Component extends Element {
data,
row,
value: this.validationValue,
paths: this.paths,
path: this.path || this.component.key,
instance: this,
form: this.root ? this.root._form : {},
Expand Down
94 changes: 37 additions & 57 deletions src/components/_classes/nested/NestedComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import _ from 'lodash';
import Field from '../field/Field';
import Components from '../../Components';''
import { getArrayFromComponentPath, getStringFromComponentPath, getRandomComponentId } from '../../../utils/utils';
import { getComponentPaths, getRandomComponentId, componentMatches, getBestMatch, getStringFromComponentPath } from '../../../utils/utils';
import { process as processAsync, processSync } from '@formio/core/process';

/**
Expand Down Expand Up @@ -217,6 +217,10 @@ export default class NestedComponent extends Field {
*/
set rowIndex(value) {
this._rowIndex = value;
this.paths = getComponentPaths(this.component, this.parent?.component, {
...(this.parent?.paths || {}),
...{ dataIndex: value }
});
this.eachComponent((component) => {
component.rowIndex = value;
});
Expand Down Expand Up @@ -326,62 +330,36 @@ export default class NestedComponent extends Field {
}

/**
* Returns a component provided a key. This performs a deep search within the
* component tree.
* Returns a component provided a key. This performs a deep search within the component tree.
* @param {string} path - The path to the component.
* @param {Function} [fn] - Called with the component once found.
* @param {string} [originalPath] - The original path to the component.
* @returns {any} - The component that is located.
*/
getComponent(path, fn, originalPath) {
originalPath = originalPath || getStringFromComponentPath(path);
if (this.componentsMap.hasOwnProperty(originalPath)) {
if (fn) {
return fn(this.componentsMap[originalPath]);
}
else {
return this.componentsMap[originalPath];
}
}

path = getArrayFromComponentPath(path);
const pathStr = originalPath;
const newPath = _.clone(path);
let key = newPath.shift();
const remainingPath = newPath;
let comp = null;
let possibleComp = null;

if (_.isNumber(key)) {
key = remainingPath.shift();
}

if (!_.isString(key)) {
return comp;
}

this.everyComponent((component, components) => {
const matchPath = component.hasInput && component.path ? pathStr.includes(component.path) : true;
if (component.component.key === key) {
possibleComp = component;
if (matchPath) {
comp = component;
if (remainingPath.length > 0 && 'getComponent' in component) {
comp = component.getComponent(remainingPath, fn, originalPath);
}
else if (fn) {
fn(component, components);
}
return false;
}
}
getComponent(path) {
path = getStringFromComponentPath(path);
const matches = {
path: undefined,
fullPath: undefined,
localPath: undefined,
fullLocalPath: undefined,
dataPath: undefined,
localDataPath: undefined,
key: undefined,
};
this.everyComponent((component) => {
// All searches are relative to this component so replace this path from the child paths.
componentMatches(component.component, {
path: component.paths?.path?.replace(new RegExp(`^${this.paths?.path}\\.?`), ''),
fullPath: component.paths?.fullPath?.replace(new RegExp(`^${this.paths?.fullPath}\\.?`), ''),
localPath: component.paths?.localPath?.replace(new RegExp(`^${this.paths?.localPath}\\.?`), ''),
fullLocalPath: component.paths?.fullLocalPath?.replace(new RegExp(`^${this.paths?.fullLocalPath}\\.?`), ''),
dataPath: component.paths?.dataPath?.replace(new RegExp(`^${this.paths?.dataPath}\\.?`), ''),
localDataPath: component.paths?.localDataPath?.replace(new RegExp(`^${this.paths?.localDataPath}\\.?`), ''),
}, path, this.rowIndex, matches, (type, match) => {
match.instance = component;
return match;
});
});

if (!comp) {
comp = possibleComp;
}

return comp;
return getBestMatch(matches)?.instance;
}

/**
Expand Down Expand Up @@ -763,12 +741,12 @@ export default class NestedComponent extends Field {
);
}

validationProcessor({ scope, data, row, instance, component }, flags) {
validationProcessor({ scope, data, row, instance, paths }, flags) {
const { dirty } = flags;
if (this.root.hasExtraPages && this.page !== this.root.page) {
instance = this.childComponentsMap?.hasOwnProperty(component.path)
? this.childComponentsMap[component.path]
: this.getComponent(component.path);
instance = this.componentsMap?.hasOwnProperty(paths.dataPath)
? this.componentsMap[paths.dataPath]
: this.getComponent(paths.dataPath);
}
if (!instance) {
return;
Expand Down Expand Up @@ -809,6 +787,8 @@ export default class NestedComponent extends Field {
instances: this.componentsMap,
data: data,
scope: { errors: [] },
parent: this.component,
parentPaths: this.paths,
processors: [
{
process: validationProcessorProcess,
Expand Down
55 changes: 6 additions & 49 deletions src/components/_classes/nestedarray/NestedArrayComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

import _ from 'lodash';
import { Utils } from '@formio/core/utils';
const { componentPath, COMPONENT_PATH, setComponentScope } = Utils;
import { componentValueTypes, getStringFromComponentPath, isLayoutComponent } from '../../../utils/utils';
const { getComponentPaths } = Utils;
import { componentValueTypes, isLayoutComponent } from '../../../utils/utils';

import Component from '../component/Component';
import NestedDataComponent from '../nesteddata/NestedDataComponent';
Expand Down Expand Up @@ -32,9 +32,10 @@ export default class NestedArrayComponent extends NestedDataComponent {
}

set rowIndex(value) {
setComponentScope(this.component, 'dataIndex', value);
setComponentScope(this.component, 'dataPath', componentPath(this.component, COMPONENT_PATH.DATA));
setComponentScope(this.component, 'localDataPath', componentPath(this.component, COMPONENT_PATH.LOCAL_DATA));
this.paths = getComponentPaths(this.component, this.parent?.component, {
...(this.parent?.paths || {}),
...{ dataIndex: value }
});
this._rowIndex = value;
}

Expand Down Expand Up @@ -116,50 +117,6 @@ export default class NestedArrayComponent extends NestedDataComponent {
}, 'show'));
}

getComponent(path, fn, originalPath) {
originalPath = originalPath || getStringFromComponentPath(path);
if (this.componentsMap.hasOwnProperty(originalPath)) {
if (fn) {
return fn(this.componentsMap[originalPath]);
}
else {
return this.componentsMap[originalPath];
}
}
path = Array.isArray(path) ? path : [path];
let key = path.shift();
const remainingPath = path;
let result = [];
let possibleComp = null;
let comp = null;
let rowIndex = null;

if (_.isNumber(key)) {
rowIndex = key;
key = remainingPath.shift();
}
if (!_.isString(key)) {
return result;
}

this.everyComponent((component, components) => {
if (component.component.key === key) {
possibleComp = component;
if (remainingPath.length > 0 && 'getComponent' in component) {
comp = component.getComponent(remainingPath, fn, originalPath);
}
else if (fn) {
fn(component, components);
}
result = rowIndex !== null ? comp : result.concat(comp || possibleComp);
}
}, rowIndex);
if ((!result || result.length === 0) && possibleComp) {
result = rowIndex !== null ? possibleComp : [possibleComp];
}
return result;
}

everyComponent(fn, rowIndex, options = {}) {
if (_.isObject(rowIndex)) {
options = rowIndex;
Expand Down
Loading

0 comments on commit 53e9daf

Please sign in to comment.