Skip to content

Commit

Permalink
fix: Remove conditional attributes after the condition is unmatched (#57
Browse files Browse the repository at this point in the history
)

* fix: Remove stale attributes from conditionals

* Do not remove "type" while it is being deprecated

* realistic examples

* remove console.log

* Spot a bug, added a test to cover it. fix needed

* fix latest bug found. all good

* self-review

* self-review #2

* Release 0.7.2-dev.20231106093837

* delete unsused rootFieldAttrs param

* delete unneded default examples

* jsdcos remove x an y

* Revert back to 0.7.2-beta.0
  • Loading branch information
sandrina-p committed Nov 7, 2023
1 parent 629da07 commit 8bac714
Show file tree
Hide file tree
Showing 5 changed files with 427 additions and 34 deletions.
31 changes: 21 additions & 10 deletions src/calculateConditionalProperties.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,14 +69,19 @@ function rebuildFieldset(fields, property) {
* schema dependencies, and conditional logic.
*
* @param {Object} params - Parameters
* @param {Object} params.fieldParams - Current field parameters
* @param {Object} params.fieldParams - The field attributes from the first render (root)
* @param {Object} params.customProperties - Custom field properties from schema
* @param {Object} params.logic - JSON-logic
* @param {Object} params.config - Form configuration
*
* @returns {Function} A function that calculates conditional properties
*/
export function calculateConditionalProperties({ fieldParams, customProperties, logic, config }) {
/**
* @typedef {calculateConditionalPropertiesReturn}
* @property {rootFieldAttrs} - The field attributes from the first render (root)
* @property {newAttributes} - Attributes from the matched conditional
*/
/**
* Runs dynamic property calculation on a field based on a conditional that has been calculated
*
Expand All @@ -85,11 +90,11 @@ export function calculateConditionalProperties({ fieldParams, customProperties,
* @param {Object} params.conditionBranch - Condition branch
* @param {Object} params.formValues - Current form values
*
* @returns {Object} Updated field parameters
* @returns {calculateConditionalPropertiesReturn}
*/
return ({ isRequired, conditionBranch, formValues }) => {
// Check if the current field is conditionally declared in the schema

// console.log('::calc (closure original)', fieldParams.description);
const conditionalProperty = conditionBranch?.properties?.[fieldParams.name];

if (conditionalProperty) {
Expand Down Expand Up @@ -142,20 +147,26 @@ export function calculateConditionalProperties({ fieldParams, customProperties,
),
};

return omit(merge(base, presentation, newFieldParams), ['inputType']);
return {
rootFieldAttrs: fieldParams,
newAttributes: omit(merge(base, presentation, newFieldParams), ['inputType']),
};
}

// If field is not conditionally declared it should be visible if it's required
const isVisible = isRequired;

return {
isVisible,
required: isRequired,
schema: buildYupSchema({
...fieldParams,
...extractParametersFromNode(conditionBranch),
rootFieldAttrs: fieldParams,
newAttributes: {
isVisible,
required: isRequired,
}),
schema: buildYupSchema({
...fieldParams,
...extractParametersFromNode(conditionBranch),
required: isRequired,
}),
},
};
};
}
59 changes: 48 additions & 11 deletions src/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,41 @@ import { processJSONLogicNode } from './jsonLogic';
import { hasProperty } from './utils';
import { buildCompleteYupSchema, buildYupSchema } from './yupSchema';

/**
* List of custom JSF's attributes for field
* that are added dynamically after the first parsing.
*/
const dynamicInternalJsfAttrs = [
'isVisible', // Driven from conditionals state
'fields', // driven from group-array
'getComputedAttributes', // From json-logic
'computedAttributes', // From json-logic
'calculateConditionalProperties', // driven from conditionals
'calculateCustomValidationProperties', // To be deprecated in favor of json-logic
'scopedJsonSchema', // The respective JSON Schema
];
const dynamicInternalJsfAttrsObj = Object.fromEntries(
dynamicInternalJsfAttrs.map((k) => [k, true])
);

/**
*
* @param {Object} field - Current field attributes
* @param {Object} conditionalAttrs - Attributes from the matched conditional
* @param {Object} rootAttrs - Original field attributes from the root.
*/
function removeConditionalStaleAttributes(field, conditionalAttrs, rootAttrs) {
Object.keys(field).forEach((key) => {
if (
conditionalAttrs[key] === undefined &&
rootAttrs[key] === undefined && // Don't remove attrs that were declared in the root field.
dynamicInternalJsfAttrsObj[key] === undefined // ignore these because they are internal
) {
field[key] = undefined;
}
});
}

/**
* @typedef {import('./createHeadlessForm').FieldParameters} FieldParameters
* @typedef {import('./createHeadlessForm').FieldValues} FieldValues
Expand Down Expand Up @@ -202,19 +237,19 @@ function updateField(field, requiredFields, node, formValues, logic, config) {
field.isVisible = true;
}

const updateValues = (fieldValues) =>
Object.entries(fieldValues).forEach(([key, value]) => {
// some values (eg "schema") are a function, so we need to call it here
const updateAttributes = (fieldAttrs) => {
Object.entries(fieldAttrs).forEach(([key, value]) => {
// some attributes' value (eg "schema") are a function, so we need to call it here
field[key] = typeof value === 'function' ? value() : value;

if (key === 'value') {
// The value of the field should not be driven by the json-schema,
// unless it's a read-only field
// If the readOnly property has changed, use that updated value,
// otherwise use the start value of the property
const readOnlyPropertyWasUpdated = typeof fieldValues.readOnly !== 'undefined';
const readOnlyPropertyWasUpdated = typeof fieldAttrs.readOnly !== 'undefined';
const isReadonlyByDefault = field.readOnly;
const isReadonly = readOnlyPropertyWasUpdated ? fieldValues.readOnly : isReadonlyByDefault;
const isReadonly = readOnlyPropertyWasUpdated ? fieldAttrs.readOnly : isReadonlyByDefault;

// Needs field.type check because otherwise checkboxes will have an initial
// value of "true" when they should be not checked. !8755 for full context
Expand All @@ -226,36 +261,38 @@ function updateField(field, requiredFields, node, formValues, logic, config) {
}
}
});
};

if (field.getComputedAttributes) {
const computedFieldValues = field.getComputedAttributes({
const newAttributes = field.getComputedAttributes({
field,
isRequired: fieldIsRequired,
node,
formValues,
config,
logic,
});
updateValues(computedFieldValues);
updateAttributes(newAttributes);
}

// If field has a calculateConditionalProperties closure, run it and update the field properties
if (field.calculateConditionalProperties) {
const newFieldValues = field.calculateConditionalProperties({
const { rootFieldAttrs, newAttributes } = field.calculateConditionalProperties({
isRequired: fieldIsRequired,
conditionBranch: node,
formValues,
});
updateValues(newFieldValues);
updateAttributes(newAttributes);
removeConditionalStaleAttributes(field, newAttributes, rootFieldAttrs);
}

if (field.calculateCustomValidationProperties) {
const newFieldValues = field.calculateCustomValidationProperties(
const newAttributes = field.calculateCustomValidationProperties(
fieldIsRequired,
node,
formValues
);
updateValues(newFieldValues);
updateAttributes(newAttributes);
}
}

Expand Down
Loading

0 comments on commit 8bac714

Please sign in to comment.