@digigov/form 0.9.0 → 0.10.0

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.
@@ -1,148 +1,580 @@
1
1
  ---
2
- id: MultiplicityField
3
- title: Multiplicity Field
4
- sidebar_label: Recursively ask user data
2
+ id: ask-users-recursive
3
+ title: Ask users for recursive data
5
4
  ---
6
5
 
7
6
  import LeadText from "@site/src/components/LeadText";
8
7
 
9
- <LeadText>Multiplicity field enables users add instances of a particular schema for an
10
- arbitrary amount of times until they are done. It can be used with a fixed
11
- length of instances, or a minimum and maximum length.</LeadText>
8
+ <LeadText>Multiplicity & FieldArray fields allow users to input data described
9
+ by a particular schema for an arbitrary number of times until they are
10
+ done. It can be used to input a fixed, a minimum, and a maximum
11
+ length of instances.</LeadText>
12
12
 
13
13
  ## What we are trying to achieve
14
14
 
15
- This field was developed as an accessible and guided way to fill arrays with
16
- objects described by a schema. For example, we can define the schema for a usual form by describing the fields
17
- it will render.
15
+ First off, a bit of history. This field was developed to meet the needs of several
16
+ GOV.GR services. They needed an easy way to add multiple objects in an array of
17
+ data while they could still keep the usability of a web form, submitting data and
18
+ validating the fields as well. As part of the prototype phase, an initial
19
+ implementation was designed and used in Dilosi for several document templates.
20
+ It used a single button to add new items in the array and opened a modal,
21
+ blocking the rest of the user interface and prompting users to input data.
22
+ During this process, the `FieldArray` component was conceived.
18
23
 
19
- These fields may describe an entity and its properties and it can be reused
20
- multiple times to generate data objects following this schema.
24
+ Meanwhile, Digigov SDK got these requirements and started working on a more
25
+ versatile version of this feature. The new field was developed from scratch as
26
+ an accessible and guided way to fill arrays with objects. Also, we wanted to
27
+ provide an easy-to-use component for developers. The input fields that will
28
+ collect data for each item in the array are described by a JSON schema, then
29
+ provided to `@digigov/form` to manage internally. These fields describe
30
+ properties for a data entity like a citizen and their personal details, and it
31
+ can be reused internally multiple times by `@digigov/form` to generate data
32
+ objects based on this schema.
21
33
 
22
- ```js
23
- const fields = [
24
- {
25
- key: "fullname",
26
- name: "fullname",
27
- type: "string",
28
- required: true
29
- },
30
- {
31
- key: "afm",
32
- name: "afm",
33
- type: "afm",
34
- required: true
35
- },
36
- ];
37
- ```
34
+ ## Accessibility & usability
35
+
36
+ We aim to have semantically correct and valid forms, and as a result we cannot
37
+ mix form-related HTML elements, like `form`, `input`, `label` with generic ones,
38
+ eg. `div`, cards or other custom components. For example, if we try to add a
39
+ paragraph element inside a form, React will throw warnings in our console.
40
+ Instead of creating complex interfaces that try to do too much too fast, Digigov
41
+ Form recommends using forms in their original way, filling and validating
42
+ inputs, then submitting the form as a whole.
38
43
 
39
- We aim to have semantically correct and valid forms. What this means is that we
40
- cannot mix form-related HTML elements (eg. form, input, label) with generic ones
41
- (eg. div, cards, custom components). React will start throwing warnings left and
42
- right if we try to add a paragraph element inside a form.
43
-
44
- Apart from this, Digigov Form recommends using forms in their original way of
45
- filling and validating inputs, then submitting the form as a whole. The process
46
- repeats if validation fails or proceeds if validation is successful. In any
47
- case, it is imperative to provide meaningful information to the users on
48
- how they proceed to fix the validation errors.
49
-
50
- Last but not least, we should make our interfaces entirely accessible to screen
51
- readers and other accessibilty tools and this requirement adds another dimension
52
- to this problem. Forms and fields should read like a well-written piece of text
53
- and make sense to the users. Thus we should avoid the use of magic UX solutions
54
- that require users to have some well-developed digital skills and most of the
55
- time are used for expert users.
56
-
57
- Users should fill in data as this schema dictates, and every time they add
58
- something we should ask them if they want to continue adding or escape the loop.
59
- We can also add some limitations so we can ask for an array with specific
60
- cardinality. In this way we can validate each form state regardless of whether a
61
- user is in the process of data input or reviewing what they've added. In any
62
- case, the form should guide users through steps and validation errors, and avoid
63
- giving too much freedom in actions that could lead to estranged users losing
64
- their way out of the maze.
65
-
66
- But how can we code this feature as part of a Digigov form?
67
-
68
- ## Enter the Multiplicity field
69
-
70
- To deal with this problem, we have decided to create a composite field that uses
71
- externally defined object schema alongside some internal helpers. This field in
72
- fact is considered as a single key-value pair by the form. The only difference is that
73
- the value will be of type `Array` instead of any other primitive used for a
74
- field type.
75
-
76
- The component internally uses the
44
+ Adding another dimension to this problem, we should make sure our interfaces
45
+ are entirely accessible to screen readers and other accessibility tools. This is
46
+ the reason we decided to design the flow to be as guided and recoverable as
47
+ possible. Forms and fields should read like well-written pieces of text and
48
+ make sense to the users. While validation fails, the system provides users with
49
+ meaningful error messages and instructs them on how to fix the validation errors to
50
+ proceed. Once the validation is successful, the flow can proceed with
51
+ submmitting data. In any case, it is imperative to provide meaningful
52
+ information to the users at each step. Thus, we should avoid the use of 🌈
53
+ “magic“ UX solutions that presume users have developed digital skills and
54
+ most of the time are used for expert use cases.
55
+
56
+ Initially, we used an add button that users clicked to explicitly create with
57
+ their action a new item in the array, but in this way we cannot programmatically
58
+ ensure that users still have stuff to enter to the system. What we decided to do
59
+ is to turn this state check into a direct question that will be a part of the final
60
+ form. We created a question that will make sure that users always provide the
61
+ form with their intention.
62
+
63
+ Both FieldArray and MultiplicityField will result in the same data output when
64
+ users submit the form, which is an `array` that contains `objects` described by
65
+ the `extra.fields` key. The component internally uses the
77
66
  [useFieldArray](https://www.react-hook-form.com/api/usefieldarray) hook from
78
67
  react-hook-form.
79
68
 
69
+ :::caution
70
+
71
+ We strongly recommend that services concerning citizens use `Multiplicity`, instead
72
+ of `FieldArray`.
73
+
74
+ Please be careful when and how you use a `FieldArray` component. Many users are not
75
+ familiar with modern user interface design practices, and it is likely they will
76
+ be confused. If you choose to use it, please provide meaningful hints in the
77
+ `extra.label.object.nothing_added` and `extra.label.object.title_added` keys.
78
+
79
+ :::
80
+
81
+
82
+ Users should fill in data as this schema dictates, and every time
83
+ they add something, we should ask them if they want to continue adding or escape
84
+ the loop. We could also add some limits to the number of items users create,
85
+ resulting in an array with specific cardinality. By doing so, we can validate the
86
+ form state at any time, regardless of whether a user is in the process of data input
87
+ or reviewing what they've added. In any case, the form should guide users
88
+ through steps and validation errors, and avoid giving too much freedom of
89
+ action that could lead to estranged users losing the intended path or their
90
+ focus.
91
+
92
+ Those are a lot of things to keep in mind, but luckily we've got you covered. How
93
+ can we code this feature as part of a Digigov form, you ask?
94
+
95
+ ## Introducing the Multiplicity field
96
+
97
+ To deal with these features, we have decided to create a composite field that uses
98
+ custom object schema for the input fields alongside some internal helpers to
99
+ make sure that the flow is usable from beginning to end. In reality, this
100
+ field is in fact considered as a normal single key-value pair by the
101
+ `@digigov/form`, same as every other input type. The only difference is that the
102
+ type of the value will be an `Array` instead of any other primitive used for a
103
+ field type, like `string`, `number`, etc. Inside the array, each one of the
104
+ indexes will be an object with nested field values.
105
+
106
+ This field is the most complex component we currently offer, and it uses the
107
+ [useFieldArray](https://www.react-hook-form.com/api/usefieldarray) hook from
108
+ react-hook-form under the hood. First, let's take a look at what it looks like, and then we can
109
+ break down its functionality and how you can use it.
110
+
80
111
  ```jsx live
81
112
  import React from 'react';
82
113
  import Form, { Field } from '@digigov/form';
114
+ import Button from '@digigov/ui/core/Button';
83
115
 
84
116
  export default function SimpleForm() {
85
117
  return <Form>
86
118
  <Field
87
- key="children"
88
- label={{ primary: "Children" }}
119
+ key="vehicles"
120
+ name="vehicles"
121
+ label={{ primary: "Vehicles" }}
89
122
  type="array"
90
- multiplicity={true}
91
123
  extra={{
124
+ border: true,
125
+ label: {
126
+ object: {
127
+ title: 'Vehicle',
128
+ title_added: 'The vehicles you have already added',
129
+ add: 'Add',
130
+ delete: 'Remove',
131
+ },
132
+ question: {
133
+ title: 'Do you want to add more vehicles?',
134
+ objectLabel: {
135
+ primary: 'Add one more of your vehicles',
136
+ secondary: 'Fill in the details below and then click “Add“.',
137
+ },
138
+ yes: 'Yes',
139
+ no: 'No',
140
+ },
141
+ },
142
+ of: {
143
+ type: 'object',
144
+ label: {
145
+ primary: 'Vehicle details',
146
+ secondary: 'See and change the details of a vehicle',
147
+ },
148
+ extra: {
149
+ fields: [
150
+ {
151
+ key: 'registration_date',
152
+ type: 'date',
153
+ required: true,
154
+ label: { primary: 'Registration date' },
155
+ },
156
+ {
157
+ key: 'make',
158
+ type: 'string',
159
+ required: true,
160
+ label: {
161
+ primary: 'Make',
162
+ },
163
+ },
164
+ {
165
+ key: 'model',
166
+ type: 'string',
167
+ required: true,
168
+ label: {
169
+ primary: 'Model',
170
+ },
171
+ },
172
+ ],
173
+ },
174
+ },
175
+ }}
176
+ required
177
+ />
178
+ <Button type="submit">Submit</Button>
179
+ </Form>
180
+ }
181
+ ```
182
+
183
+ ### How to use it
92
184
 
93
- label: {
94
- object: {
95
- title: 'Συνυπογράφοντας',
96
- title_added: 'Οι συνυπογράφοντες που έχετε προσθέσει',
97
- add: 'Προσθήκη',
98
- delete: 'Αφαίρεση συνυπογράφοντος',
185
+ Before we begin, we should setup an empty form. We are going to need a main `Form`
186
+ and a submit `Button` that will trigger the `onSubmit` events in our React form.
187
+
188
+ ```jsx
189
+ import React from 'react';
190
+ import Form from '@digigov/form';
191
+ import Button from '@digigov/ui/core/Button';
192
+
193
+ export default function MultiplicityForm() {
194
+ return <Form>
195
+ {/* this is where all fields will eventually be rendered */}
196
+ <Button type="submit">Submit</Button>
197
+ </Form>
198
+ }
199
+ ```
200
+
201
+ As you may have already assumed, this boilerplate code will not get us very far.
202
+ We need an actual `Field` component that will be of `type: 'array'` since we
203
+ need a value that contains multiple instances of a single schema. Given that we
204
+ are asking users for information about their vehicles, we can also fill in the `key`,
205
+ `label.primary`, as well as the `type` and `required`
206
+ props.
207
+
208
+ ```jsx
209
+ <Field
210
+ key="vehicles"
211
+ name="vehicles"
212
+ label={{ primary: "Vehicles" }}
213
+ type="array"
214
+ required
215
+ />
216
+ ```
217
+
218
+ Although this code may seem enough at first glance, it will soon become clear
219
+ that Multiplicity packs a lot of functionality. It needs context in the form of
220
+ natural language, Greek, English, or any other language dictated by the user needs
221
+ in order to carry meaning to the end users.
222
+
223
+ It also needs the schema of the form fields to be filled for each iteration. To
224
+ do so, inside the `extra` key we can add an `of` key. This `of` key will contain
225
+ a key named `label` and a key name `type`, signifying the label text and the type of the
226
+ inner form, respectively.
227
+
228
+ Inside the `of` key you can add an `extra` key that will then contain a `fields`
229
+ key describing the field props for all inputs needed.
230
+
231
+ ```js
232
+ // this will be used as value for the `extra` prop
233
+ {
234
+ of: {
235
+ type: 'object',
236
+ label: {
237
+ primary: 'Vehicle details',
238
+ secondary: 'See and change the details of a vehicle',
239
+ },
240
+ extra: {
241
+ fields: [
242
+ {
243
+ key: 'registration_date',
244
+ type: 'date',
245
+ required: true,
246
+ label: { primary: 'Registration date' },
99
247
  },
100
- question: {
101
- title: 'Do you want to add any additional children?',
102
- objectLabel: {
103
- primary: 'Προσθήκη νέου συνυπογράφοντα',
104
- secondary: 'Συμπληρώστε τα στοιχεία και μετά πατήστε «Προσθήκη»',
105
- },
106
- yes: 'Yes',
107
- no: 'No',
248
+ {
249
+ key: 'make',
250
+ type: 'string',
251
+ required: true,
252
+ label: {
253
+ primary: 'Make',
254
+ },
108
255
  },
109
- },
110
- of: {
111
- type: 'object',
112
- label: {
113
- primary: 'Στοιχεία συνυπογράφοντα',
114
- secondary: 'Δείτε και αλλάξτε τα στοιχεία του συνυπογράφοντα',
256
+ {
257
+ key: 'model',
258
+ type: 'string',
259
+ required: true,
260
+ label: {
261
+ primary: 'Model',
262
+ },
115
263
  },
116
- extra: {
117
- fields: [
118
- {
119
- key: 'afm',
120
- type: 'afm',
121
- required: true,
122
- label: { primary: 'ΑΦΜ' },
264
+ ],
265
+ },
266
+ },
267
+ }
268
+ ```
269
+
270
+ ## Introducing the FieldArray field
271
+
272
+ As a simpler alternative to Multiplicity, we also offer the original
273
+ implementation called `FieldArray` which may be used in dashboards or other apps
274
+ used by expert users or administrators of any kind. FieldArray skips all the
275
+ extra safety features and protections of the Multiplicity and offers more
276
+ flexibility for the end user.
277
+
278
+ ### How to use it
279
+
280
+ We tried to maintain the differences in the API between the two implementations
281
+ as little as possible. The only thing that is required for you to do to use the
282
+ FieldArray instead of the Multiplicity, is to explicitly disable the
283
+ multiplicity functionality, by using the `multiplicity={false}` prop.
284
+
285
+ As a result, if you learn how to use one, you can easily use the other, and
286
+ naturally, you can also change their behaviour by changing a single prop in your
287
+ React code.
288
+
289
+ ```jsx
290
+ <Field
291
+ key="vehicles"
292
+ name="vehicles"
293
+ label={{ primary: "Vehicles" }}
294
+ type="array"
295
+ // highlight-next-line
296
+ multiplicity={false}
297
+ required
298
+ />
299
+ ```
300
+
301
+ The end result looks very different from Multiplicity at first glance, but you
302
+ can achieve the same results with both of them.
303
+
304
+ ```jsx live
305
+ import React from 'react';
306
+ import Form, { Field } from '@digigov/form';
307
+ import Button from '@digigov/ui/core/Button';
308
+
309
+ export default function SimpleForm() {
310
+ return <Form>
311
+ <Field
312
+ key="vehicles"
313
+ name="vehicles"
314
+ label={{ primary: "Vehicles" }}
315
+ type="array"
316
+ multiplicity={false}
317
+ extra={{
318
+ border: true,
319
+ label: {
320
+ object: {
321
+ title: 'Vehicle',
322
+ title_added: 'The vehicles you have already added',
323
+ nothing_added: 'You have not added any vehicles yet.',
324
+ add: 'Add vehicle',
325
+ delete: 'Remove',
326
+ },
327
+ question: {
328
+ title: 'Do you want to add more vehicles?',
329
+ objectLabel: {
330
+ primary: 'Add one more of your vehicles',
331
+ secondary: 'Fill in the details below and then click “Add“.',
123
332
  },
124
- {
125
- key: 'firstName',
126
- required: true,
127
- type: 'string',
128
- label: {
129
- primary: 'Όνομα',
333
+ yes: 'Yes',
334
+ no: 'No',
335
+ },
336
+ },
337
+ of: {
338
+ type: 'object',
339
+ label: {
340
+ primary: 'Vehicle details',
341
+ secondary: 'See and change the details of a vehicle',
342
+ },
343
+ extra: {
344
+ fields: [
345
+ {
346
+ key: 'registration_date',
347
+ type: 'date',
348
+ required: true,
349
+ label: { primary: 'Registration date' },
350
+ },
351
+ {
352
+ key: 'make',
353
+ type: 'string',
354
+ required: true,
355
+ label: {
356
+ primary: 'Make',
357
+ },
358
+ },
359
+ {
360
+ key: 'model',
361
+ type: 'string',
362
+ required: true,
363
+ label: {
364
+ primary: 'Model',
365
+ },
130
366
  },
367
+ ],
368
+ },
369
+ },
370
+ }}
371
+ required
372
+ />
373
+ <Button type="submit">Submit</Button>
374
+ </Form>
375
+ }
376
+ ```
377
+
378
+ ## Validating form data
379
+
380
+ Luckily, if you want to validate the values of the nested fields, for instance,
381
+ the first name of a vehicle, you don't have to perform any further actions. The fields'
382
+ validation will work right out of the box, same as any other form.
383
+
384
+ There is another use case that is available as part of the
385
+ MultiplicityField. Given that the value of this field type is an array, we
386
+ should expect to need some length validation sooner than later.
387
+
388
+ ### Exact length
389
+
390
+ You can easily validate the exact length of the items added to the multipliticy
391
+ array, simply by using the `extra.length` prop.
392
+
393
+ ```jsx
394
+ <Field
395
+ extra={{
396
+ length: 2,
397
+ of: { ... },
398
+ label: { ... },
399
+ }}
400
+ />
401
+ ```
402
+
403
+ The example below will validate this and show all the error messages necessary
404
+ to make sure that the users understand what is going wrong with the process.
405
+
406
+ ```jsx live
407
+ import React from 'react';
408
+ import Form, { Field } from '@digigov/form';
409
+ import Button from '@digigov/ui/core/Button';
410
+
411
+ export default function SimpleForm() {
412
+ return <Form>
413
+ <Field
414
+ key="vehicles"
415
+ name="vehicles"
416
+ label={{ primary: "Vehicles" }}
417
+ type="array"
418
+ multiplicity={true}
419
+ extra={{
420
+ border: true,
421
+ length: 3,
422
+ label: {
423
+ object: {
424
+ title: 'Vehicle',
425
+ title_added: 'The vehicles you have already added',
426
+ add: 'Add',
427
+ delete: 'Remove',
428
+ },
429
+ question: {
430
+ title: 'Do you want to add more vehicles?',
431
+ objectLabel: {
432
+ primary: 'Add one more of your vehicles',
433
+ secondary: 'Fill in the details below and then click “Add“.',
131
434
  },
132
- {
133
- key: 'lastName',
134
- required: true,
135
- type: 'string',
136
- label: {
137
- primary: 'Επώνυμο',
435
+ yes: 'Yes',
436
+ no: 'No',
437
+ },
438
+ },
439
+ of: {
440
+ type: 'object',
441
+ label: {
442
+ primary: 'Vehicle details',
443
+ secondary: 'See and change the details of a vehicle',
444
+ },
445
+ extra: {
446
+ fields: [
447
+ {
448
+ key: 'registration_date',
449
+ type: 'date',
450
+ required: true,
451
+ label: { primary: 'Registration date' },
452
+ },
453
+ {
454
+ key: 'make',
455
+ type: 'string',
456
+ required: true,
457
+ label: {
458
+ primary: 'Make',
459
+ },
138
460
  },
461
+ {
462
+ key: 'model',
463
+ type: 'string',
464
+ required: true,
465
+ label: {
466
+ primary: 'Model',
467
+ },
468
+ },
469
+ ],
470
+ },
471
+ },
472
+ }}
473
+ required
474
+ />
475
+ <Button type="submit">Submit</Button>
476
+ </Form>
477
+ }
478
+ ```
479
+
480
+ ### Min or max length
481
+
482
+ You can also use the min or max length validation to define the cardinality
483
+ range for the array. You can use only the `min` key to add a minimum length of
484
+ items or only the `max` key to limit them.
485
+
486
+ :::caution
487
+
488
+ Do not mix and match the exact `length` validation with the `min` or `max`.
489
+
490
+ :::
491
+
492
+ ```jsx
493
+ <Field
494
+ extra={{
495
+ min: 2,
496
+ max: 5,
497
+ of: { ... },
498
+ label: { ... },
499
+ }}
500
+ />
501
+ ```
502
+
503
+ The example below will validate this and show all the error messages necessary
504
+ to make sure that the users understand what is going wrong with the process.
505
+
506
+ ```jsx live
507
+ import React from 'react';
508
+ import Form, { Field } from '@digigov/form';
509
+ import Button from '@digigov/ui/core/Button';
510
+
511
+ export default function SimpleForm() {
512
+ return <Form>
513
+ <Field
514
+ key="vehicles"
515
+ name="vehicles"
516
+ label={{ primary: "Vehicles" }}
517
+ type="array"
518
+ multiplicity={true}
519
+ extra={{
520
+ border: true,
521
+ min: 2,
522
+ max: 5,
523
+ label: {
524
+ object: {
525
+ title: 'Vehicle',
526
+ title_added: 'The vehicles you have already added',
527
+ nothing_added: 'You have not added any vehicles yet.',
528
+ add: 'Add',
529
+ delete: 'Remove',
530
+ },
531
+ question: {
532
+ title: 'Do you want to add more vehicles?',
533
+ objectLabel: {
534
+ primary: 'Add one more of your vehicles',
535
+ secondary: 'Fill in the details below and then click “Add“.',
139
536
  },
140
- ],
537
+ yes: 'Yes',
538
+ no: 'No',
539
+ },
540
+ },
541
+ of: {
542
+ type: 'object',
543
+ label: {
544
+ primary: 'Vehicle details',
545
+ secondary: 'See and change the details of a vehicle',
546
+ },
547
+ extra: {
548
+ fields: [
549
+ {
550
+ key: 'registration_date',
551
+ type: 'date',
552
+ required: true,
553
+ label: { primary: 'Registration date' },
554
+ },
555
+ {
556
+ key: 'make',
557
+ type: 'string',
558
+ required: true,
559
+ label: {
560
+ primary: 'Make',
561
+ },
562
+ },
563
+ {
564
+ key: 'model',
565
+ type: 'string',
566
+ required: true,
567
+ label: {
568
+ primary: 'Model',
569
+ },
570
+ },
571
+ ],
572
+ },
141
573
  },
142
- },
143
574
  }}
144
575
  required
145
576
  />
577
+ <Button type="submit">Submit</Button>
146
578
  </Form>
147
579
  }
148
- ```
580
+ ```