formscript
v0.0.3
Published
A JSON-based language used to describe form-content declaratively.
Downloads
2
Readme
Formscript
Version 0.0.4
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 anendSet
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 theid
ofuserWantsToGiveFeedback
) which is by default set tofalse
. - The second widget is a
textarea
box (with theid
offeedback
) 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