Dynamic per-question properties
In the previous articles, we learned how to create custom versions of basic input types. We set various constraints and properties, such as maximum text length and placeholder texts, and created reusable question types. These reusable input types promote consistency throughout your model(s).
But let’s face it: there can be times pre-defined, reusable, answer options increasingly just don’t cut it.
You encounter a question of a type you’d only need once or twice, and it pains you to pollute the list of input types with yet another variation. That you already know will not be reused. Solution: deal with it?
Or you want to set a certain constraint that depends on a previously answered question. Solution: shell out a —virtually similar— node based on the answer to this previous question, with just one question that differs in just one aspect? What if the
max value for a number should be then 3, then 5, then 8? Shell out three nodes? Create a separate ‘error’ node, requiring our user to ‘go back’ after reading?
What if you’d like this question to actually be on the same node as the previous one? And what if the value you’d like to use in the constraint cannot be known in advance (there is no finite set)?
What if you want a question to show up then yes, then no.
Out of luck?
Luckily, no. The action
setproperty() comes to your rescue.
setproperty() action lets you apply contraints and properties plus their visibility to individual questions. On top of that, those constraints and properties can use values from within the same node (as well as others).
You can imagine that this eases model maintainance considerably and enables us to create a pretty dynamic and engaging interface!
Let’s start off with a simple example, featuring a calendar, with no constraint:
Now let’s say the minimum possible date to choose should be ‘today’. The value of the concept ‘today’ will differ for each day our question will be asked. So we need this value for this ‘minimum date constraint’ to be dynamic, and be set each time again.
In order to set a property on an individual question, we are going to use the
setproperty() action. The name of our calendar is
start_date. Before (above) the Question itself, create the following Action:
setproperty(^start_date, 'min', now())
The first argument is the question asking for a start date (
^start_date). The second argument (
'min') is the property we want to customize. And the third,
now(), returns the current date.
Note the caron character (
^start_date. This means we are pointing to the Question itself, not its value.
In this example we used the function
now(), but the third argument can take any expression that returns a value: literal pieces of text or literal numbers, mathematical calculations, references to other variables — you name it.
Now let’s get fancy. Ask the user for a second date, called
end_date, which is to be within two weeks from our previous
start_date. First create the date:
Now make the
'min' property dependent upon
start_date. Create an Action for the minimum date of
setproperty(^end_date, 'min', editedvalue(^start_date))
Note we use the function
editedvalue here, because we want to know what the edited value of
start_date is, that is, the value after the user has answered the question.
Note the caron character (
^end_date. This is again because we want to give
editedvalue()a reference to Question itself, not its value (which, well the edited value it will return for us).
Then, add our maximum date, which is two weeks (
2 * 7) after the edited value of
setproperty(^end_date, 'max', adddays(editedvalue(^start_date), 2 * 7))
Note that these two calendars can occur on the same node, and the second calendar will respond live to the date chosen in the first calendar! This makes for a pretty dynamic system.
The only thing you cannot set, is the basic input type of a question: you still need to know design-time whether you want a number input or a check box.
Now how does this work under the hood?
Whenever you use
setproperty inside a node, this node is tagged as being ‘dynamic’. Now every time the end-user changes an answer on that node, it is checked whether this answer influences a property of another. To do so, all actions and formulas in that node up until the first question are re-run.
This may also help to explain the need for the look-ahead function
editedvalue() in our examples: the
setproperty() action actually runs before the Question itself. This also means that when basing the value of a property on an answer that occured in an ‘earlier node’, the look-ahead call to
editedvalue() is not needed. Since we are not looking ahead, something like the following will do:
setproperty(^end_date, 'min', earlier_node.start_date)
The fact that formulas are also re-run means you can use intricate logic to compute the value of the property by making use of intermediate values and loops.
NOTE that re-run actions and formulas means just that: if one for instance invokes other graphs (which is not an action nor a formula), these other graphs will not re-run. And if you think of it, this restriction makes sense: what if the other graph contained questions of its own?
Supported settable properties
- Sets the maximum number of characters for text input
- Sets the minimum number or date
- Sets the maximum number or date
- Sets whether the input is required (1) or not (0)
- Sets whether the input is readonly (1) or not (0)
- Sets the decimal precision of numerical input, e.g.
0for whole numbers,
- Mighty powerful; set to show (1) or hide (0) the input
- Sets the regular expression (‘stringmask’) against which to test the user input
- Sets a custom error text
- Sets the placeholder text