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

Commit

Permalink
feat: add 'enabled' conditional expression (#49)
Browse files Browse the repository at this point in the history
  • Loading branch information
billiegoose authored Apr 9, 2019
1 parent 8f1cf0e commit 6cc7400
Show file tree
Hide file tree
Showing 8 changed files with 137 additions and 7 deletions.
5 changes: 5 additions & 0 deletions docs/FormtronSchema.md
Original file line number Diff line number Diff line change
Expand Up @@ -211,10 +211,15 @@ Just add a `show` property to a form field with a JavaScript expression in a str
```

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

### Conditional enable/disable of fields <!-- omit in toc -->

Exactly the same as for `show` except the field is called `enabled`.

### Dynamic `options` for selects <!-- omit in toc -->

The ui-kit "select" and "multiselect" Formtron components allow specifying an `evalOptions` property.
Expand Down
3 changes: 3 additions & 0 deletions formtron-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
"show": {
"type": "string"
},
"enabled": {
"type": "string"
},
"area": {
"type": "string"
},
Expand Down
19 changes: 19 additions & 0 deletions src/__stories__/examples/enable/data.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"_selection": "securityDefinitions.petstore_auth",
"securityDefinitions": {
"petstore_auth": {
"type": "oauth2",
"authorizationUrl": "https://petstore.swagger.io/oauth/dialog",
"flow": "implicit",
"scopes": {
"write:pets": "modify pets in your account",
"read:pets": "read your pets"
}
},
"api_key": {
"type": "apiKey",
"name": "api_key",
"in": "header"
}
}
}
62 changes: 62 additions & 0 deletions src/__stories__/examples/enable/schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
{
"$schema": "../../../../ui-schema.json",
"type": "form",
"title": "Security",
"description": "A shared security scheme definition",
"fields": {
"securityDefinitions.?": {
"type": "string",
"title": "securityId"
},
"securityDefinitions.*.type": {
"type": "select",
"title": "Type",
"options": ["basic", "apiKey", "oauth2"]
},
"securityDefinitions.*.description": {
"type": "string",
"title": "Description"
},
"securityDefinitions.*.name": {
"type": "string",
"title": "Name",
"enabled": "type === 'apiKey'"
},
"securityDefinitions.*.in": {
"type": "select",
"title": "In",
"options": ["query", "header"],
"enabled": "type === 'apiKey'"
},
"securityDefinitions.*.flow": {
"type": "select",
"title": "Flow",
"options": ["implicit", "password", "application", "accessCode"],
"enabled": "type === 'oauth2'"
},
"securityDefinitions.*.authorizationUrl": {
"type": "string",
"title": "Authorization URL",
"enabled": "type === 'oauth2' && (flow === 'implicit' || flow === 'accessCode')"
},
"securityDefinitions.*.tokenUrl": {
"type": "string",
"title": "Token URL",
"enabled": "type === 'oauth2' && (flow === 'password' || flow === 'application' || flow === 'accessCode')"
},
"securityDefinitions.*.scopes": {
"type": "object",
"title": "Scopes",
"default": "",
"keys": {
"type": "string",
"title": "Name"
},
"values": {
"type": "string",
"title": "Description"
},
"enabled": "type === 'oauth2'"
}
}
}
6 changes: 6 additions & 0 deletions src/__stories__/stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ const objectSchema = require('./examples/object/schema.json');
const showData = require('./examples/show/data.json');
const showSchema = require('./examples/show/schema.json');

const enableData = require('./examples/enable/data.json');
const enableSchema = require('./examples/enable/schema.json');

const evalOptionsData = require('./examples/evalOptions/data.json');
const evalOptionsSchema = require('./examples/evalOptions/schema.json');

Expand Down Expand Up @@ -82,6 +85,9 @@ storiesOf('formtron', module)
.add('show', () => {
return <FormtronDebugger input={showData} schema={showSchema} selection={showData._selection} />;
})
.add('enable', () => {
return <FormtronDebugger input={enableData} schema={enableSchema} selection={enableData._selection} />;
})
.add('evalOptions', () => {
return (
<FormtronDebugger input={evalOptionsData} schema={evalOptionsSchema} selection={evalOptionsData._selection} />
Expand Down
12 changes: 8 additions & 4 deletions src/components/Form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,15 @@ export const Form: React.FunctionComponent<IFormtronControl> = ({
const { title, description, fields, layouts } = schema;

const gridAreaToName = {};
const shortValue = {};
const fallbackRows = [];
let contentElems: React.ReactElement | React.ReactElement[] = [];

for (const fieldName in fields) {
const { area } = fields[fieldName];
const gridArea = area || shortName(fieldName);
gridAreaToName[gridArea] = fieldName;
shortValue[gridArea] = value[fieldName];
fallbackRows.push([gridArea]);
}

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

const propSchema = schema.fields[name];
const { show, evalOptions, type } = propSchema;
const { show, evalOptions, enabled, type } = propSchema;

// if evalutating show is false skip area
if (show && !evaluate(show, value, name, true)) {
if (show && !evaluate(show, shortValue, gridArea, true)) {
return;
}

if (evalOptions) {
propSchema.options = evaluate(evalOptions, value, name, []);
propSchema.options = evaluate(evalOptions, shortValue, gridArea, []);
}

const enableField = !enabled || evaluate(enabled, shortValue, gridArea, true);

const Widget = fieldComponents[type];
if (Widget === undefined) {
cells.push(<Box flex={flex[gridArea]}>No appropriate widget could be found for type "{type}"</Box>);
Expand All @@ -92,7 +96,7 @@ export const Form: React.FunctionComponent<IFormtronControl> = ({
onChange(v);
}}
fieldComponents={fieldComponents}
disabled={disabled}
disabled={disabled || !enableField}
layout={layout}
/>
</Box>
Expand Down
4 changes: 1 addition & 3 deletions src/components/evaluate.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
const expr = require('expression-eval');
const memoize = require('lodash/memoize');
import { shortName } from './utils/shortName';

// Compile expression or return cached compiled expression
const compile = memoize(expr.compile);
Expand All @@ -12,8 +11,7 @@ export function evaluate(str: string, context: any, currentProp: string, fallbac
// Only consider properties ABOVE the current property in the schema
// (This enforces a top-to-bottom data dependency which is just nice.)
if (prop === currentProp) break;
const short = shortName(prop);
_context[short] = context[prop];
_context[prop] = context[prop];
}
// Evaluate expression
try {
Expand Down
33 changes: 33 additions & 0 deletions ui-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@
"show": {
"type": "string"
},
"enabled": {
"type": "string"
},
"layouts": {
"type": "object",
"additionalProperties": {
Expand Down Expand Up @@ -78,6 +81,9 @@
"show": {
"type": "string"
},
"enabled": {
"type": "string"
},
"area": {
"type": "string"
},
Expand Down Expand Up @@ -105,6 +111,9 @@
"show": {
"type": "string"
},
"enabled": {
"type": "string"
},
"area": {
"type": "string"
},
Expand Down Expand Up @@ -135,6 +144,9 @@
"show": {
"type": "string"
},
"enabled": {
"type": "string"
},
"area": {
"type": "string"
},
Expand All @@ -159,6 +171,9 @@
"show": {
"type": "string"
},
"enabled": {
"type": "string"
},
"area": {
"type": "string"
},
Expand Down Expand Up @@ -192,6 +207,9 @@
"show": {
"type": "string"
},
"enabled": {
"type": "string"
},
"area": {
"type": "string"
},
Expand Down Expand Up @@ -231,6 +249,9 @@
"show": {
"type": "string"
},
"enabled": {
"type": "string"
},
"area": {
"type": "string"
},
Expand Down Expand Up @@ -270,6 +291,9 @@
"show": {
"type": "string"
},
"enabled": {
"type": "string"
},
"area": {
"type": "string"
},
Expand All @@ -296,6 +320,9 @@
"show": {
"type": "string"
},
"enabled": {
"type": "string"
},
"area": {
"type": "string"
},
Expand Down Expand Up @@ -323,6 +350,9 @@
"show": {
"type": "string"
},
"enabled": {
"type": "string"
},
"area": {
"type": "string"
},
Expand All @@ -344,6 +374,9 @@
"show": {
"type": "string"
},
"enabled": {
"type": "string"
},
"area": {
"type": "string"
},
Expand Down

0 comments on commit 6cc7400

Please sign in to comment.