Skip to content

Commit

Permalink
Merge pull request #193 from formio/refactor-component-paths
Browse files Browse the repository at this point in the history
Refactor the component paths to ensure we are always referencing the correct path.
  • Loading branch information
brendanbond authored Dec 9, 2024
2 parents c7ac015 + 7248a7d commit eb1091f
Show file tree
Hide file tree
Showing 45 changed files with 1,171 additions and 909 deletions.
2 changes: 1 addition & 1 deletion src/error/FieldError.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getComponentErrorField } from 'process/validation/util';
import { ValidationContext } from 'types';
import { getComponentErrorField } from 'utils/formUtil';

type FieldErrorContext = ValidationContext & {
field?: string;
Expand Down
17 changes: 8 additions & 9 deletions src/modules/jsonlogic/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { BaseEvaluator, EvaluatorOptions } from 'utils';
import { normalizeContext } from 'utils/formUtil';
import { jsonLogic } from './jsonLogic';
import { BaseEvaluator, EvaluatorOptions, EvaluatorContext } from 'utils/Evaluator';
export class JSONLogicEvaluator extends BaseEvaluator {
public static evaluate(
func: any,
Expand All @@ -24,12 +25,6 @@ export class JSONLogicEvaluator extends BaseEvaluator {
}
}

export type EvaluatorContext = {
evalContext?: (context: any) => any;
instance?: any;
[key: string]: any;
};

export type EvaluatorFn = (context: EvaluatorContext) => any;

export function evaluate(
Expand All @@ -40,7 +35,9 @@ export function evaluate(
options: EvaluatorOptions = {},
) {
const { evalContext, instance } = context;
const evalContextValue = evalContext ? evalContext(context) : context;
const evalContextValue = evalContext
? evalContext(normalizeContext(context))
: normalizeContext(context);
if (evalContextFn) {
evalContextFn(evalContextValue);
}
Expand All @@ -63,7 +60,9 @@ export function interpolate(
evalContextFn?: EvaluatorFn,
): string {
const { evalContext, instance } = context;
const evalContextValue = evalContext ? evalContext(context) : context;
const evalContextValue = evalContext
? evalContext(normalizeContext(context))
: normalizeContext(context);
if (evalContextFn) {
evalContextFn(evalContextValue);
}
Expand Down
9 changes: 5 additions & 4 deletions src/process/__tests__/process.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
skipValidForLogicallyHiddenComp,
skipValidWithHiddenParentComp,
} from './fixtures';
import { get } from 'lodash';
import _ from 'lodash';

/*
describe('Process Tests', () => {
Expand Down Expand Up @@ -959,6 +959,7 @@ describe('Process Tests', function () {

const errors: any = [];
const context = {
_,
form,
submission,
data: submission.data,
Expand Down Expand Up @@ -1114,8 +1115,8 @@ describe('Process Tests', function () {
submission.data = context.data;
context.processors = ProcessTargets.evaluator;
processSync(context);
assert.equal(get(context.submission.data, 'form1.data.form.data.textField'), 'one 1');
assert.equal(get(context.submission.data, 'form1.data.form.data.textField1'), 'two 2');
assert.equal(_.get(context.submission.data, 'form1.data.form.data.textField'), 'one 1');
assert.equal(_.get(context.submission.data, 'form1.data.form.data.textField1'), 'two 2');
});

it('should remove submission data not in a nested form definition', async function () {
Expand Down Expand Up @@ -3437,7 +3438,7 @@ describe('Process Tests', function () {
});
});

it('Should allow the submission to go through without errors if there is no the subform reference value', async function () {
it('Should allow the submission to go through without errors if there is no subform reference value', async function () {
const form = {
_id: '66bc5cff7ca1729623a182db',
title: 'form2',
Expand Down
5 changes: 4 additions & 1 deletion src/process/calculation/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
ProcessorInfo,
} from 'types';
import { set } from 'lodash';
import { normalizeContext } from 'utils/formUtil';

export const shouldCalculate = (context: CalculationContext): boolean => {
const { component, config } = context;
Expand All @@ -23,7 +24,9 @@ export const calculateProcessSync: ProcessorFnSync<CalculationScope> = (
if (!shouldCalculate(context)) {
return;
}
const evalContextValue = evalContext ? evalContext(context) : context;
const evalContextValue = evalContext
? evalContext(normalizeContext(context))
: normalizeContext(context);
evalContextValue.value = value || null;
if (!scope.calculated) scope.calculated = [];
const newValue = JSONLogicEvaluator.evaluate(component.calculateValue, evalContextValue, 'value');
Expand Down
10 changes: 4 additions & 6 deletions src/process/clearHidden.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
ProcessorFnSync,
ConditionsScope,
} from 'types';
import { getComponentAbsolutePath } from 'utils/formUtil';

type ClearHiddenScope = ProcessorScope & {
clearHidden: {
Expand All @@ -19,7 +18,6 @@ type ClearHiddenScope = ProcessorScope & {
*/
export const clearHiddenProcess: ProcessorFnSync<ClearHiddenScope> = (context) => {
const { component, data, value, scope, path } = context;
const absolutePath = getComponentAbsolutePath(component) || path;

// No need to unset the value if it's undefined
if (value === undefined) {
Expand All @@ -32,18 +30,18 @@ export const clearHiddenProcess: ProcessorFnSync<ClearHiddenScope> = (context) =

// Check if there's a conditional set for the component and if it's marked as conditionally hidden
const isConditionallyHidden = (scope as ConditionsScope).conditionals?.find((cond) => {
return absolutePath === cond.path && cond.conditionallyHidden;
return path === cond.path && cond.conditionallyHidden;
});

const shouldClearValueWhenHidden =
!component.hasOwnProperty('clearOnHide') || component.clearOnHide;

if (
shouldClearValueWhenHidden &&
(isConditionallyHidden || component.hidden || component.ephemeralState?.conditionallyHidden)
(isConditionallyHidden || component.hidden || component.scope?.conditionallyHidden)
) {
unset(data, absolutePath);
scope.clearHidden[absolutePath] = true;
unset(data, path);
scope.clearHidden[path] = true;
}
};

Expand Down
10 changes: 4 additions & 6 deletions src/process/conditions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
ProcessorInfo,
ConditionsContext,
} from 'types';
import { registerEphemeralState } from 'utils';
import { setComponentScope } from 'utils/formUtil';
import {
checkCustomConditional,
checkJsonConditional,
Expand All @@ -15,7 +15,6 @@ import {
isSimpleConditional,
isJSONConditional,
} from 'utils/conditions';
import { getComponentAbsolutePath } from 'utils/formUtil';

const hasCustomConditions = (context: ConditionsContext): boolean => {
const { component } = context;
Expand Down Expand Up @@ -85,24 +84,23 @@ export type ConditionallyHidden = (context: ConditionsContext) => boolean;

export const conditionalProcess = (context: ConditionsContext, isHidden: ConditionallyHidden) => {
const { scope, path, component } = context;
const absolutePath = getComponentAbsolutePath(component) || path;
if (!hasConditions(context)) {
return;
}

if (!scope.conditionals) {
scope.conditionals = [];
}
let conditionalComp = scope.conditionals.find((cond) => cond.path === absolutePath);
let conditionalComp = scope.conditionals.find((cond) => cond.path === path);
if (!conditionalComp) {
conditionalComp = { path: absolutePath, conditionallyHidden: false };
conditionalComp = { path, conditionallyHidden: false };
scope.conditionals.push(conditionalComp);
}

conditionalComp.conditionallyHidden =
conditionalComp.conditionallyHidden || isHidden(context) === true;
if (conditionalComp.conditionallyHidden) {
registerEphemeralState(context.component, 'conditionallyHidden', true);
setComponentScope(component, 'conditionallyHidden', true);
}
};

Expand Down
6 changes: 4 additions & 2 deletions src/process/defaultValue/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
DefaultValueContext,
} from 'types';
import { set, has } from 'lodash';
import { getComponentKey } from 'utils/formUtil';
import { getComponentKey, normalizeContext } from 'utils/formUtil';

export const hasCustomDefaultValue = (context: DefaultValueContext): boolean => {
const { component } = context;
Expand Down Expand Up @@ -48,7 +48,9 @@ export const customDefaultValueProcessSync: ProcessorFnSync<ConditionsScope> = (
}
let defaultValue = null;
if (component.customDefaultValue) {
const evalContextValue = evalContext ? evalContext(context) : context;
const evalContextValue = evalContext
? evalContext(normalizeContext(context))
: normalizeContext(context);
evalContextValue.value = null;
defaultValue = JSONLogicEvaluator.evaluate(
component.customDefaultValue,
Expand Down
6 changes: 4 additions & 2 deletions src/process/fetch/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
} from 'types';
import { get, set } from 'lodash';
import { Evaluator } from 'utils';
import { getComponentKey } from 'utils/formUtil';
import { getComponentKey, normalizeContext } from 'utils/formUtil';

export const shouldFetch = (context: FetchContext): boolean => {
const { component, config } = context;
Expand Down Expand Up @@ -38,7 +38,9 @@ export const fetchProcess: ProcessorFn<FetchScope> = async (context: FetchContex
return;
}
if (!scope.fetched) scope.fetched = {};
const evalContextValue = evalContext ? evalContext(context) : context;
const evalContextValue = evalContext
? evalContext(normalizeContext(context))
: normalizeContext(context);
const url = Evaluator.interpolateString(get(component, 'fetch.url', ''), evalContextValue);
if (!url) {
return;
Expand Down
1 change: 0 additions & 1 deletion src/process/filter/__tests__/filter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ describe('Filter processor', function () {
type: 'editgrid',
key: 'editGrid',
input: true,
path: 'editGrid',
components: [
{
type: 'textfield',
Expand Down
16 changes: 7 additions & 9 deletions src/process/filter/index.ts
Original file line number Diff line number Diff line change
@@ -1,46 +1,44 @@
import { FilterContext, FilterScope, ProcessorFn, ProcessorFnSync, ProcessorInfo } from 'types';
import { set } from 'lodash';
import { Utils } from 'utils';
import { get, isObject } from 'lodash';
import { getComponentAbsolutePath } from 'utils/formUtil';
import { getModelType } from 'utils/formUtil';
export const filterProcessSync: ProcessorFnSync<FilterScope> = (context: FilterContext) => {
const { scope, component, path } = context;
const { value } = context;
const absolutePath = getComponentAbsolutePath(component) || path;
if (!scope.filter) scope.filter = {};
if (value !== undefined) {
const modelType = Utils.getModelType(component);
const modelType = getModelType(component);
switch (modelType) {
case 'dataObject':
scope.filter[absolutePath] = {
scope.filter[path] = {
compModelType: modelType,
include: true,
value: { data: {} },
};
break;
case 'nestedArray':
scope.filter[absolutePath] = {
scope.filter[path] = {
compModelType: modelType,
include: true,
value: [],
};
break;
case 'nestedDataArray':
scope.filter[absolutePath] = {
scope.filter[path] = {
compModelType: modelType,
include: true,
value: Array.isArray(value) ? value.map((v) => ({ ...v, data: {} })) : [],
};
break;
case 'object':
scope.filter[absolutePath] = {
scope.filter[path] = {
compModelType: modelType,
include: true,
value: component.type === 'address' ? false : {},
};
break;
default:
scope.filter[absolutePath] = {
scope.filter[path] = {
compModelType: modelType,
include: true,
};
Expand Down
10 changes: 4 additions & 6 deletions src/process/hideChildren.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,24 @@ import {
ConditionsScope,
ProcessorFn,
} from 'types';
import { registerEphemeralState } from 'utils';
import { getComponentAbsolutePath } from 'utils/formUtil';
import { setComponentScope } from 'utils/formUtil';

/**
* This processor function checks components for the `hidden` property and, if children are present, sets them to hidden as well.
*/
export const hideChildrenProcessor: ProcessorFnSync<ConditionsScope> = (context) => {
const { component, path, parent, scope } = context;
const absolutePath = getComponentAbsolutePath(component) || path;
// Check if there's a conditional set for the component and if it's marked as conditionally hidden
const isConditionallyHidden = scope.conditionals?.find((cond) => {
return absolutePath === cond.path && cond.conditionallyHidden;
return path === cond.path && cond.conditionallyHidden;
});

if (!scope.conditionals) {
scope.conditionals = [];
}

if (isConditionallyHidden || component.hidden || parent?.ephemeralState?.conditionallyHidden) {
registerEphemeralState(component, 'conditionallyHidden', true);
if (isConditionallyHidden || component.hidden || parent?.scope?.conditionallyHidden) {
setComponentScope(component, 'conditionallyHidden', true);
}
};

Expand Down
24 changes: 11 additions & 13 deletions src/process/populate/index.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,31 @@
import { get, set } from 'lodash';
import { set } from 'lodash';
import { PopulateContext, PopulateScope, ProcessorFnSync } from 'types';
import { componentPath, getContextualRowPath, getModelType } from 'utils/formUtil';
import { getModelType } from 'utils/formUtil';

// This processor ensures that a "linked" row context is provided to every component.
export const populateProcessSync: ProcessorFnSync<PopulateScope> = (context: PopulateContext) => {
const { component, path, scope } = context;
const { component, path, scope, value } = context;
const { data } = scope;
const compDataPath = componentPath(component, getContextualRowPath(component, path));
const compData: any = get(data, compDataPath);
if (!scope.populated) scope.populated = [];
switch (getModelType(component)) {
case 'nestedArray':
if (!compData || !compData.length) {
set(data, compDataPath, [{}]);
scope.row = get(data, compDataPath)[0];
if (!value || !value.length) {
const newValue = [{}];
set(data, path, newValue);
scope.row = newValue[0];
scope.populated.push({
path,
row: get(data, compDataPath)[0],
});
}
break;
case 'dataObject':
case 'object':
if (!compData || typeof compData !== 'object') {
set(data, compDataPath, {});
scope.row = get(data, compDataPath);
if (!value || typeof value !== 'object') {
const newValue = {};
set(data, value, newValue);
scope.row = newValue;
scope.populated.push({
path,
row: get(data, compDataPath),
});
}
break;
Expand Down
Loading

0 comments on commit eb1091f

Please sign in to comment.