Skip to content
This repository was archived by the owner on Jun 28, 2024. It is now read-only.

Commit 6cc7400

Browse files
authored
feat: add 'enabled' conditional expression (#49)
1 parent 8f1cf0e commit 6cc7400

File tree

8 files changed

+137
-7
lines changed

8 files changed

+137
-7
lines changed

docs/FormtronSchema.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,10 +211,15 @@ Just add a `show` property to a form field with a JavaScript expression in a str
211211
```
212212

213213
The variables used in expressions are the field keys, trimmed after the last period.
214+
(Setting an `area` overrides this and will use that as the variable name.)
214215
You can only reference a field that precedes the current field.
215216
(E.g. you cannot have a field's visibility depend on its own value, or the value of a field below it.)
216217
This ensures a nice top-to-bottom data dependency that keeps the form from becoming a nightmare to debug.
217218

219+
### Conditional enable/disable of fields <!-- omit in toc -->
220+
221+
Exactly the same as for `show` except the field is called `enabled`.
222+
218223
### Dynamic `options` for selects <!-- omit in toc -->
219224

220225
The ui-kit "select" and "multiselect" Formtron components allow specifying an `evalOptions` property.

formtron-schema.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616
"show": {
1717
"type": "string"
1818
},
19+
"enabled": {
20+
"type": "string"
21+
},
1922
"area": {
2023
"type": "string"
2124
},
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"_selection": "securityDefinitions.petstore_auth",
3+
"securityDefinitions": {
4+
"petstore_auth": {
5+
"type": "oauth2",
6+
"authorizationUrl": "https://petstore.swagger.io/oauth/dialog",
7+
"flow": "implicit",
8+
"scopes": {
9+
"write:pets": "modify pets in your account",
10+
"read:pets": "read your pets"
11+
}
12+
},
13+
"api_key": {
14+
"type": "apiKey",
15+
"name": "api_key",
16+
"in": "header"
17+
}
18+
}
19+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
{
2+
"$schema": "../../../../ui-schema.json",
3+
"type": "form",
4+
"title": "Security",
5+
"description": "A shared security scheme definition",
6+
"fields": {
7+
"securityDefinitions.?": {
8+
"type": "string",
9+
"title": "securityId"
10+
},
11+
"securityDefinitions.*.type": {
12+
"type": "select",
13+
"title": "Type",
14+
"options": ["basic", "apiKey", "oauth2"]
15+
},
16+
"securityDefinitions.*.description": {
17+
"type": "string",
18+
"title": "Description"
19+
},
20+
"securityDefinitions.*.name": {
21+
"type": "string",
22+
"title": "Name",
23+
"enabled": "type === 'apiKey'"
24+
},
25+
"securityDefinitions.*.in": {
26+
"type": "select",
27+
"title": "In",
28+
"options": ["query", "header"],
29+
"enabled": "type === 'apiKey'"
30+
},
31+
"securityDefinitions.*.flow": {
32+
"type": "select",
33+
"title": "Flow",
34+
"options": ["implicit", "password", "application", "accessCode"],
35+
"enabled": "type === 'oauth2'"
36+
},
37+
"securityDefinitions.*.authorizationUrl": {
38+
"type": "string",
39+
"title": "Authorization URL",
40+
"enabled": "type === 'oauth2' && (flow === 'implicit' || flow === 'accessCode')"
41+
},
42+
"securityDefinitions.*.tokenUrl": {
43+
"type": "string",
44+
"title": "Token URL",
45+
"enabled": "type === 'oauth2' && (flow === 'password' || flow === 'application' || flow === 'accessCode')"
46+
},
47+
"securityDefinitions.*.scopes": {
48+
"type": "object",
49+
"title": "Scopes",
50+
"default": "",
51+
"keys": {
52+
"type": "string",
53+
"title": "Name"
54+
},
55+
"values": {
56+
"type": "string",
57+
"title": "Description"
58+
},
59+
"enabled": "type === 'oauth2'"
60+
}
61+
}
62+
}

src/__stories__/stories.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ const objectSchema = require('./examples/object/schema.json');
3838
const showData = require('./examples/show/data.json');
3939
const showSchema = require('./examples/show/schema.json');
4040

41+
const enableData = require('./examples/enable/data.json');
42+
const enableSchema = require('./examples/enable/schema.json');
43+
4144
const evalOptionsData = require('./examples/evalOptions/data.json');
4245
const evalOptionsSchema = require('./examples/evalOptions/schema.json');
4346

@@ -82,6 +85,9 @@ storiesOf('formtron', module)
8285
.add('show', () => {
8386
return <FormtronDebugger input={showData} schema={showSchema} selection={showData._selection} />;
8487
})
88+
.add('enable', () => {
89+
return <FormtronDebugger input={enableData} schema={enableSchema} selection={enableData._selection} />;
90+
})
8591
.add('evalOptions', () => {
8692
return (
8793
<FormtronDebugger input={evalOptionsData} schema={evalOptionsSchema} selection={evalOptionsData._selection} />

src/components/Form.tsx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,15 @@ export const Form: React.FunctionComponent<IFormtronControl> = ({
2626
const { title, description, fields, layouts } = schema;
2727

2828
const gridAreaToName = {};
29+
const shortValue = {};
2930
const fallbackRows = [];
3031
let contentElems: React.ReactElement | React.ReactElement[] = [];
3132

3233
for (const fieldName in fields) {
3334
const { area } = fields[fieldName];
3435
const gridArea = area || shortName(fieldName);
3536
gridAreaToName[gridArea] = fieldName;
37+
shortValue[gridArea] = value[fieldName];
3638
fallbackRows.push([gridArea]);
3739
}
3840

@@ -59,17 +61,19 @@ export const Form: React.FunctionComponent<IFormtronControl> = ({
5961
const formId = `${name}-${index}`;
6062

6163
const propSchema = schema.fields[name];
62-
const { show, evalOptions, type } = propSchema;
64+
const { show, evalOptions, enabled, type } = propSchema;
6365

6466
// if evalutating show is false skip area
65-
if (show && !evaluate(show, value, name, true)) {
67+
if (show && !evaluate(show, shortValue, gridArea, true)) {
6668
return;
6769
}
6870

6971
if (evalOptions) {
70-
propSchema.options = evaluate(evalOptions, value, name, []);
72+
propSchema.options = evaluate(evalOptions, shortValue, gridArea, []);
7173
}
7274

75+
const enableField = !enabled || evaluate(enabled, shortValue, gridArea, true);
76+
7377
const Widget = fieldComponents[type];
7478
if (Widget === undefined) {
7579
cells.push(<Box flex={flex[gridArea]}>No appropriate widget could be found for type "{type}"</Box>);
@@ -92,7 +96,7 @@ export const Form: React.FunctionComponent<IFormtronControl> = ({
9296
onChange(v);
9397
}}
9498
fieldComponents={fieldComponents}
95-
disabled={disabled}
99+
disabled={disabled || !enableField}
96100
layout={layout}
97101
/>
98102
</Box>

src/components/evaluate.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
const expr = require('expression-eval');
22
const memoize = require('lodash/memoize');
3-
import { shortName } from './utils/shortName';
43

54
// Compile expression or return cached compiled expression
65
const compile = memoize(expr.compile);
@@ -12,8 +11,7 @@ export function evaluate(str: string, context: any, currentProp: string, fallbac
1211
// Only consider properties ABOVE the current property in the schema
1312
// (This enforces a top-to-bottom data dependency which is just nice.)
1413
if (prop === currentProp) break;
15-
const short = shortName(prop);
16-
_context[short] = context[prop];
14+
_context[prop] = context[prop];
1715
}
1816
// Evaluate expression
1917
try {

ui-schema.json

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@
4545
"show": {
4646
"type": "string"
4747
},
48+
"enabled": {
49+
"type": "string"
50+
},
4851
"layouts": {
4952
"type": "object",
5053
"additionalProperties": {
@@ -78,6 +81,9 @@
7881
"show": {
7982
"type": "string"
8083
},
84+
"enabled": {
85+
"type": "string"
86+
},
8187
"area": {
8288
"type": "string"
8389
},
@@ -105,6 +111,9 @@
105111
"show": {
106112
"type": "string"
107113
},
114+
"enabled": {
115+
"type": "string"
116+
},
108117
"area": {
109118
"type": "string"
110119
},
@@ -135,6 +144,9 @@
135144
"show": {
136145
"type": "string"
137146
},
147+
"enabled": {
148+
"type": "string"
149+
},
138150
"area": {
139151
"type": "string"
140152
},
@@ -159,6 +171,9 @@
159171
"show": {
160172
"type": "string"
161173
},
174+
"enabled": {
175+
"type": "string"
176+
},
162177
"area": {
163178
"type": "string"
164179
},
@@ -192,6 +207,9 @@
192207
"show": {
193208
"type": "string"
194209
},
210+
"enabled": {
211+
"type": "string"
212+
},
195213
"area": {
196214
"type": "string"
197215
},
@@ -231,6 +249,9 @@
231249
"show": {
232250
"type": "string"
233251
},
252+
"enabled": {
253+
"type": "string"
254+
},
234255
"area": {
235256
"type": "string"
236257
},
@@ -270,6 +291,9 @@
270291
"show": {
271292
"type": "string"
272293
},
294+
"enabled": {
295+
"type": "string"
296+
},
273297
"area": {
274298
"type": "string"
275299
},
@@ -296,6 +320,9 @@
296320
"show": {
297321
"type": "string"
298322
},
323+
"enabled": {
324+
"type": "string"
325+
},
299326
"area": {
300327
"type": "string"
301328
},
@@ -323,6 +350,9 @@
323350
"show": {
324351
"type": "string"
325352
},
353+
"enabled": {
354+
"type": "string"
355+
},
326356
"area": {
327357
"type": "string"
328358
},
@@ -344,6 +374,9 @@
344374
"show": {
345375
"type": "string"
346376
},
377+
"enabled": {
378+
"type": "string"
379+
},
347380
"area": {
348381
"type": "string"
349382
},

0 commit comments

Comments
 (0)