Asynchronously Load HTML Templates For Backbone Views

UPDATE: It turns out this is a really bad idea. Don’t async fetch individual templates.

In the end, having done asynchronous fetching of individual templates on a few productions apps, it’s a really bad idea. The network latency and multiple requests that are made back to the server destroy any sense of speed or responsiveness that an app may have had, and the additional code that was necessary to do this added more layers of complexity and performance problems.

 

As JavaScript applications become larger and larger, we have to think about the download size, memory usage and other performance constraints for our end users. There are a number of aspects to consider, one of which is how to deliver the HTML templates that your application is using, to the user’s browser, so that your application can render the templates.

For my smaller applications, I tend to stuff a number of <script> blocks in to the HTML that the user downloads. This makes it easy for me to work with and I don’t have to worry about whether or not the template exists. But when the number of templates gets to be more than 5 or 6 small ones, this gets out of hand quickly. It makes it hard for me to manage them as I have to scroll through a lot of template code. Putting them in external files and then including them in the page with some server side technology helps the developer problem but it doesn’t solve the client problem of having to download all of these templates even if they are never used.

To deal with the issues surrounding templates in HTML files, we can split our templates in to separate files and then use asynchronous calls to our server to load them as needed.

Backbone.View Render Semantics

One of my goals, other than the asynchronous template loading, is to keep the general semantics of a Backbone View’s `render` method. It’s a short list, but it’s an important list as most of the Backbone community expects the render method to work this way.

Semantics are generally important as they give us information about how methods and objects are expected to be used, as well. This, in turn, informs the method signature and behavior. And all of this comes back to the Liskov Substitution Principle (LSP) from the SOLID principles, which tells us that we need to pay attention to semantics so that we can drop in replacements as needed.

The general semantics and method signature of a view’s render include:

  • No parameters: the render method shouldn’t require any parameters. You should be able to call `view.render()` and have it work fine
  • Chainable: the render method should return `this` so that it can be chained with other method and attribute calls. This is most commonly done as `view.render().el` to grab the element that was rendered to
  • Populate `el`, don’t replace it: the render method should populate a view’s `el` with any contents that the view needs to display. It generally shouldn’t replace the `el` as a whole

Aside from these three items, there’s a lot of flexibility in how a view will typically be rendered. There’s also plenty of room for interpretation and divergence from this list. Many applications use a render method signature that takes parameters, or that replaces the `el` entirely. When changes like this are made, it’s a good idea to document them so that people will know why the changes are in place.

The benefit of keeping these semantics, though, is that you can swap out a synchronous, pre-loaded template rendering view with a view that uses an asynchronous template loading mechanism. Or, better yet, you can have an intelligent system that uses asynchronous calls to get the template the first time it needs it, and then uses caching to keep the template around and do synchronous rendering on subsequent requests for this view / template. If the semantics for the view are kept in place, it doesn’t matter how the view implements the rendering. The view can be dropped in or removed as needed, without having to change the surrounding code that uses it.

Simple Async Template Retrieval

We can keep this very simple, to begin with, using jQuery’s AJAX calls to load the template with a callback to do the actual rendering after the template is loaded.

Backbone.View.extend({
  template: 'my-view-template',

  render: function(){
    var that = this;
    $.get("/templates/" + this.template + ".html", function(template){
      var html = $(template).tmpl();
      that.$el.html(html);
    });
    return this;
  }

});

In this example, we’re assuming that the view has a `template` attribute. This attribute represents the file that will be loaded from the server, and that file contain the actual template to be used.

We’re also using a convention of `/templates/{name}.html` for the template location on the server. This can be implemented easily in many different web server technologies. In Sinatra, for example, you can create a “public/templates” folder and put your HTML template files directly in that folder. They will be available without having to do anything more than start the Sinatra server.

When the call to `render` is made, the code makes an AJAX call back to the server to retrieve the specified template. A callback method is provided – and at this point, it assumes a successful call to get the template. When the template is returned from the server, the callback is fired, and the standard render code (using jQuery templates in this example) is executed.

Note that the `el` for the view is populated inside of the callback, but we are still calling `return this` at the end of the function. Even if we chain access to the `el` from the `render` method and immediately add the el’s contents to the DOM, this will still work:

$(“#content”).html(view.render().el);

The reason this works is that we are only populating the `el` with contents. We are not replacing it. When the AJAX call finally returns with the template, rendering it and populating the `el` will show the contents immediately because we’re setting the `html` method of an HTML element that is already attached to the DOM. It’s as if we had called `$(“#content”).html(“<div>some html</div>”);` directly.

Caching Templates

Now that we have a template loading asynchronously, it would be nice to only load it once instead of every time it needs to be used. This will improve the overall performance of the application, from the user’s perspective.

To do this, we’ll need a little more than just some code in the render method. We want to re-use templates that we’ve already loaded, which means we can’t just store the template on the view instance. We need to store it in a place where any view instance can grab a copy of the template if it exists, or have an asynchronous call back to the server done to get the template when needed.

TemplateManager = {
  templates: {},

  get: function(id, callback){
    var template = this.templates[id];

    if (template) {
      callback(template);

    } else {

      var that = this;
      $.get("/templates/" + id + ".html", function(template)){
        var $tmpl = $(template);
        that.templates[id] = $tmpl;
        callback($tmpl);
      }

    }

  }

}

The template manager object has one primary method that we call: `get`. This method takes in a template name to be loaded and a callback method that is executed when the template is found. By using a callback method instead of returning a value directly, we can ensure both synchronous and asynchronous calls will work correctly.

When you call `get`, it will check a hash / object literal to see if the template you want is already loaded using the template name that you provide to make this check. If it exists, it executes the callback immediately and passes the template along. If it does not exist yet, an AJAX call is made with jQuery to get the template from the server. Once the template is loaded, the callback that you passed in will be executed and the template is passed to it.

We can now update our view to use the template manager:

Backbone.View.extend({
  template: 'my-view-template',

  render: function(){
    var that = this;
    TemplateManager.get(this.template, function(template){
      var html = $(template).tmpl();
      that.$el.html(html);
    });
    return this;
  }

});

This isn’t much of a change from the view’s perspective. It’s better encapsulated, though, and a little easier to read. The real work is now being done in the TemplateManager object and we can change how it behaves as needed without having to update our views.

Beyond Simple Caching

There’s one remaining problem that this code has. If you have multiple instances of a view requesting the same template at roughly the same time, multiple AJAX calls will be made – one for each instance of the view. The net result is a slowdown in the application’s performance from too many network calls, and also a visual oddity where the views will appear one at a time as the AJAX calls finish. This can be a pretty big drag on performance and UI responsiveness.

Thanks to some previous digging in to jQuery’s deferred and some help from Steve Flitcroft (@red_square), I was able to solve this problem fairly easily. I put the following code in to my BBCloneMail app, which uses my Backbone.Marionette plugin. It’s not directly implemented in Marionette’s `TemplateManager` but most of what you need is already there. There’s only a few additional lines of code that you need to make this work.

(function(){
  var promises = {};

  // Cache the returned deferred/promise by the id of the template
  // so that we can prevent multiple requests for the same template
  // from making the ajax call.
  //
  // This code is only safe to run synchronously. There exists a
  // race condition in this function, when run asynchronously,
  // which would nullify the benefit under certain circumstances.
  var loadTemplateAsync = function(tmpId){
    var promise = promises[tmpId] || $.get("/templates/" + tmpId + ".html");
    promises[tmpId] = promise;
    return promise;
  }

  // Use jQuery to asynchronously load the template. 
  Backbone.Marionette.TemplateManager.loadTemplate = function(templateId, callback){
    var tmpId = templateId.replace("#", "");
    var promise = loadTemplateAsync(tmpId);
    promise.done(function(template){
      callback.call(this, $(template));
    });
  }
})();

The basic idea is that I’m using a jQuery deferred / promise to fire the callback method from the loadTemplate parameters. To prevent multiple requests for the same template heading back to the server, I’m caching the promises by the template id. When a call to loadTemplate is made through the template manager, I check to see if I have a promise for that template already. If I do, I register the loadTemplate’s callback parameter with the promise. If I don’t, I create the promise and then store it by the template’s Id. Either way, I register the callback with the promise which guarantees it will be executed. Once the template is returned from the server and the promise is fulfilled (resolved), all of the callbacks are fired off with the template data and everything renders correctly.

Note, though, that this code is not 100% safe. If you call the template manager from code that is already asynchronous, you can end up with a race condition where multiple promises are created and multiple calls to get the template are done. You’ll still get all of your views rendered just fine, but this will eliminate the benefit of using a promise to reduce network calls. Calling this code synchronously, though, won’t cause this race condition and the templates will only load once before they are cached and re-used.

See It In Action

If you’d like to see an example of this code in action (including the deferred/promise code from above), check out my BBCloneMail application and it’s source code. You’ll see the update to the TemplateManager’s `loadTemplate` function in the code. When you view the live site, switch to the Contacts view and refresh the page. There will be a small delay in the template loading, and then all of the contacts (which all individually requested the same contact template) will all display at once. Subsequent requests to those areas of the app will be cached, of course, improving the user experience and performance even more.


Post Footer automatically generated by Add Post Footer Plugin for wordpress.

About Derick Bailey

Derick Bailey is an entrepreneur, problem solver (and creator? :P ), software developer, screecaster, writer, blogger, speaker and technology leader in central Texas (north of Austin). He runs SignalLeaf.com - the amazingly awesome podcast audio hosting service that everyone should be using, and WatchMeCode.net where he throws down the JavaScript gauntlets to get you up to speed. He has been a professional software developer since the late 90's, and has been writing code since the late 80's. Find me on twitter: @derickbailey, @mutedsolutions, @backbonejsclass Find me on the web: SignalLeaf, WatchMeCode, Kendo UI blog, MarionetteJS, My Github profile, On Google+.
This entry was posted in Async, Backbone, Javascript, JQuery, Marionette, Principles and Patterns, User Experience. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • http://twitter.com/rwaldron rick waldron

    Your TemplateManager is leaking “that = this;” globally and missing a closing paren on the $.get() call. 

    • http://mutedsolutions.com Derick Bailey

      that’s what i get for typing code directly in to a gist :D

      • http://twitter.com/pasz Pascal Altena

        There’s still a typo, in 2.js

        $.get(“/templates/” + id + “.html”, function(template))

        That last ) shouldn’t be there, it should be moved to the end of the $.get block.

  • http://domenicdenicola.com/ Domenic Denicola

     The approach we prefer is to load the template HTML with the RequireJS text plugin:

    // TheView.js
    define(function (require) {
        var templateHtml = require(“text!./template.html”);

        return Backbone.View.extend({
            render: function () {
                that.$el.html(templateHtml);
                return this;
            }
        });
    });

    I mean, why re-implement the wheel?

    See also https://github.com/davidpadbury/html5-trader/tree/master/public/scripts/StockTrader for a demo of this being used extensively.

    • John Teague

      yes, this is what we do too.  Here is a good writeup about modular backbone.

      https://github.com/addyosmani/backbone-fundamentals#modularjs

      There is a section on loading templates and compiling them as part of the view

    • http://twitter.com/red_square red_square

      I do not see what this buys? Importing another dependency and wrapping funcs in define statements to do the same job? I would love someone to convince me this has a major benefit.

      • http://domenicdenicola.com/ Domenic Denicola

         If you’re not using a module system for your JavaScript applications, I wouldn’t admit that in public…

        • http://twitter.com/red_square red_square

          Domenic – nice list of benefits, thanks.

    • http://domenicdenicola.com/ Domenic Denicola

       I just found https://github.com/ZeeAgency/requirejs-tpl which looks pretty helpful as well.

    • Bill

      I think the below syntax is a little more succinct. But, requirejs is a pain in the butt for most applications, because of the extra deployment step of optimizing :) I haven’t seen any major benefits in using it unless your app is HUGE….require([ "text!some/module.html"],
      function(templateHtml) {

       return Backbone.View.extend({        render: function () {            that.$el.html(templateHtml);            return this;        }    });

      }
      );

  • http://twitter.com/commadelimited Andy Matthews

    Thanks Derick…

    Backbone newbie here. I assume the .tmpl() function simply preps the html string and converts it into a DOM node? Or something of that nature?

    • http://mutedsolutions.com Derick Bailey

      yeah – that’s jQuery.Templates, specifically. I’m tending to use underscore.js’ templates instead, these days, but still have a lot of code using jQuery templates

      • Kris

         I was wondering if it wouldn’t be better to have a function as callback parameter to which you pass the object with the template parameters? If you do that, you can do whatever you want in your templatemanager, including caching compiled handlebars templates (which are, functions).

        => more liberty to change tech later on because your templating technology is only known in your template manager.  Your function returns the templating result.

  • Gregg Caines

    What does the in-javascript caching get you that the browser cache doesn’t?  The browser cache also gets you an invalidation mechanism (Cache-Control, ETag), for when the templates inevitably change.

    • http://mutedsolutions.com Derick Bailey

      mostly just a guarantee of caching, without the need to set up the XHR call to hope that the browser did cache it.

      it’s probably a safe bet that the browser will cache the request, as long as it’s a GET request, though

  • Josh Noe

    Have you had any luck getting this to work with Backbone Collections?

  • life like weeds

    What are your thoughts on using a technique for a larger scale app which could have a dozen or more templates on a given view? Surely a production-mode type mechanism that flattens all related templates into one file, then that view could just fetch them with a single async request, but still make them accessible via a technique like you’ve posted here.

    I don’t know, too esoteric? Seems common for full-fledged apps.

    • http://mutedsolutions.com Derick Bailey

      i ran into a situation where i needed something like this, last week. i haven’t implemented anything yet, but I don’t think it would be very difficult. 

      1) $.get all-templates.html
      2) jquery select all elements with type=”text/template”
      3) stuff all the selected templates in to the template cache

      or alternate:

      1) $.get all-templates.html
      2) stuff the results in to a hidden div or in to the head of the page

      • Cocovan

        Hi Derick,

        I’m implementing exactly that at the moment (backbone complete n00b) and wanted my templates in a separate file).

        Pulled them in via a $.get() and prepended them to the body so they were before the load of the views. However, the views can’t seem to find them and the app fails.

        Did you have success with that approach?

  • http://twitter.com/foxdogapps Michael Martin

    Derick,
    Backbone.Marionette is really, really cool. I ran into an issue while loading nested views using this async method, in which the parent view would render too quickly, ie. before the template was actually loaded into Backbone.Marionette.TemplateCache.templates
    and I just wanted to share my solution here as well as elicit feedback.

    Here is the meat and potatoes:

    Backbone.Marionette.TemplateCache.loadTemplate = function (templateId, callback) {
                var that = this;

                if (templateId && templateId.length) {

                    var tmpId = templateId.replace(“#”, ”");
                    var url = ”/templates/” + tmpId + ”.htm”;
                    var promise = $.trafficCop(url);
                    promise.done(function (templateHtml) {
                        var $template = $(templateHtml);
                        var template = that.compileTemplate($template.html());
                        callback(template);
                        App.vent.trigger(templateId + ”:loaded”); // necessary to load sub views in conjunction with ’NestedViewsApp.loadSubView’ method.
                    });
                }
            }

            App.loadSubView = function (parentView, childView) {

                if (Backbone.Marionette.TemplateCache.templates[parentView.template]) {

                    var $viewContainer = parentView.$el.find(parentView.subViewContainer);

                    $viewContainer.html(childView.el);
                }
                else {
                    NestedViewsApp.vent.bind(parentView.template + ”:loaded”, function () {

                        var $viewContainer = parentView.$el.find(parentView.subViewContainer);

                        $viewContainer.html(childView.el);
                    });
                }
            }

    And the call to loadSubView is within the parentView.render method as follows:

    Parent.ParentView = Marionette.ItemView.extend({

                    tagName: ”div”,
                    className: ”parent-view”,
                    template: ”#parent-view-template”,

                    render: function () {

                        Marionette.ItemView.prototype.render.apply(this, arguments);

                        var childView = App.Children.getChildView();

                        App.loadSubView(this, childView);
                    }
                });

    BTW, this works well with Marionette.CompositeView as the childView, which rocks. If anyone can think of a more elegant way to handle this or provide any feedback, sharing is always appreciated.

    Have a great Memorial Day weekend!

    • http://mutedsolutions.com Derick Bailey

      Hey Michael,

      Can I get you to post that code in to an issue on the Marionette project issue list: https://github.com/derickbailey/backbone.marionette/issues

      A lot of people are having that problem these days, and getting that info out to everyone while I’m trying to find time to dig in and fix this, would be awesome :)

      • http://twitter.com/foxdogapps Michael Martin

        Done and done, thanks!

  • http://twitter.com/svlada Stanković Vlada

    You are calling TemplateManager within View code. Do you use one TemplateManager instance per View? If not, it looks tedious to pass around TemplateManager reference all the time. Should we get our templates outside of View code.

    Your blog posts are really inspiring. Thank you on your efforts.

  • johndurbinn

    Derick, you seem like a good guy; but I can’t help but get the feeling that people like you are obsessed with the infrastructure around development, rather than actual development as a way to solve real world problems. Setting up all this infrastructure is only appropriate for a VERY SMALL PERCENTAGE OF DEV PROJECTS.

    • http://www.facebook.com/scottsilvi Scott Silvi

      You could make the argument that people like him are building many of those “small percentage of dev projects” – furthermore, he states right in the article that he will inline the templates if there’s only a handful, but beyond that it gets difficult. Furthermore of the furthermore, if you right modulized code, you only have to set it up for the first project, and can then reuse it any future projects. Not setting up this infrastructure for the first time MEANS YOU HAVE TO REWRITE IDENTICAL/NEARLY IDENTICAL CODE IN THE VAST MAJORITY OF small DEV PROJECTS.

      • johndurbinn

        Another architecture astronaut i see. Have fun building castles in the sky while I actually ship products and make money.

        • justin

          Then go build and ship them rather than attack random people in an old article on reasonable/easy to implement techniques for building scalable backbone applications. You don’t agree with async loading templates, great. This article isn’t for you. No need to get mean about it.

          • johndurbinn

            Yeah, well fuck you