Throughout these tutorials, we’ve glossed over two-way bindings without going into much detail of how they work, yet they were a vital component of every application as the bindings to the UI elements have all been two-way bindings.
Each node has a number of contexts which store information about how to compute the node’s value, i.e. what function to use and what dependencies are operands to the function. The active context of a node, at a given moment in time, is the context which is used to compute the node’s value. In general, a context is activated when the value of an operand node of the context changes. By default, a node context is created for each dependency of a node which was added by an explicit binding.
Example.
a -> x # Context created for dependency `a` b -> x # Context created for dependency `b` c -> x # Context created for dependency `c`
In this example node x has three contexts one for each of its
dependency nodes, a, b and c, to which it is bound explicitly.
An implicit binding between a meta-node instance and the meta-node arguments does not result in the creation of a context for each operand.
a + b
Nodes a and b are implicitly added as dependencies of a + b
however they are added as operands to the same context with the +
function.
The following application demonstrates how different contexts are activated, when the values of their operand nodes change.
ui.html.
<?
x -> node
y -> node
z -> node
?>
<!doctype html>
<html>
<head>
<title>Node Contexts</title>
</head>
<body>
<div><label>X: <input value="<?@ x ?>"/></label></div>
<div><label>Y: <input id="b" value="<?@ y ?>"/></label></div>
<div><label>Z: <input value="<?@ z ?>"/></label></div>
<hr/>
<div><strong>Last value entered: <?@ node ?></strong></div>
</body>
</html>
This is a simple application consisting of three text input fields
bound to nodes x, y and z. Nodes x, y and z are each
explicitly bound to node, the value of which is displayed below the
fields.
Let’s enter a value in each field and see what happens. Observe the value displayed below the fields after each change:



Notice that after each change, the value that was just entered is displayed.
Now let’s try changing the values of the fields which were edited previously:

In this case the value of the second field, Y, was changed to 10 and that value was immediately displayed below the fields.
The value of the field that was changed last is displayed. To understand why this is so, let’s examine the sequence of steps taken when a value is entered in the X field.
x, which is bound to the value in the X field, is
updated.
x -> node is activated
due to the value of x being updated.
node is updated to the value of x.
Contexts make two-way bindings possible:
Example.
input1 -> a # Two-way binding a -> b; a -> b input2 -> b
The |
a has two contexts corresponding to dependency nodes input1 and
b (which is also an observer). b has two contexts corresponding to
dependency nodes input2 and a.
When input1 is changed, the contexts corresponding to the bindings
in the following direction are activated:
input1 -> a
a -> b
When input2 is changed, the contexts corresponding to the bindings
in the following direction are activated:
input2 -> b
b -> a
The context of a binding can be set explicitly to a named context,
using the @ operator from the core module.
a -> b @ context-id
The binding a -> b is established in the context, of b, with
identifier context-id.
When multiple bindings are established to the same explicit context, the observer node takes on the value of the first operand which does not evaluate to a failure. The operands are ordered by the order in which the explicit bindings are declared in the source file. If all the operands evaluate to failures, the node evaluates to the failure value of the last operand.
This is better explained with an example application:
ui.html.
<?
/import(core)
x -> node @ context
y -> node @ context
z -> node @ context
?>
<!doctype html>
<html>
<head>
<title>Explicit Contexts</title>
</head>
<body>
<div><label>X: <input value="<?@ to-int(x) ?>"/></label></div>
<div><label>Y: <input value="<?@ to-int(y) ?>"/></label></div>
<div><label>Z: <input value="<?@ to-int(z) ?>"/></label></div>
<hr/>
<div><strong>Value: <?@ node ?></strong></div>
</body>
</html>
This application is similar to the previous application except the
bindings from nodes x, y and z, to node are established in an
explicit context with identifier context. Additionally the input
fields are bound to to-int instances, of x, y and z which
results in x, y and z being bound to the values entered in the
fields converted to integers. If a non-integer value is entered in a
field, the corresponding node is bound to a failure value.
Let’s try it out. Enter some integer values in each of the fields:

The value entered in the first field, X, was displayed. Since a
valid integer was entered, node x evaluates to the integer value
1. The binding x -> node was established first, as the declaration
occurs first in the source file, and since x does not evaluate to a
failure, node takes on the value of x. The values of y and z
are ignored.
Now let’s change x to a non-integer value:

The value entered in the second field, 2, is displayed. Since a
non-integer value was entered in the first field, x evaluates to a
failure. node thus takes on the value of the next dependency, bound
to the explicit context, which does not evaluate to a failure. The
dependency is y which evaluates to the integer entered in the second
field, 2.
Let’s see what happens if we enter a non-integer value in the third field:

The displayed value is unchanged since the second dependency, node
y, already evaluates to a value which is not a failure value. The
value of the third dependency z, corresponding to the value entered
in the third field, is ignored, regardless of whether it evaluates to
a failure or not.
Explicit contexts are a useful tool for handling failures. In the previous application a failure originating from the first input field, was handled by taking the value of the node bound to the second field. Similarly a failure originating from the second input field is handled by taking the value entered in the third field.
The @ operator also allows a binding to be activated only if the
result of the previous binding(s), in the same context, is a failure
value with a given type. When the context identifier is of the form
when(context, type) the binding is only activated if the result of
the previous binding(s) is a failure of type type.
Example.
x -> node @ context y -> node @ when(context, Invalid-Integer) z -> node @ when(context, Negative-Number)
Three bindings to node are established in the explicit context
context.
node is primarily bound to the value of x if it does not evaluate
to a failure. If x evaluates to a failure of type Invalid-Integer,
node is bound to the value of y. If x, or y evaluate to a
failure of type Negative-Number, then node is bound to the value
of z.
To try this out replace the binding declarations, in the application
from the previous section, with the declarations in the example
above. Also copy over the definition of the meta-nodes valid-int,
validate and the Negative-Number failure type from
Section 8.3, “Target-Node for own Meta-Nodes”, into the Tridash code tag. Replace
to-int with valid-int in the inline node declarations within the
input field values.
Enter a non-integer value in the first field, and an integer value in the second and third fields:

The value of the second field is displayed, since node is bound to
it when the value in the first field is not an integer.
Now change the value of the second field to a negative integer, or alternatively enter a negative integer value in the first field:


The value of the third field is displayed in both cases, even when the
value of the second field is a valid positive integer. This is due to
node being bound to the value of the third field when either the
value of the first field or second field is a negative number.
a -> b @ when(context, type) can be rewritten as: a -> b @ context when type |
Whilst the error handling logic in the Adding Numbers application,
from Section 8, “Target Node Transforms”, is adequate and correct, the
definition of the error-message meta-node, responsible for selecting
an appropriate error message, can be improved using explicit
contexts. The current definition repeatedly checks whether the failure
type of the value argument is of a given type using the fail-type?
meta-node. This is repetitive and does not convey the intent that this
is error handling/reporting logic.
The error-message meta-node returns:
value argument does not evaluate to a
failure.
value evaluates to a
failure of type Invalid-Integer.
value evaluates to a failure of type Negative-Number.
We can re-implement this logic using bindings to the self node with
explicit contexts.
The self node should primarily be bound to the empty string, if the
value argument does not evaluate to a failure. There is a handy
utility meta-node, !-, in the core module, which returns the value
of its second argument if the first argument does not evaluate to a
failure. If the first argument evaluates to a failure value, it is
returned. This meta-node is registered as an infix operator thus can
be placed between its arguments.
The primary binding can thus be written as follows:
value !- "" ->
self @ contextIf this binding results in a failure of type Invalid-Integer, self
should be bound to the constant string “Not a valid number!”. This
is achieved with the following:
"Not a valid number!" ->
self @ context when Invalid-IntegerFinally self should be bound to “Number must be greater than or
equal to 0!”, if the previous bindings resulted in a failure of type
Negative-Number.
"Number must be greater than or equal to 0!" ->
self @ context when Negative-NumberPutting it all together we have the following definition of
error-message re-implemented using explicit contexts:
New implementation of error-message.
error-message(value) : {
value !- "" ->
self @ context
"Not a valid number!" ->
self @ context when Invalid-Integer
"Number must be greater than or equal to 0!" ->
self @ context when Negative-Number
}
The advantage of this implementation is that it more explicitly
conveys the intent that this is error handling logic. As such it can
be optimized more effectively, e.g. if self evaluates to a failure
of type Negative-Number, the check for whether the failure type is
Invalid-Integer can be skipped altogether.
An additional advantage of this implementation is that the third
binding is activated on failures of type Negative-Number originating
both from the first and second bindings whereas the previous
implementation only handled failures originating in value. In this
case it doesn’t make a difference as the second binding cannot result
in a failure of type Negative-Number. However this does make a
difference, in more complex error handling logic, where the handling
of an error may itself result in a new error.
This implementation does, however, have a difference from the previous
implementation in that if value evaluates to a failure of a type
other than Invalid-Integer or Negative-Number it returns a
failure, whereas the previous implementation returned the empty
string. In this application it doesn’t make a difference as the
arguments passed to error-message do not evaluate to failures of
other types.
Coming up with a context identifier and typing it out repeatedly can become tiresome. The original reason for having an identifier for explicit contexts is to distinguish them from the remaining contexts which are implicitly created and to allow for multiple explicit contexts. However, there is usually only a single explicit context used for handling failures.
To shorten the syntax for binding to an explicit context, a default
identifier, such as _ can be given to all explicit contexts which
are used only for handling failures. Alternatively, the @ operator
can take a single argument, the node, in which case it is a shorthand
for the explicit context with identifier default.
# The following: x -> y @ default # Is equivalent to: x -> @(y)
When the context identifier is of the form when(type), that is
omitting the context identifier and leaving only the failure type, the
explicit context with identifier default is, once again, assumed.
# The following: x -> y @ default when type # Is equivalent to: x -> y @ when(type)