Conditional questions
Some questions only make sense in some circumstances. A guarantor is asked when the loan is large. A second address is asked when the country is not the United Kingdom.
The when property says so:
form loan version = "1.0" {
field loan_amount { type = number }
field guarantor_name { type = string when = loan_amount > 10000 }
}
What when means
A field whose when is false is inapplicable. That is a stronger word
than "hidden":
- it is not rendered;
- it is absent from the output document — not
null, absent; - it does not block submission;
- its constraints do not run.
A question that is not asked cannot be answered wrongly.
The comparison is three-valued
loan_amount > 10000 is not always true or false. Before the applicant
has typed the amount, it is unknown — and an unknown condition makes the
field inapplicable.
So the field is not asked at the start of the form, and it appears once the amount is high enough. That is the behaviour you want: a form that demanded a guarantor from everybody would be a form nobody finishes.
The rule in one line: when is true → applicable; when is false or
unknown → inapplicable.
A guard that reads another group
A guard can read a field in a group, by path:
form loan version = "1.0" {
group applicant {
field income { type = number }
}
field guarantor_name { type = string when = applicant.income > 10000 }
}
applicant.income is a dotted path that names a field, so the value of the
path is that field's own answer — the one the applicant typed, not the group's
composition as it stood at some earlier moment.
That precision is not academic. A guard that reads a stale value asks the wrong question, and the specification was amended to say exactly which value a dotted path names (Spec-6.20) after a real disagreement between two runtimes about it.
A guard on a group or a repeat
when works on anything, not only on fields:
form loan version = "1.0" {
group applicant {
field income { type = number }
}
group guarantor {
when = applicant.income > 10000
field name { type = string }
}
}
When the group is inapplicable, everything inside it is too.
A repeat whose guard changes
A repeat grows and shrinks as the person adds and removes rows, and each row can have its own guard:
form household version = "1.0" {
repeat dependants {
max = 5
field name { type = string }
field age { type = integer }
field school { type = string when = age < 18 }
}
}
The school question appears in a row once that row's age says the
dependant is a child — and it disappears again if the age is corrected.
When the guard is read
This is the part that is easy to get wrong, so the specification states it plainly (Spec-9.12):
A structural change to a repeat recomputes the applicability of every entity it holds, and a new element's guard is read in that element.
In practice:
- when a repeat gains a row, the guards of every row are read again, not only the new row's;
- a new row's guard is read in that row — with that row's own answers, not with the answers of the first row;
- when an answer changes, the guards that read it are recomputed.
So a row whose school question was hidden because age was 30 does not stay
hidden because a different row changed.
The shadow store
When a field stops applying, its answer is not thrown away — it is retained
and can be read back with shadow(path):
field previous_city { type = string value = shadow(["address", "city"]) }
That is what makes a form that hides and shows questions feel like a form rather than a series of separate forms.
isActive
To ask whether a field is being asked at all, there is a builtin:
field summary { type = string value = isActive(["applicant", "guarantor_name"]) ? "Guarantor asked" : "No guarantor" }
isActive(path) is true when the field at that path is currently applicable.
The path is written as a list of its segments — ["applicant", "guarantor_name"]
— because a path is not a name, it is a place.
It is one of the four builtins that read the form rather than a value — see the builtin library.