In my last post about jQuery Templates, I showed you how to use template composition to build a template out of simple sub-templates. These composite templates are a great way to address the complexity that creeps into real-world UIs, as they inevitably grow and become more intricate. However, one feature missing from my last example was the ability to store those composite templates in external files and load them asynchronously for rendering.

I’ve described how to accomplish that with single templates in the past, using jQuery’s AJAX utilities and a particular usage of tmpl(). Unfortunately, remotely loading a group of composite templates from a single file is not quite as simple, and the technique I’ve described previously will not work.

Not to worry though, it’s still relatively easy.

In this post, I’ll show you how to move a group of composite templates to an external file, how to load and render them with jQuery Templates, and how to take advantage of an expected benefit to improve separation of concerns.

Caution: If you haven’t read my previous posts about remotely loading jQuery Templates definitions and using {{tmpl}} to achieve template composition, read them before continuing with this post. I’m not going to cover that material again here, and this may not make much sense without those prerequisites.

Moving the templates to an external file

Breaking the invoice template apart helped make it more approachable and maintainable, but I don’t like leaving the template embedded in the page’s markup. The larger a single file becomes, the more difficult it is to understand and work with – especially over time.

Disentangling chunks of the presentation tier and moving them to separate files is a great way to attack the problem of bloated pages and views. We’ve been doing that since the beginning of the web, from seemingly-ancient techniques like SSI includes, to file includes in scripting frameworks like ASP and PHP, to partial views in MVC frameworks. So, why stop now just because the templates are rendered in the browser?

Moving the previous example’s template definitions to a separate file is as simple as it sounds. Just take the invoice template and both its row templates, script wrappers included, and move them into a new file of your choosing. I’m going to move them to a file named _invoice.tmpl.htm:

<!-- Tip: It's safe to use HTML comments in the file -->
 
<!-- Invoice container template -->
<script id="invoiceTemplate" type="x-jquery-tmpl">
  <table class="invoice">
  {{each lineItems}}
    {{tmpl($value) get_invoiceRowTemplateName(type)}}
  {{/each}}
  </table>
</script>
 
<!-- Invoice row templates -->
<script id="serviceRowTemplate" type="x-jquery-tmpl">
  <tr class="service">
    <td colspan="2">${service}</td>
    <td colspan="2">${price}</td>
  </tr>
</script>
 
<script id="itemRowTemplate" type="x-jquery-tmpl">
  <tr class="item">
    <td>${item}</td>
    <td>${description}</td>
    <td>${price}</td>
    <td>${qty}</td>
  </tr>
</script>

Naming the template file

If you’re wondering why I chose that somewhat convoluted filename for the template, I’ll explain:

Ideally, I would like to call the template something like invoiceTemplate.tmpl, but most popular web servers refuse to serve files with non-standard extensions by default. You can circumvent that with a bit of manual configuration, but it’s not worth the unending hassle of extra configuration work on every server and/or site where you use this technique. So, .tmpl is out.

I really liked Nathan Smith’s suggestion for a naming compromise on my previous post about remote loading, which boils down to this:

  • Prefix the filename with an underscore. This denotes a partial view in many modern view engines and is a useful convention for indicating that the file is not a valid/complete HTML document.
  • Nathan suggested following _templateName with .tpl, to indicate that it’s a template. I’m going to tweak that slightly and use .tmpl, so it’s more clear that the template is intended for use with jQuery Templates.
  • The file should end in .htm, to be sure the file will be readily served up under almost any web server’s default configuration. Using .htm or another text/html extension also improves the odds that the template definitions will be served with appropriate compression and caching.

The first two are just suggestions, of course. You can use any arbitrary naming scheme you prefer and the approach described in this post will still work fine.

I do recommend sticking with an .htm or .html extension though. I (stubbornly) went through the configuration hassle of using .tpl when I was originally working with jTemplates’ remote loading feature a couple years ago, and eventually had to give up on it. The ongoing configuration hassles ultimately outweighed the benefit of a uniquely descriptive extension.

Loading and rendering the template

With the templates moved to an external file, we need a way to load and render them. As I mentioned earlier, the approach I’ve previously described for remote template loading isn’t viable in this scenario. Treating the external file as a simple template string only works if the file contains a single template definition and no extraneous markup. Our file fails on both counts.

Another disadvantage my previous approach is caching. When you render a string-based template, jQuery Templates doesn’t cache the compiled template. If you end up rendering the same template more than once per pageview, the string-based approach was slower than it could have been.

To solve both those issues, we can use jQuery to load the contents of the template file, inject all of it into the document, and then work with the templates as if they had been embedded in the page all along.

// The invoice object to render (see previous post).
var invoice = {
  invoiceItems: [
    { type: 'item', 
      part: '99Designs', description: '99 Designs Logo', 
      price: 450.00, qty: 1 },
    { type: 'service',
      service: 'Web development and testing', 
      price: 25000.00 },
    { type: 'item',
      part: 'LinodeMonthly', description: 'Monthly site hosting', 
      price: 40.00, qty: 12 }
  ]
};
 
// Asynchronously load the template definition file.
$.get('_invoice.tmpl.htm', function(templates) {
  // Inject all those templates at the end of the document.
  $('body').append(templates);
 
  // Select the newly injected invoiceTemplate and use it
  //  render the invoice data.
  $('#invoiceTemplate').tmpl(invoice).appendTo('body');
});

Injecting the template definitions into the page’s markup allows you to render them with the same $('#templateId').tmpl(data) syntax that you’ve seen in most jQuery Templates examples.

Bringing the row template resolver along for the ride

One remaining annoyance is the now-orphaned row template name resolver, get_invoiceRowTemplateName() (covered in my composite template post). Keeping that function with the rest of the page’s JavaScript does work, but I’m not happy with it. To get the full benefit of encapsulating the template in an external file, we should keep any dependent JavaScript code with the template itself.

That’s especially true if the template is used from more than one page.

As it turns out, accomplishing that is much easier than it may seem at first. All we need to do is move the resolver function right into _invoice.tmpl.htm:

<!-- Invoice container template -->
<script id="invoiceTemplate" type="x-jquery-tmpl">
  <table class="invoice">
  {{each lineItems}}
    {{tmpl($value) get_invoiceRowTemplateName(type)}}
  {{/each}}
  </table>
</script>
 
<!-- Invoice row templates -->
<script id="serviceRowTemplate" type="x-jquery-tmpl">
  <tr class="service">
    <td colspan="2">${service}</td>
    <td colspan="2">${price}</td>
  </tr>
</script>
 
<script id="itemRowTemplate" type="x-jquery-tmpl">
  <tr class="item">
    <td>${item}</td>
    <td>${description}</td>
    <td>${price}</td>
    <td>${qty}</td>
  </tr>
</script>
 
<!-- This function is used by the invoice container to determine -->
<!--  which row template to render for a given line item -->
<script type="text/javascript">
  function get_invoiceRowTemplateName(type) {
    // Return a template selector that matches our 
    //  convention of <type>RowTemplate.
    return '#' + type + 'RowTemplate';
  }
</script>

When you inject markup into the DOM, browsers will immediately parse and evaluate that markup just as they do during the initial page load – including any JavaScript code in the markup. Since browsers do this in the same single-thread that they execute JavaScript, it’s safe to assume that embedded functions are available immediately after the markup has been injected.

It seems almost too good to be true, like one of those handy techniques that works in every browser except some version of IE. However, embedding the supporting function inside the template file works in every browser I’ve tested, even including the full complement of JavaScript-enabled options on BrowserShots.org.

Conclusion

I’ve been using this exact technique in production for about a month now. I initially came up with several approaches, but wanted to try them in real-world code before I recommended one. I’m happy to report that this has been working great for me so far. I hope you’ll find it helpful too.

One caveat is that you should be careful about loading and injecting the external template more than once. Injecting multiple copies of the template definitions over and over actually doesn’t break the rendering process in browsers I’ve tested, but it will unecessarily impact performance as the DOM grows larger with every new injection.

I’m going to cover that and another templating concurrency issue soon, but if you want a head start: test $.template('#templateName') to determine whether a particular template is already loaded and cached.