12.54. DD 54: Dynamic Web Form¶
12.54.1. Summary¶
This document outlines the approach for implementing a dynamic web form feature.
12.54.2. 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.
12.54.3. Requirements¶
A form consist of a layout and a set of fields.
12.54.3.1. 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.
12.54.3.2. 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.
12.54.4. 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).
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
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
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.
function MyFormComponent():VNode {
return <FormProvider>
<InputAmount name="amount" />
<InputText name="subject" />
<button type="submit"> Confirm </button>
</FormProvider>
}
12.54.4.1. Example¶
Consider a form shape represented by the TypeScript type:
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:
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
.
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,
},
},
}
}