taler-docs

Documentation for GNU Taler components, APIs and protocols
Log | Files | Refs | README | LICENSE

054-dynamic-form.rst (15484B)


      1 .. _dd54dynamicforms:
      2 
      3 DD 54: Dynamic Forms
      4 ####################
      5 
      6 Summary
      7 =======
      8 
      9 This document outlines the design of forms defined in the
     10 backend in a JSON file which will be rendered by a client
     11 for asking information to a person.
     12 
     13 
     14 Motivation
     15 ==========
     16 
     17 Currently, creating a new form for a web app involves coding a new
     18 page with HTML, CSS, and JS. Exchange AML requires multiple forms,
     19 and different instances may have distinct forms based on jurisdiction.
     20 Being able to define forms in a JSON file that a client software
     21 (not just web SPA) could use to ask the information helps to change
     22 it without requiring a new upgrade of the client app.
     23 
     24 
     25 Requirements
     26 ============
     27 
     28 A form consist of a layout, a set of fields and metadata required to
     29 recognice which form configuration was used to produce the saved value.
     30 
     31 Layout requirements
     32 -------------------
     33 
     34 * **editable by system admin**: System admins should be able to create new forms
     35   or edit current one shipped with the source.
     36 
     37 * **accesibility**: Forms should meet accessibility level AA.
     38 
     39 * **responsive**: Forms should be responsive and function on all devices.
     40 
     41 * **metadata**: Generated form information should contain enough data
     42   to handle multiple form versions.
     43 
     44 Fields requirements
     45 -------------------
     46 
     47 * **validations**: Each field may require custom validation
     48 
     49 * **custom data type**: A field may consist of a list, string, number, or a
     50   complex composite structure.
     51 
     52 
     53 Metadata requirements
     54 ---------------------
     55 
     56 * **identification**: the form configuration instance should have an unique
     57   non reusable id.
     58 
     59 * **label**: the form should have a name recognizable for the user
     60 
     61 * **version**: the same form, with the same id, could be updated. This will
     62   increase the version number. A newer form should support older forms.
     63 
     64 Proposed Solutions
     65 ==================
     66 
     67 The propose solution defines the structure of a form, the fields and additional
     68 type form-configuration which links a form with a set of fields.
     69 
     70 Form metadata
     71 -------------
     72 
     73 This is the root object of the configuration.
     74 
     75 .. code-block:: typescript
     76 
     77   type FormMetadata = {
     78     label: string;
     79     id: string;
     80     version: number;
     81     config: FormConfiguration;
     82   };
     83 
     84 
     85 Form configuration
     86 ------------------
     87 
     88 Defies a basic structure and the set of fields the form is going to have.
     89 
     90 The ``FormConfiguration`` is an enumerated type which list can be extended in the
     91 future.
     92 
     93 .. code-block:: typescript
     94 
     95   type FormConfiguration = DoubleColumnForm;
     96 
     97   type DoubleColumnForm = {
     98     type: "double-column";
     99     design: DoubleColumnFormSection[];
    100   }
    101 
    102   type DoubleColumnFormSection = {
    103     title: string;
    104     description?: string;
    105     fields: UIFormElementConfig[];
    106   };
    107 
    108 
    109 Form fields
    110 -----------
    111 
    112 A form can have two type of element: decorative/informative or input.
    113 
    114 An example of a decorative element is a grouping element which make all the fields
    115 inside the group look into the same section. This element will not allow the user
    116 to enter information and won't produce any value in the resulting JSON.
    117 
    118 An example of an input field is a text field which allows the user to enter text.
    119 This element should have an ``id`` which will point into the location in which the
    120 value will be stored in the resulting JSON. Note that two field in the same form
    121 with the same ``id`` will result in undefined behavior.
    122 
    123 The ``UIFormElementConfig`` is an enumerated type with all type of fields supported
    124 
    125 .. code-block:: typescript
    126 
    127   type UIFormElementConfig =
    128     | UIFormElementGroup
    129     | UIFormElementCaption
    130     | UIFormFieldAbsoluteTime
    131     | UIFormFieldAmount
    132     | UIFormFieldArray
    133     | UIFormFieldChoiseHorizontal
    134     | UIFormFieldChoiseStacked
    135     | UIFormFieldFile
    136     | UIFormFieldInteger
    137     | UIFormFieldSelectMultiple
    138     | UIFormFieldSelectOne
    139     | UIFormFieldText
    140     | UIFormFieldTextArea
    141     | UIFormFieldToggle;
    142 
    143 
    144 All form elements should extend from ``UIFieldElementDescription`` which defines a base
    145 configuration to show a field.
    146 
    147 .. code-block:: typescript
    148 
    149   type UIFieldElementDescription = {
    150     /* label if the field, visible for the user */
    151     label: string;
    152 
    153     /* long text to be shown on user demand */
    154     tooltip?: string;
    155 
    156     /* short text to be shown close to the field, usually below and dimmer*/
    157     help?: string;
    158 
    159     /* name of the field, useful for a11y */
    160     name: string;
    161 
    162     /* if the field should be initially hidden */
    163     hidden?: boolean;
    164 
    165   };
    166 
    167 That will be enough for a decorative form element (like group element or
    168 a text element) but if it defines an input field then it should extend
    169 from ``UIFormFieldBaseConfig`` which add more information to the previously
    170 defined ``UIFieldElementDescription``.
    171 
    172 
    173 .. code-block:: typescript
    174 
    175   type UIFormFieldBaseConfig = UIFieldElementDescription & {
    176     /* example to be shown inside the field */
    177     placeholder?: string;
    178 
    179     /* show a mark as required */
    180     required?: boolean;
    181 
    182     /* readonly and dim */
    183     disabled?: boolean;
    184 
    185     /* conversion id to convert the string into the value type
    186         the id should be known to the ui impl
    187     */
    188     converterId?: string;
    189 
    190     /* property id of the form */
    191     id: UIHandlerId;
    192   };
    193 
    194   /**
    195    * string which defined a json path
    196    *
    197    */
    198   type UIHandlerId = string
    199 
    200 
    201 The ``id`` property defines the location in which this information is going
    202 to be saved in the JSON result. Formally formally, it should be a ``dot-selector``
    203 
    204 .. code-block:: ini
    205 
    206   dot-selector    = "." dot-member-name
    207   dot-member-name = name-first *name-char
    208   name-first = ALPHA / "_"
    209   name-char = DIGIT / name-first
    210 
    211   DIGIT           =  %x30-39              ; 0-9
    212   ALPHA           =  %x41-5A / %x61-7A    ; A-Z / a-z
    213 
    214 
    215 All the input fields will create a string value located where the id
    216 points, unless a ``convertedId`` is specified. The ``convertedId`` is a reference
    217 to a converter that the client software implements. For example, an input field
    218 with ``convertedId = "Taler.Amount"`` will transform the value the user
    219 entered into a *AmountString* with the currency in the configuration.
    220 
    221 
    222 Description of supported fields
    223 -------------------------------
    224 
    225 All of this fields defines an UI handler which help the user to input
    226 the value with as handy as possible. The type of the field doesn't define
    227 the type of the value in the resulting JSON, that's defined by the ``converterId``.
    228 
    229 Decorative elements
    230 ```````````````````
    231 
    232 To show some additional text
    233 
    234 .. code-block:: typescript
    235 
    236   type UIFormElementCaption = { type: "caption" } & UIFieldElementDescription;
    237 
    238 To group fields in the UI and maybe show a collapsable handler.
    239 
    240 .. code-block:: typescript
    241 
    242   type UIFormElementGroup = {
    243     type: "group";
    244     fields: UIFormElementConfig[];
    245   } & UIFieldElementDescription;
    246 
    247 Example
    248 '''''''
    249 
    250 .. code-block:: json
    251 
    252   {
    253       "label": "Example form",
    254       "id": "example",
    255       "version": 1,
    256       "config": {
    257         "type": "double-column",
    258         "design": [
    259           {
    260             "title": "Decorative elements",
    261             "fields": [
    262               {
    263                 "type": "caption",
    264                 "name": "cap",
    265                 "label": "This is a caption"
    266               },
    267               {
    268                 "type": "group",
    269                 "name": "group",
    270                 "label": "The first name and last name are in a group",
    271                 "fields": [
    272                   {
    273                     "type": "text",
    274                     "name": "firstName",
    275                     "id": ".person.name",
    276                     "label": "First name"
    277                   },
    278                   {
    279                     "type": "text",
    280                     "name": "lastName",
    281                     "id": ".person.lastName",
    282                     "label": "Last name"
    283                   }
    284                 ]
    285               }
    286             ]
    287           }
    288         ]
    289       }
    290     }
    291 
    292 .. image:: ../screenshots/dynamic-forms.decorative-elements.png
    293   :width: 400
    294 
    295 Time input
    296 ``````````
    297 
    298 This may be rendered as a calendar
    299 
    300 .. code-block:: typescript
    301 
    302   type UIFormFieldAbsoluteTime = {
    303     type: "absoluteTimeText";
    304     max?: TalerProtocolTimestamp;
    305     min?: TalerProtocolTimestamp;
    306     pattern: string;
    307   } & UIFormFieldBaseConfig;
    308 
    309 .. code-block:: json
    310 
    311   {
    312       "label": "Example form",
    313       "id": "example",
    314       "version": 1,
    315       "config": {
    316         "type": "double-column",
    317         "design": [
    318           {
    319             "title": "Time inputs",
    320             "fields": [
    321               {
    322                 "type": "absoluteTime",
    323                 "name": "thedate",
    324                 "id": ".birthdate",
    325                 "converterId": "Taler.AbsoluteTime",
    326                 "help": "the day you born",
    327                 "pattern":"dd/MM/yyyy",
    328                 "label": "Birthdate"
    329               }
    330             ]
    331           }
    332         ]
    333       }
    334     }
    335 
    336 .. image:: ../screenshots/dynamic-forms.time.png
    337   :width: 400
    338 
    339 
    340 Amount input
    341 ````````````
    342 
    343 Money input.
    344 
    345 .. code-block:: typescript
    346 
    347   type UIFormFieldAmount = {
    348     type: "amount";
    349     max?: Integer;
    350     min?: Integer;
    351     currency: string;
    352   } & UIFormFieldBaseConfig;
    353 
    354 .. code-block:: json
    355 
    356   {
    357       "label": "Example form",
    358       "id": "example",
    359       "version": 1,
    360       "config": {
    361         "type": "double-column",
    362         "design": [
    363           {
    364             "title": "Amount inputs",
    365             "fields": [
    366               {
    367                 "type": "amount",
    368                 "name": "thedate",
    369                 "id": ".amount",
    370                 "converterId": "Taler.Amount",
    371                 "help": "how much do you have?",
    372                 "currency":"EUR",
    373                 "label": "Amount"
    374               }
    375             ]
    376           }
    377         ]
    378       }
    379     }
    380 
    381 .. image:: ../screenshots/dynamic-forms.amount.png
    382   :width: 400
    383 
    384 
    385 List input
    386 ``````````
    387 
    388 This input allows to enter more than element in the same field, and the
    389 resulting JSON will have a json list. The UI should show the elements
    390 already present in the list, and for that it will use ``labelFieldId``.
    391 
    392 .. code-block:: typescript
    393 
    394   type UIFormFieldArray = {
    395     type: "array";
    396     // id of the field shown when the array is collapsed
    397     labelFieldId: UIHandlerId;
    398     fields: UIFormElementConfig[];
    399   } & UIFormFieldBaseConfig;
    400 
    401 .. code-block:: json
    402 
    403   {
    404       "label": "Example form",
    405       "id": "example",
    406       "version": 1,
    407       "config": {
    408         "type": "double-column",
    409         "design": [
    410           {
    411             "title": "Amount inputs",
    412             "fields": [
    413               {
    414                 "type": "array",
    415                 "name": "people",
    416                 "id": ".people",
    417                 "help": "who is coming to the party?",
    418                 "labelFieldId": ".name",
    419                 "fields": [{
    420                     "type": "text",
    421                     "name": "firstName",
    422                     "id": ".name",
    423                     "label": "First name"
    424                   },
    425                   {
    426                     "type": "text",
    427                     "name": "lastName",
    428                     "id": ".lastName",
    429                     "label": "Last name"
    430                   }],
    431               }
    432             ]
    433           }
    434         ]
    435       }
    436     }
    437 
    438 
    439 .. image:: ../screenshots/dynamic-forms.list.png
    440   :width: 400
    441 
    442 Choice input
    443 ````````````
    444 
    445 To be used when the user need to choose on predefined values
    446 
    447 .. code-block:: typescript
    448 
    449   interface SelectUiChoice {
    450     label: string;
    451     description?: string;
    452     value: string;
    453   }
    454 
    455 A set of buttons next to each other
    456 
    457 .. code-block:: typescript
    458 
    459   type UIFormFieldChoiseHorizontal = {
    460     type: "choiceHorizontal";
    461     choices: Array<SelectUiChoice>;
    462   } & UIFormFieldBaseConfig;
    463 
    464 
    465 A set of buttons next on top of each other
    466 
    467 .. code-block:: typescript
    468 
    469   type UIFormFieldChoiseStacked = {
    470     type: "choiceStacked";
    471     choices: Array<SelectUiChoice>;
    472   } & UIFormFieldBaseConfig;
    473 
    474 A drop down list to select one of the elements
    475 
    476 .. code-block:: typescript
    477 
    478   type UIFormFieldSelectOne = {
    479     type: "selectOne";
    480     choices: Array<SelectUiChoice>;
    481   } & UIFormFieldBaseConfig;
    482 
    483 A drop down list to select multiple of the element, which
    484 will produce a list of values in the resulting JSON.
    485 
    486 .. code-block:: typescript
    487 
    488   type UIFormFieldSelectMultiple = {
    489     type: "selectMultiple";
    490     max?: Integer;
    491     min?: Integer;
    492     unique?: boolean;
    493     choices: Array<SelectUiChoice>;
    494   } & UIFormFieldBaseConfig;
    495 
    496 
    497 .. code-block:: json
    498 
    499   {
    500       "label": "Example form",
    501       "id": "example",
    502       "version": 1,
    503       "config": {
    504         "type": "double-column",
    505         "design": [
    506           {
    507             "title": "Choice inputs",
    508             "fields": [
    509               {
    510                 "type": "choiceHorizontal",
    511                 "name": "food",
    512                 "label": "Food",
    513                 "id": ".food",
    514                 "choices": [
    515                   {
    516                     "value": "meat",
    517                     "label": "Meat"
    518                   },
    519                   {
    520                     "value": "sushi",
    521                     "label": "Sushi"
    522                   },
    523                   {
    524                     "value": "taco",
    525                     "label": "Taco"
    526                   },
    527                   {
    528                     "value": "salad",
    529                     "label": "Salad"
    530                   }
    531                 ]
    532               }
    533             ]
    534           }
    535         ]
    536       }
    537     }
    538 
    539 .. image:: ../screenshots/dynamic-forms.choice.png
    540   :width: 400
    541 
    542 File input
    543 ``````````
    544 
    545 .. code-block:: typescript
    546 
    547   type UIFormFieldFile = {
    548     type: "file";
    549     maxBytes?: Integer;
    550     minBytes?: Integer;
    551     // comma-separated list of one or more file types
    552     // https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/accept#unique_file_type_specifiers
    553     accept?: string;
    554   } & UIFormFieldBaseConfig;
    555 
    556 
    557 .. code-block:: json
    558 
    559   {
    560     "label": "Example form",
    561     "id": "example",
    562     "version": 1,
    563     "config": {
    564       "type": "double-column",
    565       "design": [
    566         {
    567           "title": "File inputs",
    568           "fields": [
    569             {
    570               "type": "file",
    571               "name": "photo",
    572               "id": ".photo",
    573               "label": "Photo",
    574               "accept": "*.png"
    575             }
    576           ]
    577         }
    578       ]
    579     }
    580   }
    581 
    582 .. image:: ../screenshots/dynamic-forms.file.png
    583   :width: 400
    584 
    585 Number input
    586 ````````````
    587 
    588 .. code-block:: typescript
    589 
    590   type UIFormFieldInteger = {
    591     type: "integer";
    592     max?: Integer;
    593     min?: Integer;
    594   } & UIFormFieldBaseConfig;
    595 
    596 
    597 .. image:: ../screenshots/dynamic-forms.number.png
    598   :width: 400
    599 
    600 Text input
    601 ``````````
    602 
    603 A simple line of text
    604 
    605 .. code-block:: typescript
    606 
    607   type UIFormFieldText = { type: "text" } & UIFormFieldBaseConfig;
    608 
    609 A bigger multi-line of text
    610 
    611 .. code-block:: typescript
    612 
    613   type UIFormFieldTextArea = { type: "textArea" } & UIFormFieldBaseConfig;
    614 
    615 
    616 .. image:: ../screenshots/dynamic-forms.text.png
    617   :width: 400
    618 
    619 Boolean input
    620 `````````````
    621 
    622 .. code-block:: typescript
    623 
    624   type UIFormFieldToggle = { type: "toggle" } & UIFormFieldBaseConfig;
    625 
    626 
    627 .. code-block:: json
    628 
    629   {
    630     "label": "Example form",
    631     "id": "example",
    632     "version": 1,
    633     "config": {
    634       "type": "double-column",
    635       "design": [
    636         {
    637           "title": "Boolean inputs",
    638           "fields": [
    639             {
    640               "type": "toggle",
    641               "name": "the_question",
    642               "id": ".the_question",
    643               "label": "Yes or no?"
    644             }
    645           ]
    646         }
    647       ]
    648     }
    649   }
    650 
    651 .. image:: ../screenshots/dynamic-forms.boolean.png
    652   :width: 400
    653 
    654 Examples
    655 ========
    656 
    657 
    658 
    659 Q / A
    660 =====