dev

April 15, 2008

jQuery template engine

Filed under: AJAX, HTML, Javascript, template — Tags: , , , , — Zeal_ @ 7:58 pm

As per popular demand (well, one person asked for it), this is the jQuery version of the template engine described in my earlier post, titled MooTools template engine (a new approach). To see what it does and for some usage examples I recommend reading that post first.

Here, instead of uploading the .js file as an attachment, I publish it section-by-section, providing comments along the way.

This is the $type function taken straight from Mootools 1.11, customized somewhat so it meets my needs better.

function $type(obj){
        if (obj == undefined) return false;
        if (obj.htmlElement) return 'element';
        var type = typeof obj;
        if (type == 'object' && obj.nodeName){
                switch(obj.nodeType){
                        case 1: return 'element';
                        case 3: return (/\S/).test(obj.nodeValue) ? 'textnode' : 'whitespace';
                }
        }
        if (type == 'object' || type == 'function'){
                switch(obj.constructor){
                        case Array: return 'array';
                        case RegExp: return 'regexp';
                }
                if (typeof obj.length == 'number'){
                        if (obj.item) return 'collection';
                        if (obj.callee) return 'arguments';
                }
        }
        return type;
};

The next function, fillTemplate, is used internally by both public methods for doing the actual templating work. The argument target represents a template to be fileld, and data is the data to fill the template with.


function fillTemplate(target, data) {
    var target = $(target);
    for(var key in data) {
        var tmpEls = target.find('.' + key);
        var obj = data[key];
        switch($type(obj)) {
        case 'array':
            // clone array of 'el'
            for(var i=0;i<obj.length;i++) {
                var tmpEl = $(tmpEls&#91;i%tmpEls.length&#93;);
                var a = tmpEl.clone(true);
                tmpEl.parent().append(a);
                if (($type(obj&#91;i&#93;) == 'array') || ($type(obj&#91;i&#93;) == 'object')) {
                    fillTemplate(a, obj&#91;i&#93;);
                } else {
                    a.text(obj&#91;i&#93;);
                }
            }
            tmpEls.remove();
            break;
        case 'object':
            // object, descend
            fillTemplate(tmpEls&#91;0&#93;, obj);
            break;
        default:
            // set text of el to obj
            tmpEls.text(obj);
        }
    }
}
&#91;/sourcecode&#93;

And now for the public functions. On the details of their usage, refer to the relevant parts of the original post linked above.

Our first public function, <strong>expandTemplate</strong> copies a template DOM node to the <strong>target,</strong> and fills it with <strong>data.</strong>


function expandTemplate(template, target, data) {
    var template = $(template);
    var target = $(target);
    target.empty();
    target.append(template.clone().contents());
    fillTemplate(target, data);
}

The function tmpl is the AJAX front-end for fillTemplate. It gets JSON data from the given url posting any optional data (postData) and fills the template with the response. This slightly differs from the approach of the mootools version.

function tmpl(template, target, url, postData) {
    var template = $(template);
    var target = $(target);
    $.getJSON(url, function(data) {
        expandTemplate(template, target, data);
    }, postData);
}

Summary

Using jQuery, the code got slightly simpler and a tad bit faster. Have fun using it :)

(As a side note, a PHP version is still on the way, it has some performance issues.)

Advertisements

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

Create a free website or blog at WordPress.com.