centripetal.ca

JavaScript templating with Django

I've just spent some time finally getting around to investigating client-side JavaScript templating. Prior to this none of the projects I've done have managed to cross the web app event horizon -- that point at which a lot of page assembly is done in the browser, you have to maintain a lot of state, your code become complex enough that readability is becoming a concern, etc. and the traditional web "site" technique of building pages with plugins and lots of DOM parsing and reassembling becomes tedious and difficult.

The most common JS templating options seem to be mustache.js, jQuery's Templates plugin (which is destined to become part of jQuery core at some point) and Underscore's template method. Unfortunately my backends are usually provided by Django, and as there are some implementation challenges with this I thought I'd note down my options, although I have no nice solutions. (Also note that the more you move template rendering out to the client with JavaScript, the more you are basically yanking page assembly out of Django's responsibility, and as one of the three MTV pillars that Django provides, perhaps it begins to make less sense to use a large framework and you want to start investigating another tool. But that's a different argument for another time. For now I think it's still a viable option that Django handles drawing the initial page load or the non-JS version, and then everything else is done in the browser.)

Templates in JavaScript

So the first "hello world" you see with templates is that they get defined inside your JavaScript files:

var artistTemplate = '<div class="artist">{{ artist-name }}</div>',
    artistData = {
        'artist-name': 'John Coltrane'
    },
    artistHtml = Mustache.to_html(artistTemplate, artistData);
$("#someDiv").html(artistHtml);

The problem with this is obviously that you mix HTML into your JavaScript, which is ugly -- you lose nice linebreaks, syntax highlighting, it's harder for making markup changes, etc. You can of course pull in these templates via AJAX calls, but that seems a bit more heavy in that you have to deal with setting that up on the backend somehow.

Templates in HTML

The other place you can put JS templates is inside your HTML templates. As John Resig first described back in 2008, you can declare a script block with a nonsense type attribute. The browser doesn't know how to parse it and so ignores it, but it can then be grabbed and used like any other DOM element from within your jQuery code.

<!-- in html source -->
<script id="artistTemplate" type="text/x-jquery-template">
    <div class="artist">{{ artist-name }}</div>
</script>
<!-- or {% include %} a reusable template to avoid DRY violations -->
// in javascript
var artistTemplate = $("#artistTemplate").html();
// ... same rendering as before ...

But here we run into trouble, because {{ and }} are parsed by Django looking for template context variables, so this means both Mustache and jQuery templates won't work as expected. This is a known issue with jQuery templates but it remains to be seen if/when it will be addressed.

Some workarounds:

1) Use Django's templatetag tag instead of {{. This seems quite unreadable for anything other than trivial snippets.

<script id="artistTemplate" type="text/x-jquery-template">
    <div class="artist">{% templatetag openvariable %} artist-name {% templatetag closevariable %}<div>
</script>

2) Use a different set of characters to delimit template code (eg., [[ and ]]) in your script blocks, and use a regex to replace occurrences of them with the correct ones. This seems kind of ugly I guess.

$("script").each(function(){
    var new_replaced_txt = $(this).text().replace(/\[\[/g, "{{").replace(/\]\]/g, "}}");
    $(this).text(new_replaced_txt);
});

3) Use Underscore's templating, which uses ERB-style tags:

<script id="artistTemplate" type="text/x-underscore-template">
    <div class="artist"><%= artist-name %><div>
</script>

If you can tolerate that it will work, but for me that markup will never get under my fingers (and is kind of heretical to a Django person ;). Note you can change what Underscore uses for delimiters, but that basically does the same regex behind the scenes.

So essentially, no quick easy elegant solution that I can see. If you have a better solution, please let me know!

Comments