react-jsonschema-form-conditionals 0.3.15 → 0.3.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -201
- package/README.md +752 -752
- package/dist/main.js +8 -8
- package/dist/main.js.map +1 -1
- package/lib/applyRules.js +21 -26
- package/package.json +123 -123
package/README.md
CHANGED
@@ -1,752 +1,752 @@
|
|
1
|
-
[](https://travis-ci.org/RxNT/react-jsonschema-form-conditionals)
|
2
|
-
[](https://coveralls.io/github/RxNT/react-jsonschema-form-conditionals?branch=master)
|
3
|
-
[](https://badge.fury.io/js/react-jsonschema-form-conditionals)
|
4
|
-
# Form with conditionals
|
5
|
-
|
6
|
-
This project extends [react-jsonschema-form](https://github.com/mozilla-services/react-jsonschema-form) with
|
7
|
-
conditional logic, which allow to have more complicated logic expressed and controlled with JSON schema.
|
8
|
-
This is primarily useful for complicated schemas with extended business logic,
|
9
|
-
which are suspect to changes and need to be manageable and changeable without modifying running application.
|
10
|
-
|
11
|
-
If you need simple rule logic, that does not change a lot, you can use original [mozilla project](https://github.com/mozilla-services/react-jsonschema-form),
|
12
|
-
by following examples like https://jsfiddle.net/69z2wepo/68259/
|
13
|
-
|
14
|
-
The project is done to be fully compatible with mozilla,
|
15
|
-
without imposing additional limitations.
|
16
|
-
|
17
|
-
## Features
|
18
|
-
|
19
|
-
- Support for [Json Rules Engine](https://github.com/CacheControl/json-rules-engine) and [json-rules-engine-simplified](https://github.com/RxNT/json-rules-engine-simplified)
|
20
|
-
- Extensible action mechanism
|
21
|
-
- Configuration over coding
|
22
|
-
- Lightweight and extensible
|
23
|
-
|
24
|
-
## Installation
|
25
|
-
|
26
|
-
Install `react-jsonschema-form-conditionals` by running:
|
27
|
-
|
28
|
-
```bash
|
29
|
-
npm install --s react-jsonschema-form-conditionals
|
30
|
-
```
|
31
|
-
|
32
|
-
## Usage
|
33
|
-
|
34
|
-
The simplest example of using `react-jsonschema-form-conditionals`
|
35
|
-
|
36
|
-
```jsx
|
37
|
-
import applyRules from 'react-jsonschema-form-conditionals';
|
38
|
-
import Engine from 'json-rules-engine-simplified';
|
39
|
-
import Form from "react-jsonschema-form";
|
40
|
-
|
41
|
-
...
|
42
|
-
|
43
|
-
const rules = [{
|
44
|
-
...
|
45
|
-
}];
|
46
|
-
|
47
|
-
let FormWithConditionals = applyRules(schema, uiSchema, rules, Engine)(Form);
|
48
|
-
|
49
|
-
ReactDOM.render(
|
50
|
-
<FormWithConditionals .../>,
|
51
|
-
document.querySelector('#app')
|
52
|
-
);
|
53
|
-
```
|
54
|
-
|
55
|
-
To show case uses for this library we'll be using simple registration schema example
|
56
|
-
|
57
|
-
```jsx
|
58
|
-
|
59
|
-
import applyRules from 'react-jsonschema-form-conditionals';
|
60
|
-
import Form from "react-jsonschema-form";
|
61
|
-
|
62
|
-
let schema = {
|
63
|
-
definitions: {
|
64
|
-
hobby: {
|
65
|
-
type: "object",
|
66
|
-
properties: {
|
67
|
-
name: { type: "string" },
|
68
|
-
durationInMonth: { "type": "integer" },
|
69
|
-
}
|
70
|
-
}
|
71
|
-
},
|
72
|
-
title: "A registration form",
|
73
|
-
description: "A simple form example.",
|
74
|
-
type: "object",
|
75
|
-
required: [
|
76
|
-
"firstName",
|
77
|
-
"lastName"
|
78
|
-
],
|
79
|
-
properties: {
|
80
|
-
firstName: {
|
81
|
-
type: "string",
|
82
|
-
title: "First name"
|
83
|
-
},
|
84
|
-
lastName: {
|
85
|
-
type: "string",
|
86
|
-
title: "Last name"
|
87
|
-
},
|
88
|
-
age: {
|
89
|
-
type: "integer",
|
90
|
-
title: "Age",
|
91
|
-
},
|
92
|
-
bio: {
|
93
|
-
type: "string",
|
94
|
-
title: "Bio",
|
95
|
-
},
|
96
|
-
country: {
|
97
|
-
type: "string",
|
98
|
-
title: "Country"
|
99
|
-
},
|
100
|
-
state: {
|
101
|
-
type: "string",
|
102
|
-
title: "State"
|
103
|
-
},
|
104
|
-
zip: {
|
105
|
-
type: "string",
|
106
|
-
title: "ZIP"
|
107
|
-
},
|
108
|
-
password: {
|
109
|
-
type: "string",
|
110
|
-
title: "Password",
|
111
|
-
minLength: 3
|
112
|
-
},
|
113
|
-
telephone: {
|
114
|
-
type: "string",
|
115
|
-
title: "Telephone",
|
116
|
-
minLength: 10
|
117
|
-
},
|
118
|
-
hobbies: {
|
119
|
-
type: "array",
|
120
|
-
items: { "$ref": "#/definitions/hobby" }
|
121
|
-
}
|
122
|
-
}
|
123
|
-
}
|
124
|
-
|
125
|
-
let rules = [{
|
126
|
-
...
|
127
|
-
}]
|
128
|
-
|
129
|
-
let FormWithConditionals = applyRules(schema, uiSchema, rules, Engine)(Form);
|
130
|
-
|
131
|
-
render((
|
132
|
-
<FormWithConditionals />
|
133
|
-
), document.getElementById("app"));
|
134
|
-
```
|
135
|
-
|
136
|
-
Conditionals functionality is build using 2 things
|
137
|
-
- Rules engine ([Json Rules Engine](https://github.com/CacheControl/json-rules-engine) or [Simplified Json Rules Engine](https://github.com/RxNT/json-rules-engine-simplified))
|
138
|
-
- Schema action mechanism
|
139
|
-
|
140
|
-
Rules engine responsibility is to trigger events, action mechanism
|
141
|
-
performs needed actions on the requests.
|
142
|
-
|
143
|
-
## Rules engine
|
144
|
-
|
145
|
-
Project supports 2 rules engines out of the box:
|
146
|
-
- [Json Rules Engine](https://github.com/CacheControl/json-rules-engine)
|
147
|
-
- [Simplified Json Rules Engine](https://github.com/RxNT/json-rules-engine-simplified)
|
148
|
-
|
149
|
-
In order to use either of those, you need to specify `Engine` in `applyRules` configuration.
|
150
|
-
|
151
|
-
For example:
|
152
|
-
|
153
|
-
To use [Simplified Json Rules Engine](https://github.com/RxNT/json-rules-engine-simplified), you can do following:
|
154
|
-
```js
|
155
|
-
|
156
|
-
import applyRules from 'react-jsonschema-form-conditionals';
|
157
|
-
import Form from "react-jsonschema-form";
|
158
|
-
|
159
|
-
import Engine from 'json-rules-engine-simplified';
|
160
|
-
|
161
|
-
...
|
162
|
-
|
163
|
-
let FormWithConditionals = applyRules(schema, uiSchema, rules, Engine)(Form);
|
164
|
-
|
165
|
-
ReactDOM.render(
|
166
|
-
<FormWithConditionals />,
|
167
|
-
document.querySelector('#app')
|
168
|
-
);
|
169
|
-
```
|
170
|
-
|
171
|
-
To use [Json Rules Engine](https://github.com/RxNT/json-rules-engine-simplified), is almost the same:
|
172
|
-
|
173
|
-
```js
|
174
|
-
|
175
|
-
import applyRules from 'react-jsonschema-form-conditionals';
|
176
|
-
import Engine from 'json-rules-engine';
|
177
|
-
import Form from "react-jsonschema-form";
|
178
|
-
|
179
|
-
// ...
|
180
|
-
|
181
|
-
let FormWithConditionals = applyRules(schema, uiSchema, rules, Engine)(Form);
|
182
|
-
|
183
|
-
ReactDOM.render(
|
184
|
-
<FormWithConditionals />,
|
185
|
-
document.querySelector('#app')
|
186
|
-
);
|
187
|
-
```
|
188
|
-
|
189
|
-
### Extending rules engine
|
190
|
-
|
191
|
-
If non of the provided engines satisfies, your needs, you can
|
192
|
-
implement your own `Engine` which should
|
193
|
-
comply to following:
|
194
|
-
|
195
|
-
```js
|
196
|
-
class Engine {
|
197
|
-
constructor(rules, schema) {
|
198
|
-
}
|
199
|
-
addRule = (rule) => {
|
200
|
-
}
|
201
|
-
run = (formData) => {
|
202
|
-
return Promise[Event]
|
203
|
-
}
|
204
|
-
}
|
205
|
-
```
|
206
|
-
|
207
|
-
Original `rules` and `schema` is used as a parameter for a factory call,
|
208
|
-
in order to be able to have additional functionality, such as rules to schema compliance validation,
|
209
|
-
like it's done in Simplified Json Rules Engine](https://github.com/RxNT/json-rules-engine-simplified)
|
210
|
-
|
211
|
-
## Schema action mechanism
|
212
|
-
|
213
|
-
Rules engine emits events, which are expected to have a `type` and `params` field,
|
214
|
-
`type` is used to distinguish action that is needed, `params` are used as input for that action:
|
215
|
-
|
216
|
-
```json
|
217
|
-
{
|
218
|
-
"type": "remove",
|
219
|
-
"params": {
|
220
|
-
"field": "name"
|
221
|
-
}
|
222
|
-
}
|
223
|
-
```
|
224
|
-
|
225
|
-
By default action mechanism defines a supported set of rules, which you can extend as needed:
|
226
|
-
|
227
|
-
- `remove` removes a field or set of fields from `schema` and `uiSchema`
|
228
|
-
- `require` makes a field or set of fields required
|
229
|
-
|
230
|
-
### Remove action
|
231
|
-
|
232
|
-
If you want to remove a field, your configuration should look like this:
|
233
|
-
|
234
|
-
```json
|
235
|
-
{
|
236
|
-
"conditions": { },
|
237
|
-
"event": {
|
238
|
-
"type": "remove",
|
239
|
-
"params": {
|
240
|
-
"field": "password"
|
241
|
-
}
|
242
|
-
}
|
243
|
-
}
|
244
|
-
```
|
245
|
-
When `condition` is met, `password` will be removed from both `schema` and `uiSchema`.
|
246
|
-
|
247
|
-
In case you want to remove multiple fields `name`, `password`, rule should look like this:
|
248
|
-
|
249
|
-
```json
|
250
|
-
{
|
251
|
-
"conditions": { },
|
252
|
-
"event": {
|
253
|
-
"type": "remove",
|
254
|
-
"params": {
|
255
|
-
"field": [ "name", "password" ]
|
256
|
-
}
|
257
|
-
}
|
258
|
-
}
|
259
|
-
```
|
260
|
-
|
261
|
-
To remove nested schema properties, use json dot notation. e.g. For schema object:
|
262
|
-
|
263
|
-
```json
|
264
|
-
{
|
265
|
-
"type": "object",
|
266
|
-
"properties": {
|
267
|
-
"someParentWrapper": {
|
268
|
-
"type": "object",
|
269
|
-
"properties": {
|
270
|
-
"booleanValA": {
|
271
|
-
"type": "boolean",
|
272
|
-
"title": "Some boolean input"
|
273
|
-
},
|
274
|
-
"booleanValB": {
|
275
|
-
"type": "boolean",
|
276
|
-
"title": "Another boolean input"
|
277
|
-
}
|
278
|
-
}
|
279
|
-
}
|
280
|
-
}
|
281
|
-
}
|
282
|
-
|
283
|
-
```
|
284
|
-
|
285
|
-
You can remove the nested booleanValA or booleanValB like so:
|
286
|
-
|
287
|
-
```json
|
288
|
-
{
|
289
|
-
"conditions": { },
|
290
|
-
"event": {
|
291
|
-
"type": "remove",
|
292
|
-
"params": {
|
293
|
-
"field": "someParentWrapper.booleanValA"
|
294
|
-
}
|
295
|
-
}
|
296
|
-
}
|
297
|
-
```
|
298
|
-
|
299
|
-
### Require action
|
300
|
-
|
301
|
-
The same convention goes for `require` action
|
302
|
-
|
303
|
-
For a single field:
|
304
|
-
|
305
|
-
```json
|
306
|
-
{
|
307
|
-
"conditions": { },
|
308
|
-
"event": {
|
309
|
-
"type": "require",
|
310
|
-
"params": {
|
311
|
-
"field": "password"
|
312
|
-
}
|
313
|
-
}
|
314
|
-
}
|
315
|
-
```
|
316
|
-
|
317
|
-
For multiple fields:
|
318
|
-
|
319
|
-
```json
|
320
|
-
{
|
321
|
-
"conditions": { },
|
322
|
-
"event": {
|
323
|
-
"type": "require",
|
324
|
-
"params": {
|
325
|
-
"field": [ "name", "password"]
|
326
|
-
}
|
327
|
-
}
|
328
|
-
}
|
329
|
-
```
|
330
|
-
|
331
|
-
## UiSchema actions
|
332
|
-
|
333
|
-
API defines a set of actions, that you can take on `uiSchema`, they cover most of the
|
334
|
-
|
335
|
-
- `uiAppend` appends `uiSchema` specified in params with an original `uiSchema`
|
336
|
-
- `uiOverride` replaces field in original `uiSchema` with fields in `params`, keeping unrelated entries
|
337
|
-
- `uiRepalce` replaces whole `uiSchema` with a conf schema
|
338
|
-
|
339
|
-
To show case, let's take a simple `schema`
|
340
|
-
|
341
|
-
```json
|
342
|
-
{
|
343
|
-
"properties": {
|
344
|
-
"lastName": { "type": "string" },
|
345
|
-
"firstName": { "type": "string" },
|
346
|
-
"nickName": { "type": "string"}
|
347
|
-
}
|
348
|
-
}
|
349
|
-
```
|
350
|
-
|
351
|
-
and `uiSchema`
|
352
|
-
|
353
|
-
```json
|
354
|
-
{
|
355
|
-
"ui:order": ["firstName"],
|
356
|
-
"lastName": {
|
357
|
-
"classNames": "col-md-1",
|
358
|
-
},
|
359
|
-
"firstName": {
|
360
|
-
"ui:disabled": false,
|
361
|
-
"num": 23
|
362
|
-
},
|
363
|
-
"nickName": {
|
364
|
-
"classNames": "col-md-12"
|
365
|
-
}
|
366
|
-
}
|
367
|
-
```
|
368
|
-
with event `params` something like this
|
369
|
-
```json
|
370
|
-
{
|
371
|
-
"ui:order": [ "lastName" ],
|
372
|
-
"lastName": {
|
373
|
-
"classNames": "has-error"
|
374
|
-
},
|
375
|
-
"firstName" : {
|
376
|
-
"classNames": "col-md-6",
|
377
|
-
"ui:disabled": true,
|
378
|
-
"num": 22
|
379
|
-
}
|
380
|
-
}
|
381
|
-
```
|
382
|
-
|
383
|
-
And look at different results depend on the choosen action.
|
384
|
-
|
385
|
-
### uiAppend
|
386
|
-
|
387
|
-
UiAppend can handle `arrays` and `string`, with fallback to `uiOverride` behavior for all other fields.
|
388
|
-
|
389
|
-
So the expected result `uiSchema` will be:
|
390
|
-
```json
|
391
|
-
{
|
392
|
-
"ui:order": ["firstName", "lastName"],
|
393
|
-
"lastName": {
|
394
|
-
"classNames": "col-md-1 has-error"
|
395
|
-
},
|
396
|
-
"firstName": {
|
397
|
-
"classNames": "col-md-6",
|
398
|
-
"ui:disabled": true,
|
399
|
-
"num": 22
|
400
|
-
},
|
401
|
-
"nickName": {
|
402
|
-
"classNames": "col-md-12"
|
403
|
-
}
|
404
|
-
}
|
405
|
-
```
|
406
|
-
|
407
|
-
In this case it
|
408
|
-
- added `lastName` to `ui:order` array,
|
409
|
-
- appended `has-error` to `classNames` in `lastName` field
|
410
|
-
- added `classNames` and enabled `firstName`
|
411
|
-
- as for the `num` in `firstName` it just overrode it
|
412
|
-
|
413
|
-
This is useful for example if you want to add some additional markup in your code, without touching layout that you've defined.
|
414
|
-
|
415
|
-
### uiOverride
|
416
|
-
|
417
|
-
`uiOverride` behaves similar to append, but instead of appending it completely replaces overlapping values
|
418
|
-
|
419
|
-
So the expected result `uiSchema` will be:
|
420
|
-
```json
|
421
|
-
{
|
422
|
-
"ui:order": [ "lastName" ],
|
423
|
-
"lastName": {
|
424
|
-
"classNames": "has-error"
|
425
|
-
},
|
426
|
-
"firstName": {
|
427
|
-
"classNames": "col-md-6",
|
428
|
-
"ui:disabled": true,
|
429
|
-
"num": 22
|
430
|
-
},
|
431
|
-
"nickName": {
|
432
|
-
"classNames": "col-md-12"
|
433
|
-
}
|
434
|
-
}
|
435
|
-
```
|
436
|
-
|
437
|
-
In this case it
|
438
|
-
- `ui:order` was replaced with configured value
|
439
|
-
- `className` for the `lastName` was replaced with `has-error`
|
440
|
-
- added `classNames` and enabled `firstName`
|
441
|
-
- as for the `num` in `firstName` it just overrode it
|
442
|
-
|
443
|
-
### uiReplace
|
444
|
-
|
445
|
-
`uiReplace` just replaces all fields in `uiSchema` with `params` fields, leaving unrelated fields untouched.
|
446
|
-
|
447
|
-
So the result `uiSchema` will be
|
448
|
-
```json
|
449
|
-
{
|
450
|
-
"ui:order": [ "lastName" ],
|
451
|
-
"lastName": {
|
452
|
-
"classNames": "has-error"
|
453
|
-
},
|
454
|
-
"firstName" : {
|
455
|
-
"classNames": "col-md-6",
|
456
|
-
"ui:disabled": true,
|
457
|
-
"num": 22
|
458
|
-
},
|
459
|
-
"nickName": {
|
460
|
-
"classNames": "col-md-12"
|
461
|
-
}
|
462
|
-
}
|
463
|
-
```
|
464
|
-
|
465
|
-
## Extension mechanism
|
466
|
-
|
467
|
-
You can extend existing actions list, by specifying `extraActions` on the form.
|
468
|
-
|
469
|
-
Let's say we need to introduce `replaceClassNames` action, that
|
470
|
-
would just specify `classNames` `col-md-4` for all fields except for `ignore`d one.
|
471
|
-
We also want to trigger it only when `password` is `empty`.
|
472
|
-
|
473
|
-
This is how we can do this:
|
474
|
-
|
475
|
-
```js
|
476
|
-
import applyRules from 'react-jsonschema-form-conditionals';
|
477
|
-
import Engine from 'json-rules-engine-simplified';
|
478
|
-
import Form from "react-jsonschema-form";
|
479
|
-
|
480
|
-
...
|
481
|
-
|
482
|
-
const rules = [
|
483
|
-
{
|
484
|
-
conditons: {
|
485
|
-
password: "empty"
|
486
|
-
},
|
487
|
-
event: {
|
488
|
-
type: "replaceClassNames",
|
489
|
-
params: {
|
490
|
-
classNames: "col-md-4",
|
491
|
-
ignore: [ "password" ]
|
492
|
-
}
|
493
|
-
}
|
494
|
-
}
|
495
|
-
];
|
496
|
-
|
497
|
-
|
498
|
-
let extraActions = {
|
499
|
-
replaceClassNames: function(params, schema, uiSchema, formData) {
|
500
|
-
Object.keys(schema.properties).forEach((field) => {
|
501
|
-
if (uiSchema[field] === undefined) {
|
502
|
-
uiSchema[field] = {}
|
503
|
-
}
|
504
|
-
uiSchema[field].classNames = params.classNames;
|
505
|
-
}
|
506
|
-
}
|
507
|
-
};
|
508
|
-
|
509
|
-
let FormWithConditionals = applyRules(schema, uiSchema, rules, Engine, extraActions)(Form);
|
510
|
-
|
511
|
-
ReactDOM.render(
|
512
|
-
<FormWithConditionals/>,
|
513
|
-
document.querySelector('#app')
|
514
|
-
);
|
515
|
-
```
|
516
|
-
|
517
|
-
Provided snippet does just that.
|
518
|
-
|
519
|
-
### Extension with calculated values
|
520
|
-
|
521
|
-
In case you need to calculate value, based on other field values, you can also do that.
|
522
|
-
|
523
|
-
Let's say we want to have schema with `a`, `b` and `sum` fields
|
524
|
-
|
525
|
-
```js
|
526
|
-
import applyRules from 'react-jsonschema-form-conditionals';
|
527
|
-
import Engine from 'json-rules-engine-simplified';
|
528
|
-
import Form from "react-jsonschema-form";
|
529
|
-
|
530
|
-
...
|
531
|
-
|
532
|
-
const rules = [
|
533
|
-
{
|
534
|
-
conditons: {
|
535
|
-
a: { not: "empty" },
|
536
|
-
b: { not: "empty" }
|
537
|
-
},
|
538
|
-
event: {
|
539
|
-
type: "updateSum"
|
540
|
-
}
|
541
|
-
}
|
542
|
-
];
|
543
|
-
|
544
|
-
|
545
|
-
let extraActions = {
|
546
|
-
updateSum: function(params, schema, uiSchema, formData) {
|
547
|
-
formData.sum = formData.a + formData.b;
|
548
|
-
}
|
549
|
-
};
|
550
|
-
|
551
|
-
let FormWithConditionals = applyRules(schema, uiSchema, rules, Engine, extraActions)(Form);
|
552
|
-
|
553
|
-
ReactDOM.render(
|
554
|
-
<FormWithConditionals/>,
|
555
|
-
document.querySelector('#app')
|
556
|
-
);
|
557
|
-
```
|
558
|
-
|
559
|
-
This is how you can do that.
|
560
|
-
|
561
|
-
> WARNING!!! You need to be careful with a rules order, when using calculated values.
|
562
|
-
> Put calculation rules at the top of your rules specification.
|
563
|
-
|
564
|
-
For example, let's say you want to mark `sum` field, if you have sum `greater` than `10`. The rule would look something like this:
|
565
|
-
|
566
|
-
```json
|
567
|
-
{
|
568
|
-
"conditions": {
|
569
|
-
"sum": { "greater" : 10 }
|
570
|
-
},
|
571
|
-
"event": {
|
572
|
-
"type": "appendClass",
|
573
|
-
"classNames": "has-success"
|
574
|
-
}
|
575
|
-
}
|
576
|
-
```
|
577
|
-
|
578
|
-
But it will work only if you put it after `updateSum` rule, like this
|
579
|
-
```json
|
580
|
-
[
|
581
|
-
{
|
582
|
-
"conditons": {
|
583
|
-
"a": { "not": "empty" },
|
584
|
-
"b": { "not": "empty" }
|
585
|
-
},
|
586
|
-
"event": {
|
587
|
-
"type": "updateSum"
|
588
|
-
}
|
589
|
-
},
|
590
|
-
{
|
591
|
-
"conditions": {
|
592
|
-
"sum": { "greater" : 10 }
|
593
|
-
},
|
594
|
-
"event": {
|
595
|
-
"type": "appendClass",
|
596
|
-
"classNames": "has-success"
|
597
|
-
}
|
598
|
-
}
|
599
|
-
];
|
600
|
-
```
|
601
|
-
|
602
|
-
Otherwise it will work with **old `sum` values** and therefor show incorrect value.
|
603
|
-
|
604
|
-
### Rules order
|
605
|
-
|
606
|
-
Originally actions performed in sequence defined in the array. If you have interdependent rules, that you need to run in order
|
607
|
-
you can specify `order` on a rule, so that it would be executed first. Rules are executed based on order from lowest to highest with
|
608
|
-
rules without order executed last.
|
609
|
-
|
610
|
-
For example to make updateSum work regardless the order rules were added, you can do following:
|
611
|
-
```json
|
612
|
-
[
|
613
|
-
{
|
614
|
-
"conditions": {
|
615
|
-
"sum": { "greater" : 10 }
|
616
|
-
},
|
617
|
-
"order": 1,
|
618
|
-
"event": {
|
619
|
-
"type": "appendClass",
|
620
|
-
"classNames": "has-success"
|
621
|
-
}
|
622
|
-
},
|
623
|
-
{
|
624
|
-
"conditons": {
|
625
|
-
"a": { "not": "empty" },
|
626
|
-
"b": { "not": "empty" }
|
627
|
-
},
|
628
|
-
"order": 0,
|
629
|
-
"event": {
|
630
|
-
"type": "updateSum"
|
631
|
-
}
|
632
|
-
}
|
633
|
-
]
|
634
|
-
```
|
635
|
-
Here although `updateSum` comes after `appendClass`, it will be executed first, since it has a lower order.
|
636
|
-
|
637
|
-
## Action validation mechanism
|
638
|
-
|
639
|
-
All default actions are validated by default, checking that field exists in the schema, to save you some headaches.
|
640
|
-
There are 2 levels of validation
|
641
|
-
|
642
|
-
- `propTypes` validation, using FB `prop-types` package
|
643
|
-
- explicit validation
|
644
|
-
|
645
|
-
You can define those validations in your actions as well, to improve actions usability.
|
646
|
-
|
647
|
-
All validation is disabled in production.
|
648
|
-
|
649
|
-
### Prop types action validation
|
650
|
-
|
651
|
-
This is reuse of familiar `prop-types` validation used with React components, and it's used in the same way:
|
652
|
-
|
653
|
-
In case of `require` it can look like this:
|
654
|
-
```js
|
655
|
-
require.propTypes = {
|
656
|
-
field: PropTypes.oneOfType([
|
657
|
-
PropTypes.string,
|
658
|
-
PropTypes.arrayOf(PropTypes.string),
|
659
|
-
]).isRequired,
|
660
|
-
};
|
661
|
-
```
|
662
|
-
|
663
|
-
The rest is magic.
|
664
|
-
|
665
|
-
WARNING, the default behavior of `prop-types` is to send errors to console,
|
666
|
-
which you need to have running in order to see them.
|
667
|
-
|
668
|
-
For our `replaceClassNames` action, it can look like this:
|
669
|
-
|
670
|
-
```js
|
671
|
-
replaceClassNames.propTypes = {
|
672
|
-
classNames: PropTypes.string.isRequired,
|
673
|
-
ignore: PropTypes.arrayOf(PropTypes.string)
|
674
|
-
};
|
675
|
-
```
|
676
|
-
|
677
|
-
## Explicit validation
|
678
|
-
|
679
|
-
In order to provide more granular validation, you can specify validate function on
|
680
|
-
your action, that will receive `params`, `schema` and `uiSchema` so you could provide appropriate validation.
|
681
|
-
|
682
|
-
For example, validation for `require` can be done like this:
|
683
|
-
|
684
|
-
```js
|
685
|
-
require.validate = function({ field }, schema, uiSchema) {
|
686
|
-
if (Array.isArray(field)) {
|
687
|
-
field
|
688
|
-
.filter(f => schema && schema.properties && schema.properties[f] === undefined)
|
689
|
-
.forEach(f => console.error(`Field "${f}" is missing from schema on "require"`));
|
690
|
-
} else if (
|
691
|
-
schema &&
|
692
|
-
schema.properties &&
|
693
|
-
schema.properties[field] === undefined
|
694
|
-
) {
|
695
|
-
console.error(`Field "${field}" is missing from schema on "require"`);
|
696
|
-
}
|
697
|
-
};
|
698
|
-
```
|
699
|
-
|
700
|
-
Validation is not mandatory, and will be done only if field is provided.
|
701
|
-
|
702
|
-
For our `replaceClassNames` action, it would look similar:
|
703
|
-
```js
|
704
|
-
replaceClassNames.validate = function({ ignore }, schema, uiSchema) {
|
705
|
-
if (Array.isArray(field)) {
|
706
|
-
ignore
|
707
|
-
.filter(f => schema && schema.properties && schema.properties[f] === undefined)
|
708
|
-
.forEach(f => console.error(`Field "${f}" is missing from schema on "replaceClassNames"`));
|
709
|
-
} else if (
|
710
|
-
schema &&
|
711
|
-
schema.properties &&
|
712
|
-
schema.properties[ignore] === undefined
|
713
|
-
) {
|
714
|
-
console.error(`Field "${ignore}" is missing from schema on "replaceClassNames"`);
|
715
|
-
}
|
716
|
-
};
|
717
|
-
```
|
718
|
-
|
719
|
-
# Listening to configuration changes
|
720
|
-
|
721
|
-
In order to listen for configuration changes you can specify `onSchemaConfChange`, which will be notified every time `schema` or `uiSchema` changes it's value.
|
722
|
-
|
723
|
-
```js
|
724
|
-
let FormWithConditionals = applyRules(schema, uiSchema, rules, Engine, extraActions)(Form);
|
725
|
-
|
726
|
-
ReactDOM.render(
|
727
|
-
<FormWithConditionals onSchemaConfChange = {({ schema, uiSchema }) => { console.log("configuration changed") }}/>,
|
728
|
-
document.querySelector('#app')
|
729
|
-
);
|
730
|
-
|
731
|
-
```
|
732
|
-
|
733
|
-
## Contribute
|
734
|
-
|
735
|
-
- Issue Tracker: github.com/RxNT/react-jsonschema-form-conditionals/issues
|
736
|
-
- Source Code: github.com/RxNT/react-jsonschema-form-conditionals
|
737
|
-
|
738
|
-
## Support
|
739
|
-
|
740
|
-
If you are having issues, please let us know.
|
741
|
-
|
742
|
-
## License
|
743
|
-
|
744
|
-
The project is licensed under the Apache-2.0 license.
|
745
|
-
|
746
|
-
|
747
|
-
## Migration
|
748
|
-
|
749
|
-
### Migration to 0.4.x
|
750
|
-
|
751
|
-
The only significant change is signature of `applyRules` call. In 0.4.0 `schema`, `uiSchema`, `rules`, `Engine` and `extraActions` all consider to be constant that is why, they moved to `applyRules` call.
|
752
|
-
This helps improve performance on large schemas.
|
1
|
+
[](https://travis-ci.org/RxNT/react-jsonschema-form-conditionals)
|
2
|
+
[](https://coveralls.io/github/RxNT/react-jsonschema-form-conditionals?branch=master)
|
3
|
+
[](https://badge.fury.io/js/react-jsonschema-form-conditionals)
|
4
|
+
# Form with conditionals
|
5
|
+
|
6
|
+
This project extends [react-jsonschema-form](https://github.com/mozilla-services/react-jsonschema-form) with
|
7
|
+
conditional logic, which allow to have more complicated logic expressed and controlled with JSON schema.
|
8
|
+
This is primarily useful for complicated schemas with extended business logic,
|
9
|
+
which are suspect to changes and need to be manageable and changeable without modifying running application.
|
10
|
+
|
11
|
+
If you need simple rule logic, that does not change a lot, you can use original [mozilla project](https://github.com/mozilla-services/react-jsonschema-form),
|
12
|
+
by following examples like https://jsfiddle.net/69z2wepo/68259/
|
13
|
+
|
14
|
+
The project is done to be fully compatible with mozilla,
|
15
|
+
without imposing additional limitations.
|
16
|
+
|
17
|
+
## Features
|
18
|
+
|
19
|
+
- Support for [Json Rules Engine](https://github.com/CacheControl/json-rules-engine) and [json-rules-engine-simplified](https://github.com/RxNT/json-rules-engine-simplified)
|
20
|
+
- Extensible action mechanism
|
21
|
+
- Configuration over coding
|
22
|
+
- Lightweight and extensible
|
23
|
+
|
24
|
+
## Installation
|
25
|
+
|
26
|
+
Install `react-jsonschema-form-conditionals` by running:
|
27
|
+
|
28
|
+
```bash
|
29
|
+
npm install --s react-jsonschema-form-conditionals
|
30
|
+
```
|
31
|
+
|
32
|
+
## Usage
|
33
|
+
|
34
|
+
The simplest example of using `react-jsonschema-form-conditionals`
|
35
|
+
|
36
|
+
```jsx
|
37
|
+
import applyRules from 'react-jsonschema-form-conditionals';
|
38
|
+
import Engine from 'json-rules-engine-simplified';
|
39
|
+
import Form from "react-jsonschema-form";
|
40
|
+
|
41
|
+
...
|
42
|
+
|
43
|
+
const rules = [{
|
44
|
+
...
|
45
|
+
}];
|
46
|
+
|
47
|
+
let FormWithConditionals = applyRules(schema, uiSchema, rules, Engine)(Form);
|
48
|
+
|
49
|
+
ReactDOM.render(
|
50
|
+
<FormWithConditionals .../>,
|
51
|
+
document.querySelector('#app')
|
52
|
+
);
|
53
|
+
```
|
54
|
+
|
55
|
+
To show case uses for this library we'll be using simple registration schema example
|
56
|
+
|
57
|
+
```jsx
|
58
|
+
|
59
|
+
import applyRules from 'react-jsonschema-form-conditionals';
|
60
|
+
import Form from "react-jsonschema-form";
|
61
|
+
|
62
|
+
let schema = {
|
63
|
+
definitions: {
|
64
|
+
hobby: {
|
65
|
+
type: "object",
|
66
|
+
properties: {
|
67
|
+
name: { type: "string" },
|
68
|
+
durationInMonth: { "type": "integer" },
|
69
|
+
}
|
70
|
+
}
|
71
|
+
},
|
72
|
+
title: "A registration form",
|
73
|
+
description: "A simple form example.",
|
74
|
+
type: "object",
|
75
|
+
required: [
|
76
|
+
"firstName",
|
77
|
+
"lastName"
|
78
|
+
],
|
79
|
+
properties: {
|
80
|
+
firstName: {
|
81
|
+
type: "string",
|
82
|
+
title: "First name"
|
83
|
+
},
|
84
|
+
lastName: {
|
85
|
+
type: "string",
|
86
|
+
title: "Last name"
|
87
|
+
},
|
88
|
+
age: {
|
89
|
+
type: "integer",
|
90
|
+
title: "Age",
|
91
|
+
},
|
92
|
+
bio: {
|
93
|
+
type: "string",
|
94
|
+
title: "Bio",
|
95
|
+
},
|
96
|
+
country: {
|
97
|
+
type: "string",
|
98
|
+
title: "Country"
|
99
|
+
},
|
100
|
+
state: {
|
101
|
+
type: "string",
|
102
|
+
title: "State"
|
103
|
+
},
|
104
|
+
zip: {
|
105
|
+
type: "string",
|
106
|
+
title: "ZIP"
|
107
|
+
},
|
108
|
+
password: {
|
109
|
+
type: "string",
|
110
|
+
title: "Password",
|
111
|
+
minLength: 3
|
112
|
+
},
|
113
|
+
telephone: {
|
114
|
+
type: "string",
|
115
|
+
title: "Telephone",
|
116
|
+
minLength: 10
|
117
|
+
},
|
118
|
+
hobbies: {
|
119
|
+
type: "array",
|
120
|
+
items: { "$ref": "#/definitions/hobby" }
|
121
|
+
}
|
122
|
+
}
|
123
|
+
}
|
124
|
+
|
125
|
+
let rules = [{
|
126
|
+
...
|
127
|
+
}]
|
128
|
+
|
129
|
+
let FormWithConditionals = applyRules(schema, uiSchema, rules, Engine)(Form);
|
130
|
+
|
131
|
+
render((
|
132
|
+
<FormWithConditionals />
|
133
|
+
), document.getElementById("app"));
|
134
|
+
```
|
135
|
+
|
136
|
+
Conditionals functionality is build using 2 things
|
137
|
+
- Rules engine ([Json Rules Engine](https://github.com/CacheControl/json-rules-engine) or [Simplified Json Rules Engine](https://github.com/RxNT/json-rules-engine-simplified))
|
138
|
+
- Schema action mechanism
|
139
|
+
|
140
|
+
Rules engine responsibility is to trigger events, action mechanism
|
141
|
+
performs needed actions on the requests.
|
142
|
+
|
143
|
+
## Rules engine
|
144
|
+
|
145
|
+
Project supports 2 rules engines out of the box:
|
146
|
+
- [Json Rules Engine](https://github.com/CacheControl/json-rules-engine)
|
147
|
+
- [Simplified Json Rules Engine](https://github.com/RxNT/json-rules-engine-simplified)
|
148
|
+
|
149
|
+
In order to use either of those, you need to specify `Engine` in `applyRules` configuration.
|
150
|
+
|
151
|
+
For example:
|
152
|
+
|
153
|
+
To use [Simplified Json Rules Engine](https://github.com/RxNT/json-rules-engine-simplified), you can do following:
|
154
|
+
```js
|
155
|
+
|
156
|
+
import applyRules from 'react-jsonschema-form-conditionals';
|
157
|
+
import Form from "react-jsonschema-form";
|
158
|
+
|
159
|
+
import Engine from 'json-rules-engine-simplified';
|
160
|
+
|
161
|
+
...
|
162
|
+
|
163
|
+
let FormWithConditionals = applyRules(schema, uiSchema, rules, Engine)(Form);
|
164
|
+
|
165
|
+
ReactDOM.render(
|
166
|
+
<FormWithConditionals />,
|
167
|
+
document.querySelector('#app')
|
168
|
+
);
|
169
|
+
```
|
170
|
+
|
171
|
+
To use [Json Rules Engine](https://github.com/RxNT/json-rules-engine-simplified), is almost the same:
|
172
|
+
|
173
|
+
```js
|
174
|
+
|
175
|
+
import applyRules from 'react-jsonschema-form-conditionals';
|
176
|
+
import Engine from 'json-rules-engine';
|
177
|
+
import Form from "react-jsonschema-form";
|
178
|
+
|
179
|
+
// ...
|
180
|
+
|
181
|
+
let FormWithConditionals = applyRules(schema, uiSchema, rules, Engine)(Form);
|
182
|
+
|
183
|
+
ReactDOM.render(
|
184
|
+
<FormWithConditionals />,
|
185
|
+
document.querySelector('#app')
|
186
|
+
);
|
187
|
+
```
|
188
|
+
|
189
|
+
### Extending rules engine
|
190
|
+
|
191
|
+
If non of the provided engines satisfies, your needs, you can
|
192
|
+
implement your own `Engine` which should
|
193
|
+
comply to following:
|
194
|
+
|
195
|
+
```js
|
196
|
+
class Engine {
|
197
|
+
constructor(rules, schema) {
|
198
|
+
}
|
199
|
+
addRule = (rule) => {
|
200
|
+
}
|
201
|
+
run = (formData) => {
|
202
|
+
return Promise[Event]
|
203
|
+
}
|
204
|
+
}
|
205
|
+
```
|
206
|
+
|
207
|
+
Original `rules` and `schema` is used as a parameter for a factory call,
|
208
|
+
in order to be able to have additional functionality, such as rules to schema compliance validation,
|
209
|
+
like it's done in Simplified Json Rules Engine](https://github.com/RxNT/json-rules-engine-simplified)
|
210
|
+
|
211
|
+
## Schema action mechanism
|
212
|
+
|
213
|
+
Rules engine emits events, which are expected to have a `type` and `params` field,
|
214
|
+
`type` is used to distinguish action that is needed, `params` are used as input for that action:
|
215
|
+
|
216
|
+
```json
|
217
|
+
{
|
218
|
+
"type": "remove",
|
219
|
+
"params": {
|
220
|
+
"field": "name"
|
221
|
+
}
|
222
|
+
}
|
223
|
+
```
|
224
|
+
|
225
|
+
By default action mechanism defines a supported set of rules, which you can extend as needed:
|
226
|
+
|
227
|
+
- `remove` removes a field or set of fields from `schema` and `uiSchema`
|
228
|
+
- `require` makes a field or set of fields required
|
229
|
+
|
230
|
+
### Remove action
|
231
|
+
|
232
|
+
If you want to remove a field, your configuration should look like this:
|
233
|
+
|
234
|
+
```json
|
235
|
+
{
|
236
|
+
"conditions": { },
|
237
|
+
"event": {
|
238
|
+
"type": "remove",
|
239
|
+
"params": {
|
240
|
+
"field": "password"
|
241
|
+
}
|
242
|
+
}
|
243
|
+
}
|
244
|
+
```
|
245
|
+
When `condition` is met, `password` will be removed from both `schema` and `uiSchema`.
|
246
|
+
|
247
|
+
In case you want to remove multiple fields `name`, `password`, rule should look like this:
|
248
|
+
|
249
|
+
```json
|
250
|
+
{
|
251
|
+
"conditions": { },
|
252
|
+
"event": {
|
253
|
+
"type": "remove",
|
254
|
+
"params": {
|
255
|
+
"field": [ "name", "password" ]
|
256
|
+
}
|
257
|
+
}
|
258
|
+
}
|
259
|
+
```
|
260
|
+
|
261
|
+
To remove nested schema properties, use json dot notation. e.g. For schema object:
|
262
|
+
|
263
|
+
```json
|
264
|
+
{
|
265
|
+
"type": "object",
|
266
|
+
"properties": {
|
267
|
+
"someParentWrapper": {
|
268
|
+
"type": "object",
|
269
|
+
"properties": {
|
270
|
+
"booleanValA": {
|
271
|
+
"type": "boolean",
|
272
|
+
"title": "Some boolean input"
|
273
|
+
},
|
274
|
+
"booleanValB": {
|
275
|
+
"type": "boolean",
|
276
|
+
"title": "Another boolean input"
|
277
|
+
}
|
278
|
+
}
|
279
|
+
}
|
280
|
+
}
|
281
|
+
}
|
282
|
+
|
283
|
+
```
|
284
|
+
|
285
|
+
You can remove the nested booleanValA or booleanValB like so:
|
286
|
+
|
287
|
+
```json
|
288
|
+
{
|
289
|
+
"conditions": { },
|
290
|
+
"event": {
|
291
|
+
"type": "remove",
|
292
|
+
"params": {
|
293
|
+
"field": "someParentWrapper.booleanValA"
|
294
|
+
}
|
295
|
+
}
|
296
|
+
}
|
297
|
+
```
|
298
|
+
|
299
|
+
### Require action
|
300
|
+
|
301
|
+
The same convention goes for `require` action
|
302
|
+
|
303
|
+
For a single field:
|
304
|
+
|
305
|
+
```json
|
306
|
+
{
|
307
|
+
"conditions": { },
|
308
|
+
"event": {
|
309
|
+
"type": "require",
|
310
|
+
"params": {
|
311
|
+
"field": "password"
|
312
|
+
}
|
313
|
+
}
|
314
|
+
}
|
315
|
+
```
|
316
|
+
|
317
|
+
For multiple fields:
|
318
|
+
|
319
|
+
```json
|
320
|
+
{
|
321
|
+
"conditions": { },
|
322
|
+
"event": {
|
323
|
+
"type": "require",
|
324
|
+
"params": {
|
325
|
+
"field": [ "name", "password"]
|
326
|
+
}
|
327
|
+
}
|
328
|
+
}
|
329
|
+
```
|
330
|
+
|
331
|
+
## UiSchema actions
|
332
|
+
|
333
|
+
API defines a set of actions, that you can take on `uiSchema`, they cover most of the
|
334
|
+
|
335
|
+
- `uiAppend` appends `uiSchema` specified in params with an original `uiSchema`
|
336
|
+
- `uiOverride` replaces field in original `uiSchema` with fields in `params`, keeping unrelated entries
|
337
|
+
- `uiRepalce` replaces whole `uiSchema` with a conf schema
|
338
|
+
|
339
|
+
To show case, let's take a simple `schema`
|
340
|
+
|
341
|
+
```json
|
342
|
+
{
|
343
|
+
"properties": {
|
344
|
+
"lastName": { "type": "string" },
|
345
|
+
"firstName": { "type": "string" },
|
346
|
+
"nickName": { "type": "string"}
|
347
|
+
}
|
348
|
+
}
|
349
|
+
```
|
350
|
+
|
351
|
+
and `uiSchema`
|
352
|
+
|
353
|
+
```json
|
354
|
+
{
|
355
|
+
"ui:order": ["firstName"],
|
356
|
+
"lastName": {
|
357
|
+
"classNames": "col-md-1",
|
358
|
+
},
|
359
|
+
"firstName": {
|
360
|
+
"ui:disabled": false,
|
361
|
+
"num": 23
|
362
|
+
},
|
363
|
+
"nickName": {
|
364
|
+
"classNames": "col-md-12"
|
365
|
+
}
|
366
|
+
}
|
367
|
+
```
|
368
|
+
with event `params` something like this
|
369
|
+
```json
|
370
|
+
{
|
371
|
+
"ui:order": [ "lastName" ],
|
372
|
+
"lastName": {
|
373
|
+
"classNames": "has-error"
|
374
|
+
},
|
375
|
+
"firstName" : {
|
376
|
+
"classNames": "col-md-6",
|
377
|
+
"ui:disabled": true,
|
378
|
+
"num": 22
|
379
|
+
}
|
380
|
+
}
|
381
|
+
```
|
382
|
+
|
383
|
+
And look at different results depend on the choosen action.
|
384
|
+
|
385
|
+
### uiAppend
|
386
|
+
|
387
|
+
UiAppend can handle `arrays` and `string`, with fallback to `uiOverride` behavior for all other fields.
|
388
|
+
|
389
|
+
So the expected result `uiSchema` will be:
|
390
|
+
```json
|
391
|
+
{
|
392
|
+
"ui:order": ["firstName", "lastName"],
|
393
|
+
"lastName": {
|
394
|
+
"classNames": "col-md-1 has-error"
|
395
|
+
},
|
396
|
+
"firstName": {
|
397
|
+
"classNames": "col-md-6",
|
398
|
+
"ui:disabled": true,
|
399
|
+
"num": 22
|
400
|
+
},
|
401
|
+
"nickName": {
|
402
|
+
"classNames": "col-md-12"
|
403
|
+
}
|
404
|
+
}
|
405
|
+
```
|
406
|
+
|
407
|
+
In this case it
|
408
|
+
- added `lastName` to `ui:order` array,
|
409
|
+
- appended `has-error` to `classNames` in `lastName` field
|
410
|
+
- added `classNames` and enabled `firstName`
|
411
|
+
- as for the `num` in `firstName` it just overrode it
|
412
|
+
|
413
|
+
This is useful for example if you want to add some additional markup in your code, without touching layout that you've defined.
|
414
|
+
|
415
|
+
### uiOverride
|
416
|
+
|
417
|
+
`uiOverride` behaves similar to append, but instead of appending it completely replaces overlapping values
|
418
|
+
|
419
|
+
So the expected result `uiSchema` will be:
|
420
|
+
```json
|
421
|
+
{
|
422
|
+
"ui:order": [ "lastName" ],
|
423
|
+
"lastName": {
|
424
|
+
"classNames": "has-error"
|
425
|
+
},
|
426
|
+
"firstName": {
|
427
|
+
"classNames": "col-md-6",
|
428
|
+
"ui:disabled": true,
|
429
|
+
"num": 22
|
430
|
+
},
|
431
|
+
"nickName": {
|
432
|
+
"classNames": "col-md-12"
|
433
|
+
}
|
434
|
+
}
|
435
|
+
```
|
436
|
+
|
437
|
+
In this case it
|
438
|
+
- `ui:order` was replaced with configured value
|
439
|
+
- `className` for the `lastName` was replaced with `has-error`
|
440
|
+
- added `classNames` and enabled `firstName`
|
441
|
+
- as for the `num` in `firstName` it just overrode it
|
442
|
+
|
443
|
+
### uiReplace
|
444
|
+
|
445
|
+
`uiReplace` just replaces all fields in `uiSchema` with `params` fields, leaving unrelated fields untouched.
|
446
|
+
|
447
|
+
So the result `uiSchema` will be
|
448
|
+
```json
|
449
|
+
{
|
450
|
+
"ui:order": [ "lastName" ],
|
451
|
+
"lastName": {
|
452
|
+
"classNames": "has-error"
|
453
|
+
},
|
454
|
+
"firstName" : {
|
455
|
+
"classNames": "col-md-6",
|
456
|
+
"ui:disabled": true,
|
457
|
+
"num": 22
|
458
|
+
},
|
459
|
+
"nickName": {
|
460
|
+
"classNames": "col-md-12"
|
461
|
+
}
|
462
|
+
}
|
463
|
+
```
|
464
|
+
|
465
|
+
## Extension mechanism
|
466
|
+
|
467
|
+
You can extend existing actions list, by specifying `extraActions` on the form.
|
468
|
+
|
469
|
+
Let's say we need to introduce `replaceClassNames` action, that
|
470
|
+
would just specify `classNames` `col-md-4` for all fields except for `ignore`d one.
|
471
|
+
We also want to trigger it only when `password` is `empty`.
|
472
|
+
|
473
|
+
This is how we can do this:
|
474
|
+
|
475
|
+
```js
|
476
|
+
import applyRules from 'react-jsonschema-form-conditionals';
|
477
|
+
import Engine from 'json-rules-engine-simplified';
|
478
|
+
import Form from "react-jsonschema-form";
|
479
|
+
|
480
|
+
...
|
481
|
+
|
482
|
+
const rules = [
|
483
|
+
{
|
484
|
+
conditons: {
|
485
|
+
password: "empty"
|
486
|
+
},
|
487
|
+
event: {
|
488
|
+
type: "replaceClassNames",
|
489
|
+
params: {
|
490
|
+
classNames: "col-md-4",
|
491
|
+
ignore: [ "password" ]
|
492
|
+
}
|
493
|
+
}
|
494
|
+
}
|
495
|
+
];
|
496
|
+
|
497
|
+
|
498
|
+
let extraActions = {
|
499
|
+
replaceClassNames: function(params, schema, uiSchema, formData) {
|
500
|
+
Object.keys(schema.properties).forEach((field) => {
|
501
|
+
if (uiSchema[field] === undefined) {
|
502
|
+
uiSchema[field] = {}
|
503
|
+
}
|
504
|
+
uiSchema[field].classNames = params.classNames;
|
505
|
+
}
|
506
|
+
}
|
507
|
+
};
|
508
|
+
|
509
|
+
let FormWithConditionals = applyRules(schema, uiSchema, rules, Engine, extraActions)(Form);
|
510
|
+
|
511
|
+
ReactDOM.render(
|
512
|
+
<FormWithConditionals/>,
|
513
|
+
document.querySelector('#app')
|
514
|
+
);
|
515
|
+
```
|
516
|
+
|
517
|
+
Provided snippet does just that.
|
518
|
+
|
519
|
+
### Extension with calculated values
|
520
|
+
|
521
|
+
In case you need to calculate value, based on other field values, you can also do that.
|
522
|
+
|
523
|
+
Let's say we want to have schema with `a`, `b` and `sum` fields
|
524
|
+
|
525
|
+
```js
|
526
|
+
import applyRules from 'react-jsonschema-form-conditionals';
|
527
|
+
import Engine from 'json-rules-engine-simplified';
|
528
|
+
import Form from "react-jsonschema-form";
|
529
|
+
|
530
|
+
...
|
531
|
+
|
532
|
+
const rules = [
|
533
|
+
{
|
534
|
+
conditons: {
|
535
|
+
a: { not: "empty" },
|
536
|
+
b: { not: "empty" }
|
537
|
+
},
|
538
|
+
event: {
|
539
|
+
type: "updateSum"
|
540
|
+
}
|
541
|
+
}
|
542
|
+
];
|
543
|
+
|
544
|
+
|
545
|
+
let extraActions = {
|
546
|
+
updateSum: function(params, schema, uiSchema, formData) {
|
547
|
+
formData.sum = formData.a + formData.b;
|
548
|
+
}
|
549
|
+
};
|
550
|
+
|
551
|
+
let FormWithConditionals = applyRules(schema, uiSchema, rules, Engine, extraActions)(Form);
|
552
|
+
|
553
|
+
ReactDOM.render(
|
554
|
+
<FormWithConditionals/>,
|
555
|
+
document.querySelector('#app')
|
556
|
+
);
|
557
|
+
```
|
558
|
+
|
559
|
+
This is how you can do that.
|
560
|
+
|
561
|
+
> WARNING!!! You need to be careful with a rules order, when using calculated values.
|
562
|
+
> Put calculation rules at the top of your rules specification.
|
563
|
+
|
564
|
+
For example, let's say you want to mark `sum` field, if you have sum `greater` than `10`. The rule would look something like this:
|
565
|
+
|
566
|
+
```json
|
567
|
+
{
|
568
|
+
"conditions": {
|
569
|
+
"sum": { "greater" : 10 }
|
570
|
+
},
|
571
|
+
"event": {
|
572
|
+
"type": "appendClass",
|
573
|
+
"classNames": "has-success"
|
574
|
+
}
|
575
|
+
}
|
576
|
+
```
|
577
|
+
|
578
|
+
But it will work only if you put it after `updateSum` rule, like this
|
579
|
+
```json
|
580
|
+
[
|
581
|
+
{
|
582
|
+
"conditons": {
|
583
|
+
"a": { "not": "empty" },
|
584
|
+
"b": { "not": "empty" }
|
585
|
+
},
|
586
|
+
"event": {
|
587
|
+
"type": "updateSum"
|
588
|
+
}
|
589
|
+
},
|
590
|
+
{
|
591
|
+
"conditions": {
|
592
|
+
"sum": { "greater" : 10 }
|
593
|
+
},
|
594
|
+
"event": {
|
595
|
+
"type": "appendClass",
|
596
|
+
"classNames": "has-success"
|
597
|
+
}
|
598
|
+
}
|
599
|
+
];
|
600
|
+
```
|
601
|
+
|
602
|
+
Otherwise it will work with **old `sum` values** and therefor show incorrect value.
|
603
|
+
|
604
|
+
### Rules order
|
605
|
+
|
606
|
+
Originally actions performed in sequence defined in the array. If you have interdependent rules, that you need to run in order
|
607
|
+
you can specify `order` on a rule, so that it would be executed first. Rules are executed based on order from lowest to highest with
|
608
|
+
rules without order executed last.
|
609
|
+
|
610
|
+
For example to make updateSum work regardless the order rules were added, you can do following:
|
611
|
+
```json
|
612
|
+
[
|
613
|
+
{
|
614
|
+
"conditions": {
|
615
|
+
"sum": { "greater" : 10 }
|
616
|
+
},
|
617
|
+
"order": 1,
|
618
|
+
"event": {
|
619
|
+
"type": "appendClass",
|
620
|
+
"classNames": "has-success"
|
621
|
+
}
|
622
|
+
},
|
623
|
+
{
|
624
|
+
"conditons": {
|
625
|
+
"a": { "not": "empty" },
|
626
|
+
"b": { "not": "empty" }
|
627
|
+
},
|
628
|
+
"order": 0,
|
629
|
+
"event": {
|
630
|
+
"type": "updateSum"
|
631
|
+
}
|
632
|
+
}
|
633
|
+
]
|
634
|
+
```
|
635
|
+
Here although `updateSum` comes after `appendClass`, it will be executed first, since it has a lower order.
|
636
|
+
|
637
|
+
## Action validation mechanism
|
638
|
+
|
639
|
+
All default actions are validated by default, checking that field exists in the schema, to save you some headaches.
|
640
|
+
There are 2 levels of validation
|
641
|
+
|
642
|
+
- `propTypes` validation, using FB `prop-types` package
|
643
|
+
- explicit validation
|
644
|
+
|
645
|
+
You can define those validations in your actions as well, to improve actions usability.
|
646
|
+
|
647
|
+
All validation is disabled in production.
|
648
|
+
|
649
|
+
### Prop types action validation
|
650
|
+
|
651
|
+
This is reuse of familiar `prop-types` validation used with React components, and it's used in the same way:
|
652
|
+
|
653
|
+
In case of `require` it can look like this:
|
654
|
+
```js
|
655
|
+
require.propTypes = {
|
656
|
+
field: PropTypes.oneOfType([
|
657
|
+
PropTypes.string,
|
658
|
+
PropTypes.arrayOf(PropTypes.string),
|
659
|
+
]).isRequired,
|
660
|
+
};
|
661
|
+
```
|
662
|
+
|
663
|
+
The rest is magic.
|
664
|
+
|
665
|
+
WARNING, the default behavior of `prop-types` is to send errors to console,
|
666
|
+
which you need to have running in order to see them.
|
667
|
+
|
668
|
+
For our `replaceClassNames` action, it can look like this:
|
669
|
+
|
670
|
+
```js
|
671
|
+
replaceClassNames.propTypes = {
|
672
|
+
classNames: PropTypes.string.isRequired,
|
673
|
+
ignore: PropTypes.arrayOf(PropTypes.string)
|
674
|
+
};
|
675
|
+
```
|
676
|
+
|
677
|
+
## Explicit validation
|
678
|
+
|
679
|
+
In order to provide more granular validation, you can specify validate function on
|
680
|
+
your action, that will receive `params`, `schema` and `uiSchema` so you could provide appropriate validation.
|
681
|
+
|
682
|
+
For example, validation for `require` can be done like this:
|
683
|
+
|
684
|
+
```js
|
685
|
+
require.validate = function({ field }, schema, uiSchema) {
|
686
|
+
if (Array.isArray(field)) {
|
687
|
+
field
|
688
|
+
.filter(f => schema && schema.properties && schema.properties[f] === undefined)
|
689
|
+
.forEach(f => console.error(`Field "${f}" is missing from schema on "require"`));
|
690
|
+
} else if (
|
691
|
+
schema &&
|
692
|
+
schema.properties &&
|
693
|
+
schema.properties[field] === undefined
|
694
|
+
) {
|
695
|
+
console.error(`Field "${field}" is missing from schema on "require"`);
|
696
|
+
}
|
697
|
+
};
|
698
|
+
```
|
699
|
+
|
700
|
+
Validation is not mandatory, and will be done only if field is provided.
|
701
|
+
|
702
|
+
For our `replaceClassNames` action, it would look similar:
|
703
|
+
```js
|
704
|
+
replaceClassNames.validate = function({ ignore }, schema, uiSchema) {
|
705
|
+
if (Array.isArray(field)) {
|
706
|
+
ignore
|
707
|
+
.filter(f => schema && schema.properties && schema.properties[f] === undefined)
|
708
|
+
.forEach(f => console.error(`Field "${f}" is missing from schema on "replaceClassNames"`));
|
709
|
+
} else if (
|
710
|
+
schema &&
|
711
|
+
schema.properties &&
|
712
|
+
schema.properties[ignore] === undefined
|
713
|
+
) {
|
714
|
+
console.error(`Field "${ignore}" is missing from schema on "replaceClassNames"`);
|
715
|
+
}
|
716
|
+
};
|
717
|
+
```
|
718
|
+
|
719
|
+
# Listening to configuration changes
|
720
|
+
|
721
|
+
In order to listen for configuration changes you can specify `onSchemaConfChange`, which will be notified every time `schema` or `uiSchema` changes it's value.
|
722
|
+
|
723
|
+
```js
|
724
|
+
let FormWithConditionals = applyRules(schema, uiSchema, rules, Engine, extraActions)(Form);
|
725
|
+
|
726
|
+
ReactDOM.render(
|
727
|
+
<FormWithConditionals onSchemaConfChange = {({ schema, uiSchema }) => { console.log("configuration changed") }}/>,
|
728
|
+
document.querySelector('#app')
|
729
|
+
);
|
730
|
+
|
731
|
+
```
|
732
|
+
|
733
|
+
## Contribute
|
734
|
+
|
735
|
+
- Issue Tracker: github.com/RxNT/react-jsonschema-form-conditionals/issues
|
736
|
+
- Source Code: github.com/RxNT/react-jsonschema-form-conditionals
|
737
|
+
|
738
|
+
## Support
|
739
|
+
|
740
|
+
If you are having issues, please let us know.
|
741
|
+
|
742
|
+
## License
|
743
|
+
|
744
|
+
The project is licensed under the Apache-2.0 license.
|
745
|
+
|
746
|
+
|
747
|
+
## Migration
|
748
|
+
|
749
|
+
### Migration to 0.4.x
|
750
|
+
|
751
|
+
The only significant change is signature of `applyRules` call. In 0.4.0 `schema`, `uiSchema`, `rules`, `Engine` and `extraActions` all consider to be constant that is why, they moved to `applyRules` call.
|
752
|
+
This helps improve performance on large schemas.
|