Mold

Mold is a very light-weight (200 lines of JavaScript) client-side JavaScript-based templating library. It compiles ('bakes') plain-text templates to functions that map JavaScript values to strings (the filling in of the template) and provides an interface for directly inserting the result of such functions into documents ('casting' the molds).

The majority of Mold's operators are concerned with plain text, but a few of them are HTML-specific, in that they refer to the nodes created by the template. Some magic is involved in this (see section 'Implementation'), but Mold.cast encapsulates that magic.

In a typical use scenario, the web application somehow gets template strings from the server (it could directly fetch text files, or the strings might be in script files), compiles them once, and then uses the resulting 'molds' to build up parts of the document.

Contents

Download and license

Mold consists of only a single (small) script file: mold.js. The library is licenced under a zlib-style license. For those wanting to work on it, the git repository can be gotten from http://marijnhaverbeke.nl/git/mold. The code is also on github.

I am not doing numbered releases, since the userbase is still small. The library interface can be considered stable, though.

Support

You are welcome to mail me (Marijn Haverbeke) with any questions or problems you have.

Example template

A template to build a simple multiple choice widget, for example, might look like this (refer to the explanation below if something confuses you):

<div class="choice">
  <h2>Choose one:</h2>
  <?do var odd = false;?>
  <ol>
    <?label choiceList?>
    <?for choice $arg?>
      <li class="option [?text (odd = !odd) ? "odd" : "even"?]">
        <?event click handleChoice(choice);?>
        <?if typeof choice == "string"?>
          <strong><?text choice?></strong>
        <?else?>
          <?html choice.render()?>
        <?/if?>
      </li>
    <?/for?>
  </ol>
</div>

This loops over an array of choices, building up a representation of each choice, with a special case for choices that are simple strings. Note that choice.render can also be a mold function. Templates can easily be composed like this.

Useage

To use Mold, you include mold.js in your document, which creates a Mold object that is the interface to the library.

The Mold.bake function, which takes a single string as an argument, is used to transform a template string into a mold. A mold is a function that takes a single argument (or none, if your template does not use any parameter data) and returns a string.

Mold uses XML processing instruction syntax for template operators. This means '<?' and '?>' are used around all instructions, as in PHP. Contrary to PHP, though, Mold uses several different processing instructions which serve different roles. After the '<?', an instruction name should follow (see below). In cases where you don't want to use angle brackets (inside attributes in a valid XML document, for example), '[?' and '?]' can be used as equivalents.

(Note that the scanner used by Mold is rather simplistic. If you have a string or regular expression containing '?>' inside your instruction, you should use '?\>' instead, to prevent it from being recognised as the end of the instruction.)

The following instructions are recognised:

text (or t)
Should be followed by a JavaScript expression. On template expansion, this expression will be evaluated (the argument given to the template will be available as $arg throughout), and inserted as text ― meaning any HTML special character in it will be escaped.
html (or h)
Works like text, but the result of the expression is not escaped, so HTML tags can be inserted.
do (or d)
Evaluates a block of code (given after the do) without producing output. Can be useful for defining local variables (any variables defined in such a block will be visible to instructions coming after it.)
if
Should be given a JavaScript expression. If this expression evaluates to false, the block (up to the matching /if, elif, or else) will not be evaluated/expanded.
elif
If following an if or other elif that did not fire, evaluates the expression it is given, and only expands the block following it if this produces a true value.
else
Allowed at the end of a chain of ifs and elifs. The block following else is expanded only if none of the previous conditions fired.
/if
Closes a chain of if/elif/else instructions.
for
Used to loop over arrays or objects. Can follow several forms: <?for x [expr]?> loops over the array created by the given expression. <?for key, value in [expr]?> loops over an object. The value variable in the second form can be omitted when not needed. The template fragment in the loop will be repeated once for every element in the collection, with the named variables bound to the relevant values (bound by function call, not reassigned, so they can be closed over). The variable $i, inside the loop body, refers to the current index, starting at 0.
/for
Closes a for block.
event
Used to attach an event to an HTML node. The instruction should not be placed between the '<' and '>' of an HTML tag. The tag it applies to is determined as follows (the same applies for the instructions after this): When it sits before all non-text nodes in the enclosing node, it applies to the enclosing node. Otherwise, it applies to the first non-text node before it. Directly after the word event, an event type should be given (without 'on', so something like 'click' or 'submit'). After that the handler body follows, which may refer to the $event variable to get the event object. Mold supports one non-standard event, enter, which will fire for keypress events with a key-code of 13.
run (or r)
This works like do, except that the $node variable refers to the nearest HTML node. Having access to this node means that it has to be run 'out of line', after the template has been expanded, so variables defined in this instruction are not visible to the rest of the template code. Variables defined during template expansion, on the other hand, are visible in this block.
label (or l)
This is used to get a reference to the nearest (as in event) HTML node. It takes a name as argument, which will become a property in the object that Mold.cast (see below) returns, pointing to the node. When a label occurs inside a loop, the property will refer to an array of nodes. (Note that nested loops will still produce a single, flat array.)

It should be noted that, while Mold.bake does some error checking, it is still rather easy to pass nonsense parameters to instructions. This can result in rather hard to debug run-time failures (see 'Implementation'), but could, if you really want to, be used to make the library do things it is not designed for ― you could create your own PHP-style loops, for example, with two do instructions, one opening the loop and one closing it.

To build HTML from a compiled template, the Mold.cast function is used. It takes three arguments (node, mold, arg), and replaces the content of node with the result of applying mold to arg. The last argument can be left off for molds that do not require data. If there were any labels defined in the mold used (or molds called from that mold), the value returned by Mold.cast will be an object containing links to the labeled nodes.

As an alternative, there is also Mold.castAppend, which takes the same arguments as cast, but appends the new nodes to the target node, instead of replacing its content.

It is possible to define custom instructions with Mold.define, which accepts a string (the instruction name) and a mold-like function (takes one argument, returns a string) and defines a new instruction that takes an (optional) expression as its argument and expands to the result of calling the given function with the value of this expression. This is basically a way to define pleasant shortcuts for things like <?html myTemplate("something")?>.

There are a few more properties of the Mold object that can be useful. The most important one is Mold.attachEvent. By default, Mold uses a very simplistic event handler framework, which leaves most of the browser incompatibilities intact (it only chooses between addEventListener and attachEvent, and makes sure the event object arrives properly). Most JavaScript libraries provide a nicer wrapper, which can be wired into Mold by setting Mold.attachEvent to a function of three arguments: (node, eventName, func), where eventName is the type of event (without 'on' prefix), and func is a function of one argument (the event object).

When using Prototype, for example, you might do this:

Mold.attachEvent = Event.observe;

There is also Mold.escapeHTML, which HTML-escapes the characters '<>"&'. This is not directly needed for using Mold, but since it was defined internally anyway, and might come in useful, it is exported.

Implementation

What Mold.bake does is decompose the template into instructions and pieces of plain text, and then build a (textual) JavaScript function from these pieces, and use eval to turn it into a real function. This way, loops and conditionals are straightforwardly borrowed from the underlying interpreter, and a lot of work must only be done once.

The event, run, and label instructions need information about the nodes in the resulting DOM tree. Rather than implement some kind of HTML parser, Mold delays their execution until after the text has been parsed by the browser. At expansion time their code is stored in a (numbered) function, which closes over the variables that are active in the template. At the place where they occur, a dummy tag (I used empty var tags with a special class) in inserted. Then, after using innerHTML to transform the text into a DOM tree, Mold.cast searches for such tags, removes them, and runs the code on the relevant nodes. (If you directly call mold functions that use these instructions, you will see the dummy tags.)

Whether a system like this ― intricately connected to the dodgy innerHTML property ― is the most ideal is debatable. My initial intention was to use XHTML templates with a bunch of special tags, a kind of XSLT for JavaScript/JSON data, and use DOM manipulation for all the transformations. But, while innerHTML is implemented pretty well in most browsers, stuff like cloneNode and transforming XML trees to HTML is slightly flaky in a lot of them (and extremely flaky in one particular browser... you guess which).