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:: ../images/dynamic-forms.decorative-elements.png :width: 800 Time input `````````` This may be rendered as a calendar .. code-block:: typescript type UIFormFieldAbsoluteTime = { type: "absoluteTime"; max?: TalerProtocolTimestamp; min?: TalerProtocolTimestamp; pattern: string; } & UIFormFieldBaseConfig; Amount input ```````````` Money input. .. code-block:: typescript type UIFormFieldAmount = { type: "amount"; max?: Integer; min?: Integer; currency: string; } & UIFormFieldBaseConfig; 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; 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; } & UIFormFieldBaseConfig; A set of buttons next on top of each other .. code-block:: typescript type UIFormFieldChoiseStacked = { type: "choiceStacked"; choices: Array; } & UIFormFieldBaseConfig; A drop down list to select one of the elements .. code-block:: typescript type UIFormFieldSelectOne = { type: "selectOne"; choices: Array; } & 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; } & UIFormFieldBaseConfig; 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; Number input ```````````` .. code-block:: typescript type UIFormFieldInteger = { type: "integer"; max?: Integer; min?: Integer; } & UIFormFieldBaseConfig; 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; Boolean input ````````````` .. code-block:: typescript type UIFormFieldToggle = { type: "toggle" } & UIFormFieldBaseConfig; Examples ======== Q / A =====