12.54. DD 54: Dynamic Forms

12.54.1. 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.

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

12.54.3. 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.

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.3.3. 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.

12.54.4. 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.

12.54.4.1. Form metadata

This is the root object of the configuration.

type FormMetadata = {
  label: string;
  id: string;
  version: number;
  config: FormConfiguration;
};

12.54.4.2. 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.

type FormConfiguration = DoubleColumnForm;

type DoubleColumnForm = {
  type: "double-column";
  design: DoubleColumnFormSection[];
}

type DoubleColumnFormSection = {
  title: string;
  description?: string;
  fields: UIFormElementConfig[];
};

12.54.4.3. 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

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.

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.

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

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.

12.54.4.4. 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.

12.54.4.4.1. Decorative elements

To show some additional text

type UIFormElementCaption = { type: "caption" } & UIFieldElementDescription;

To group fields in the UI and maybe show a collapsable handler.

type UIFormElementGroup = {
  type: "group";
  fields: UIFormElementConfig[];
} & UIFieldElementDescription;

12.54.4.4.1.1. Example
{
    "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"
                }
              ]
            }
          ]
        }
      ]
    }
  }

../_images/dynamic-forms.decorative-elements.png

12.54.4.4.2. Time input

This may be rendered as a calendar

type UIFormFieldAbsoluteTime = {
  type: "absoluteTimeText";
  max?: TalerProtocolTimestamp;
  min?: TalerProtocolTimestamp;
  pattern: string;
} & UIFormFieldBaseConfig;

{
    "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"
            }
          ]
        }
      ]
    }
  }

../_images/dynamic-forms.time.png

12.54.4.4.3. Amount input

Money input.

type UIFormFieldAmount = {
  type: "amount";
  max?: Integer;
  min?: Integer;
  currency: string;
} & UIFormFieldBaseConfig;

{
    "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"
            }
          ]
        }
      ]
    }
  }

../_images/dynamic-forms.amount.png

12.54.4.4.4. 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.

type UIFormFieldArray = {
  type: "array";
  // id of the field shown when the array is collapsed
  labelFieldId: UIHandlerId;
  fields: UIFormElementConfig[];
} & UIFormFieldBaseConfig;

{
    "label": "Example form",
    "id": "example",
    "version": 1,
    "config": {
      "type": "double-column",
      "design": [
        {
          "title": "Amount inputs",
          "fields": [
            {
              "type": "array",
              "name": "people",
              "id": ".people",
              "help": "who is coming to the party?",
              "labelFieldId": ".name",
              "fields": [{
                  "type": "text",
                  "name": "firstName",
                  "id": ".name",
                  "label": "First name"
                },
                {
                  "type": "text",
                  "name": "lastName",
                  "id": ".lastName",
                  "label": "Last name"
                }],
            }
          ]
        }
      ]
    }
  }

../_images/dynamic-forms.list.png

12.54.4.4.5. Choice input

To be used when the user need to choose on predefined values

interface SelectUiChoice {
  label: string;
  description?: string;
  value: string;
}

A set of buttons next to each other

type UIFormFieldChoiseHorizontal = {
  type: "choiceHorizontal";
  choices: Array<SelectUiChoice>;
} & UIFormFieldBaseConfig;

A set of buttons next on top of each other

type UIFormFieldChoiseStacked = {
  type: "choiceStacked";
  choices: Array<SelectUiChoice>;
} & UIFormFieldBaseConfig;

A drop down list to select one of the elements

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.

type UIFormFieldSelectMultiple = {
  type: "selectMultiple";
  max?: Integer;
  min?: Integer;
  unique?: boolean;
  choices: Array<SelectUiChoice>;
} & UIFormFieldBaseConfig;

{
    "label": "Example form",
    "id": "example",
    "version": 1,
    "config": {
      "type": "double-column",
      "design": [
        {
          "title": "Choice inputs",
          "fields": [
            {
              "type": "choiceHorizontal",
              "name": "food",
              "label": "Food",
              "id": ".food",
              "choices": [
                {
                  "value": "meat",
                  "label": "Meat"
                },
                {
                  "value": "sushi",
                  "label": "Sushi"
                },
                {
                  "value": "taco",
                  "label": "Taco"
                },
                {
                  "value": "salad",
                  "label": "Salad"
                }
              ]
            }
          ]
        }
      ]
    }
  }

../_images/dynamic-forms.choice.png

12.54.4.4.6. File input

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;

{
  "label": "Example form",
  "id": "example",
  "version": 1,
  "config": {
    "type": "double-column",
    "design": [
      {
        "title": "File inputs",
        "fields": [
          {
            "type": "file",
            "name": "photo",
            "id": ".photo",
            "label": "Photo",
            "accept": "*.png"
          }
        ]
      }
    ]
  }
}

../_images/dynamic-forms.file.png

12.54.4.4.7. Number input

type UIFormFieldInteger = {
  type: "integer";
  max?: Integer;
  min?: Integer;
} & UIFormFieldBaseConfig;

screenshots/dynamic-forms.number.png

12.54.4.4.8. Text input

A simple line of text

type UIFormFieldText = { type: "text" } & UIFormFieldBaseConfig;

A bigger multi-line of text

type UIFormFieldTextArea = { type: "textArea" } & UIFormFieldBaseConfig;

screenshots/dynamic-forms.text.png

12.54.4.4.9. Boolean input

type UIFormFieldToggle = { type: "toggle" } & UIFormFieldBaseConfig;

{
  "label": "Example form",
  "id": "example",
  "version": 1,
  "config": {
    "type": "double-column",
    "design": [
      {
        "title": "Boolean inputs",
        "fields": [
          {
            "type": "toggle",
            "name": "the_question",
            "id": ".the_question",
            "label": "Yes or no?"
          }
        ]
      }
    ]
  }
}

../_images/dynamic-forms.boolean.png

12.54.5. Examples

12.54.6. Q / A