Fields and types
A field is a question. Its type says what kind of answer it expects, and the type decides what the runtime will accept, what it will refuse, and what happens when the answer is not there.
form applicant version = "1.0" {
field name { type = string }
field age { type = integer }
field income { type = number }
field active { type = boolean }
field start { type = date }
field signed_at { type = datetime }
field term { type = duration }
field status { type = enum("employed" = "Employed", "self" = "Self-employed") }
field tags { type = array of string }
field address { type = object { street: string, city: string } }
}
The types
| Type | What it holds |
|---|---|
string |
text |
integer |
a whole number, signed, 64-bit |
number |
a decimal number, exact, 128-bit |
boolean |
true or false |
date |
a calendar date |
datetime |
a moment in time |
duration |
a length of time |
enum |
a closed set of named options |
array |
a list of values of one type |
object |
a fixed set of named members |
file, signature, location and geo exist too, for the kinds of answer
that are a document, a signature, a place or a point on a map.
integer is not number
This is the first thing that surprises people, so it is worth being precise:
- an
integeris a whole number.1,-3,0. - a
numberis a decimal.1,1.5,0.05.
1 + 1 is an integer. 1 + 1.0 is a number. The result types differ
even when the values look alike, and the difference is observable: the
specification's conformance rows check it.
The reason is money. A loan form that adds interest must not lose a cent to
floating point, so number is an exact decimal with 128 bits of it, and
integer is a whole number. When you write 0.05 / 12 for a monthly rate,
you have written a number on purpose.
Types can narrow themselves
A type can carry the range of answers it will accept, and the runtime checks it:
form applicant version = "1.0" {
field postcode { type = string(2..10) }
field age { type = integer(0..130) }
field amount { type = number(0..1000000) }
field birth { type = date("YYYY-MM-DD") }
}
string(2..10)— between two and ten characters.integer(0..130)— between zero and a hundred and thirty.date("YYYY-MM-DD")— a date, and the format it is written in.
A range written in the type is checked before the user answers, so a form that can never be satisfied is reported when it is compiled rather than when somebody fills it in.
enum carries its members
An enum is a closed set of options, and it writes down both the key and the
label a person sees:
form applicant version = "1.0" {
field status {
type = enum("employed" = "Employed", "self" = "Self-employed")
}
}
"employed" is the key — what the data says. "Employed" is the
label — what the person reads. They are different strings, and keeping
them apart is what lets a form be translated without touching its data.
If an option has no label of its own, the key is the label:
form applicant version = "1.0" {
field status { type = enum("employed", "self") }
}
Arrays and objects
form applicant version = "1.0" {
field tags { type = array of string }
field address { type = object { street: string, city: string } }
}
An array is a list of values of one type. An object is a fixed set of
named members, each with its own type. Both are types you can nest, so an
object may hold an array of objects.
There are no implicit conversions
FormScript does not convert "36" into 36, and it does not treat "" as
false. If a field is a number and the answer is text, that is an error, and
the error is reported where it happens rather than swallowed.
That is a deliberate choice. A form that quietly turns "1,50" into 150 is
a form that will be wrong in a way nobody can see. When you do want a
conversion, you ask for it: toNumber(name), toString(age),
toBoolean(flag). Those are builtins,
and they are in the built-in list of section 7.
What a field can also declare
A field is more than its type. It can also carry:
label— the text a person reads;hint— a line of help under the label;placeholder— a hint inside the control, which never replaces the label;default— the answer until somebody answers;value— a computed answer;options— the choices, when the type is not anenum;validate { … }— the rules the answer must satisfy;when— the condition under which the question is asked at all.
The next two sections are about value and default, because they are the
difference between a form that asks and a form that calculates.