You’ve already made use of subnodes in the previous tutorials, when binding to attributes of HTML elements. Now we’ll explores subnodes in depth.
A subnode is a node which references a value out of a dictionary of values stored in a parent node.
Subnode Syntax.
parent.key
The left hand side of the subnode . operator is the parent node
expression and the right hand side is the key identifying the
dictionary entry.
|
A dictionary can be created in a node by binding to a subnode of the node.
Example.
"John" -> person.name "Smith" -> person.surname
In this example, the value of the node person is a dictionary with
two entries
|
|
Bound to the string constant “John”. |
|
|
Bound to the string constant “Smith”. |
The meter application developed during the previous tutorial was a bit of mess with the various color components scattered through the code.
To change the colors you’d first have to change the hue components, in the following code:
hue <- lerp(120, 0, scale)
It isn’t clear what the numbers 120 and 0 are supposed to be or
which number corresponds to the hue component of which color.
To change the luminance and saturation components, you’d have to modify the following:
self.meter.style.backgroundColor <-
make-hsl(hue, 90, 45)There is also no interpolation of the saturation or luminance components.
The code can be made significantly more readable and maintainable by making use of a dedicated color object.
We’ll create a meta-node Color which takes the three color
components as arguments and returns a dictionary storing the
components under the entries: hue, saturation and luminance.
How are we going to return a dictionary from a meta-node? We can create a dedicated local node, in which the dictionary is created, such as the following:
Color(hue, saturation, luminance) : {
hue -> color.hue
saturation -> color.saturation
luminance -> color.luminance
color
}Or we can simply bind to subnodes of the self node.
Meta-Node Color.
Color(hue, saturation, luminance) : {
hue -> self.hue
saturation -> self.saturation
luminance -> self.luminance
}
The dictionary returned by Color is how colors will be represented
in our application. Let’s create color objects for the two colors and
bind them to nodes:
color-empty <- Color(120, 90, 45) color-full <- Color(0, 90, 45)
|
Rather than interpolating between the components of color-empty and
color-full in the global scope, we can create a meta-node that takes
two colors and the alpha coefficient, and returns the interpolated
color.
Meta-Node lerp-color.
lerp-color(c1, c2, alpha) :
Color(
lerp(c1.hue, c2.hue, alpha),
lerp(c1.saturation, c2.saturation, alpha),
lerp(c1.luminance, c2.luminance, alpha)
)
The lerp-color meta-node simply creates a new color, using the
Color meta-node, with each component interpolated between the two
colors, using lerp.
We can use this to easily interpolate between the colors:
color <- lerp-color(color-empty, color-full, scale)
To convert the Color object to a CSS color string we have to pass each
component to make-hsl as an individual argument like so:
make-hsl(color.hue, color.saturation, color.luminance)
However, the internal representational details of the color are leaking into the application logic. All it takes is to accidentally pass a single component twice or pass the components in the wrong order and there is a bug.
To rectify this we can rewrite make-hsl to take a Color object or we
can bind a subnode of the Color object to the CSS color string.
Modify Color to the following:
Color(hue, saturation, luminance) : {
hue -> self.hue
saturation -> self.saturation
luminance -> self.luminance
make-hsl(hue, saturation, luminance) -> self.hsl-string
}We’ve added a new declaration to Color which binds the hsl-string
subnode of self to the CSS HSL color string, created using
make-hsl. Since the values of nodes are only evaluated if they are
used, and subnodes are no different, the value of the subnode
hsl-string will only be computed for the final color object, not
the color-empty and color-full objects.
If you’d like to make the code even neater you can move the
definition of the |
The interpolated color can be bound to the meter’s background color with the following:
color.hsl-string -> self.meter.style.backgroundColor
We now have a new more readable and maintainable version of the meter application. Replace the Tridash code tag with the following:
<?
/import(core)
# Utilities
lerp(a, b, alpha) : a + alpha * (b - a)
clamp(x, min, max) :
case (
x < min : min,
x > max : max,
x
)
make-hsl(h, s, l) :
format("hsl(%s,%s%%,%s%%)", h, s, l)
Color(hue, saturation, luminance) : {
hue -> self.hue
saturation -> self.saturation
luminance -> self.luminance
make-hsl(hue, saturation, luminance) -> self.hsl-string
}
lerp-color(c1, c2, alpha) :
Color(
lerp(c1.hue, c2.hue, alpha),
lerp(c1.saturation, c2.saturation, alpha),
lerp(c1.luminance, c2.luminance, alpha)
)
# Application Logic
color-empty <- Color(120, 90, 45)
color-full <- Color(0, 90, 45)
scale <- clamp(quantity / maximum, 0, 1)
color <- lerp-color(color-empty, color-full, scale)
color.hsl-string -> self.meter.style.backgroundColor
format("%s%%", scale * 100) -> self.meter.style.width
?>Compared to the previous version, this version has a number of benefits: