Skip to content

Commit 166f7fc

Browse files
committed
wip
1 parent 08cc3e7 commit 166f7fc

File tree

2 files changed

+93
-22
lines changed

2 files changed

+93
-22
lines changed

packages/form-js-editor/src/features/properties-panel/entries/JSFunctionEntry.js

Lines changed: 51 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { FeelEntry, isFeelEntryEdited, TextAreaEntry, isTextAreaEntryEdited } from '@bpmn-io/properties-panel';
1+
import { FeelEntry, isFeelEntryEdited, TextAreaEntry, isTextAreaEntryEdited, ToggleSwitchEntry, isToggleSwitchEntryEdited } from '@bpmn-io/properties-panel';
22
import { get } from 'min-dash';
33

44
import { useService, useVariables } from '../hooks';
@@ -12,7 +12,7 @@ export function JSFunctionEntry(props) {
1212
const entries = [
1313
{
1414
id: 'variable-mappings',
15-
component: VariableMappings,
15+
component: FunctionParameters,
1616
editField: editField,
1717
field: field,
1818
isEdited: isFeelEntryEdited,
@@ -25,13 +25,21 @@ export function JSFunctionEntry(props) {
2525
field: field,
2626
isEdited: isTextAreaEntryEdited,
2727
isDefaultVisible: (field) => field.type === 'jsfunc'
28+
},
29+
{
30+
id: 'on-load-only',
31+
component: OnLoadOnlyEntry,
32+
editField: editField,
33+
field: field,
34+
isEdited: isToggleSwitchEntryEdited,
35+
isDefaultVisible: (field) => field.type === 'jsfunc'
2836
}
2937
];
3038

3139
return entries;
3240
}
3341

34-
function VariableMappings(props) {
42+
function FunctionParameters(props) {
3543
const {
3644
editField,
3745
field,
@@ -42,7 +50,7 @@ function VariableMappings(props) {
4250

4351
const variables = useVariables().map(name => ({ name }));
4452

45-
const path = [ 'variableMappings' ];
53+
const path = [ 'functionParameters' ];
4654

4755
const getValue = () => {
4856
return get(field, path, '');
@@ -52,13 +60,23 @@ function VariableMappings(props) {
5260
return editField(field, path, value || '');
5361
};
5462

63+
const tooltip = <div>
64+
Functions parameters should be described as an object, e.g.:
65+
<pre><code>{`{
66+
name: user.name,
67+
age: user.age
68+
}`}</code></pre>
69+
</div>;
70+
5571
return FeelEntry({
5672
debounce,
5773
feel: 'required',
5874
element: field,
5975
getValue,
6076
id,
61-
label: 'Variable mappings',
77+
label: 'Function parameters',
78+
tooltip,
79+
description: 'Define the parameters to pass to the javascript context.',
6280
setValue,
6381
variables
6482
});
@@ -87,8 +105,35 @@ function FunctionDefinition(props) {
87105
debounce,
88106
element: field,
89107
getValue,
108+
description: 'Access function parameters via `data`, set results with `setValue`, and register cleanup functions with `onCleanup`.',
109+
id,
110+
label: 'Javascript code',
111+
setValue
112+
});
113+
}
114+
115+
function OnLoadOnlyEntry(props) {
116+
const {
117+
editField,
118+
field,
119+
id
120+
} = props;
121+
122+
const path = [ 'onLoadOnly' ];
123+
124+
const getValue = () => {
125+
return !!get(field, path, false);
126+
};
127+
128+
const setValue = (value) => {
129+
editField(field, path, value);
130+
};
131+
132+
return ToggleSwitchEntry({
133+
element: field,
90134
id,
91-
label: 'Function',
135+
label: 'Execute on load only',
136+
getValue,
92137
setValue
93138
});
94139
}
Lines changed: 42 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,27 @@
1-
import { useCallback, useEffect } from 'preact/hooks';
2-
import { useExpressionEvaluation, useDeepCompareMemoize } from '../../hooks';
1+
import { useCallback, useEffect, useState } from 'preact/hooks';
2+
import { useExpressionEvaluation, useDeepCompareMemoize, usePrevious } from '../../hooks';
33
import { isObject } from 'min-dash';
44

55
const type = 'jsfunc';
66

77
export function JSFunctionField(props) {
88
const { field, onChange } = props;
9-
const { jsFunction, variableMappings } = field;
9+
const { jsFunction, functionParameters, onLoadOnly } = field;
1010

11-
const data = useExpressionEvaluation(variableMappings);
12-
const dataMemo = useDeepCompareMemoize(data);
11+
const [ loadLatch, setLoadLatch ] = useState(false);
1312

14-
const evaluateExpression = useCallback(() => {
15-
try {
13+
const paramsEval = useExpressionEvaluation(functionParameters);
14+
const params = useDeepCompareMemoize(isObject(paramsEval) ? paramsEval : {});
1615

17-
// return early if no dependency data
18-
if (!isObject(dataMemo) || !Object.keys(dataMemo).length) {
19-
return;
20-
}
16+
const functionMemo = useCallback((params) => {
17+
18+
const cleanupCallbacks = [];
2119

22-
const func = new Function('data', 'setResult', jsFunction);
23-
func(dataMemo, value => onChange({ field, value }));
20+
try {
21+
22+
setLoadLatch(true);
23+
const func = new Function('data', 'setValue', 'onCleanup', jsFunction);
24+
func(params, value => onChange({ field, value }), callback => cleanupCallbacks.push(callback));
2425

2526
} catch (error) {
2627

@@ -32,11 +33,34 @@ export function JSFunctionField(props) {
3233
console.error('Error evaluating expression:', error);
3334
onChange({ field, value: null });
3435
}
35-
}, [ jsFunction, dataMemo, field, onChange ]);
36+
37+
return () => {
38+
cleanupCallbacks.forEach(fn => fn());
39+
};
40+
41+
}, [ jsFunction, field, onChange ]);
42+
43+
const previousFunctionMemo = usePrevious(functionMemo);
44+
const previousParams = usePrevious(params);
3645

3746
useEffect(() => {
38-
evaluateExpression();
39-
}, [ evaluateExpression ]);
47+
48+
// reset load latch
49+
if (!onLoadOnly && loadLatch) {
50+
setLoadLatch(false);
51+
}
52+
53+
const functionChanged = previousFunctionMemo !== functionMemo;
54+
const paramsChanged = previousParams !== params;
55+
const alreadyLoaded = onLoadOnly && loadLatch;
56+
57+
const shouldExecute = functionChanged || paramsChanged && !alreadyLoaded;
58+
59+
if (shouldExecute) {
60+
return functionMemo(params);
61+
}
62+
63+
}, [ previousFunctionMemo, functionMemo, previousParams, params, loadLatch, onLoadOnly ]);
4064

4165
return null;
4266
}
@@ -48,6 +72,8 @@ JSFunctionField.config = {
4872
keyed: true,
4973
escapeGridRender: true,
5074
create: (options = {}) => ({
75+
jsFunction: 'setValue(data.value)',
76+
functionParameters: '={\n value: 42\n}',
5177
...options,
5278
})
5379
};

0 commit comments

Comments
 (0)