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: