@bolttech/form-engine-core 0.0.2-beta.5 → 0.0.2-beta.6
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/README.md +295 -1457
- package/index.esm.js +190 -46
- package/package.json +1 -1
- package/src/helpers/helpers.d.ts +2 -1
- package/src/managers/field.d.ts +16 -2
- package/src/managers/form.d.ts +21 -2
- package/src/managers/formGroup.d.ts +12 -6
- package/src/types/event.d.ts +31 -1
- package/src/types/schema.d.ts +30 -11
package/README.md
CHANGED
|
@@ -1,1620 +1,458 @@
|
|
|
1
1
|
# Form Engine Core
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
1. [Basic setup](#markdown-header-basic-setup)
|
|
8
|
-
2. [Step by step](#markdown-header-step-by-step)
|
|
9
|
-
3. [Form Features](#markdown-header-available-features)
|
|
10
|
-
|
|
11
|
-
- 3.1. [Validations - Allow form to run validations in the field](#markdown-header-validations)
|
|
12
|
-
- 3.1.1. [Named Validations](#markdown-header-validations)
|
|
13
|
-
- 3.1.2. [Error Messages](#markdown-header-validations)
|
|
14
|
-
- 3.1.3. [Available Validations](#markdown-header-validations)
|
|
15
|
-
- 3.2. [Filters - Allow only what you want in the field](#markdown-header-filters)
|
|
16
|
-
- 3.2.1. [Available formatters](#markdown-header-formatters)
|
|
17
|
-
- 3.3. [Formatters - Style your field value with formatters](#markdown-header-formatters)
|
|
18
|
-
- 3.4. [Masks - Modify the field value, while maintaining the original with masks](#markdown-header-masks)
|
|
19
|
-
- 3.4.1. [Available Masks](#markdown-header-formatters)
|
|
20
|
-
- 3.5. [Visibility conditions - Configure when to show hide/components](#markdown-header-visibility-conditions)
|
|
21
|
-
- 3.6. [Clear fields](#markdown-header-clear-fields)
|
|
22
|
-
- 3.7. [Api - Make api calls on certain form events](#markdown-header-api)
|
|
23
|
-
- 3.8. [Data binding - Allow to have dynamic data in the form, binding and subscribing to form changes](#markdown-header-data-binding)
|
|
24
|
-
- 3.8.1. [Scopes](#markdown-header-scope)
|
|
25
|
-
- 3.8.2. [Templates](#markdown-header-templates)
|
|
26
|
-
- 3.8.3. [varOps](#markdown-header-varops)
|
|
27
|
-
- 3.8.4. [Direct Fields Binding](#markdown-header-direct-fields-binding)
|
|
28
|
-
- 3.9. [State - Define component state](#markdown-header-state)
|
|
29
|
-
- 3.10. [Group](#markdown-header-group)
|
|
30
|
-
- 3.11. [Steps](#markdown-header-form-step)
|
|
31
|
-
|
|
32
|
-
## **Basic setup**
|
|
33
|
-
|
|
34
|
-
Serve your forms in JSON to your frontend, and allow it to be agnostic of your forms logic.
|
|
35
|
-
|
|
36
|
-
3 simple steps
|
|
37
|
-
|
|
38
|
-
1. Map your components to the form (Section - Build your mappers)
|
|
39
|
-
2. Build json schema
|
|
40
|
-
3. Use it
|
|
41
|
-
|
|
42
|
-
**1. BUILD MAPPERS**
|
|
3
|
+
Development maintenance documentation
|
|
43
4
|
|
|
44
|
-
|
|
45
|
-
import Input from 'Components/Input';
|
|
46
|
-
import Other from 'Components/Other';
|
|
47
|
-
|
|
48
|
-
const Mappings = {
|
|
49
|
-
input: { component: Input },
|
|
50
|
-
other: { component: Other },
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
const formBuilderPropsMapping = {
|
|
54
|
-
//default prop names
|
|
55
|
-
__default__: {
|
|
56
|
-
getValue: 'onChange',
|
|
57
|
-
setValue: 'value',
|
|
58
|
-
},
|
|
59
|
-
//component specific prop names
|
|
60
|
-
other: {
|
|
61
|
-
getValue: 'onChangeCallback',
|
|
62
|
-
setValue: 'data',
|
|
63
|
-
setErrorMessage: 'errorMessageArray',
|
|
64
|
-
setErrorState: 'isErrored',
|
|
65
|
-
onBlur: 'onBlurCallback',
|
|
66
|
-
onFocus: 'onFocusCallback',
|
|
67
|
-
onKeyUp: 'onKeyUpCallback',
|
|
68
|
-
onKeyDown: 'onKeyDownCallback',
|
|
69
|
-
},
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
export { Mappings, formBuilderPropsMapping };
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
**2. BUILD SCHEMA**
|
|
76
|
-
|
|
77
|
-
```json
|
|
78
|
-
{
|
|
79
|
-
"components": [
|
|
80
|
-
{
|
|
81
|
-
"component": "",
|
|
82
|
-
"name": "",
|
|
83
|
-
"children": [
|
|
84
|
-
{
|
|
85
|
-
"component": "${componentName}",
|
|
86
|
-
"name": "${componentFormName}",
|
|
87
|
-
"props": {
|
|
88
|
-
"fullWidth": true
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
]
|
|
92
|
-
}
|
|
93
|
-
]
|
|
94
|
-
}
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
**USE IT (React version)**
|
|
98
|
-
|
|
99
|
-
```javascript
|
|
100
|
-
import { Mappings, formBuilderPropsMapping } from './my-component-mappings';
|
|
101
|
-
import { getFormSchema } from './my-api-wrapper';
|
|
102
|
-
...
|
|
103
|
-
const schema = useMemo(() => getFormSchema('myInstanceContext'), []);
|
|
104
|
-
|
|
105
|
-
...
|
|
106
|
-
<Form mappings={Mappings} propsMappings={formBuilderPropsMapping} schema={schema}>
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
Nexts steps ? Checkout what you can do in the storybook with `npm run storybook` or see the best effort readme :(
|
|
110
|
-
|
|
111
|
-
## **Step by step**
|
|
112
|
-
|
|
113
|
-
### Build your mappers
|
|
114
|
-
|
|
115
|
-
The form uses mappings to connect to UI components so that its easy to connect to any set of components.
|
|
116
|
-
|
|
117
|
-
You can build your own mappings file or you can use the `bolttech` and if you want extend it with your set of components.
|
|
118
|
-
|
|
119
|
-
In the mappings file you need to specify the component definition and a name to refer in the JSON's latter, and how the form will connect to component props.
|
|
120
|
-
|
|
121
|
-
See this example
|
|
122
|
-
|
|
123
|
-
```javascript
|
|
124
|
-
import Input from '@bit/bolttech.components.ui.input';
|
|
125
|
-
import Checkbox from '@bit/bolttech.components.ui.checkbox';
|
|
126
|
-
import FormGroup from '@bit/bolttech.components.common.form-group';
|
|
127
|
-
|
|
128
|
-
const Mappings = {
|
|
129
|
-
input: { component: Input },
|
|
130
|
-
checkbox: { component: Checkbox },
|
|
131
|
-
formGroup: { component: FormGroup },
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
const formBuilderPropsMapping = {
|
|
135
|
-
input: {
|
|
136
|
-
getValue: 'onChange',
|
|
137
|
-
setValue: 'value',
|
|
138
|
-
setErrorMessage: 'errorMessage',
|
|
139
|
-
setErrorState: 'isErrored',
|
|
140
|
-
onBlur: 'onBlur',
|
|
141
|
-
onFocus: 'onFocus',
|
|
142
|
-
onKeyUp: 'onKeyUp',
|
|
143
|
-
onKeyDown: 'onKeyDown',
|
|
144
|
-
},
|
|
145
|
-
checkbox: {
|
|
146
|
-
getValue: 'onChange',
|
|
147
|
-
setValue: 'checked',
|
|
148
|
-
},
|
|
149
|
-
};
|
|
150
|
-
|
|
151
|
-
export { Mappings, formBuilderPropsMapping };
|
|
152
|
-
```
|
|
5
|
+
1. [Arquitecture Overview](#arquitecture-overview)
|
|
153
6
|
|
|
154
|
-
|
|
7
|
+
- 1.1 [FormGroup responsability](#formgroup-responsability)
|
|
8
|
+
- 1.2 [FormCore responsability](#formcore-responsability)
|
|
9
|
+
- 1.3 [FormField responsability](#formfield-responsability)
|
|
155
10
|
|
|
156
|
-
|
|
11
|
+
2. [Changing private field class properties implementation](#changing-private-field-class-properties-implementation)
|
|
12
|
+
3. [Value change Rules](#value-change-rules)
|
|
13
|
+
4. [Props change and visibility rules](#props-change-and-visibility-rules)
|
|
14
|
+
5. [Error messages display](#error-messages-display)
|
|
15
|
+
6. [subject dependencies](#subject-dependencies)
|
|
157
16
|
|
|
158
|
-
|
|
17
|
+
- 6.1 [field](#field)
|
|
18
|
+
- 6.1.1 [templateSubject$](#templatesubject)
|
|
19
|
+
- 6.1.2 [fieldEventSubject$](#fieldeventsubject)
|
|
20
|
+
- 6.1.3 [dataSubject$](#datasubject)
|
|
21
|
+
- 6.1.4 [formValidNotification$](#formvalidnotification)
|
|
159
22
|
|
|
160
|
-
|
|
161
|
-
import Input from '@bit/bolttech.components.ui.input';
|
|
162
|
-
import Checkbox from '@bit/bolttech.components.ui.checkbox';
|
|
23
|
+
7. [build fields with a schema](#build-fields-with-a-schema)
|
|
163
24
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
checkbox: { component: Checkbox },
|
|
167
|
-
};
|
|
25
|
+
- 7.1 [fields](#fields)
|
|
26
|
+
- 7.2 [templates](#templates)
|
|
168
27
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
getValue: 'onChange',
|
|
172
|
-
setValue: 'value',
|
|
173
|
-
},
|
|
174
|
-
checkbox: {
|
|
175
|
-
getValue: 'onChange',
|
|
176
|
-
setValue: 'checked',
|
|
177
|
-
},
|
|
178
|
-
};
|
|
28
|
+
8. [build fields without schema](#build-fields-without-schema)
|
|
29
|
+
9. [form group event callback register](#form-group-event-callback-register)
|
|
179
30
|
|
|
180
|
-
|
|
181
|
-
|
|
31
|
+
- 9.1 [form group onData](#form-group-ondata)
|
|
32
|
+
- 9.2 [form group onValid](#form-group-onvalid)
|
|
182
33
|
|
|
183
|
-
|
|
34
|
+
<a id="arquitecture-overview"></a>
|
|
184
35
|
|
|
185
|
-
|
|
36
|
+
## Arquitecture Overview
|
|
186
37
|
|
|
187
|
-
|
|
188
|
-
<FormProvider mapper={Mappings} propsMapping={formBuilderPropsMapping}>
|
|
189
|
-
{children}
|
|
190
|
-
</FormProvider>
|
|
191
|
-
```
|
|
38
|
+

|
|
192
39
|
|
|
193
|
-
|
|
40
|
+
Form-engine basic arquitecture is class based, composed by FormGroup, Form and Field classes and the communication is Observable Based in order to feed the adapters that will be the responsible to emit and recieve the reactive manipulations configured on the schema.
|
|
194
41
|
|
|
195
|
-
|
|
42
|
+
Each class is independent from bottom to top, meaning that you can instanciate a Field independently, but a Form relies on Fields to work, because a Form class manages Fields that interact with each others, same as FormGroup that relies on Forms in order to work.
|
|
196
43
|
|
|
197
|
-
|
|
44
|
+
FormGroup manages forms stored as a Map of key/value, same with Form that stores fields as a Map of key/value.
|
|
198
45
|
|
|
199
|
-
|
|
46
|
+
<a id="formgroup-responsability"></a>
|
|
200
47
|
|
|
201
|
-
|
|
202
|
-
- ON_FIELD_CHANGE
|
|
203
|
-
- ON_FIELD_BLUR
|
|
204
|
-
- ON_FIELD_FOCUS
|
|
205
|
-
- ON_FIELD_KEYUP
|
|
206
|
-
- ON_FIELD_KEYDOWN
|
|
207
|
-
- ON_FIELD_CLICK
|
|
208
|
-
- ON_FIELD_CLEARED
|
|
209
|
-
- ON_FIELD_BINDED
|
|
210
|
-
- AFTER_FIELD_API_CALL
|
|
48
|
+
### FormGroup responsability
|
|
211
49
|
|
|
212
|
-
|
|
50
|
+
- manage the add and removal of forms by it's key
|
|
51
|
+
- manage data inserted by user onto forms to provide callbacks to the adapter
|
|
52
|
+
- manage validity of forms to provide callbacks to the adapter
|
|
53
|
+
- work is in progress to check which functionalities it needs
|
|
213
54
|
|
|
214
|
-
|
|
55
|
+
<a id="formcore-responsability"></a>
|
|
215
56
|
|
|
216
|
-
|
|
57
|
+
### FormCore responsability
|
|
217
58
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
//...your feature goes here
|
|
226
|
-
}
|
|
227
|
-
```
|
|
59
|
+
- manage the add and removal of fields by it's key
|
|
60
|
+
- manage visibility of fields based on schema configuration
|
|
61
|
+
- manage resetValues based on schema configuration
|
|
62
|
+
- manage templating properties change emitted across fields
|
|
63
|
+
- manage component configuration based on mapper list config
|
|
64
|
+
- manage callback subscription on events emmited by fields (adapter external actions trigger)
|
|
65
|
+
- manage form values submission and validity check
|
|
228
66
|
|
|
229
|
-
|
|
67
|
+
<a id="formfield-responsability"></a>
|
|
230
68
|
|
|
231
|
-
|
|
69
|
+
### FormField responsability
|
|
232
70
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
"ON_FIELD_BLUR": {
|
|
237
|
-
"email": true
|
|
238
|
-
},
|
|
239
|
-
"ON_FIELD_CHANGE": {
|
|
240
|
-
"required": true
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
```
|
|
71
|
+
- manage storage and emission of properties such as validations, value, error messages
|
|
72
|
+
- manage api schema configurations for requests
|
|
73
|
+
- manage initialization of adapters external configuration binding generalization
|
|
245
74
|
|
|
246
|
-
|
|
75
|
+
<a id="changing-private-field-class-properties-implementation"></a>
|
|
247
76
|
|
|
248
|
-
|
|
77
|
+
## Changing private field class properties implementation
|
|
249
78
|
|
|
250
|
-
|
|
79
|
+
On the process of implementation, it's crucial to understand that the reactivity of the form relies on **RXJS**, the way this properties change can cause side effects on other properties and all are managed by **RXJS** subjects, so for all this class properties, each time they change, they need to emit to the corresponding subject, so all this properties have a subject emmited on the end.
|
|
251
80
|
|
|
252
|
-
|
|
253
|
-
{
|
|
254
|
-
"validations": {
|
|
255
|
-
"ON_FIELD_BLUR": {
|
|
256
|
-
"blurRequire": {
|
|
257
|
-
"require": true
|
|
258
|
-
}
|
|
259
|
-
},
|
|
260
|
-
"ON_FIELD_CHANGE": {
|
|
261
|
-
"email": true,
|
|
262
|
-
"changeRequire": {
|
|
263
|
-
"require": true
|
|
264
|
-
},
|
|
265
|
-
"changeRestOfValidations": {
|
|
266
|
-
"length": 50
|
|
267
|
-
//...
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
```
|
|
81
|
+
If you work in any maintenance involving this properties:
|
|
273
82
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
},
|
|
285
|
-
"eventMessages": {
|
|
286
|
-
"ON_FIELD_BLUR": ["required"],
|
|
287
|
-
"ON_FIELD_CHANGE": ["email"]
|
|
288
|
-
},
|
|
289
|
-
"messages": {
|
|
290
|
-
"default": "Default error message",
|
|
291
|
-
"email": "Invalid e-mail"
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
```
|
|
296
|
-
|
|
297
|
-
This schema part, will add messages to validations error.
|
|
298
|
-
|
|
299
|
-
- Each time the field has an e-mail error it will send the "Invalid e-mail" message to the component
|
|
300
|
-
- If there is and field error, but no message is specified, it will send what you have in `default` key. In this example, `required` error does not have message and will send "Default error message"
|
|
301
|
-
|
|
302
|
-
**With named validations**
|
|
303
|
-
|
|
304
|
-
If you have a named validation, you can use its name in the error messages, having better granularity on it.
|
|
305
|
-
|
|
306
|
-
```json
|
|
307
|
-
{
|
|
308
|
-
"validations": {
|
|
309
|
-
"methods": {
|
|
310
|
-
"blurRequire": {
|
|
311
|
-
"require": true
|
|
312
|
-
},
|
|
313
|
-
"email": true,
|
|
314
|
-
"changeRequire": {
|
|
315
|
-
"require": true
|
|
316
|
-
},
|
|
317
|
-
"changeRestOfValidations": {
|
|
318
|
-
"length": 50
|
|
319
|
-
}
|
|
320
|
-
},
|
|
321
|
-
"eventMessages": {
|
|
322
|
-
"ON_FIELD_BLUR": ["blurRequire"],
|
|
323
|
-
"ON_FIELD_CHANGE": ["email", "changeRequire", "changeRestOfValidations"]
|
|
324
|
-
},
|
|
325
|
-
"messages": {
|
|
326
|
-
"default": "Default error message",
|
|
327
|
-
"email": "Invalid e-mail",
|
|
328
|
-
"blurRequire": "When you blur, this component is required",
|
|
329
|
-
"changeRequire": "You should not leave the field blank",
|
|
330
|
-
"changeRestOfValidations": "You are changing into an incorrect state"
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
```
|
|
335
|
-
|
|
336
|
-
### Available validations (TBD)
|
|
337
|
-
|
|
338
|
-
Refer to the `TAvailableValidations` types here:
|
|
339
|
-
|
|
340
|
-
````typescript
|
|
341
|
-
{
|
|
342
|
-
/**
|
|
343
|
-
* The bool function is a validation function that checks if a given value indicating whether the validation has failed or not.
|
|
344
|
-
*
|
|
345
|
-
* @example - in a test environment
|
|
346
|
-
* ```
|
|
347
|
-
* const result = bool({
|
|
348
|
-
* validationValue: false,
|
|
349
|
-
* });
|
|
350
|
-
* console.log(result); // { fail: false }
|
|
351
|
-
* ```
|
|
352
|
-
*
|
|
353
|
-
* @example - real json usage with field value
|
|
354
|
-
* ```
|
|
355
|
-
* {
|
|
356
|
-
* validations: {
|
|
357
|
-
* bool: '${fields.input.value}'
|
|
358
|
-
* },
|
|
359
|
-
* },
|
|
360
|
-
* ```
|
|
361
|
-
*
|
|
362
|
-
* @example - real json usage with iVar value
|
|
363
|
-
* ```
|
|
364
|
-
* {
|
|
365
|
-
* validations: {
|
|
366
|
-
* bool: '${global.validation}'
|
|
367
|
-
* },
|
|
368
|
-
* },
|
|
369
|
-
* ```
|
|
370
|
-
*/
|
|
371
|
-
bool?: string | boolean;
|
|
372
|
-
/**
|
|
373
|
-
* Validation based on conditions
|
|
374
|
-
*
|
|
375
|
-
* @example - Compare own field to two. Origin and target default to field value
|
|
376
|
-
* ```
|
|
377
|
-
* conditions: {
|
|
378
|
-
* rule: 'and',
|
|
379
|
-
* set: [
|
|
380
|
-
* {
|
|
381
|
-
* condition: '===',
|
|
382
|
-
* target: '2',
|
|
383
|
-
* },
|
|
384
|
-
* {
|
|
385
|
-
* origin: '2',
|
|
386
|
-
* condition: '===',
|
|
387
|
-
* },
|
|
388
|
-
* ],
|
|
389
|
-
* },
|
|
390
|
-
* ```
|
|
391
|
-
* @example - Binded to Postcode field value. Must be greater than or equal two
|
|
392
|
-
* ```
|
|
393
|
-
* conditions: {
|
|
394
|
-
* rule: 'or',
|
|
395
|
-
* set: [
|
|
396
|
-
* {
|
|
397
|
-
* origin: '${fields.postcode.value}',
|
|
398
|
-
* condition: '>',
|
|
399
|
-
* target: '2',
|
|
400
|
-
* },
|
|
401
|
-
* {
|
|
402
|
-
* origin: '${fields.postcode.value}',
|
|
403
|
-
* condition: '===',
|
|
404
|
-
* target: '2',
|
|
405
|
-
* },
|
|
406
|
-
* ],
|
|
407
|
-
* },
|
|
408
|
-
* ```
|
|
409
|
-
* @example - Binded to Postcode field value. Must be equal to two
|
|
410
|
-
* ```
|
|
411
|
-
* conditions: {
|
|
412
|
-
* rule: 'or',
|
|
413
|
-
* set: [
|
|
414
|
-
* {
|
|
415
|
-
* origin: '${fields.postcode.value}',
|
|
416
|
-
* condition: '===',
|
|
417
|
-
* target: '2',
|
|
418
|
-
* },
|
|
419
|
-
* ],
|
|
420
|
-
* },
|
|
421
|
-
* ```
|
|
422
|
-
*/
|
|
423
|
-
conditions?: TVAvailableValidationConditions;
|
|
424
|
-
/**
|
|
425
|
-
* Applies multiple validations on the given value based on the specified truth table rule.
|
|
426
|
-
*
|
|
427
|
-
* @param {Object} TVAvailableValidationMultipleValidations - The multiple validations object param.
|
|
428
|
-
* @param {'AND' | 'OR' | 'NOT'} TVAvailableValidationMultipleValidations.rule - The rule to be applied based of truth table ('AND', 'OR', or 'NOT').
|
|
429
|
-
* @param {Object} TVAvailableValidationMultipleValidations.validations - Object containing validation rules.
|
|
430
|
-
*
|
|
431
|
-
* @example - Validating with the expression AND where current field must have a value of 'yes' and input 2 must have a value of 'no'
|
|
432
|
-
* ```
|
|
433
|
-
* multipleValidations: {
|
|
434
|
-
* rule: 'AND',
|
|
435
|
-
* validations: {
|
|
436
|
-
* value: 'yes',
|
|
437
|
-
* conditions: {
|
|
438
|
-
* rule: 'and',
|
|
439
|
-
* set: [
|
|
440
|
-
* {
|
|
441
|
-
* origin: '${fields.input2.value}',
|
|
442
|
-
* condition: '===',
|
|
443
|
-
* target: 'no'
|
|
444
|
-
* },
|
|
445
|
-
* ],
|
|
446
|
-
* },
|
|
447
|
-
* },
|
|
448
|
-
* },
|
|
449
|
-
* ```
|
|
450
|
-
* @example - Validating with the expression OR where current field must have a value of '1995-12-12' or be a valid date with regex
|
|
451
|
-
* ```
|
|
452
|
-
* multipleValidations: {
|
|
453
|
-
* rule: 'OR',
|
|
454
|
-
* validations: {
|
|
455
|
-
* value: '1995-12-12',
|
|
456
|
-
* regex: '^\d{4}-\d{2}-\d{2}$',
|
|
457
|
-
* },
|
|
458
|
-
* },
|
|
459
|
-
* ```
|
|
460
|
-
* @example - Validating with the expression NOT where current field doesn't have a value of '1995-12-12' and not be a valid date with regex
|
|
461
|
-
* ```
|
|
462
|
-
* multipleValidations: {
|
|
463
|
-
* rule: 'NOT',
|
|
464
|
-
* validations: {
|
|
465
|
-
* value: '1995-12-12',
|
|
466
|
-
* regex: '^\d{4}-\d{2}-\d{2}$',
|
|
467
|
-
* },
|
|
468
|
-
* },
|
|
469
|
-
* ```
|
|
470
|
-
*/
|
|
471
|
-
multipleValidations?: TVAvailableValidationMultipleValidations;
|
|
472
|
-
/**
|
|
473
|
-
* Between validations
|
|
474
|
-
*
|
|
475
|
-
* @example - Between ages
|
|
476
|
-
* ``` type teste = Pick<TVAvailableValidations, 'date'>
|
|
477
|
-
* between: {
|
|
478
|
-
* dates: [
|
|
479
|
-
* {
|
|
480
|
-
* operator: '>=',
|
|
481
|
-
* origin: {
|
|
482
|
-
* format: 'YYYYMMDD',
|
|
483
|
-
* intervals: {
|
|
484
|
-
* years: 18,
|
|
485
|
-
* },
|
|
486
|
-
* },
|
|
487
|
-
* },
|
|
488
|
-
* {
|
|
489
|
-
* operator: '<=',
|
|
490
|
-
* origin: {
|
|
491
|
-
* format: 'YYYYMMDD',
|
|
492
|
-
* intervals: {
|
|
493
|
-
* years: 75,
|
|
494
|
-
* },
|
|
495
|
-
* },
|
|
496
|
-
* }
|
|
497
|
-
* ]
|
|
498
|
-
* }
|
|
499
|
-
* ```
|
|
500
|
-
*
|
|
501
|
-
* @example - Between numbers
|
|
502
|
-
* ```
|
|
503
|
-
* between: {
|
|
504
|
-
* start: '3',
|
|
505
|
-
* end: '4',
|
|
506
|
-
* isIncludedBoundaries: true
|
|
507
|
-
* },
|
|
508
|
-
* ```
|
|
509
|
-
*
|
|
510
|
-
*/
|
|
511
|
-
between?: {
|
|
512
|
-
/**
|
|
513
|
-
* Array of date validations. To make it possible, ensure that the conditions is > and < in both date validations.
|
|
514
|
-
*/
|
|
515
|
-
dates?: TVAvailableValidations['date'][];
|
|
516
|
-
/**
|
|
517
|
-
* The first number of the validation. If the current value is grater than this number, the validation will pass.
|
|
518
|
-
*/
|
|
519
|
-
start?: number;
|
|
520
|
-
/**
|
|
521
|
-
* The second number of the validation. If the current value is lower than this number, the validation will pass.
|
|
522
|
-
*/
|
|
523
|
-
end?: number;
|
|
524
|
-
/**
|
|
525
|
-
* If it's true, the comparision is transformed to >= and <=. So, the numbers that were passed are included in the validation.
|
|
526
|
-
*/
|
|
527
|
-
isIncludedBoundaries?: boolean;
|
|
528
|
-
};
|
|
529
|
-
/**
|
|
530
|
-
* Dates validations
|
|
531
|
-
*
|
|
532
|
-
* @example - Dates should be different
|
|
533
|
-
* ```
|
|
534
|
-
* date: {
|
|
535
|
-
* operator: '!==',
|
|
536
|
-
* origin: {
|
|
537
|
-
* format: 'DDMMYYYY',
|
|
538
|
-
* },
|
|
539
|
-
* target: {
|
|
540
|
-
* format: 'DDMMYYYY',
|
|
541
|
-
* value: '10/10/2001',
|
|
542
|
-
* },
|
|
543
|
-
* },*
|
|
544
|
-
* ```
|
|
545
|
-
*
|
|
546
|
-
* @example - Compare only valid dates using intervals
|
|
547
|
-
* ```
|
|
548
|
-
* date: {
|
|
549
|
-
* onlyValidDate: true,
|
|
550
|
-
* operator: '<',
|
|
551
|
-
* origin: {
|
|
552
|
-
* format: 'DD/MM/YYYY',
|
|
553
|
-
* intervals: {
|
|
554
|
-
* years: 1,
|
|
555
|
-
* },
|
|
556
|
-
* },
|
|
557
|
-
* },
|
|
558
|
-
* ```
|
|
559
|
-
*
|
|
560
|
-
* @example - Should have at max 85 years
|
|
561
|
-
* ```
|
|
562
|
-
* {
|
|
563
|
-
* operator: '>',
|
|
564
|
-
* origin: {
|
|
565
|
-
* format: 'DDMMYYYY',
|
|
566
|
-
* value: eightyFiveYearsDate,
|
|
567
|
-
* intervals: {
|
|
568
|
-
* years: 85,
|
|
569
|
-
* },
|
|
570
|
-
* },
|
|
571
|
-
* }
|
|
572
|
-
* }
|
|
573
|
-
*
|
|
574
|
-
* ```
|
|
575
|
-
*/
|
|
576
|
-
date?: {
|
|
577
|
-
/**
|
|
578
|
-
* Flag to force only valid dates. Valid dates must be of min length of 8
|
|
579
|
-
*/
|
|
580
|
-
onlyValidDate?: boolean;
|
|
581
|
-
/**
|
|
582
|
-
* List of operations you can do
|
|
583
|
-
* - between origin and target dates
|
|
584
|
-
* - between origin and intervals
|
|
585
|
-
*/
|
|
586
|
-
operator: TValidationDateOperators;
|
|
587
|
-
/**
|
|
588
|
-
* The origin configurations
|
|
589
|
-
*/
|
|
590
|
-
origin: {
|
|
591
|
-
/**
|
|
592
|
-
* Origin date value
|
|
593
|
-
*/
|
|
594
|
-
value?: string | number;
|
|
595
|
-
/**
|
|
596
|
-
* The available date formats
|
|
597
|
-
*/
|
|
598
|
-
format: TValidationDateFormats;
|
|
599
|
-
/**
|
|
600
|
-
* Intervals to compare with the original date.
|
|
601
|
-
*
|
|
602
|
-
* It will use todays date for comparison.
|
|
603
|
-
*
|
|
604
|
-
* @example
|
|
605
|
-
*
|
|
606
|
-
* origin date = 10/10/2022
|
|
607
|
-
* interval.year = 1
|
|
608
|
-
* operator = '==='
|
|
609
|
-
*
|
|
610
|
-
* It will compare (10/10/2022 + 1) with the current date and check if they are the same
|
|
611
|
-
*/
|
|
612
|
-
intervals?: {
|
|
613
|
-
years?: number;
|
|
614
|
-
months?: number;
|
|
615
|
-
days?: number;
|
|
616
|
-
};
|
|
617
|
-
};
|
|
618
|
-
target?: {
|
|
619
|
-
value: string | number;
|
|
620
|
-
format: TValidationDateFormats;
|
|
621
|
-
};
|
|
622
|
-
};
|
|
623
|
-
/**
|
|
624
|
-
* Allow to validate if the input string is a valid date format
|
|
625
|
-
*/
|
|
626
|
-
validDate?: TValidationDateFormats;
|
|
627
|
-
/**
|
|
628
|
-
* Allow to define a maximum length for the input to have no error
|
|
629
|
-
*/
|
|
630
|
-
length?: number;
|
|
631
|
-
|
|
632
|
-
/**
|
|
633
|
-
* Specifies a regular expression pattern that the value should match.
|
|
634
|
-
*/
|
|
635
|
-
regex?: string;
|
|
636
|
-
|
|
637
|
-
/**
|
|
638
|
-
* Specifies the maximum length of the value (if it's a string).
|
|
639
|
-
*/
|
|
640
|
-
maxLength?: number;
|
|
641
|
-
|
|
642
|
-
/**
|
|
643
|
-
* Specifies the minimum length of the value (if it's a string).
|
|
644
|
-
*/
|
|
645
|
-
minLength?: number;
|
|
646
|
-
|
|
647
|
-
/**
|
|
648
|
-
* Specifies whether the field is required.
|
|
649
|
-
*/
|
|
650
|
-
required?: boolean;
|
|
651
|
-
|
|
652
|
-
/**
|
|
653
|
-
* Specifies that the value should contain only letters.
|
|
654
|
-
*/
|
|
655
|
-
onlyLetters?: boolean;
|
|
656
|
-
|
|
657
|
-
/**
|
|
658
|
-
* Specifies a value that the field should match.
|
|
659
|
-
*/
|
|
660
|
-
value?: string | number | boolean;
|
|
661
|
-
|
|
662
|
-
/**
|
|
663
|
-
* Specifies that the field should not be empty.
|
|
664
|
-
*/
|
|
665
|
-
notEmpty?: boolean;
|
|
666
|
-
|
|
667
|
-
/**
|
|
668
|
-
* A callback function that performs custom validation on the value.
|
|
669
|
-
* @param value - The value to be validated.
|
|
670
|
-
* @returns An object with validation results.
|
|
671
|
-
*/
|
|
672
|
-
callback?(value: string | number): boolean;
|
|
673
|
-
|
|
674
|
-
/**
|
|
675
|
-
* Specifies a numeric range for the value.
|
|
676
|
-
*/
|
|
677
|
-
numericRange?: { start: number | string; end: number | string };
|
|
678
|
-
|
|
679
|
-
/**
|
|
680
|
-
* Specifies whether the value should be a number.
|
|
681
|
-
*/
|
|
682
|
-
isNumber?: boolean;
|
|
683
|
-
|
|
684
|
-
/**
|
|
685
|
-
* Specifies that the field should not have extra spaces.
|
|
686
|
-
*/
|
|
687
|
-
hasNoExtraSpaces?: boolean;
|
|
688
|
-
|
|
689
|
-
/**
|
|
690
|
-
* Specifies that the value should be an email address.
|
|
691
|
-
*/
|
|
692
|
-
email?: boolean;
|
|
693
|
-
|
|
694
|
-
/**
|
|
695
|
-
* Specifies that the value should be less than a given number or string.
|
|
696
|
-
*/
|
|
697
|
-
lessThan?: number | string;
|
|
698
|
-
|
|
699
|
-
/**
|
|
700
|
-
* Specifies that the value should be a sequential number.
|
|
701
|
-
*/
|
|
702
|
-
sequentialNumber?: boolean;
|
|
703
|
-
|
|
704
|
-
/**
|
|
705
|
-
* Specifies that the value should not contain repeated numbers.
|
|
706
|
-
*/
|
|
707
|
-
repeatedNumbers?: boolean;
|
|
708
|
-
|
|
709
|
-
/**
|
|
710
|
-
* Specifies that the value should be a URL.
|
|
711
|
-
*/
|
|
712
|
-
url?: boolean;
|
|
713
|
-
|
|
714
|
-
/**
|
|
715
|
-
* Specifies a path error type.
|
|
716
|
-
*/
|
|
717
|
-
path?: TPathError;
|
|
718
|
-
|
|
719
|
-
/**
|
|
720
|
-
* Specifies that the value should be a valid credit card number.
|
|
721
|
-
*/
|
|
722
|
-
isCreditCard?: string[];
|
|
723
|
-
|
|
724
|
-
/**
|
|
725
|
-
* Specifies that the value should be a valid credit card number with a specific length.
|
|
726
|
-
*/
|
|
727
|
-
isCreditCardAndLength?: string[];
|
|
728
|
-
|
|
729
|
-
/**
|
|
730
|
-
* Specifies that the value should match a credit card code with available options.
|
|
731
|
-
*/
|
|
732
|
-
isCreditCodeMatch?: { numberCard: string; availableOptions: string[] };
|
|
733
|
-
|
|
734
|
-
/**
|
|
735
|
-
* Specifies custom validation options for the field.
|
|
736
|
-
*/
|
|
737
|
-
customValidation?: ICustomValidationValue[];
|
|
738
|
-
|
|
739
|
-
/**
|
|
740
|
-
* Specifies that spaces are not allowed in the field.
|
|
741
|
-
*/
|
|
742
|
-
notAllowSpaces?: true;
|
|
743
|
-
|
|
744
|
-
/**
|
|
745
|
-
* Specifies that the value should be in a predefined list of values.
|
|
746
|
-
*/
|
|
747
|
-
isInTheList?: string[] | number[] | string;
|
|
748
|
-
|
|
749
|
-
/**
|
|
750
|
-
* Specifies field validation rules for nested fields.
|
|
751
|
-
*/
|
|
752
|
-
fields?: {
|
|
753
|
-
/**
|
|
754
|
-
* The rule for validating nested fields (e.g., 'every').
|
|
755
|
-
*/
|
|
756
|
-
rule: 'every';
|
|
757
|
-
|
|
758
|
-
/**
|
|
759
|
-
* An array of nested field validation configurations.
|
|
760
|
-
*/
|
|
761
|
-
set: {
|
|
762
|
-
/**
|
|
763
|
-
* The binding for the nested field.
|
|
764
|
-
*/
|
|
765
|
-
bind: string;
|
|
766
|
-
|
|
767
|
-
/**
|
|
768
|
-
* The field name for the nested field.
|
|
769
|
-
*/
|
|
770
|
-
fieldName: string;
|
|
771
|
-
|
|
772
|
-
/**
|
|
773
|
-
* Validation options for the nested field, excluding the 'fields' property.
|
|
774
|
-
*/
|
|
775
|
-
validations: Omit<TVAvailableValidations, 'fields'>;
|
|
776
|
-
}[];
|
|
777
|
-
};
|
|
778
|
-
|
|
779
|
-
/**
|
|
780
|
-
* Specifies that the value should be existed.
|
|
781
|
-
*/
|
|
782
|
-
exists?: string | number ;
|
|
783
|
-
}
|
|
784
|
-
````
|
|
785
|
-
|
|
786
|
-
## **Formatters**
|
|
787
|
-
|
|
788
|
-
Formatting a field means mutating the field value to a given... format.
|
|
789
|
-
|
|
790
|
-
This options will allow you to force a give field to have the format you whant while the user is performing some action on the form.
|
|
791
|
-
|
|
792
|
-
**NOTE** - When receiving the values of the form, you will have the value with the specified format, not the raw value the user entered
|
|
793
|
-
|
|
794
|
-
You have several formatters. THe following example shows splitter that is a more generic one, allowing you to split the input text
|
|
795
|
-
|
|
796
|
-
**BIG NOTE** - Formatters won't execute when a value is deleting (eg. user pressed backspace to delete a character), to execute on every
|
|
797
|
-
value change, use masks instead
|
|
798
|
-
|
|
799
|
-
```json
|
|
800
|
-
{
|
|
801
|
-
"formatters": {
|
|
802
|
-
"ON_FIELD_MOUNT": {
|
|
803
|
-
"splitter": [
|
|
804
|
-
{
|
|
805
|
-
"position": 2,
|
|
806
|
-
"value": "/"
|
|
807
|
-
},
|
|
808
|
-
{
|
|
809
|
-
"position": 5,
|
|
810
|
-
"value": "/"
|
|
811
|
-
}
|
|
812
|
-
]
|
|
813
|
-
}
|
|
814
|
-
}
|
|
815
|
-
}
|
|
816
|
-
```
|
|
817
|
-
|
|
818
|
-
The above example will split your word in position 2 and 5, adding there the `/`. This will give you a date format like `10/10/1987` (you would have to limit the input length. More on that on FILTERS)
|
|
819
|
-
|
|
820
|
-
### Available Formatters (TBD)
|
|
821
|
-
|
|
822
|
-
Refer to the types on `TSchema`
|
|
823
|
-
|
|
824
|
-
#### Regex
|
|
825
|
-
|
|
826
|
-
Specifies a regular expression pattern that the value should match to be replaced.
|
|
827
|
-
|
|
828
|
-
```json
|
|
829
|
-
{
|
|
830
|
-
"formatters": {
|
|
831
|
-
"ON_FIELD_MOUNT": {
|
|
832
|
-
"regex": "[0-9]"
|
|
833
|
-
}
|
|
834
|
-
}
|
|
835
|
-
}
|
|
83
|
+
```typescript
|
|
84
|
+
private _props: Record<string, unknown>;
|
|
85
|
+
private _value: unknown;
|
|
86
|
+
private _stateValue: Record<string, unknown>;
|
|
87
|
+
private _metadata: unknown;
|
|
88
|
+
private _visibility: boolean;
|
|
89
|
+
private _errors: TErrorMessages;
|
|
90
|
+
private _api: TApiResponse;
|
|
91
|
+
private _valid: boolean;
|
|
92
|
+
private _mounted: boolean;
|
|
836
93
|
```
|
|
837
94
|
|
|
838
|
-
|
|
95
|
+
they all have a `get` and `set` method constructors
|
|
839
96
|
|
|
840
|
-
|
|
97
|
+
The `get` constructor is responsible to deliver the value
|
|
841
98
|
|
|
842
|
-
|
|
843
|
-
const removeDots = (value: string | number): string | number => {
|
|
844
|
-
return value.split('.').join('');
|
|
845
|
-
};
|
|
99
|
+
The `set` constructor is responsible to set the value on this private properties along with emitting to the corresponding subject and the form template subject
|
|
846
100
|
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
},
|
|
853
|
-
};
|
|
101
|
+
```javascript
|
|
102
|
+
private _stateValue: unknown;
|
|
103
|
+
private _metadata: unknown;
|
|
104
|
+
errorsString: string;
|
|
105
|
+
errorsList: string[];
|
|
854
106
|
```
|
|
855
107
|
|
|
856
|
-
|
|
108
|
+
This properties are computed based on other private properties,
|
|
857
109
|
|
|
858
|
-
|
|
110
|
+
`_stateValue` has the mask applied to the `_value` when `set value` method is called
|
|
859
111
|
|
|
860
|
-
|
|
861
|
-
{
|
|
862
|
-
"masks": {
|
|
863
|
-
"ON_FIELD_BLUR": {
|
|
864
|
-
"replaceAll": "*"
|
|
865
|
-
},
|
|
866
|
-
"ON_FIELD_FOCUS": {
|
|
867
|
-
"cleanMask": true
|
|
868
|
-
}
|
|
869
|
-
}
|
|
870
|
-
}
|
|
871
|
-
```
|
|
112
|
+
`_metadata` has the metadata sent by the **valueChangeEvent** mapper function when `set value` is called
|
|
872
113
|
|
|
873
|
-
|
|
114
|
+
when `set errors` method is called, `errorsString` is the errors message list concatenated in a string, separated by commas `,` and `errorsList` is the list of the errors message
|
|
874
115
|
|
|
875
|
-
|
|
876
|
-
- On Focus , you tell form to clean the mask with `cleanMask` directive.
|
|
116
|
+
**If you try to change this values directly without using it's constructors, it can cause serious problems on the form reactivity, even in a last resort scenario, evaluate cautiously if you should change this values directly on any maintenance or feature implementation**
|
|
877
117
|
|
|
878
|
-
|
|
118
|
+
<a id="value-change-rules"></a>
|
|
879
119
|
|
|
880
|
-
|
|
120
|
+
## Value change Rules
|
|
881
121
|
|
|
882
|
-
|
|
122
|
+
If you manage to work on value change, take in consideration that the value change is treated as a special property on the configuration, let's take in consideration what happens on a component in react:
|
|
883
123
|
|
|
884
|
-
|
|
124
|
+
```javascript
|
|
125
|
+
const [value, setValue] = useState('');
|
|
885
126
|
|
|
886
|
-
|
|
887
|
-
const
|
|
888
|
-
|
|
127
|
+
const handleChange = (e) => {
|
|
128
|
+
const val = e.currentTarget.value;
|
|
129
|
+
val ? (setValue = val) : '';
|
|
889
130
|
};
|
|
890
131
|
|
|
891
|
-
|
|
892
|
-
formatters: {
|
|
893
|
-
ON_FIELD_CHANGE: {
|
|
894
|
-
callback: removeDots,
|
|
895
|
-
},
|
|
896
|
-
},
|
|
897
|
-
};
|
|
132
|
+
<input onChange={handleChange} value={value} />;
|
|
898
133
|
```
|
|
899
134
|
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
Filters very predictable and work like the word says, they filter a given word to a given patter/directive.
|
|
135
|
+
The value prop is binded to the value state and the onChange prop is binded to the function that will change this value.
|
|
903
136
|
|
|
904
|
-
|
|
137
|
+
In form-engine, the property that holds the value and the property that changes this value are:
|
|
905
138
|
|
|
906
|
-
```
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
}
|
|
139
|
+
```javascript
|
|
140
|
+
class FormField
|
|
141
|
+
private _value: unknown;
|
|
142
|
+
(...)
|
|
143
|
+
get value(): unknown {
|
|
144
|
+
return this._value;
|
|
913
145
|
}
|
|
914
|
-
}
|
|
915
|
-
```
|
|
916
146
|
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
Eg: You want to hide another field, when a given field `originalField` has a given value on it.
|
|
932
|
-
|
|
933
|
-
```json
|
|
934
|
-
[
|
|
935
|
-
{
|
|
936
|
-
"name": "originalField",
|
|
937
|
-
"component": "checkbox",
|
|
938
|
-
"visibilityConditions": {
|
|
939
|
-
"ON_FIELD_MOUNT": [
|
|
940
|
-
{
|
|
941
|
-
"validations": {
|
|
942
|
-
"value": "Yes"
|
|
943
|
-
},
|
|
944
|
-
"fieldName": "targetField"
|
|
945
|
-
}
|
|
946
|
-
],
|
|
947
|
-
"ON_FIELD_CHANGE": [
|
|
948
|
-
{
|
|
949
|
-
"validations": {
|
|
950
|
-
"value": "Yes"
|
|
951
|
-
},
|
|
952
|
-
"fieldName": "targetField"
|
|
953
|
-
}
|
|
954
|
-
]
|
|
955
|
-
},
|
|
956
|
-
"props": {
|
|
957
|
-
//...
|
|
147
|
+
set value(value: unknown) {
|
|
148
|
+
/*
|
|
149
|
+
too much unstable, if the valueChangeEvent parses the template event
|
|
150
|
+
value, might occur unexpected results
|
|
151
|
+
*/
|
|
152
|
+
let val;
|
|
153
|
+
if (this.valueChangeEvent) {
|
|
154
|
+
try {
|
|
155
|
+
val = this.valueChangeEvent(value, { props: this.props });
|
|
156
|
+
} catch (e) {
|
|
157
|
+
val = value;
|
|
158
|
+
}
|
|
159
|
+
} else {
|
|
160
|
+
val = value;
|
|
958
161
|
}
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
162
|
+
if (typeof val === 'undefined' || val === null) return;
|
|
163
|
+
if (typeof val === 'object' && '_value' in val && '_metadata' in val) {
|
|
164
|
+
this._value = this.formatValue(val['_value']);
|
|
165
|
+
this._stateValue = this.mapper.events?.setValue
|
|
166
|
+
? {
|
|
167
|
+
[this.mapper.events.setValue]: this.maskValue(
|
|
168
|
+
this.formatValue(val['_value'])
|
|
169
|
+
),
|
|
170
|
+
}
|
|
171
|
+
: {};
|
|
172
|
+
this._metadata = val._metadata;
|
|
173
|
+
} else {
|
|
174
|
+
this._value = this.formatValue(val);
|
|
175
|
+
this._stateValue = this.mapper.events?.setValue
|
|
176
|
+
? {
|
|
177
|
+
[this.mapper.events?.setValue]: this.maskValue(
|
|
178
|
+
this.formatValue(val)
|
|
179
|
+
),
|
|
180
|
+
}
|
|
181
|
+
: {};
|
|
182
|
+
this.maskValue(this.formatValue(val));
|
|
183
|
+
this._metadata = val;
|
|
965
184
|
}
|
|
185
|
+
this.stateValue && this.valueSubject$.next(this.stateValue);
|
|
186
|
+
this.templateSubject$.next({ key: this.name, event: 'ON_VALUE' });
|
|
966
187
|
}
|
|
967
|
-
]
|
|
968
188
|
```
|
|
969
189
|
|
|
970
|
-
|
|
190
|
+
In this case, the value passed to this setter method can have multiple formats (it's the **valueChangeEvent** mapper function that is responsible to pass the value on each of this formats):
|
|
971
191
|
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
- Otherwise, make it invisible
|
|
975
|
-
|
|
976
|
-
You can also for each visibility condition, apply it to multiple field names with `fieldNames` key that will accept an array.
|
|
977
|
-
|
|
978
|
-
```json
|
|
979
|
-
{
|
|
980
|
-
"visibilityConditions": {
|
|
981
|
-
"ON_FIELD_MOUNT": [
|
|
982
|
-
{
|
|
983
|
-
"validations": {
|
|
984
|
-
"value": "Yes"
|
|
985
|
-
},
|
|
986
|
-
"fieldNames": ["targetFieldOne", "targetFieldTwo"]
|
|
987
|
-
}
|
|
988
|
-
]
|
|
989
|
-
}
|
|
990
|
-
}
|
|
192
|
+
```javascript
|
|
193
|
+
value: unknown;
|
|
991
194
|
```
|
|
992
195
|
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
### Form Level
|
|
996
|
-
|
|
997
|
-
You can also use those in form level.
|
|
196
|
+
The value is formatted and stored onto `fieldinstance._value` and the value prop name with the value into `fieldinstance._stateValue`
|
|
998
197
|
|
|
999
198
|
```javascript
|
|
1000
|
-
|
|
1001
|
-
iVars={{ roofUpdated: state }}
|
|
1002
|
-
initialValues={{ roofUpdatedYear: 'diogos' }}
|
|
1003
|
-
schema={{
|
|
1004
|
-
visibilityConditions: {
|
|
1005
|
-
ON_FORM_MOUNT: [
|
|
1006
|
-
{
|
|
1007
|
-
validations: {
|
|
1008
|
-
value: '${global.roofUpdated}',
|
|
1009
|
-
},
|
|
1010
|
-
fieldName: 'roofUpdatedYear',
|
|
1011
|
-
},
|
|
1012
|
-
],
|
|
1013
|
-
ON_FIELD_CHANGE: [
|
|
1014
|
-
{
|
|
1015
|
-
validations: {
|
|
1016
|
-
value: 'abc',
|
|
1017
|
-
},
|
|
1018
|
-
fieldName: 'roofUpdatedYear',
|
|
1019
|
-
},
|
|
1020
|
-
],
|
|
1021
|
-
},
|
|
1022
|
-
components: [{...}],
|
|
1023
|
-
}}
|
|
1024
|
-
/>
|
|
199
|
+
{ _value: unknown, _metadata: unknown }
|
|
1025
200
|
```
|
|
1026
201
|
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
- in form mount we will hide the value when the `roofUpdatedYear` equals to the iVar `roofUpdated`
|
|
1030
|
-
- in each field change we will hide the value when the `roofUpdatedYear` equals to `abc`
|
|
1031
|
-
- also you can use the prop `rule` to decide what type of validations rules to execute (and == every | or == some)
|
|
1032
|
-
- if you want to show an field only if all or at least one validation is true, use `showOnlyIfTrue` property.
|
|
202
|
+
The `_value` key is formatted and stored onto `fieldinstance._value` and the value prop name with the value into `fieldinstance._stateValue`
|
|
1033
203
|
|
|
1034
|
-
|
|
204
|
+
The `_metadata` key is stored at `fieldinstance._metadata` as it is, and can be retrieved using `fieldinstance.metadata` readonly property
|
|
1035
205
|
|
|
1036
|
-
|
|
206
|
+
But, this change can have sideEffects defined on the Schema, like validations, visibilityConditions and so on, so proper way to change this value and trigger all side effects in order to update the component that is using this instance is using the `fieldinstance.emitValue` class function:
|
|
1037
207
|
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
Let's say you want to clear a given field when `originalField` has a given value.
|
|
1041
|
-
|
|
1042
|
-
```json
|
|
1043
|
-
{
|
|
1044
|
-
"clearFields": {
|
|
1045
|
-
"ON_FIELD_CHANGE": [
|
|
1046
|
-
{
|
|
1047
|
-
"validations": {
|
|
1048
|
-
"value": "Yes"
|
|
1049
|
-
},
|
|
1050
|
-
"field": "targetValue",
|
|
1051
|
-
"clearedValue": false
|
|
1052
|
-
}
|
|
1053
|
-
]
|
|
1054
|
-
}
|
|
1055
|
-
}
|
|
208
|
+
```javascript
|
|
209
|
+
fieldinstance.emitValue({ event: 'ON_FIELD_CHANGE', value: 'hello' });
|
|
1056
210
|
```
|
|
1057
211
|
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
Just like before, you can specify multiple fields with `fields` key for the same rule.
|
|
212
|
+
This function will set the value along with triggering all the side effects and triggers the onData callbacks defined:
|
|
1061
213
|
|
|
1062
|
-
```
|
|
1063
|
-
{
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
"fields": ["targetValue"],
|
|
1071
|
-
"clearedValue": false
|
|
1072
|
-
}
|
|
1073
|
-
]
|
|
214
|
+
```javascript
|
|
215
|
+
emitValue(prop: {
|
|
216
|
+
value: unknown | { _value: unknown; _stateValue: unknown };
|
|
217
|
+
event: TEvents;
|
|
218
|
+
}): void {
|
|
219
|
+
this.value = prop.value;
|
|
220
|
+
this.emitEvents({ event: prop.event });
|
|
221
|
+
this.dataSubject$.next({ key: this.name, event: prop.event });
|
|
1074
222
|
}
|
|
1075
|
-
}
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
"clearFields": {
|
|
1086
|
-
"ON_FIELD_CHANGE": [
|
|
1087
|
-
{
|
|
1088
|
-
"validations": {
|
|
1089
|
-
"value": "Yes"
|
|
1090
|
-
},
|
|
1091
|
-
"field": "targetValue",
|
|
1092
|
-
"clearedValue": false,
|
|
1093
|
-
"defaultClearedValue": true,
|
|
1094
|
-
"defaultClearedProps": {
|
|
1095
|
-
"disabled": false
|
|
1096
|
-
}
|
|
1097
|
-
}
|
|
1098
|
-
]
|
|
223
|
+
emitEvents({ event }: { event: TEvents }): void {
|
|
224
|
+
this.setFieldValidity({ event });
|
|
225
|
+
this.validateVisibility({ event, key: this.name });
|
|
226
|
+
this.resetValue({ event, key: this.name });
|
|
227
|
+
this.apiEventQueueSubject$.next({ event });
|
|
228
|
+
this.fieldEventSubject$.next({
|
|
229
|
+
event,
|
|
230
|
+
fieldName: this.name,
|
|
231
|
+
fieldInstance: this,
|
|
232
|
+
});
|
|
1099
233
|
}
|
|
1100
|
-
}
|
|
1101
234
|
```
|
|
1102
235
|
|
|
1103
|
-
|
|
1104
|
-
Useful after an API call to set a default response value or a value stored earlier.
|
|
1105
|
-
You can also set a `defaultClearedProps` attribute to change the props of the component if the validation passes.
|
|
1106
|
-
|
|
1107
|
-
For definition clearValues runs validations on the target field and not on the field it is declaring.
|
|
1108
|
-
Therefore, you can set `useCurrentFieldValidation` prop with true if you want validations to be performed on the current field.
|
|
236
|
+
To emit a capture of this value change, value setter emits on the end, setting the value on the configured property on the `mapper -> events -> setValue` when the value is being retrieved, an object is created with the correspondent key configured to be passed as props on the component.
|
|
1109
237
|
|
|
1110
|
-
|
|
1111
|
-
> you must listen to the `ON_FIELD_CLEARED` event to execute such a call on the target field.
|
|
238
|
+
Also, there is an helper function to help register this callback function that is responsible to change this value on the adapter called `subscribeValue`.
|
|
1112
239
|
|
|
1113
|
-
|
|
240
|
+
So, to compare the basic react implementation from a simple binded state along with a change handler to a form-engine controlled input handler, the implementation goes like this:
|
|
1114
241
|
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
```json
|
|
1118
|
-
{
|
|
1119
|
-
"api": {
|
|
1120
|
-
"ON_FIELD_CHANGE": [
|
|
1121
|
-
{
|
|
1122
|
-
"blockRequestWhenInvalid": true,
|
|
1123
|
-
"method": "GET",
|
|
1124
|
-
"url": "https://api.chucknorris.io/jokes/random",
|
|
1125
|
-
"scope": "chuck"
|
|
1126
|
-
}
|
|
1127
|
-
]
|
|
1128
|
-
}
|
|
1129
|
-
}
|
|
1130
|
-
```
|
|
242
|
+
```javascript
|
|
243
|
+
const [valueState, setValueState] = useState<Record<string, unknown>>(fieldinstance.stateValue || {});
|
|
1131
244
|
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
| blockRequestWhenInvalid | boolean | Specify if this call should be blocked when the field is invalid (due to validations) |
|
|
1139
|
-
| method | string | HTTP verb. Get, Post, Put or delete |
|
|
1140
|
-
| url | string | The api url |
|
|
1141
|
-
| scope | string | This lets you put the api result inside the form scope in the given key. THis will allow to use the call result latter on on some field |
|
|
1142
|
-
| body | object | Body to send to the API |
|
|
1143
|
-
| headers | object | Api headers |
|
|
1144
|
-
| fieldValueAsParams | object | An object with key (name of the field in the form) and value (name that I want to be in the query, which if nothing is passed, uses the name of the field as the key) and then it transforms it into a query string |
|
|
1145
|
-
| fieldValueAsPathParams | array of strings | an array of field names where he uses it as a key to capture the value and transforms it into path param |
|
|
1146
|
-
| debounceTime | number | Allow you to debounce the api call by X seconds |
|
|
1147
|
-
| preConditions | TValidations | Allow you to specify validations that should not fail in order to call the API |
|
|
1148
|
-
|
|
1149
|
-
### PreConditions
|
|
1150
|
-
|
|
1151
|
-
You can specify the pre-conditions that need to be met, in order for the request to start.
|
|
1152
|
-
|
|
1153
|
-
```json
|
|
1154
|
-
{
|
|
1155
|
-
"api": {
|
|
1156
|
-
"ON_FIELD_CHANGE": [
|
|
1157
|
-
{
|
|
1158
|
-
"method": "GET",
|
|
1159
|
-
"url": "https://api.chucknorris.io/jokes/random",
|
|
1160
|
-
"scope": "chuck",
|
|
1161
|
-
"preConditions": {
|
|
1162
|
-
"required": true,
|
|
1163
|
-
"value": "run"
|
|
1164
|
-
}
|
|
1165
|
-
}
|
|
1166
|
-
]
|
|
245
|
+
fieldInstance = new FormField(
|
|
246
|
+
(...)
|
|
247
|
+
mapper: {
|
|
248
|
+
events: {
|
|
249
|
+
setValue: 'value'
|
|
250
|
+
}
|
|
1167
251
|
}
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
In the above example, the api specified will only be called
|
|
252
|
+
(...)
|
|
253
|
+
)
|
|
1172
254
|
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
255
|
+
useEffect(() => {
|
|
256
|
+
fieldInstance.subscribeValue((value) => setValueState(value));
|
|
257
|
+
return () => {
|
|
258
|
+
fieldInstance?.destroyField();
|
|
259
|
+
};
|
|
260
|
+
}, []);
|
|
1178
261
|
|
|
1179
|
-
|
|
262
|
+
const handleChange = useCallback((event: unknown) => {
|
|
263
|
+
fieldInstance?.emitValue({ value: event, event: 'ON_FIELD_CHANGE' });
|
|
264
|
+
}, []);
|
|
1180
265
|
|
|
1181
|
-
|
|
1182
|
-
{
|
|
1183
|
-
"api": {
|
|
1184
|
-
"ON_FIELD_CHANGE": [
|
|
1185
|
-
{
|
|
1186
|
-
"method": "GET",
|
|
1187
|
-
"url": "https://api.chucknorris.io/jokes",
|
|
1188
|
-
"scope": "chuck",
|
|
1189
|
-
"fieldValueAsParams": {
|
|
1190
|
-
"input": "name",
|
|
1191
|
-
"date": ""
|
|
1192
|
-
}
|
|
1193
|
-
}
|
|
1194
|
-
]
|
|
1195
|
-
}
|
|
1196
|
-
}
|
|
266
|
+
return <input onChange={handleChange} {...valueState}>
|
|
1197
267
|
```
|
|
1198
268
|
|
|
1199
|
-
|
|
269
|
+
The listener is registered when the field mounts calling `subscribeValue` on the field instance that will update the state.
|
|
1200
270
|
|
|
1201
|
-
|
|
1202
|
-
# GET https://api.chucknorris.io/jokes?name=random&date=2013-20-01
|
|
1203
|
-
```
|
|
271
|
+
The handleChange calls the field instance `emitValue`
|
|
1204
272
|
|
|
1205
|
-
|
|
273
|
+
The rest of the process to manage the validation, visibility and so on occurs internally on form-engine-core, the only thing that the adapter needs to do is inform the value that is set as input, the output is handled and delivered on the callback function defined on the effect.
|
|
1206
274
|
|
|
1207
|
-
|
|
275
|
+
**Note:** _this is for demonstration purposes, the final implementation of the adapter only resembles to this_
|
|
1208
276
|
|
|
1209
|
-
|
|
277
|
+
<a id="props-change-and-visibility-rules"></a>
|
|
1210
278
|
|
|
1211
|
-
|
|
1212
|
-
{
|
|
1213
|
-
"api": {
|
|
1214
|
-
"ON_FIELD_CHANGE": [
|
|
1215
|
-
{
|
|
1216
|
-
"method": "GET",
|
|
1217
|
-
"url": "https://api.chucknorris.io/jokes",
|
|
1218
|
-
"scope": "chuck",
|
|
1219
|
-
"fieldValueAsPathParams": ["input", "date"]
|
|
1220
|
-
}
|
|
1221
|
-
]
|
|
1222
|
-
}
|
|
1223
|
-
}
|
|
1224
|
-
```
|
|
279
|
+
## Props change and visibility rules
|
|
1225
280
|
|
|
1226
|
-
|
|
281
|
+
Props change can be defined on the schema or can be changed with templating, let's take into consideration what happens in react:
|
|
1227
282
|
|
|
1228
|
-
```
|
|
1229
|
-
|
|
283
|
+
```javascript
|
|
284
|
+
return <input label={props.label} placeholder={props.placeholder}>
|
|
1230
285
|
```
|
|
1231
286
|
|
|
1232
|
-
|
|
287
|
+
React will rerender this component each time props are changed, in the case of form-engine, this props are passed on the schema and can have templating that will change based on any field side effect, what manages the props change is an RXJS subject, to properly register the props and let the form-engine manage the side effects, this is needed to be done:
|
|
1233
288
|
|
|
1234
|
-
|
|
289
|
+
```javascript
|
|
290
|
+
const [state, setState] = useState<Partial<IState>>({
|
|
291
|
+
visibility: fieldInstance?.visibility || true,
|
|
292
|
+
props: fieldInstance?.props || props,
|
|
293
|
+
});
|
|
1235
294
|
|
|
1236
|
-
|
|
295
|
+
useEffect(() => {
|
|
296
|
+
fieldInstance.subscribeState((props) => {
|
|
297
|
+
setState((prev) => ({
|
|
298
|
+
...prev,
|
|
299
|
+
...props,
|
|
300
|
+
}))
|
|
301
|
+
})
|
|
1237
302
|
|
|
1238
|
-
|
|
303
|
+
return () => {
|
|
304
|
+
fieldInstance?.destroyField();
|
|
305
|
+
};
|
|
306
|
+
},[])
|
|
1239
307
|
|
|
1240
|
-
|
|
1241
|
-
[
|
|
1242
|
-
{
|
|
1243
|
-
"name": "one",
|
|
1244
|
-
"component": "input",
|
|
1245
|
-
"props": {
|
|
1246
|
-
"label": "${fields.two.value}"
|
|
1247
|
-
}
|
|
1248
|
-
},
|
|
1249
|
-
{
|
|
1250
|
-
"name": "one",
|
|
1251
|
-
"component": "input",
|
|
1252
|
-
"props": {
|
|
1253
|
-
//...
|
|
1254
|
-
}
|
|
1255
|
-
}
|
|
1256
|
-
]
|
|
308
|
+
return {state.visibility && <input {...props}>}
|
|
1257
309
|
```
|
|
1258
310
|
|
|
1259
|
-
|
|
311
|
+
Field instance has an helper function to register the callback function `subscribeState` to be called each time props change internally on form-engine, and on this callback function, you need to change the state of the props in order to occur onto the adapter, also the visibility control is made by the `visibility` prop given by the callback function, so the condition to show or hide the component is made onto the adapter depending on this prop value.
|
|
1260
312
|
|
|
1261
|
-
|
|
313
|
+
**NOTE: trying to pass the value sending the value prop will not work except if this value is a template, also, templates that will change based on the same field will not work either**
|
|
1262
314
|
|
|
1263
|
-
|
|
315
|
+
**Note:** _this is for demonstration purposes, the final implementation of the adapter only resembles to this_
|
|
1264
316
|
|
|
1265
|
-
|
|
1266
|
-
| --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1267
|
-
| global | This namespace contains all the data that comes from the client implementing the Form and is injected in iVars |
|
|
1268
|
-
| fields | Automatically generated scope. This namespace contains all the fields with everything that is done in them per field. Eg: value, errors, visible, mask etc. Refer to the types for more info |
|
|
1269
|
-
| api | This scope is where you can store the api responses with the api scope key. |
|
|
1270
|
-
| hooks | This one is retrieved by the hooks configured on the client |
|
|
1271
|
-
| configs | All the configs that the client gave to the form, will be stored here |
|
|
1272
|
-
|
|
1273
|
-
Templating basically allows a given component to subscribe to any scope change, be notified and changed according to that. In the following example, the component named `make` is subscribed to `api` namespace on `data` key.
|
|
1274
|
-
|
|
1275
|
-
```json
|
|
1276
|
-
{
|
|
1277
|
-
"name": "make",
|
|
1278
|
-
"component": "dropdown",
|
|
1279
|
-
"props": {
|
|
1280
|
-
"id": "make",
|
|
1281
|
-
"name": "make",
|
|
1282
|
-
"label": "Make",
|
|
1283
|
-
"placeholder": "",
|
|
1284
|
-
"options": "${api.makes.data||[]}"
|
|
1285
|
-
}
|
|
1286
|
-
}
|
|
1287
|
-
```
|
|
317
|
+
<a id="error-messages-display"></a>
|
|
1288
318
|
|
|
1289
|
-
|
|
319
|
+
## Error messages display
|
|
1290
320
|
|
|
1291
|
-
|
|
321
|
+
For error message display, based on validation logic occuring in the field configured onto the schema, the component need to have a property configured onto the mappers configuration onto `mappers -> events -> setErrorMessage`, then, each time an error occurs, the field will automatically have an error displayed on the corresponding prop.
|
|
1292
322
|
|
|
1293
|
-
|
|
1294
|
-
{
|
|
1295
|
-
"component": "input",
|
|
1296
|
-
"name": "destination",
|
|
1297
|
-
"props": {
|
|
1298
|
-
"name": "destination",
|
|
1299
|
-
"label": "Dynamic -> ${global.name.${fields.myfield.value||test}}"
|
|
1300
|
-
}
|
|
1301
|
-
}
|
|
1302
|
-
```
|
|
323
|
+
Example of a basic component in react:
|
|
1303
324
|
|
|
1304
|
-
|
|
325
|
+
```javascript
|
|
326
|
+
const [errorMessage, setErrorMessage] = useState('')
|
|
1305
327
|
|
|
1306
|
-
|
|
1307
|
-
{
|
|
1308
|
-
"global": {
|
|
1309
|
-
"name": {
|
|
1310
|
-
"test": "test",
|
|
1311
|
-
"other": "other"
|
|
1312
|
-
}
|
|
1313
|
-
}
|
|
1314
|
-
}
|
|
328
|
+
return <Input errorMessage={errorMessage}>
|
|
1315
329
|
```
|
|
1316
330
|
|
|
1317
|
-
|
|
331
|
+
In form-engine, the error message will be passed onto the subscribedState on `errors` with the prop name passed onto the mapper config and all the error messages that were triggered
|
|
1318
332
|
|
|
1319
|
-
|
|
333
|
+
Example:
|
|
1320
334
|
|
|
1321
|
-
```
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
}
|
|
1328
|
-
},
|
|
1329
|
-
"fields": {
|
|
1330
|
-
"myfield": {
|
|
1331
|
-
"value": "other"
|
|
1332
|
-
//...
|
|
335
|
+
```javascript
|
|
336
|
+
fieldInstance = new FormField(
|
|
337
|
+
(...)
|
|
338
|
+
mapper: {
|
|
339
|
+
events: {
|
|
340
|
+
setErrorMessage: 'errorMessage'
|
|
1333
341
|
}
|
|
1334
342
|
}
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
In this case would access `global.name.other` and the final result would be _"Dynamic -> other"_
|
|
1339
|
-
|
|
1340
|
-
### Templates
|
|
1341
|
-
|
|
1342
|
-
We talked about templates, but let's go a step further. THe definition of template, is a just a string that has a given prefix and suffix like `${...}`.
|
|
1343
|
-
|
|
1344
|
-
Whatever comes inside the delimiters will be later extracted by the engine and mapped with the current scope in order to find a replacement value.
|
|
1345
|
-
|
|
1346
|
-
The only limitation is that the template must be a string representing an object path. That object path will be looked for in the [scope](#scope) like `#{api.myapicall.response.data.value}`.
|
|
1347
|
-
|
|
1348
|
-
**Default values**
|
|
1349
|
-
|
|
1350
|
-
You can set template default values with `||` like `${fields.foo.value||default-value}`. This will lead to, if the scope has value in `fields.foo` set the value in template value, otherwise set the string `default-value`.
|
|
1351
|
-
You can also use as many values as you want as defaults. Only what is valid and has a value will go into the field, otherwise it will be undefined.
|
|
1352
|
-
|
|
1353
|
-
**Template nesting**
|
|
1354
|
-
|
|
1355
|
-
You can also nest multiple templates reaching extreme situations. For example
|
|
343
|
+
(...)
|
|
344
|
+
)
|
|
1356
345
|
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
- If `global.fieldname` exists and has value `bar` for example, and form `bar` field contains value lets say value 2 - Output will be 2
|
|
1362
|
-
- If `global.fieldname` exists and has value `bar` for example, and form `bar` field does not contain value - Output will be non value
|
|
1363
|
-
- If `global.fieldname` does not exists , and form `foo` field does not contain value - Output will be non value
|
|
1364
|
-
- If `global.fieldname` does not exists , and form `foo` field contains value lets say value 3 - Output will be 3
|
|
346
|
+
const [state, setState] = useState<Partial<IState>>({
|
|
347
|
+
visibility: fieldInstance?.visibility || true,
|
|
348
|
+
props: fieldInstance?.props || props,
|
|
349
|
+
});
|
|
1365
350
|
|
|
1366
|
-
|
|
351
|
+
useEffect(() => {
|
|
352
|
+
fieldInstance.subscribeState((props) => {
|
|
353
|
+
setState((prev) => ({
|
|
354
|
+
...prev,
|
|
355
|
+
...props,
|
|
356
|
+
}))
|
|
357
|
+
})
|
|
1367
358
|
|
|
1368
|
-
|
|
359
|
+
return () => {
|
|
360
|
+
fieldInstance?.destroyField();
|
|
361
|
+
};
|
|
362
|
+
},[])
|
|
1369
363
|
|
|
1370
|
-
|
|
1371
|
-
{
|
|
1372
|
-
"component": "input",
|
|
1373
|
-
"name": "password",
|
|
1374
|
-
"validations": {
|
|
1375
|
-
"methods": {
|
|
1376
|
-
"required": true,
|
|
1377
|
-
"value": "varOps.concatenate(${fields.email.value||0},${fields.email2.value||0})"
|
|
1378
|
-
},
|
|
1379
|
-
"eventMessages": {
|
|
1380
|
-
"ON_FIELD_CHANGE": ["required", "value" ]
|
|
1381
|
-
},
|
|
1382
|
-
"messages": {
|
|
1383
|
-
"required": "Password is required",
|
|
1384
|
-
"value": "Error value must be varOps.concatenate(${fields.email.value||0},${fields.email2.value||0})"
|
|
1385
|
-
}
|
|
1386
|
-
},
|
|
1387
|
-
"props": {
|
|
1388
|
-
"variants": "default_border",
|
|
1389
|
-
"placeholder": "Please enter your password",
|
|
1390
|
-
"label": "Password"
|
|
1391
|
-
}
|
|
1392
|
-
}
|
|
364
|
+
return <Input {...state.errors}>
|
|
1393
365
|
```
|
|
1394
366
|
|
|
1395
|
-
|
|
367
|
+
The error will contain an object with a key named `errorMessage` with all the errors that occured separated with a comma, the key configured onto the mapper varies based on the component props, if another component sets error messages on another prop, needs to be configured onto the mapper config onto setErrorMessage inside events.
|
|
1396
368
|
|
|
1397
|
-
|
|
369
|
+
**Note:** _this is for demonstration purposes, the final implementation of the adapter only resembles to this_
|
|
1398
370
|
|
|
1399
|
-
|
|
371
|
+
<a id="subject-dependencies"></a>
|
|
1400
372
|
|
|
1401
|
-
|
|
1402
|
-
varOps.concatenate('foo', 'bar');
|
|
1403
|
-
```
|
|
1404
|
-
|
|
1405
|
-
This will map to an operation function and the function return value will be replaced by the varOps like
|
|
373
|
+
## subject dependencies
|
|
1406
374
|
|
|
1407
|
-
|
|
1408
|
-
{
|
|
1409
|
-
"validations": {
|
|
1410
|
-
"ON_FIELD_CHANGE": {
|
|
1411
|
-
"required": true,
|
|
1412
|
-
"value": "foo_bar"
|
|
1413
|
-
}
|
|
1414
|
-
}
|
|
1415
|
-
}
|
|
1416
|
-
```
|
|
375
|
+
<a id="field"></a>
|
|
1417
376
|
|
|
1418
|
-
|
|
377
|
+
### field
|
|
1419
378
|
|
|
1420
|
-
```
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
"required": "Password is required",
|
|
1426
|
-
"value": "Error value must be foo_bar"
|
|
1427
|
-
}
|
|
1428
|
-
}
|
|
1429
|
-
}
|
|
379
|
+
```typescript
|
|
380
|
+
templateSubject$: Subject<TTemplateEvent>;
|
|
381
|
+
fieldEventSubject$: Subject<TFieldEvent>;
|
|
382
|
+
dataSubject$: Subject<{ key: string; event: TEvents }>;
|
|
383
|
+
formValidNotification$: Subject<Pick<TFormValidationPayload, 'fieldTrigger'>>;
|
|
1430
384
|
```
|
|
1431
385
|
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
#### Available VarOps
|
|
1435
|
-
|
|
1436
|
-
- concatenate(arg1,arg2)
|
|
1437
|
-
- add(arg1,arg2)
|
|
1438
|
-
- subtract(arg1,arg2)
|
|
1439
|
-
|
|
1440
|
-
### Direct fields binding
|
|
1441
|
-
|
|
1442
|
-
You can change multiple field values and properties with `bindFields` using the form ref in the react adapter, example:
|
|
1443
|
-
|
|
1444
|
-
```js
|
|
1445
|
-
{
|
|
1446
|
-
const ref = useRef < TFormRefActions > null;
|
|
1447
|
-
|
|
1448
|
-
return (
|
|
1449
|
-
<>
|
|
1450
|
-
<Form id="form" ref={ref} schema={foo} />
|
|
1451
|
-
</>
|
|
1452
|
-
);
|
|
1453
|
-
}
|
|
1454
|
-
|
|
1455
|
-
const handleFields = (input1: string, input2: number) =>
|
|
1456
|
-
ref.current?.bindFields({
|
|
1457
|
-
input1: {
|
|
1458
|
-
value: input1,
|
|
1459
|
-
props: {
|
|
1460
|
-
disabled: false,
|
|
1461
|
-
},
|
|
1462
|
-
},
|
|
1463
|
-
input2: {
|
|
1464
|
-
value: input2,
|
|
1465
|
-
props: {
|
|
1466
|
-
disabled: true,
|
|
1467
|
-
},
|
|
1468
|
-
},
|
|
1469
|
-
});
|
|
1470
|
-
```
|
|
386
|
+
this subjects needs to be passed as a field constructor parameters, and the field invoker needs to instanciate them, they are part of the form instance
|
|
387
|
+
implementation
|
|
1471
388
|
|
|
1472
|
-
|
|
389
|
+
<a id="templatesubject"></a>
|
|
1473
390
|
|
|
1474
|
-
|
|
391
|
+
#### templateSubject$
|
|
1475
392
|
|
|
1476
|
-
|
|
393
|
+
This subject needs to be invoked on any field mutation, this will notify the form that a field changed and any other field that has a template dependency needs to be recomputed,
|
|
1477
394
|
|
|
1478
|
-
|
|
395
|
+
Ex: `field1` label props depends on `field2` value, so: `field1.props.label` has a template `${field2.props.value}`, each time `field2` value changes,
|
|
396
|
+
the `templateSubject$` emits, the form gets the notification and checks that `field2` has a `field1` dependency and updates the `label` with the value
|
|
397
|
+
of `field2`
|
|
1479
398
|
|
|
1480
|
-
|
|
399
|
+
<a id="fieldeventsubject"></a>
|
|
1481
400
|
|
|
1482
|
-
|
|
401
|
+
#### fieldEventSubject$
|
|
1483
402
|
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
"state": {
|
|
1487
|
-
"hidden": true
|
|
1488
|
-
}
|
|
1489
|
-
}
|
|
1490
|
-
```
|
|
403
|
+
Each time a basic event mapped from a component occurs like `onChange` or `onBlur` the field emits this subject with it's event type and field name,
|
|
404
|
+
on form, if adapter invokes `subscribeFieldEvent` on an instanciated form with a callback function, this function is called, each time the field `emitEvents` is invoked, otherwise it's ignored
|
|
1491
405
|
|
|
1492
|
-
|
|
406
|
+
<a id="datasubject"></a>
|
|
1493
407
|
|
|
1494
|
-
|
|
408
|
+
#### dataSubject$
|
|
1495
409
|
|
|
1496
|
-
|
|
410
|
+
This subject triggers each time `emitValue` is called, this subject handles field value changes on any event type, if the adapter invokes `subscribeData` on an instanciated form and passes a callback function, this functions will be executed each time a field `emitValue` is invoked
|
|
1497
411
|
|
|
1498
|
-
|
|
1499
|
-
{
|
|
1500
|
-
"state": {
|
|
1501
|
-
"ignoreValue": true
|
|
1502
|
-
}
|
|
1503
|
-
}
|
|
1504
|
-
```
|
|
412
|
+
<a id="formvalidnotification"></a>
|
|
1505
413
|
|
|
1506
|
-
|
|
414
|
+
#### formValidNotification$
|
|
1507
415
|
|
|
1508
|
-
|
|
416
|
+
This subject triggers each time a field validation status change, if the adapter invokes `subscribeFormValidation` with a callback function on an instanciated form, this function will execute, each time a field validity changes, giving the form validity status
|
|
1509
417
|
|
|
1510
|
-
|
|
418
|
+
<a id="build-fields-with-a-schema"></a>
|
|
1511
419
|
|
|
1512
|
-
|
|
420
|
+
## build fields with a schema
|
|
1513
421
|
|
|
1514
|
-
|
|
1515
|
-
{
|
|
1516
|
-
"rehydrate": {
|
|
1517
|
-
"ON_FIELD_CHANGE": [
|
|
1518
|
-
{
|
|
1519
|
-
"validations": {
|
|
1520
|
-
"required": true
|
|
1521
|
-
},
|
|
1522
|
-
"fields": ["destination"]
|
|
1523
|
-
}
|
|
1524
|
-
]
|
|
1525
|
-
},
|
|
1526
|
-
"component": "dropdown",
|
|
1527
|
-
"name": "originalField"
|
|
1528
|
-
}
|
|
1529
|
-
```
|
|
422
|
+
this is a brief explanation of how building fields with a schema works, the constructor method of the form takes care of the process if a schema is passed as a parameter
|
|
1530
423
|
|
|
1531
|
-
|
|
424
|
+
<a id="fields"></a>
|
|
1532
425
|
|
|
1533
|
-
|
|
426
|
+
### fields
|
|
1534
427
|
|
|
1535
|
-
|
|
428
|
+
every schema is parsed with the form instance `serializeStructure` method, this will pick all fields, instanciates all form fields on a map and they are ready to interact with the form with the subjects passed on the constructors, it's the responsability of the adapter to pick this fields map and build the component tree, you can check the react adapter `BuildTree` method
|
|
1536
429
|
|
|
1537
|
-
|
|
430
|
+
<a id="templates"></a>
|
|
1538
431
|
|
|
1539
|
-
|
|
1540
|
-
[
|
|
1541
|
-
{
|
|
1542
|
-
"name": "checkOne",
|
|
1543
|
-
"group": "checkedGroup",
|
|
1544
|
-
"component": "checkbox",
|
|
1545
|
-
"props": {
|
|
1546
|
-
//...
|
|
1547
|
-
}
|
|
1548
|
-
},
|
|
1549
|
-
{
|
|
1550
|
-
"name": "checkTwo",
|
|
1551
|
-
"group": "checkedGroup",
|
|
1552
|
-
"component": "checkbox",
|
|
1553
|
-
"props": {
|
|
1554
|
-
//...
|
|
1555
|
-
}
|
|
1556
|
-
}
|
|
1557
|
-
]
|
|
1558
|
-
```
|
|
432
|
+
### templates
|
|
1559
433
|
|
|
1560
|
-
|
|
434
|
+
to register the templates, the form instance `subscribeTemplates` needs to be invoked, this will create a list of dependencies to be checked each time a `templateSubject$` is triggered, note that this needs to be called when all fields are instantiated, otherwise all templates might not be registered accordingly
|
|
1561
435
|
|
|
1562
|
-
|
|
436
|
+
<a id="build-fields-without-schema"></a>
|
|
1563
437
|
|
|
1564
|
-
|
|
438
|
+
## build fields without schema
|
|
1565
439
|
|
|
1566
|
-
|
|
440
|
+
if you want to add a field without a schema, the form instance has `addField` method, this will add a field onto the form instance and handles a couple of pre requisites, like checking if the name doesn't exists and regists the templates, it's the responsability of the adapter to figure out how to render this field on
|
|
1567
441
|
|
|
1568
|
-
|
|
1569
|
-
{
|
|
1570
|
-
components: [
|
|
1571
|
-
{ component: '', name: 'step1', children: [] },
|
|
1572
|
-
{ component: '', name: 'step2', children: [] },
|
|
1573
|
-
{ component: '', name: 'step3', children: [] },
|
|
1574
|
-
];
|
|
1575
|
-
}
|
|
1576
|
-
```
|
|
1577
|
-
|
|
1578
|
-
You can control the go back and forth event from the [onClick](#props) event of a button inside one of the forms using the form reference.
|
|
1579
|
-
Or Simply using the form reference in a button outside the form, for example:
|
|
442
|
+
<a id="form-group-event-callback-register"></a>
|
|
1580
443
|
|
|
1581
|
-
|
|
1582
|
-
{
|
|
1583
|
-
const ref = useRef < TFormRefActions > null;
|
|
444
|
+
## form group event callback register
|
|
1584
445
|
|
|
1585
|
-
|
|
1586
|
-
}
|
|
446
|
+
`FormGroup` instance provides methods to add and remove form instances, also they let you regist `onData` and `onValid` events on form groups, the focus will be this two events that can be used by the adapters:
|
|
1587
447
|
|
|
1588
|
-
|
|
448
|
+
<a id="form-group-ondata"></a>
|
|
1589
449
|
|
|
1590
|
-
|
|
1591
|
-
const ref = useRef < TFormRefActions > null;
|
|
450
|
+
### form group onData
|
|
1592
451
|
|
|
1593
|
-
|
|
1594
|
-
<>
|
|
1595
|
-
<Form id="form" ref={ref} />
|
|
1596
|
-
<button onClick={ref.current?.stepBack()} />
|
|
1597
|
-
</>
|
|
1598
|
-
);
|
|
1599
|
-
}
|
|
1600
|
-
```
|
|
452
|
+
formGroup `onDataSubscription` instance method let's you pass a list of indexes along with a callback function to be evoked each time any form instance emits `onData`, this is useful to handle groups of forms
|
|
1601
453
|
|
|
1602
|
-
|
|
454
|
+
<a id="form-group-onvalid"></a>
|
|
1603
455
|
|
|
1604
|
-
|
|
1605
|
-
() => ref.current?.stepForward(2);
|
|
1606
|
-
// --------------- OR -------------- //
|
|
1607
|
-
() => ref.current?.stepForward('step3');
|
|
1608
|
-
|
|
1609
|
-
() => ref.current?.stepBack(0);
|
|
1610
|
-
// --------------- OR -------------- //
|
|
1611
|
-
() => ref.current?.stepBack('step1');
|
|
1612
|
-
```
|
|
1613
|
-
|
|
1614
|
-
Additionally, you can use a `step` method provided by form reference to easily set a desired step using the `onFormMount` event. For example:
|
|
1615
|
-
|
|
1616
|
-
```js
|
|
1617
|
-
<Form ref={ref} schema={schema} onFormMount={() => ref.current?.step(1)} />
|
|
1618
|
-
```
|
|
456
|
+
### form group onValid
|
|
1619
457
|
|
|
1620
|
-
|
|
458
|
+
formGroup `onValidSubscription` instance method let's you pass a list of indexes along with a callback function to be evoked each time any form changes it's validity status, this is useful to handle groups of forms, it will returns the form group validity status and each individual form validity status and it's triggered each time a form validity status changes
|