summaryrefslogtreecommitdiff
path: root/design-documents/054-dynamic-form.rst
diff options
context:
space:
mode:
Diffstat (limited to 'design-documents/054-dynamic-form.rst')
-rw-r--r--design-documents/054-dynamic-form.rst527
1 files changed, 527 insertions, 0 deletions
diff --git a/design-documents/054-dynamic-form.rst b/design-documents/054-dynamic-form.rst
new file mode 100644
index 00000000..caae86b8
--- /dev/null
+++ b/design-documents/054-dynamic-form.rst
@@ -0,0 +1,527 @@
+DD 54: Dynamic Form
+#######################
+
+Summary
+=======
+
+This document outlines the design of forms defined in the
+backend in a JSON file which will be rendered by a client
+for asking information to a person.
+
+
+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.
+Being able to define forms in a JSON file that a client software
+(not just web SPA) could use to ask the information helps to change
+it without requiring a new upgrade of the client app.
+
+
+Requirements
+============
+
+A form consist of a layout, a set of fields and metadata required to
+recognice which form configuration was used to produce the saved value.
+
+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.
+
+
+Metadata requirements
+---------------------
+
+* **identification**: the form configuration instance should have an unique
+ non reusable id.
+
+* **label**: the form should have a name recognizable for the user
+
+* **version**: the same form, with the same id, could be updated. This will
+ increase the version number. A newer form should support older forms.
+
+Proposed Solutions
+==================
+
+The propose solution defines the structure of a form, the fields and additional
+type form-configuration which links a form with a set of fields.
+
+Form metadata
+-------------
+
+This is the root object of the configuration.
+
+.. code-block:: typescript
+
+ type FormMetadata = {
+ label: string;
+ id: string;
+ version: number;
+ config: FormConfiguration;
+ };
+
+
+Form configuration
+-------------
+
+Defies a basic structure and the set of fields the form is going to have.
+
+The ``FormConfiguration`` is an enumerated type which list can be extended in the
+future.
+
+.. code-block:: typescript
+
+ type FormConfiguration = DoubleColumnForm;
+
+ type DoubleColumnForm = {
+ type: "double-column";
+ design: DoubleColumnFormSection[];
+ }
+
+ type DoubleColumnFormSection = {
+ title: string;
+ description?: string;
+ fields: UIFormElementConfig[];
+ };
+
+
+Form fields
+-----------
+
+A form can have two type of element: decorative/informative or input.
+
+An example of a decorative element is a grouping element which make all the fields
+inside the group look into the same section. This element will not allow the user
+to enter information and won't produce any value in the resulting JSON.
+
+An example of an input field is a text field which allows the user to enter text.
+This element should have an ``id`` which will point into the location in which the
+value will be stored in the resulting JSON. Note that two field in the same form
+with the same ``id`` will result in undefined behavior.
+
+The ``UIFormElementConfig`` is an enumerated type with all type of fields supported
+
+.. code-block:: typescript
+
+ type UIFormElementConfig =
+ | UIFormElementGroup
+ | UIFormElementCaption
+ | UIFormFieldAbsoluteTime
+ | UIFormFieldAmount
+ | UIFormFieldArray
+ | UIFormFieldChoiseHorizontal
+ | UIFormFieldChoiseStacked
+ | UIFormFieldFile
+ | UIFormFieldInteger
+ | UIFormFieldSelectMultiple
+ | UIFormFieldSelectOne
+ | UIFormFieldText
+ | UIFormFieldTextArea
+ | UIFormFieldToggle;
+
+
+All form elements should extend from ``UIFieldElementDescription`` which defines a base
+configuration to show a field.
+
+.. code-block:: typescript
+
+ type UIFieldElementDescription = {
+ /* label if the field, visible for the user */
+ label: string;
+
+ /* long text to be shown on user demand */
+ tooltip?: string;
+
+ /* short text to be shown close to the field, usually below and dimmer*/
+ help?: string;
+
+ /* name of the field, useful for a11y */
+ name: string;
+
+ /* if the field should be initially hidden */
+ hidden?: boolean;
+
+ };
+
+That will be enough for a decorative form element (like group element or
+a text element) but if it defines an input field then it should extend
+from ``UIFormFieldBaseConfig`` which add more information to the previously
+defined ``UIFieldElementDescription``.
+
+
+.. code-block:: typescript
+
+ type UIFormFieldBaseConfig = UIFieldElementDescription & {
+ /* example to be shown inside the field */
+ placeholder?: string;
+
+ /* show a mark as required */
+ required?: boolean;
+
+ /* readonly and dim */
+ disabled?: boolean;
+
+ /* conversion id to convert the string into the value type
+ the id should be known to the ui impl
+ */
+ converterId?: string;
+
+ /* property id of the form */
+ id: UIHandlerId;
+ };
+
+ /**
+ * string which defined a json path
+ *
+ */
+ type UIHandlerId = string
+
+
+The ``id`` property defines the location in which this information is going
+to be saved in the JSON result. Formally formally, it should be a ``dot-selector``
+
+.. code-block:: ini
+
+ dot-selector = "." dot-member-name
+ dot-member-name = name-first *name-char
+ name-first = ALPHA / "_"
+ name-char = DIGIT / name-first
+
+ DIGIT = %x30-39 ; 0-9
+ ALPHA = %x41-5A / %x61-7A ; A-Z / a-z
+
+
+All the input fields will create a string value located where the id
+points, unless a ``convertedId`` is specified. The ``convertedId`` is a reference
+to a converter that the client software implements. For example, an input field
+with ``convertedId = "Taler.Amount"`` will transform the value the user
+entered into a *AmountString* with the currency in the configuration.
+
+
+Description of supported fields
+-------------------------------
+
+All of this fields defines an UI handler which help the user to input
+the value with as handy as possible. The type of the field doesn't define
+the type of the value in the resulting JSON, that's defined by the ``converterId``.
+
+Decorative elements
+``````````````````
+
+To show some additional text
+
+.. code-block:: typescript
+
+ type UIFormElementCaption = { type: "caption" } & UIFieldElementDescription;
+
+To group fields in the UI and maybe show a collapsable handler.
+
+.. code-block:: typescript
+
+ type UIFormElementGroup = {
+ type: "group";
+ fields: UIFormElementConfig[];
+ } & UIFieldElementDescription;
+
+Example
+'''''''
+
+.. code-block:: json
+
+ {
+ "label": "Example form",
+ "id": "example",
+ "version": 1,
+ "config": {
+ "type": "double-column",
+ "design": [
+ {
+ "title": "Decorative elements",
+ "fields": [
+ {
+ "type": "caption",
+ "name": "cap",
+ "label": "This is a caption"
+ },
+ {
+ "type": "group",
+ "name": "group",
+ "label": "The first name and last name are in a group",
+ "fields": [
+ {
+ "type": "text",
+ "name": "firstName",
+ "id": ".person.name",
+ "label": "First name"
+ },
+ {
+ "type": "text",
+ "name": "lastName",
+ "id": ".person.lastName",
+ "label": "Last name"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ }
+
+.. image:: ../screenshots/dynamic-forms.decorative-elements.png
+ :width: 400
+
+Time input
+``````````
+
+This may be rendered as a calendar
+
+.. code-block:: typescript
+
+ type UIFormFieldAbsoluteTime = {
+ type: "absoluteTimeText";
+ max?: TalerProtocolTimestamp;
+ min?: TalerProtocolTimestamp;
+ pattern: string;
+ } & UIFormFieldBaseConfig;
+
+.. code-block:: json
+
+ {
+ "label": "Example form",
+ "id": "example",
+ "version": 1,
+ "config": {
+ "type": "double-column",
+ "design": [
+ {
+ "title": "Time inputs",
+ "fields": [
+ {
+ "type": "absoluteTime",
+ "name": "thedate",
+ "id": ".birthdate",
+ "converterId": "Taler.AbsoluteTime",
+ "help": "the day you born",
+ "pattern":"dd/MM/yyyy",
+ "label": "Birthdate"
+ }
+ ]
+ }
+ ]
+ }
+ }
+
+.. image:: ../screenshots/dynamic-forms.time.png
+ :width: 400
+
+
+Amount input
+````````````
+
+Money input.
+
+.. code-block:: typescript
+
+ type UIFormFieldAmount = {
+ type: "amount";
+ max?: Integer;
+ min?: Integer;
+ currency: string;
+ } & UIFormFieldBaseConfig;
+
+.. code-block:: json
+
+ {
+ "label": "Example form",
+ "id": "example",
+ "version": 1,
+ "config": {
+ "type": "double-column",
+ "design": [
+ {
+ "title": "Amount inputs",
+ "fields": [
+ {
+ "type": "amount",
+ "name": "thedate",
+ "id": ".amount",
+ "converterId": "Taler.Amount",
+ "help": "how much do you have?",
+ "currency":"EUR",
+ "label": "Amount"
+ }
+ ]
+ }
+ ]
+ }
+ }
+
+.. image:: ../screenshots/dynamic-forms.amount.png
+ :width: 400
+
+
+List input
+``````````
+
+This input allows to enter more than element in the same field, and the
+resulting JSON will have a json list. The UI should show the elements
+already present in the list, and for that it will use ``labelFieldId``.
+
+.. code-block:: typescript
+
+ type UIFormFieldArray = {
+ type: "array";
+ // id of the field shown when the array is collapsed
+ labelFieldId: UIHandlerId;
+ fields: UIFormElementConfig[];
+ } & UIFormFieldBaseConfig;
+
+
+.. image:: ../screenshots/dynamic-forms.list.png
+ :width: 400
+
+Choice input
+````````````
+
+To be used when the user need to choose on predefined values
+
+.. code-block:: typescript
+
+ interface SelectUiChoice {
+ label: string;
+ description?: string;
+ value: string;
+ }
+
+A set of buttons next to each other
+
+.. code-block:: typescript
+
+ type UIFormFieldChoiseHorizontal = {
+ type: "choiceHorizontal";
+ choices: Array<SelectUiChoice>;
+ } & UIFormFieldBaseConfig;
+
+
+A set of buttons next on top of each other
+
+.. code-block:: typescript
+
+ type UIFormFieldChoiseStacked = {
+ type: "choiceStacked";
+ choices: Array<SelectUiChoice>;
+ } & UIFormFieldBaseConfig;
+
+A drop down list to select one of the elements
+
+.. code-block:: typescript
+
+ type UIFormFieldSelectOne = {
+ type: "selectOne";
+ choices: Array<SelectUiChoice>;
+ } & UIFormFieldBaseConfig;
+
+A drop down list to select multiple of the element, which
+will produce a list of values in the resulting JSON.
+
+.. code-block:: typescript
+
+ type UIFormFieldSelectMultiple = {
+ type: "selectMultiple";
+ max?: Integer;
+ min?: Integer;
+ unique?: boolean;
+ choices: Array<SelectUiChoice>;
+ } & UIFormFieldBaseConfig;
+
+.. image:: ../screenshots/dynamic-forms.choice.png
+ :width: 400
+
+File input
+``````````
+
+.. code-block:: typescript
+
+ type UIFormFieldFile = {
+ type: "file";
+ maxBytes?: Integer;
+ minBytes?: Integer;
+ // comma-separated list of one or more file types
+ // https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/accept#unique_file_type_specifiers
+ accept?: string;
+ } & UIFormFieldBaseConfig;
+
+.. image:: ../screenshots/dynamic-forms.file.png
+ :width: 400
+
+Number input
+````````````
+
+.. code-block:: typescript
+
+ type UIFormFieldInteger = {
+ type: "integer";
+ max?: Integer;
+ min?: Integer;
+ } & UIFormFieldBaseConfig;
+
+
+.. image:: ../screenshots/dynamic-forms.number.png
+ :width: 400
+
+Text input
+````````````
+
+A simple line of text
+
+.. code-block:: typescript
+
+ type UIFormFieldText = { type: "text" } & UIFormFieldBaseConfig;
+
+A bigger multi-line of text
+
+.. code-block:: typescript
+
+ type UIFormFieldTextArea = { type: "textArea" } & UIFormFieldBaseConfig;
+
+
+.. image:: ../screenshots/dynamic-forms.text.png
+ :width: 400
+
+Boolean input
+`````````````
+
+.. code-block:: typescript
+
+ type UIFormFieldToggle = { type: "toggle" } & UIFormFieldBaseConfig;
+
+.. image:: ../screenshots/dynamic-forms.boolean.png
+ :width: 400
+
+Examples
+========
+
+
+
+Q / A
+=====