Skip to content

Commit

Permalink
feat: implement proper variable hiding within repeated field
Browse files Browse the repository at this point in the history
Related to #808
  • Loading branch information
Skaiir committed Dec 12, 2023
1 parent 0c4f873 commit 3c03a21
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 19 deletions.
2 changes: 1 addition & 1 deletion packages/form-js-viewer/src/Form.js
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ export default class Form {

const workingErrors = {};
validateFieldRecursively(workingErrors, formFieldRegistry.getForm());
const filteredErrors = this._applyConditions(workingErrors, data, { getFilterPath: getErrorPath });
const filteredErrors = this._applyConditions(workingErrors, data, { getValuePath: getErrorPath });
this._setState({ errors: filteredErrors });

return filteredErrors;
Expand Down
4 changes: 2 additions & 2 deletions packages/form-js-viewer/src/core/PathRegistry.js
Original file line number Diff line number Diff line change
Expand Up @@ -183,8 +183,8 @@ export default class PathRegistry {
result = result && callResult;
}

// stop executing if false is specifically returned
if (result === false) {
// stop executing if false is specifically returned or if preventing recursion
if (result === false || context.preventRecursion) {
return result;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { unaryTest } from 'feelin';
import { get, isString, set, values, isObject } from 'min-dash';
import { clone } from '../../util';
import { buildExpressionContext, clone } from '../../util';

/**
* @typedef {object} Condition
Expand All @@ -17,37 +17,100 @@ export default class ConditionChecker {
/**
* For given data, remove properties based on condition.
*
* @param {Object<string, any>} properties
* @param {Object<string, any>} data
* @param {Object<string, any>} conditionData
* @param {Object} [options]
* @param {Function} [options.getFilterPath]
* @param {Function} [options.getValuePath]
*/
applyConditions(properties, data = {}, options = {}) {
applyConditions(data, conditionData = {}, options = {}) {

const newProperties = clone(properties);
const workingData = clone(data);

const {
getFilterPath = (field) => this._pathRegistry.getValuePath(field)
getValuePath = (field, indexes) => this._pathRegistry.getValuePath(field, { indexes })
} = options;

const _applyConditionsWithinScope = (rootField, scopeContext) => {

const {
indexes = {},
expressionIndexes = [],
scopeData = conditionData,
parentScopeData = null
} = scopeContext;

this._pathRegistry.executeRecursivelyOnFields(rootField, ({ field, isClosed, isRepeatable, context }) => {

const {
conditional,
components,
id
} = field;

// build the expression context in the right format
const localExpressionContext = buildExpressionContext({
data,
i: expressionIndexes,
this: scopeData,
parent: parentScopeData
});

context.isHidden = context.isHidden || (conditional && this._checkHideCondition(conditional, localExpressionContext));

// if a field is repeatable and visible, we need to implement custom recursion on its children
if (isRepeatable && !context.isHidden) {

// prevent the regular recursion behavior of executeRecursivelyOnFields
context.preventRecursion = true;

const repeaterValuePath = getValuePath(field, indexes);
const repeaterValue = get(workingData, repeaterValuePath);

// quit early if there are no children or data associated with the repeater
if (!Array.isArray(repeaterValue) || !repeaterValue.length || !Array.isArray(components) || !components.length) {
return;
}

for (let i = 0; i < repeaterValue.length; i++) {

// create a new scope context for each index
const newScopeContext = {
indexes: { ...indexes, [id]: i },
expressionIndexes: [ ...expressionIndexes, i + 1 ],
scopeData: repeaterValue[i],
parentScopeData: scopeData
};

// for each child component, apply conditions within the new repetition scope
components.forEach(component => {
_applyConditionsWithinScope(component, newScopeContext);
});

}

}

// if a field is a leaf node (or repeatable, as they behave similarly), and hidden, we need to clear the value from the data from each index
if (context.isHidden && (isClosed || isRepeatable)) {
this._clearObjectValueRecursively(getValuePath(field, indexes), workingData);
}

});

};

// apply conditions starting with the root of the form
const form = this._formFieldRegistry.getForm();

if (!form) {
throw new Error('form field registry has no form');
}

this._pathRegistry.executeRecursivelyOnFields(form, ({ field, isClosed, isRepeatable, context }) => {
const { conditional: condition } = field;

context.isHidden = context.isHidden || (condition && this._checkHideCondition(condition, data));

// if a field is a leaf node (or repeatable, as they behave similarly), and hidden, we need to clear the value from the data from each index
if (context.isHidden && (isClosed || isRepeatable)) {
this._clearObjectValueRecursively(getFilterPath(field), newProperties);
}
_applyConditionsWithinScope(form, {
scopeData: conditionData
});

return newProperties;
return workingData;
}

/**
Expand Down

0 comments on commit 3c03a21

Please sign in to comment.