summaryrefslogtreecommitdiff
path: root/design-documents/054-dynamic-form.rst
blob: d93b36840c7a127407c9c3c29b352b289eb4a18b (plain)
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:: typescript

  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:: typescript

  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:: typescript

  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:: none

  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:: typescript

  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:: typescript

  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
=====