Building Rez I knew I wanted to be make use of dynamic UI, so I needed a way of creating templates. After looking at several options I picked Handlebars. Pre-1.0 versions of Rez allowed you write @card
content as a Handlebars template. The Rez compiler shelled out to the Handlebars compiler to precompile these templates into functions that could be executed at run-time.
From the get go I was unhappy with the approach I had taken. I could never like the Handlebars syntax, how it was difficult to integrate with Rez, and the dependency on the external compiler made the installation story more annoying.
For 1.0 I decided to bite the bullet and replace Handlebars with Rez’s own template framework that would be deeply embedded. It was a bit of effort but the result is, I think, worth it.
There are four types of expression, the most simple is an interpolation. When you want to put a variable into a template you do this:
${variable}
So, for example, to display the players name
${player.name}
But you cannot execute arbitrary Javascript here so:
${player.name + "'s"}
Is not a legal template expression. But this is where the nod to Liquid comes because we have filters that can alter a value. It’s like sending the data through a pipeline where, at each stage, it is transformed. So, in this case, we would write:
${player.name | possessive}
Which makes use of the possessive
filter. Here is how that’s implemented in the stdlib:
@filter STRING_POSSESSIVE_FILTER {
%% String -> String
name: "possessive"
impl: (s) => {return s.possessive();}
}
The standard library defines over 40 such filters (for things like plurals, array manipulation, generating links) and you can implement your own filters in exactly the same way.
The second expression is the “conditional”:
$if(cond) {% true output %}, {% false output %}
If the cond
expression evaluates to true output the first sub-expression, otherwise the second sub-expression (or nothing if there isn’t a 2nd expression). These expressions may, themselves, be template expressions and nest as deeply as you need them to.
$if(player.wounded) {%
<p style="color: red;">You are wounded, and cannot move very quickly.</p>
%}, {%
<p style="color: green;">You are fit and healthy and can move normally.</p>
%}
The third type of expression is the “iterator”:
$for(x:xs) {% each-x output %}, {% separator output %}
When using the iterator xs
must Javascript expression that you can run map
over (e.g. an array). It binds x
to each value in turn, running the sub-expression, and collecting its output. Then it joins all the output together (if an optional second expression is giving it uses that to join them).
From here you can visit:<ul>
$for(loc: location.exits) {%
<li><a data-event="move" data-target="${loc.id}">${loc.name}</a></li>
%}
</ul>
With a separator:
$for(p:people) {% ${p.name} %}, {%, %}
The last type of expression is the “do” expression:
$do{...code...}
This allows for executing arbitrary code as the template is being rendered. Care should be taken here and it’s likely more appropriate to use one of the built in callbacks such as on_start
and on_render
. But the facility is there if it’s needed.
The result is a decently expressive template language that is built right in and tailored to Rez’s needs. All template expressions are compiled to Javascript functions at compile time so they are pretty fast to execute.