6. Error Handling with Failure Values

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.

6.1. Invalid Input

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:

A: 1, B: <blank>, A + B = <blank>

Let’s enter an invalid value for B, and see what happens:

A: 1, B: foo, A + B = <blank>

Again nothing. Is there something wrong with application?

Let’s change B to a valid number:

A: 1, B: 2, A + B = 3

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:

A: bar, B: 2, A + B = 3

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.

6.2. Failure Values

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.

[Note]

Remember that to-int(a) is bound, as the target, to the value entered in the text input field.

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.

6.3. Handling Failures

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.

[Tip]

A related utility meta-node ?, also from the core module, returns true if its argument does not evaluate to a failure.

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:

A: 1, B: foo, Not a valid number!; A + B =

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:

A: 1, B: 6; A + B = 7

The message disappears and the sum is computed.

Cleaning Up

The error handling logic, added in the previous section, can do with some cleaning up.

  • The error message is duplicated next to both fields. If we’d like to change the message we’d have to make sure we’ve changed it in both places.
  • The 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.

6.4. Initial Values

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:

A: 0, B: 0, A + B: 0

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.

[Note]

You may be wondering how it is that giving an initial value to the nodes a and b affects the values of the text input fields. This is due to the fact that there is a two-way binding between a and the value of the input element for A, and between b and the value of the input element for B.

6.5. Exercise

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:

A: foo, Not a number, B: 0, A + B = 0
[Note]

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.

[Tip]

To change the border color of an element bind to the style.borderColor attribute of the element node.