dev

February 22, 2008

MooTools template engine (a new approach)

tmpl.jsThis is a template engine I created for MooTools v1.11. I am using this in a recent project or two and it seems to be a rather handy little beast.

What does it do?

It takes a set of template elements, contained in the DOM tree of the current document decorated with marker classes, and expands it with some data.

The data is given as a combination of Javascript objects and arrays. An object’s key selects the DOM Element for which the corresponding value supplies the data.

An array value means the selected elements should be repeated and expanded using the elements of the array.

How does it work?

The data object is processed recursively. If an associative array is found, the first element of the template with the marker class given as the key of the association is selected and both the template and the data are processed deeper from there.

If the value of the association is an array, the elements selected by the key is repeated and filled with the elements of the array recursively.

Whenever a primitive value (ie, a number, or a string) is found, the currently selected element’s text is set accordingly, and recursion stops.

Examine the following example.

<div style="display:none" id="testTemplate">
    <table border="1">
        <tr class="header">
            <th class="columnName" />
        </tr>

        <tr class="data">
            <td class="item" />
        </tr>

        <tr class="data alt">
            <td class="item" />
        </tr>

    </table>
</div>

<div id="testTarget"></div>

Here we have a HTML template described under the div ‘template’. We intend to place an expanded version of it into ‘testTarget’. See how it is done.

        var data = {
            'columnName': ['Name', 'Price', 'Qty'],

            'data': [
                {'item': ['1. Apple', 125, 0.5]},
                {'item': ['2. Banana', 210, 0.4]},
                {'item': ['3. Cat', 300, 0.2]},
                {'item': ['4. Dog', 200, 3]}
            ]
        };

        expandTemplate('testTemplate', 'testTarget', data);

Let’s se what we have here.

  • the ‘columnName’ array is there to instruct the engine to repeat the <th class=”columnName” /> three times and set them up so they say ‘Name’, ‘Price’ and ‘Qty’.
  • the ‘data’ array says we need four copies of the <tr class=”data”>…</tr> and everything contained within
  • each of the ‘item’ arrays says we need 3 instances of <td class=”item” /> in each <tr class=”data”> -s, and they should be filled with whatever is in the arrays.

The resulting HTML looks like this:

  <table border="1">
      <tr class="header">
        <th class="columnName">Name</th>
        <th class="columnName">Price</th>
        <th class="columnName">Qty</th>
      </tr>

      <tr class="data">
        <td class="item">1. Apple</td>
        <td class="item">125</td>
        <td class="item">0.5</td>
      </tr>

      <tr class="data alt">
        <td class="item">2. Banana</td>
        <td class="item">210</td>
        <td class="item">0.4</td>
      </tr>

      <tr class="data">
        <td class="item">3. Cat</td>
        <td class="item">300</td>
        <td class="item">0.2</td>
      </tr>

      <tr class="data alt">
        <td class="item">4. Dog</td>
        <td class="item">200</td>
        <td class="item">3</td>
      </tr>
  </table>

Note that there are multiple trs having the marker class data. These are repeated in alternating succession, and this is the preferred way to expand table templates with that cool zebra look. ;)

AJAX

Yeah, Ajax is supported in a rather trivial manner. We have the shortcut method tmpl, which performs an Ajax request, interprets the resulting text as the Json representation of a data object, and expands the given template using it. It even fires the onComplete event afterwards!

E.g. the template

<div id="template">
    <div class="title"></div>
    <ul>
        <li class="item" />
        <li class="item alt" />
    </ul>
</div>
<div id="target"></div>

And the js:

tmpl('template', 'target', 'primes.php', {
    data: {
        from:2,
        to:20
    }
});

Imagine we have a primes.php that returns the primes between ‘from‘ and ‘to‘ as a Json object that looks like this:

{
    title:"List of some primes",
    item:[2,3,5,7,11,13,17,19]
}

Then we have the following result:

<div id="template">
    <div class="title">List of some primes</div>
    <ul>
        <li class="item">2</li>
        <li class="item alt">3</li>
        <li class="item">5</li>
        <li class="item alt">7</li>
        <li class="item">11</li>
        <li class="item alt">13</li>
        <li class="item">17</li>
        <li class="item alt">19</li>
    </ul>
</div>

There you have it, a zebra colored list of primes. Just as advertised.

Known limitations

Plain old data is processed using Element.setText, and that means no attributes can be set currently. Take the following example.

<div id="template">
    <img class="image" />
</div>

It would be nice if the src, alt and title attributes of the img -s could be expanded like this:

<div id="target">
    <img class="image" alt="Me jumping" title="I was jumping happily" src="me_jump.jpg" />
    <img class="image" alt="Me sitting" title="I fell on my a**" src="me_ass.jpg" />
    <img class="image" alt="Me standing up" title="Then I tried to stand up" src="me_fall_again.jpg" />
</div>

But there is no way for such a thing right now. I have yet to figure out a solution. Comments are indeed welcome :)

Summary

  • as far as I know, the engine described above takes a rather novel approach, and one that meets my needs better than anything I have seen in this field to date
  • first of all, there is no custom language with ifs and fors and whatnot. This also implies there is need for a costly parser for a tiny sublanguage. Besides, in my opinion a template is no place for any control logic. Or any logic for that matter. It is far too late to process the data when it finally comes to template expansion!
  • the ‘language’ describing the template is highly declarative. Again, I don’t think a template language is for them imperative language constructs.
  • I have yet to come up with a way to set attributes in a template. Something like having data objects like {‘src’:’image.jpg’, ‘alt’:’An image…’, ‘styles’: {‘height’:’150px’, ‘border’:’1px solid black’}} and calling Element.setProperties on them might do the trick, dunno… :/
  • There should be events before and after a leaf of the template is expanded, providing some means to prevent further expansion and there should be an event after expansion is complete.
  • Stay tuned for a PHP and an ASP.NET version ;)

Obtaining

Download from here, change extension to js: tmpl.js

Blog at WordPress.com.