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.

The 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:

A date with no constraint
A date 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())
A date with minimum set to today
A date with minimum set to today

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 (^) in ^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:

Two dates
Two dates

Now make the end_date's 'min' property dependent upon start_date. Create an gear icon Action for the minimum date of end_date:

setproperty(^end_date, 'min', editedvalue(^start_date))
Start of end date set to answer to start date
Start of end date set to answer to 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 (^) in ^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 start_date:

setproperty(^end_date, 'max', adddays(editedvalue(^start_date), 2 * 7))
Maximum end date set to answer to start date plus two weeks
Maximum end date set to answer to start date plus two weeks

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

maxlength
Sets the maximum number of characters for text input
min
Sets the minimum number or date
max
Sets the maximum number or date
notnull
Sets whether the input is required (1) or not (0)
readonly
Sets whether the input is readonly (1) or not (0)
precision
Sets the decimal precision of numerical input, e.g. 0 for whole numbers, 2 for 2.18, 3 for 3.142
visible
Mighty powerful; set to show (1) or hide (0) the input
mask
Sets the regular expression (‘stringmask’) against which to test the user input
error
Sets a custom error text
placeholder
Sets the placeholder text