formscript

A JSON-based language used to describe form-content declaratively.

Usage no npm install needed!

<script type="module">
  import formscript from 'https://cdn.skypack.dev/formscript';
</script>

README

Formscript

Version 0.0.4

Build Status FOSSA Status Known Vulnerabilities JavaScript Style Guide lerna Dependabot badge PRs Welcome

This document defines a JSON-based language used to describe form-content declaratively. The forms thus defined may be rendered and executed by software. In this document, such software is referred to as an "app".

Table of Contents

Structure of a Form

A Form is represented by a JSON Object.

Example: Simple Form

The content of a form is specified by configuring one or more widgets, which are represented by JSON objects.

  • In this example, a form is defined that contains two widgets, one that defines a suitable header (with some text and an accompanying image), followed by a second widget for letting the user enter their name.
{
  "widgets": [
    {
      "type": "header",
      "attributes": {
        "heading": "Register!",
        "desc": "Let's get to know each other a bit better...",
        "backgroundImage": "happyPeople.jpg",
        "backgroundImageAltText": "Beautiful people smiling around a laptop"
      }
    },
    {
      "id": "name",
      "type": "text",
      "attributes": {
        "heading": "Name",
        "placeholder": "e.g. Lucy Smith",
        "mandatory": true,
        "minCharacters": 1,
        "maxCharacters": 100,
        "help": "Enter your full name here"
      }
    }
  ]
}
  • The order that objects are defined within widgets is important, representing the order users will encounter them.

Concepts

Formscript is built on a handful of key concepts...

Apps

Forms defined in Formscript may be rendered and executed by software. In this document, such software is referred to an "app".

  • Apps can be implemented in any frontend-framework, language or library.
  • Formscript does not impose any aesthetic or UI constraints onto apps that implement it.
  • Formscript content can be embedded inside apps with GUI, CLI and even Voice-User interfaces.
  • Perhaps as a fallback, Formscript content can even be rendered as a hard-copy paper form.
  • Several utilities to help develop apps that use Formscript (written in Javascript) are published on here on npmjs.com (the accompanying source code and related issues can be found here).

Forms

The purpose of Formscript is to define a user interface, referred to as a "form".

  • Using an app, forms are typically used to collect information from a user: but it's entirely possible to simply convey information using a form definition as well.
  • With Formscript it's possible to configure a form with structure, validation, conditional content, dynamic values and context-sensitive behaviours (e.g. operating differently with an internet connection as opposed to without).
  • Formscript definitions are naturally stored in .json files (typically one-file-per-form).
  • In certain use-cases consider that YAML (itself just a superset of JSON) may offer a compelling alternative to serialising Formscript definitions in .json files.
  • Please note that a JSON Schema is available here, which may be used to validate the basic integrity of Formscript content.
  • For comprehensive Formscript validation, please refer to the formscript-schema package.

Widgets

Forms are constructed from an ordered list of "widgets".

  • To avoid overloading frontend-terms like 'component', Formscript refers to each object in the widgets array as a widget.
  • Consider a widget as an area of a form responsible for a particular task: either collecting a specific piece of information from a user or visualising a certain piece of information.
  • As such, widgets can be interactive (text, number, map etc.) and non-interactive (heading, stickyNote etc.)
  • The order that Widget objects appear within a form definition is important - representing the order users will encounter them.
  • The Formscript specification offers a fixed set of 26 standard widgets. Need another widget-type entirely or an extra configuration options? Pull requests are very welcome!

Ahead of the Reference section, here's a quick summary of the 26 widgets supported in Formscript 0.0.4:

Widget Type Description
address Allows the user to select a particular postal address from a provided list and store a unique reference to that property, such as a UPRN or similar.
apiLookup Allows the user to select a specific value from an API endpoint
checkboxList Offer a related set of checkboxes with accompanying labels for the user to switch on and off.
currency Just like a number widget, but for specifically collecting a monetary value.
date Allows the user to provide a specific date - without a time portion.
dateTime Collects a specific date and time from the user.
endSet Marks the end of a set of related widgets - see the Sets section for more information.
endSubForm Marks the end of a sub-form - see the Sets section for more information.
fileUpload Allows the user to upload a file.
header Displays a header for a form (with an optional background image and some text akin to a 'Hero Unit' component).
image Embeds a non-interactive image within the form.
map Displays a map to the user, and can optionally be configured to collect geo-spatial data (points, lines etc.)
number Like a text widget, but specifically for collecting numeric content.
questionnaire Offers the user a question with two or more possible responses on an appropriate scale.
radio Allows the user to select a value from a set of related options that are rendered in a Radio Button style.
richtext Offers the user a text editor with functionality to format text.
select Allows the user to select a value from a set of options, which should be rendered in an HTML Select style.
set Marks the start of a set of related widgets - see the Sets section for more information.
signature Allow the collection of a handwritten signature
slider For capturing a number along a specified range
stickyNote A panel for putting helpful text or other informative text
subForm Allows the user to enter a number of 'sub forms' (think order-lines or contact details etc.)
switch Presents a on/off style switch to the user.
text A bread-and-butter box for collecting textual information from the user.
textarea Collects simple multi-line text input from the user.
time Allows the user to provide a specific time (without being tied to a particular date)

Sets

All the widgets that define a form's content are specified in a simple array. This design helps align Formscript with vertical-scrolling interfaces with very little friction. To assist with navigation (especially around larger, more complex forms) it is common for User Interfaces to be split into logical sections.

In Formscript, sets allow widgets to be grouped into related chunks.

  • Each set begins with a set widget and ends with an endSet widget.
  • Nesting of sets is possible and sets are especially powerful when combined with dynamic expressions to conditionally show/hide content.
  • Sets enable apps to offer progress tracking components.
  • Multi-step "wizard" interfaces are also easily achieved via sets.

Expressions

Formscript uses expressions to deliver dynamic content. Expressions are used to:

  • Conditionally show/hide widgets depending on values as they change.
  • Validate form content based on more complex business rules.
  • Affect the contents of enumerated lists.
  • Default dynamic values.
  • Calculate running totals, real-time summaries etc.

Consider an expression to be something that could be evaluated in a Javascript if (...) {} statement.

{
  "widgets": [
    {
      "id": "userWantsToGiveFeedback",
      "type": "switch",
      "attributes": {
        "default": false,
        "heading": "Do you want to leave feedback?"
      }
    },
    {
      "id": "feedback",
      "showWhen": "data.userWantsToGiveFeedback",
      "type": "textarea",
      "attributes": {
        "heading": "Feedback",
        "desc": "Please tell us how you feel!"
      }
    }
  ]
}

In the example above we have two widgets:

  • The first is a simple boolean on/off switch widget (with the id of userWantsToGiveFeedback) which is by default set to false.
  • The second widget is a textarea box (with the id of feedback) for collecting feedback from the user.

The feedback widget should only show if the userWantsToGiveFeedback switch is thrown on (i.e. true).

There are a few new things going on here. Most types of widget (here the switch and textarea types) expect an app to read and write their values to an underlying data object (using their respective id values as keys). It is also expected that any app implementing Formscript should also make this data object available within a safe sandbox while evaluating expressions.

In the previous example we can see the showWhen attribute is being used on the feedback widget. The string value here is an expression, which will control the visibility of the widget (i.e. it should only be shown to the user when the expression evaluates to true).

Expression sandbox

Apps must ensure expressions are evaluated in a safe sandbox context. As such only certain objects may be referred to within an expression:

Sandbox object Description
data The current form data being stored. Should be kept fresh in real-time using UI binding techniques.
env Some environmental information, e.g. the user's name, if the app has access to an internet connection etc.
env object properties

Apps are expected to provide the following details via an env object when evaluating expressions:

Property Type Description
username string Username of the the user currently using the form.
startedOffline boolean Indicates if the form was started online, or not.

Reference

Top-Level Properties

The top-level object defining a form comprises of several properties:

Property Type Description Required?
title string A short-as-possible label to associate with the form. false
desc string A quick summary of what the form is hoping to achieve. false
version string Denotes the current version of the form definition. This will be assigned by whatever tooling and processes conjure your forms. There is a strong preference that form version strings adhere to Semantic Versioning. false
shasum string Optionally assigned by tooling, this is a checksum value based on the form-definition. Uses include client-side storage management of form definitions and integrity checking. false
widgets array The main event, 1 or more widget objects which an app should render to produce a form. true

Widget Properties

Each widget object comprise of some properties:

Attribute Name Type Description
id string A unique string which identifies the widget - often used to bind the value being collected by a widget to an underlying data model. Providing an id value is very often mandatory (depending on the type of widget involved). Regardless, it is good practice to always provide an id because it assists modification (or "patching") of form definitions.
type string A mandatory value denoting the type of widget being defined (e.g. text, number etc.)
showWhen string An expression, that when evaluating to true will cause the widget to appear (so the widget will not be shown if evaluated to be false).
attributes object A key/value object for configuring each widget - the content of which is dependent on the widget's type.

Widget Attributes

Formscript 0.0.4 supports a set of 15 common attributes from which widgets can be configured. Not one widget-type requires all these attributes. Attributes are often optional and some widget-types don't need an attributes object at all.

Attribute Name Type Description
default any A value to default a widget to if not supplied by other mechanisms.
defaultBoolean boolean A boolean value to default a widget to if not supplied by other mechanisms.
defaultNumber number A numeric value to default a widget to if not supplied by other mechanisms.
defaultString string A string value to default a widget to if not supplied by other mechanisms.
desc string Some additional advice (above and beyond the string supplied in label) to help define what data is required from the user.
enabled boolean Indicates if the user can use the widget to alter the underlying value - default to true.
heading string Some short, strong, punchy text to identify the widget.
help string More detailed guidance/advice (building on top of description content) to help shape what data is collected from the user.
label string A short piece of text to help identify what content is required by the user.
mandatory boolean Indicates if a value needs to be supplied by the user, or if it's optional.
maxCharacters number The maximum length of number of characters a user can specify.
minCharacters number The minimum length of number of characters a will need to provide.
numericValue value Explicitly assert that the widget receive and store numeric values (usually of use with title-map enumerations).
placeholder string Some example text that can be appear ina widget ahead of collecting use input.
titleMap array An array of objects denoting a set of values that the user can select from.

Widget List


The address widget

Allows the user to select a particular postal address from a provided list and store a unique reference to that property, such as a UPRN or similar.

Example

{
  "id": "patientAddress",
  "type": "address",
  "attributes": {
    "heading": "Where does the patient live?",
    "desc": "If it's not possible to ascertain an accurate address from the patient then please select 'Unknown'",
    "mandatory": true,
    "results": {
      "limit": 20,
      "pagination": true
    },
    "params": {
      "enableUnknownOption": true,
      "enableLocationAssist": false
    }
  }
}

Properties

id: Mandatory

type: Mandatory (address)

showWhen: Optional


The apiLookup widget

Allows the user to select a specific value from an API endpoint

Example

{
  "id": "",
  "type": "apiLookup",
  "attributes": {
    "apiName": "fleet",
    "heading": "Fire Appliance",
    "desc": "Please select the Fire Appliance involved with this event",
    "mandatory": true,
    "results": {
      "limit": 20,
      "pagination": true
    },
    "params": {
      "showCurrentOnly": true,
      "showOperationalOnly": true
    }
  }
}

Properties

id: Mandatory

type: Mandatory (apiLookup)

showWhen: Optional


The checkboxList widget

Offer a related set of checkboxes with accompanying labels for the user to switch on and off.

Example

{
  "id": "limbMovement",
  "type": "checkboxList",
  "attributes": {
    "heading": "Which limbs were seen to move?",
    "default": [
      "LEFT_ARM",
      "RIGHT_ARM",
      "LEFT_LEG",
      "RIGHT_LEG"
    ],
    "minLimit": 0,
    "maxLimit": 4,
    "titleMap": [
      {
        "value": "LEFT_ARM",
        "title": "Left arm"
      },
      {
        "value": "RIGHT_ARM",
        "title": "Right arm"
      },
      {
        "value": "LEFT_LEG",
        "title": "Left leg"
      },
      {
        "value": "RIGHT_LEG",
        "title": "Right leg"
      }
    ]
  }
}

Properties

id: Mandatory

type: Mandatory (checkboxList)

showWhen: Optional


The currency widget

Just like a number widget, but for specifically collecting a monetary value.

Example

{
  "id": "price",
  "type": "currency",
  "attributes": {
    "mandatory": true,
    "heading": "Purchase price",
    "desc": "How much did this stock-item cost from the supplier?"
  }
}

Properties

id: Mandatory

type: Mandatory (currency)

showWhen: Optional


The date widget

Allows the user to provide a specific date - without a time portion.

Example

{
  "id": "dateOfBirth",
  "type": "date",
  "attributes": {
    "mandatory": true,
    "heading": "Date of birth",
    "desc": "Date the employee was born",
    "historicByAtLeast": "18 years"
  }
}

Properties

id: Mandatory

type: Mandatory (date)

showWhen: Optional


The dateTime widget

Collects a specific date and time from the user.

Example

{
  "id": "appointment",
  "type": "dateTime",
  "attributes": {
    "mandatory": true,
    "heading": "Appointment",
    "desc": "The date and time this visit is scheduled for",
    "captureHistoric": false,
    "futuristicByAtMost": "3 months"
  }
}

Properties

id: Mandatory

type: Mandatory (dateTime)

showWhen: Optional


The endSet widget

Marks the end of a set of related widgets - see the Sets section for more information.

Example

{
  "widgets": [
    {
      "type": "set",
      "attributes": {
        "heading": "Incident details",
        "desc": "Please provide details of the incident at which casualty care was administered.",
        "showInTOC": true
      }
    },
    {
      "type": "endSet"
    }
  ]
}

Properties

type: Mandatory (endSet)


The endSubForm widget

Marks the end of a sub-form - see the Sets section for more information.

Example

{
  "widgets": [
    {
      "type": "subForm",
      "attributes": {
        "heading": "Explosions",
        "desc": "Please provide details of the explosions which occurred.",
        "minAllowed": 1,
        "maxAllowed": 10,
        "showAtLeastOne": true,
        "singularEntityText": "explosion",
        "pluralEntityText": "explosions"
      }
    },
    {
      "type": "endSubForm"
    }
  ]
}

Properties

type: Mandatory (endSubForm)


The fileUpload widget

Allows the user to upload a file.

Example

{
  "id": "photographicEvidence",
  "type": "fileUpload",
  "attributes": {
    "heading": "Any photographic evidence?",
    "desc": "Upload any digital photographs supporting your observations",
    "enableCaptioning": true,
    "formatRestriction": [
      "jpg",
      "jpeg"
    ],
    "maxFileSize": "15mb",
    "minNumberOfFiles": 0,
    "maxNumberOfFiles": 10
  }
}

Properties

id: Mandatory

type: Mandatory (fileUpload)

showWhen: Optional


The header widget

Displays a header for a form (with an optional background image and some text akin to a 'Hero Unit' component).

Example

{
  "type": "header",
  "attributes": {
    "heading": "Patient Report",
    "desc": "Use this form to provide details of patient care administered at the scene of an incident.",
    "backgroundImage": "wmfs/casualty-care-background.jpg",
    "backgroundImageAltText": "Photograph of activity at a Road Traffic Collision"
  }
}

Properties

type: Mandatory (header)

showWhen: Optional


The image widget

Embeds a non-interactive image within the form.

Example

{
  "id": "numberOfFloors",
  "type": "image",
  "attributes": {
    "image": "wmfs/number-of-floors-diagram.png",
    "altText": "Indicates ground-floor is referred to as '0' and one above it is referred to as '1': but the total number of floors is 2."
  }
}

Properties

id: Mandatory

type: Mandatory (image)

showWhen: Optional


The map widget

Displays a map to the user, and can optionally be configured to collect geo-spatial data (points, lines etc.)

Example

{
  "id": "incidentCoordinates",
  "type": "map",
  "attributes": {
    "heading": "Point of ignition",
    "mandatory": true,
    "desc": "Please indicate the exact position of where the fire started.",
    "enableLocationAssist": true,
    "drawingMode": "singlePoint",
    "drawingConfig": {
      "autoCentre": true,
      "iconImage": "wmfs/flame"
    },
    "relatedLayers": [
      {
        "name": "gaz",
        "heading": "Gazetteer",
        "desc": "Buildings and similar",
        "visibleByDefault": false
      }
    ]
  }
}

Properties

id: Mandatory

type: Mandatory (map)

showWhen: Optional


The number widget

Like a text widget, but specifically for collecting numeric content.

Example

{
  "id": "numShocks",
  "type": "number",
  "attributes": {
    "mandatory": true,
    "default": 2,
    "heading": "How many shocks were delivered?"
  }
}

Properties

id: Mandatory

type: Mandatory (number)

showWhen: Optional


The questionnaire widget

Offers the user a question with two or more possible responses on an appropriate scale.

Example

{
  "id": "painArrival",
  "type": "questionnaire",
  "attributes": {
    "mandatory": true,
    "heading": "Pain-score on arrival",
    "desc": "How did the carer assess the patient's pain when they first met?",
    "default": 1,
    "numericValue": true,
    "titleMap": [
      {
        "value": 0,
        "title": "0",
        "desc": "No pain"
      },
      {
        "value": 1,
        "title": "1",
        "desc": "Slight pain"
      },
      {
        "value": 2,
        "title": "2",
        "desc": "Moderate pain"
      },
      {
        "value": 3,
        "title": "3",
        "desc": "Severe pain"
      }
    ]
  }
}

Properties

id: Mandatory

type: Mandatory (questionnaire)

showWhen: Optional


The radio widget

Allows the user to select a value from a set of related options that are rendered in a Radio Button style.

Example

{
  "id": "gender",
  "type": "radio",
  "attributes": {
    "heading": "Patient gender",
    "mandatory": true,
    "titleMap": [
      {
        "value": "MALE",
        "title": "Male"
      },
      {
        "value": "FEMALE",
        "title": "Female"
      },
      {
        "value": "UNKNOWN",
        "title": "Unknown"
      }
    ]
  }
}

Properties

id: Mandatory

type: Mandatory (radio)

showWhen: Optional


The richtext widget

Offers the user a text editor with functionality to format text.

Example

{
  "id": "clinicalNotes",
  "type": "richtext",
  "attributes": {
    "heading": "Clinical Notes?",
    "mandatory": false,
    "desc": "If you have any clinical notes, please enter them here"
  }
}

Properties

id: Mandatory

type: Mandatory (richtext)

showWhen: Optional


The select widget

Allows the user to select a value from a set of options, which should be rendered in an HTML Select style.

Example

{
  "id": "choking",
  "type": "select",
  "attributes": {
    "heading": "Choking?",
    "desc": "Was the patient choking, if so what treatment was administered?",
    "mandatory": true,
    "default": "NOT_APPLICABLE",
    "titleMap": [
      {
        "value": "NOT_APPLICABLE",
        "title": "No choking - not applicable"
      },
      {
        "value": "COUGH",
        "title": "Encourage cough"
      },
      {
        "value": "BACK_SLAPS",
        "title": "Back slaps"
      },
      {
        "value": "ABDOMINAL_THRUSTS",
        "title": "Adbominal/Chest thrusts"
      },
      {
        "value": "COMPRESSIONS",
        "title": "Chest compressions (CPR)"
      },
      {
        "value": "OTHER",
        "title": "Other"
      }
    ]
  }
}

Properties

id: Mandatory

type: Mandatory (select)

showWhen: Optional


The set widget

Marks the start of a set of related widgets - see the Sets section for more information.

Example

{
  "widgets": [
    {
      "type": "set",
      "attributes": {
        "heading": "Incident details",
        "desc": "Please provide details of the incident at which casualty care was administered.",
        "showInTOC": true
      }
    },
    {
      "type": "endSet"
    }
  ]
}

Properties

id: Mandatory

type: Mandatory (set)

showWhen: Optional


The signature widget

Allow the collection of a handwritten signature

Example

{
  "id": "confirmation",
  "type": "signature",
  "attributes": {
    "heading": "Customer acknowledgement",
    "desc": "Please sign here to confirm receipt of some service",
    "help": "Hand the device over to the customer",
    "mandatory": true
  }
}

Properties

id: Mandatory

type: Mandatory (signature)

showWhen: Optional


The slider widget

For capturing a number along a specified range

Example

{
  "id": "burnArea",
  "type": "slider",
  "attributes": {
    "mandatory": true,
    "heading": "Estimated body surface area burnt (%)",
    "default": 0,
    "minimum": 0,
    "maximum": 100,
    "step": 5
  }
}

Properties

id: Mandatory

type: Mandatory (slider)

showWhen: Optional


The stickyNote widget

A panel for putting helpful text or other informative text

Example

{
  "id": "info",
  "type": "stickyNote",
  "attributes": {
    "style": "informative",
    "heading": "Remember!",
    "desc": "Floor numbering starts with 0 (ground floor)."
  }
}

Properties

id: Mandatory

type: Mandatory (stickyNote)

showWhen: Optional


The subForm widget

Allows the user to enter a number of 'sub forms' (think order-lines or contact details etc.)

Example

{
  "widgets": [
    {
      "type": "subForm",
      "attributes": {
        "heading": "Explosions",
        "desc": "Please provide details of the explosions which occurred.",
        "minAllowed": 1,
        "maxAllowed": 10,
        "showAtLeastOne": true,
        "singularEntityText": "explosion",
        "pluralEntityText": "explosions"
      }
    },
    {
      "type": "endSubForm"
    }
  ]
}

Properties

id: Mandatory

type: Mandatory (subForm)

showWhen: Optional


The switch widget

Presents a on/off style switch to the user.

Example

{
  "id": "burns",
  "type": "switch",
  "attributes": {
    "heading": "Did the patient suffer burns?",
    "default": false
  }
}

Properties

id: Mandatory

type: Mandatory (switch)

showWhen: Optional


The text widget

A bread-and-butter box for collecting textual information from the user.

Example

{
  "id": "handover",
  "type": "text",
  "attributes": {
    "heading": "Who was the patient handed over to?",
    "desc": "Please provide Emergency service and name of person.",
    "placeholder": "Service/name",
    "mandatory": false,
    "minCharacters": 10
  }
}

Properties

id: Mandatory

type: Mandatory (text)

showWhen: Optional


The textarea widget

Collects simple multi-line text input from the user.

Example

{
  "id": "clinicalNotes",
  "type": "richtext",
  "attributes": {
    "heading": "Clinical Notes?",
    "mandatory": false,
    "desc": "If you have any clinical notes, please enter them here"
  }
}

Properties

id: Mandatory

type: Mandatory (textarea)

showWhen: Optional


The time widget

Allows the user to provide a specific time (without being tied to a particular date)

Example

{
  "id": "openingTime",
  "type": "time",
  "attributes": {
    "mandatory": true,
    "heading": "Opening Time",
    "desc": "What time does the business usually open?"
  }
}

Properties

id: Mandatory

type: Mandatory (time)

showWhen: Optional


FOSSA Status