Up till this point we have completely ignored the issue of what happens if the user provides invalid input. In this tutorial, failure values and their use in handling errors will be introduced.
First let’s investigate more closely what happens when an invalid value is entered by the user. Let’s try it out with the sum application we wrote in Section 2, “Functional Bindings”.
You may have noticed that nothing happens if a number is only entered in one of the fields:
Let’s enter an invalid value for B, and see what happens:
Again nothing. Is there something wrong with application?
Let’s change B to a valid number:
Now we get the result of the addition, 3
. The application resumed
its normal operation when valid input is entered.
What will happen if we change one of the fields to an invalid value, let’s try changing A this time:
No change in the result of 3
. It appears the application does not
change the result if invalid input was entered. This demands an
explanation.
What’s really going on under the hood is that when a value, which is not a valid number, is entered in one of the input fields, the node bound to that field is set to a failure value.
A failure value is a special type of value which, when evaluated, terminates the evaluation of the node, by which it was evaluated, and the node’s value is set to the failure value. Failure values represent the failure of an operation, the absence of a value or special classes of values.
In the sum application, a failure value is returned by the to-int
meta-node, when the argument is a string which does not contain a
valid integer. Thus to-int(a)
evaluates to a failure value if the
value entered in the input field for A does not contain a valid
integer.
Remember that |
The observer of to-int(a)
is a
, and is thus set to the failure
value returned by to-int
. Node a + b
evaluates node a
, thus
evaluating the failure value. This results in the computation of the
sum (a + b
) being terminated and node a + b
, and its observer
sum
, being set to the failure value.
By default when a node, bound to a user interface element, evaluates to a failure value, the user interface is not updated. As a result the application appears to be doing nothing.
Whilst the current behaviour of the application is a step up from crashing or producing garbage results, it does not provide any indication to the user that the input entered was invalid. This is confusing to the user as the application appears to not be working properly. Proper error handling should be in place.
The core
module provides a handy utility meta-node fails?
, which
returns true if its argument node evaluates to a failure, and false
otherwise. This can be used to detect failures in our application and
display an appropriate error message.
A related utility meta-node |
We need to detect failures in the a
and b
nodes which are bound to
the values of the input fields for A and B, respectively. This can
be achieved using the expression fails?(a)
for a
and fails?(b)
for b
.
We would like to display a message, indicating that the input entered
was invalid, next to the field where invalid input was entered. This
can be achieved using a case
expression. The following is the case
expression for a
:
case( fails?(a) : "Not a valid number!", "" )
The case
expression returns the constant string “Not a valid
number!”, if fails?(a)
is true, that is a
evaluates to a failure,
otherwise it returns the empty string. To display the error message
next to the field for A, we can simply place the entire case
expression in an inline node declaration, between <?@ ... ?>
, next to
the field. We can do the same for B, substituting a
with b
, to
get an error indication for B as well.
That’s it we have added error handling to an existing application without having to make fundamental changes to our application logic. In-fact the addition of error handling was as simple as adding new UI elements.
Let’s try it out. Build and run the application and enter an invalid value in one of the input fields:
The message “Not a valid number!” is displayed next to the field containing the invalid value, B in this case.
Now correct the invalid value, to a valid number:
The message disappears and the sum is computed.
The error handling logic, added in the previous section, can do with some cleaning up.
case
expression is identical for both fields with the only
difference being the node. If we change the error handling logic, to
display a different message, we’d have to edit both the case
expressions.
The case expression can be extracted into a
meta-node, let’s call it error-message
which takes the node as input
and returns the appropriate error message.
Meta-Node error-message
.
error-message(value) : case( fails?(value) : "Not a valid number!", "" )
Add this definition to the top of the Tridash code tag.
We can now replace the case
expressions, inside the inline node
declarations with the following for field A:
error-message(a)
and the following for field B:
error-message(b)
Changes to the error message and error handling logic are now much
easier to implement as only the definition of the error-message
meta-node needs to be changed.
You may have noticed that the error messages are not displayed initially, when the input fields are empty. Similarly no visible result is observed until a value is entered in both fields. You’re probably wondering why this is so, as an empty string is certainly not a valid integer. In-fact, if you first enter a valid integer in a field, and then change its value to empty, the error message will be displayed.
The problem is that the nodes a
and b
are not given initial
values. As a result the value of the error-message(a)
node, and the
corresponding node for b
, is not computed until a
is given its
first value. But then what happens when the node a + b
is updated
after a value is entered in the first field, A, only? Since only the
dependency a
, of a + b
has been given a value, a + b
does not
have a value for b
and thus the value it uses for b
defaults to a
failure. To solve this problem we can give initial values to a
and
b
.
An explicit binding in which the source is a literal constant and the target is a node is interpreted as giving the node an initial value, equal to the constant.
The following assigns an initial value of 1 to a
and 2 to b
:
Example.
1 -> a 2 -> b
The setting of the initial values is treated as an ordinary value
change from the default failure value to the given initial value,
which occurs immediately after the application is launched. As a
result, the values of the node’s observers are updated. In this case
the nodes: a + b
, error-message(a)
and error-message(b)
will be
updated.
In our application, let’s give both a
and b
an initial value of
0
. Add the following to the Tridash code tag at the top of the file:
0 -> a 0 -> b
Build and run the application:
Both fields are initialized to 0
and the sum of 0
is
displayed.
Experiment with changing the node’s initial values and even try setting them to invalid integers.
You may be wondering how it is that giving an initial value to
the nodes |
As an exercise make the color of the border, or alternatively the background color, of the input element change to red when an invalid value is entered in it.
Try to achieve something similar to the following:
Some CSS styling rules have also been added to change the text color of the error messages to red, this is not part of the exercise. |
To change the border color of an element bind to the
|