views:

83

answers:

3

I'm trying to develop a simple way of creating templates in JavaScript. The basic idea is that I've got HTML in a page that represents the UI of an object in JavaScript, and variables in that HTML that are replaced with properties of the JavaScript object. Think of it as technique for binding JavaScript objects to HTML presentations.

Any critiques? Should I be using document fragments somehow? I'm essentially looking for a code review on this one. I'd appreciate any feedback. (Note that this should be framework independent.) Here's a working exmaple.

<html>
<body>

    <!-- where templates will be injected into -->
    <div id="thumbContainer"></div>

    <!-- template used for thumbnails -->
    <div id="thumbTemplate" style="display:none">
        <div class="thumb">
            <div>${caption}</div>
            <div>${image}</div>
        </div>
    </div>

</body>

<script type="text/javascript">
(function() {

    // Cache the templates' markup in case that template is used again

    var cache = [];

    // Magic

    document.templatized = function(templateId, properties) {

        // Get the template from cache or get template and add to cache

        var template = cache[templateId] || (function() {
            cache[templateId] = document.getElementById(templateId).innerHTML;
            return cache[templateId];
        })();

        // Create the DOM elements that represent the markup

        var shell = document.createElement('div');

        shell.innerHTML = template.replace(/\$\{(\w+)\}/g, function(match, key){
            return properties[key] || match;
        });

        // Return those DOM elements

        return shell.children[0].cloneNode(true);
    };
})();

// Create DOM elements with values bound to thumbnail object

var thumb = document.templatized('thumbTemplate', {
    caption: 'Summer',
    image: (function() {
            // More complicated logic that requires conditions here...
        return '<img src="test.png" />';
    })()
});

// Display on page by inserting into DOM

document.getElementById('thumbContainer').appendChild(thumb);
</script>
+2  A: 

The code is neat.

I'd have the template in an html comment so 1) it doesn't get rendered (no need for the display: none hack) and 2) I'd be able to have stuff that does not validate, such as direct a <tr>.

In addition I'd remove the hardcoded 'div' creation so I could support template snippets starting with any html element.

Later on I'd add graph navigation (e.g ${foo.bar}), iteration and if statements.

cherouvim
+2  A: 

+1 cherouvim: you're using the template as text, so keep it as text. You could pass it to your script as a comment, or you could just have it passed in as a JSON-encoded string, but using markup for it is potentially hazardous.

For example:

<form method="${mymethod}"><table>
    <tr><td>
        <${mylisttype}>
            <li>
                <input name="foo" value="${myvalue}">
            </li>
        </${mylisttype}>
    </td></tr>
    ${extrarow}
</table></form>

illustrates some things you can't reliably do in your templating language due to the browser ‘fixing’ the markup and changing the returned innerHTML back: the form may be mutated to method="get" (or, in IE, omitted FSR); unless you are in XML mode there will be an added <tbody> element; the mutable element won't work at all; the ${extrarow} text is invalid directly inside a table; the input.value may be mutated on page load as browsers attempt to re-set form field values and IE incorrectly reflects field value changes as attributes/innerHTML.

Also, I would strongly recommend adding an HTML-encode feature (replacing & with &amp;, < with &lt; and any attribute value delimiters with similar references). Experience with templating languages has shown it's far more common to want to insert a text string into the document than a stretch of markup. When you do:

Hello, ${name}!

without applying escaping to the variable name, you're opening your application up to cross-site-scripting attacks. Modern templating languages avoid this by applying HTML-encoding by default, because coders are far too lazy to do it manually (if they even understand the issues involved). This is why the vast majority of PHP apps are vulnerable to XSS attacks.

Don't bring the same problem to the client-side; make it escape by default, offering a flag to say you don't want escaping for the (usually relatively few) cases where you want to insert raw markup:

<div>${caption}</div>
<div>${image|markup}</div>
bobince
html encoding feature would be great
cherouvim
+1  A: 

Rather than having it in an HTML comment you can put it in tags, for example:

<script type="text/html" id="mytemplate">
   ... template here ...
</script>

The beauty here is that all browsers and all properly written robots will ignore script blocks where it doesn't know how to interpret them; and you can easily access the content via the ID (like if it was a div).

Ask Bjørn Hansen
This is how several jQuery templating plugins work as well as Microsoft's templating proposal. http://wiki.github.com/nje/jquery/jquery-templates-proposal
R0MANARMY