1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
|
DD 54: Dynamic Web Form
#######################
Summary
=======
This document outlines the approach for implementing a dynamic web form feature.
Motivation
==========
Currently, creating a new form for a web app involves coding a new
page with HTML, CSS, and JS. Exchange AML requires multiple forms,
and different instances may have distinct forms based on jurisdiction.
Requirements
============
A form consist of a layout and a set of fields.
Layout requirements
-------------------
* **editable by system admin**: System admins should be able to create new forms
or edit current one shipped with the source.
* **accesibility**: Forms should meet accessibility level AA.
* **responsive**: Forms should be responsive and function on all devices.
* **metadata**: Generated form information should contain enough data
to handle multiple form versions.
Fields requirements
-------------------
* **validations**: Each field may require custom validation
* **custom data type**: A field may consist of a list, string, number, or a
complex composite structure.
Proposed Solutions
==================
Forms are initialized using a flexible structure defined by the
TypeScript interface FormType<T>. This interface comprises properties
such as value (current form data), initial (initial form data for resetting),
readOnly (flag to disable input), onUpdate (callback on form data update),
and computeFormState (function to derive the form state based on current data).
.. code-block:: javascript
interface FormType<T extends object> {
value: Partial<T>;
initial?: Partial<T>;
readOnly?: boolean;
onUpdate?: (v: Partial<T>) => void;
computeFormState?: (v: Partial<T>) => FormState<T>;
}
``T``: is the type of the result object
``value``: is a reference to the current value of the result
``initial``: data for resetting
``readOnly``: when true, fields won't allow input
``onUpdate``: notification of the result update
``computeFormState``: compute a new state of the form based on the current value
Form state have the same shape of ``T`` but every field type is ``FieldUIOptions``.
Fields type can be:
* strings
* numbers
* boolean
* arrays
* object
The field type ``AmountJson`` and ``AbsoluteTime`` are opaque since field is used as a whole.
The form can be instanciated using
.. code-block:: javascript
import { FormProvider } from "@gnu-taler/web-util/browser";
Then the field component can access all the properties by the ``useField(name)`` hook,
which will return
.. code-block:: javascript
interface InputFieldHandler<Type> {
value: Type;
onChange: (s: Type) => void;
state: FieldUIOptions;
isDirty: boolean;
}
``value``: the current value of the field
``onChange``: a function to call anytime the user want to change the value
``state``: the state of the field (hidden, error, etc..)
``isDirty``: if the user already tried to change the value
A set of common form field exist in ``@gnu-taler/web-util``:
* InputAbsoluteTime
* InputAmount
* InputArray
* InputFile
* InputText
* InputToggle
and should be used inside a ``Form`` context.
.. code-block:: javascript
function MyFormComponent():VNode {
return <FormProvider >
<InputAmount name="amount" />
<InputText name="subject" />
<button type="submit"> Confirm </button>
</FormProvider>
}
Example
--------
Consider a form shape represented by the TypeScript type:
.. code-block:: javascript
type TheFormType = {
name: string,
age: number,
savings: AmountJson,
nextBirthday: AbsoluteTime,
pets: string[],
addres: {
street: string,
city: string,
}
}
An example instance of this form could be:
.. code-block:: javascript
const theFormValue: TheFormType = {
name: "Sebastian",
age: 15,
pets: ["dog","cat"],
address: {
street: "long",
city: "big",
}
}
For such a form, a valid state can be computed using a function like
``computeFormStateBasedOnFormValues``, returning an object indicating
the state of each field, including properties such as ``hidden``,
``disabled``, and ``required``.
.. code-block:: javascript
function computeFormStateBasedOnFormValues(formValues): {
//returning fixed state as an example
//the return state will be commonly be computed from the values of the form
return {
age: {
hidden: true,
},
pets: {
disabled: true,
elements: [{
disabled: false,
}],
},
address: {
street: {
required: true,
error: "the street name was not found",
},
city: {
required: true,
},
},
}
}
Q / A
=====
|