Skip to content

Commit 71c7e21

Browse files
committed
wip
1 parent 7bf30fd commit 71c7e21

File tree

14 files changed

+124
-92
lines changed

14 files changed

+124
-92
lines changed

packages/form-js-viewer/src/Form.js

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -330,24 +330,16 @@ export class Form {
330330
_update(update) {
331331
const { fieldInstance, value } = update;
332332

333-
const { id, valuePath, indexes } = fieldInstance;
333+
const { valuePath } = fieldInstance;
334334

335-
const { data, errors } = this._getState();
335+
const { data: _data } = this._getState();
336336

337-
const validator = this.get('validator');
337+
const data = set(_data, valuePath, value);
338+
this._setState({ data: clone(data) });
338339

339-
const fieldErrors = validator.validateFieldInstance(fieldInstance, value);
340-
341-
set(data, valuePath, value);
342-
343-
set(errors, [id, ...Object.values(indexes || {})], fieldErrors.length ? fieldErrors : undefined);
340+
this.validate();
344341

345342
this._emit('field.updated', update);
346-
347-
this._setState({
348-
data: clone(data),
349-
errors: clone(errors),
350-
});
351343
}
352344

353345
/**

packages/form-js-viewer/src/features/expressionLanguage/ConditionChecker.js

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { unaryTest } from 'feelin';
22
import { get, isString, set, values, isObject } from 'min-dash';
3-
import { buildExpressionContext, clone } from '../../util';
3+
import { wrapExpressionContext, clone } from '../../util';
44

55
/**
66
* @typedef {object} Condition
@@ -35,17 +35,15 @@ export class ConditionChecker {
3535
const { conditional, components, id } = field;
3636

3737
// build the expression context in the right format
38-
const localExpressionContext = buildExpressionContext({
38+
const expressionContext = wrapExpressionContext({
3939
this: scopeData,
4040
data: contextData,
4141
i: expressionIndexes,
4242
parent: parentScopeData,
4343
});
4444

4545
context.isHidden =
46-
startHidden ||
47-
context.isHidden ||
48-
(conditional && this._checkHideCondition(conditional, localExpressionContext));
46+
startHidden || context.isHidden || (conditional && this._checkHideCondition(conditional, expressionContext));
4947

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

packages/form-js-viewer/src/features/repeatRender/RepeatRenderManager.js

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,13 @@
33

44
import { get } from 'min-dash';
55
import { useContext, useMemo, useRef } from 'preact/hooks';
6-
import { LocalExpressionContext } from '../../render/context/LocalExpressionContext';
6+
import { ExpressionContextInfo } from '../../render/context/ExpressionContextInfo';
77

88
import ExpandSvg from '../../render/components/form-fields/icons/Expand.svg';
99
import CollapseSvg from '../../render/components/form-fields/icons/Collapse.svg';
1010
import AddSvg from '../../render/components/form-fields/icons/Add.svg';
1111
import DeleteSvg from '../../render/components/form-fields/icons/Delete.svg';
1212

13-
import { buildExpressionContext } from '../../util';
1413
import { useScrollIntoView } from '../../render/hooks';
1514
import classNames from 'classnames';
1615

@@ -48,8 +47,8 @@ export class RepeatRenderManager {
4847
const { data } = this._form._getState();
4948

5049
const repeaterField = props.field;
51-
const dataPath = this._pathRegistry.getValuePath(repeaterField, { indexes });
52-
const values = get(data, dataPath) || [];
50+
const valuePath = this._pathRegistry.getValuePath(repeaterField, { indexes });
51+
const values = get(data, valuePath) || [];
5352

5453
const nonCollapsedItems = this._getNonCollapsedItems(repeaterField);
5554
const collapseEnabled = !repeaterField.disableCollapse && values.length > nonCollapsedItems;
@@ -71,7 +70,7 @@ export class RepeatRenderManager {
7170
});
7271
};
7372

74-
const parentExpressionContextInfo = useContext(LocalExpressionContext);
73+
const parentExpressionContextInfo = useContext(ExpressionContextInfo);
7574

7675
return (
7776
<>
@@ -81,6 +80,7 @@ export class RepeatRenderManager {
8180
itemIndex={itemIndex}
8281
itemValue={itemValue}
8382
parentExpressionContextInfo={parentExpressionContextInfo}
83+
parentValuePath={valuePath}
8484
repeaterField={repeaterField}
8585
RowsRenderer={RowsRenderer}
8686
indexes={indexes}
@@ -196,6 +196,7 @@ export class RepeatRenderManager {
196196
* @param {number} props.itemIndex
197197
* @param {Object} props.itemValue
198198
* @param {Object} props.parentExpressionContextInfo
199+
* @param {string} props.parentValuePath
199200
* @param {Object} props.repeaterField
200201
* @param {Function} props.RowsRenderer
201202
* @param {Object} props.indexes
@@ -208,6 +209,7 @@ const RepetitionScaffold = (props) => {
208209
itemIndex,
209210
itemValue,
210211
parentExpressionContextInfo,
212+
parentValuePath,
211213
repeaterField,
212214
RowsRenderer,
213215
indexes,
@@ -224,26 +226,30 @@ const RepetitionScaffold = (props) => {
224226
[itemIndex, indexes, repeaterField.id, restProps],
225227
);
226228

227-
const localExpressionContextInfo = useMemo(
229+
const expressionContextInfo = useMemo(
228230
() => ({
229-
data: parentExpressionContextInfo.data,
230-
this: itemValue,
231-
parent: buildExpressionContext(parentExpressionContextInfo),
232-
i: [...parentExpressionContextInfo.i, itemIndex + 1],
231+
...parentExpressionContextInfo,
232+
segments: [
233+
...parentExpressionContextInfo.segments,
234+
{
235+
path: parentValuePath,
236+
index: itemIndex,
237+
},
238+
],
233239
}),
234-
[itemIndex, parentExpressionContextInfo, itemValue],
240+
[parentExpressionContextInfo, parentValuePath, itemIndex],
235241
);
236242

237243
return !showRemove ? (
238-
<LocalExpressionContext.Provider value={localExpressionContextInfo}>
244+
<ExpressionContextInfo.Provider value={expressionContextInfo}>
239245
<RowsRenderer {...elementProps} />
240-
</LocalExpressionContext.Provider>
246+
</ExpressionContextInfo.Provider>
241247
) : (
242248
<div class="fjs-repeat-row-container">
243249
<div class="fjs-repeat-row-rows">
244-
<LocalExpressionContext.Provider value={localExpressionContextInfo}>
250+
<ExpressionContextInfo.Provider value={expressionContextInfo}>
245251
<RowsRenderer {...elementProps} />
246-
</LocalExpressionContext.Provider>
252+
</ExpressionContextInfo.Provider>
247253
</div>
248254
<button
249255
type="button"

packages/form-js-viewer/src/render/components/FormComponent.js

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { FormField } from './FormField';
22
import { PoweredBy } from './PoweredBy';
3-
import { LocalExpressionContext } from '../context/LocalExpressionContext';
3+
import { ExpressionContextInfo } from '../context/ExpressionContextInfo';
44

55
import { useMemo } from 'preact/hooks';
6-
import { useFilteredFormData, useService } from '../hooks';
6+
import { useDeepCompareMemoize, useFilteredFormData, useService } from '../hooks';
77

88
const noop = () => {};
99

@@ -28,23 +28,22 @@ export function FormComponent(props) {
2828
onReset();
2929
};
3030

31-
const filteredFormData = useFilteredFormData();
31+
const _filteredFormData = useFilteredFormData();
32+
const filteredFormData = useDeepCompareMemoize(_filteredFormData);
3233

33-
const localExpressionContext = useMemo(
34+
const expressionContextInfo = useMemo(
3435
() => ({
3536
data: filteredFormData,
36-
parent: null,
37-
this: filteredFormData,
38-
i: [],
37+
segments: [],
3938
}),
4039
[filteredFormData],
4140
);
4241

4342
return (
4443
<form class="fjs-form" onSubmit={handleSubmit} onReset={handleReset} aria-label={ariaLabel} noValidate>
45-
<LocalExpressionContext.Provider value={localExpressionContext}>
44+
<ExpressionContextInfo.Provider value={expressionContextInfo}>
4645
<FormField field={schema} onChange={onChange} />
47-
</LocalExpressionContext.Provider>
46+
</ExpressionContextInfo.Provider>
4847
<PoweredBy />
4948
</form>
5049
);

packages/form-js-viewer/src/render/components/FormField.js

Lines changed: 26 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import isEqual from 'lodash/isEqual';
33

44
import { get } from 'min-dash';
55

6-
import { FormContext, FormRenderContext, LocalExpressionContext } from '../context';
6+
import { FormContext, FormRenderContext, ExpressionContextInfo } from '../context';
77

88
import { useCondition, useReadonly, useService } from '../hooks';
99

@@ -27,9 +27,7 @@ export function FormField(props) {
2727

2828
const { formId } = useContext(FormContext);
2929

30-
// track whether we should trigger initial validation on certain actions, e.g. field blur
31-
// disabled straight away, if viewerCommands are not available
32-
const [initialValidationTrigger, setInitialValidationTrigger] = useState(!!viewerCommands);
30+
const [validationGrace, setValidationGrace] = useState(!!viewerCommands);
3331

3432
const FormFieldComponent = formFields.get(field.type);
3533

@@ -39,7 +37,7 @@ export function FormField(props) {
3937

4038
const fieldConfig = FormFieldComponent.config;
4139

42-
const localExpressionContext = useContext(LocalExpressionContext);
40+
const expressionContextInfo = useContext(ExpressionContextInfo);
4341
const valuePath = useMemo(() => pathRegistry.getValuePath(field, { indexes }), [field, indexes, pathRegistry]);
4442

4543
const initialValue = useMemo(() => get(initialData, valuePath), [initialData, valuePath]);
@@ -56,11 +54,11 @@ export function FormField(props) {
5654
const fieldInstance = useMemo(
5755
() => ({
5856
id: field.id,
59-
expressionContextInfo: localExpressionContext,
57+
expressionContextInfo,
6058
valuePath,
6159
indexes,
6260
}),
63-
[field.id, valuePath, localExpressionContext, indexes],
61+
[field.id, valuePath, expressionContextInfo, indexes],
6462
);
6563

6664
// register form field instance
@@ -80,57 +78,62 @@ export function FormField(props) {
8078
return;
8179
}
8280

83-
const resetValidation = () => {
84-
setInitialValidationTrigger(true);
81+
const resetValidationGrace = () => {
82+
setValidationGrace(true);
8583
};
8684

87-
eventBus.on('import.done', resetValidation);
88-
eventBus.on('reset', resetValidation);
85+
eventBus.on('import.done', resetValidationGrace);
86+
eventBus.on('reset', resetValidationGrace);
8987

9088
return () => {
91-
eventBus.off('import.done', resetValidation);
92-
eventBus.off('reset', resetValidation);
89+
eventBus.off('import.done', resetValidationGrace);
90+
eventBus.off('reset', resetValidationGrace);
9391
};
9492
}, [eventBus, viewerCommands]);
9593

9694
useEffect(() => {
9795
const hasInitialValue = initialValue && !isEqual(initialValue, []);
9896

99-
if (initialValidationTrigger && hasInitialValue) {
100-
setInitialValidationTrigger(false);
97+
if (validationGrace && hasInitialValue) {
98+
setValidationGrace(false);
10199
viewerCommands.updateFieldInstanceValidation(fieldInstance, initialValue);
102100
}
103-
}, [fieldInstance, initialValidationTrigger, initialValue, viewerCommands]);
101+
}, [fieldInstance, validationGrace, initialValue, viewerCommands]);
104102

105103
const onBlur = useCallback(() => {
106104
const value = get(data, valuePath);
107-
108-
if (initialValidationTrigger) {
109-
setInitialValidationTrigger(false);
105+
if (validationGrace) {
106+
setValidationGrace(false);
110107
viewerCommands.updateFieldInstanceValidation(fieldInstance, value);
111108
}
112-
113109
eventBus.fire('formField.blur', { formField: field });
114-
}, [data, eventBus, field, fieldInstance, initialValidationTrigger, valuePath, viewerCommands]);
110+
}, [data, eventBus, field, fieldInstance, validationGrace, valuePath, viewerCommands]);
115111

116112
const onFocus = useCallback(() => {
117113
eventBus.fire('formField.focus', { formField: field });
118114
}, [eventBus, field]);
119115

120116
const onChange = useCallback(
121117
(update) => {
122-
setInitialValidationTrigger(false);
118+
setValidationGrace(false);
123119
_onChange({ field, indexes, fieldInstance, ...update });
124120
},
125121
[_onChange, field, fieldInstance, indexes],
126122
);
127123

124+
const fieldErrors = useMemo(() => {
125+
if (validationGrace) {
126+
return [];
127+
}
128+
129+
return get(errors, [field.id, ...Object.values(indexes || {})]) || [];
130+
}, [errors, field.id, indexes, validationGrace]);
131+
128132
if (hidden) {
129133
return <Hidden field={field} />;
130134
}
131135

132136
const domId = `${prefixId(field.id, formId, indexes)}`;
133-
const fieldErrors = get(errors, [field.id, ...Object.values(indexes || {})]) || [];
134137

135138
const formFieldElement = (
136139
<FormFieldComponent
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { createContext } from 'preact';
2+
3+
export const ExpressionContextInfo = createContext({
4+
data: {},
5+
segments: [],
6+
});

packages/form-js-viewer/src/render/context/LocalExpressionContext.js

Lines changed: 0 additions & 8 deletions
This file was deleted.
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
export { FormRenderContext } from './FormRenderContext';
2-
export { LocalExpressionContext } from './LocalExpressionContext';
2+
export { ExpressionContextInfo } from './ExpressionContextInfo';
33
export { FormContext } from './FormContext';

packages/form-js-viewer/src/render/hooks/useCondition.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useService } from './useService.js';
22
import { useContext, useMemo } from 'preact/hooks';
3-
import { LocalExpressionContext } from '../context/LocalExpressionContext.js';
3+
import { ExpressionContextInfo } from '../context/ExpressionContextInfo.js';
44
import { buildExpressionContext } from '../../util/expressions.js';
55

66
/**
@@ -12,7 +12,7 @@ import { buildExpressionContext } from '../../util/expressions.js';
1212
*/
1313
export function useCondition(condition) {
1414
const conditionChecker = useService('conditionChecker', false);
15-
const expressionContextInfo = useContext(LocalExpressionContext);
15+
const expressionContextInfo = useContext(ExpressionContextInfo);
1616

1717
return useMemo(() => {
1818
return conditionChecker ? conditionChecker.check(condition, buildExpressionContext(expressionContextInfo)) : null;

packages/form-js-viewer/src/render/hooks/useExpressionEvaluation.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { useService } from './useService';
2-
import { LocalExpressionContext } from '../context/LocalExpressionContext';
2+
import { ExpressionContextInfo } from '../context/ExpressionContextInfo';
33
import { useContext, useMemo } from 'preact/hooks';
44
import { runExpressionEvaluation } from '../../util/expressions';
55

@@ -13,7 +13,7 @@ import { runExpressionEvaluation } from '../../util/expressions';
1313
*/
1414
export function useExpressionEvaluation(value) {
1515
const expressionLanguage = useService('expressionLanguage');
16-
const expressionContextInfo = useContext(LocalExpressionContext);
16+
const expressionContextInfo = useContext(ExpressionContextInfo);
1717
return useMemo(
1818
() => runExpressionEvaluation(expressionLanguage, value, expressionContextInfo),
1919
[expressionLanguage, expressionContextInfo, value],

0 commit comments

Comments
 (0)