1. Introduction

During the development of our third party widgets we decided to use a templating library like Backbone recommends. A templating library would allow the HTML code to be separated from the JavaScript, maximising re-use and avoiding code cluttered with ugly HTML. Ultimately all the templates would be bundled together and packaged into the compressed, single file version of the widgets. However, we still wanted the templates to be retrievable remotely, as and when they were needed. The templating library used below is the one provided by Lo-Dash but it could just as easily be any other library. So here goes, this is how you would asynchronously load backbone view templates.
 

2. The Code to Asynchronously Load Backbone View Templates

In order to retrieve the templates remotely the Backbone View was extended in order to include a templateName and to override the default render method with our own custom method. Note that the code below assumes you have created your own library called MyLibrary which contains a noConflict copy of the Backbone library. The code is a good guideline on how to do it, but you will need to edit a few things to get it to work. So here goes, this is the code used to asynchronously retrieve templates:

(function(window, MyLibrary, undefined) {

	'use strict';

	// During the initialisation of MyLibrary, copies of the libraries are made using noConflict().
	var _ = MyLibrary._;
	var Backbone = MyLibrary.Backbone;

	// This is the root URL under which the templates will sit
	MyLibrary.rootUrl = 'http://www.yourdomain.com/';

	// Check if we have a templates namespace, if we don't then let's create it.
	// This is where the templates will live once they are fetched.
	if (!MyLibrary.templates) {
		MyLibrary.templates = {};
	}

	/**
	 * DView (Deferred View)
	 * This light wrapper around Backbone.View implements support for
	 * asynchronous template fetching and rendering.
	 * When instantiating a view of this type, a "templateName" option must be
	 * specified. Rendering is asynchronous, and any instance's custom "render"
	 * method will not be invoked until the template has been retrieved, at
	 * which time, the compiled template function will be available as the
	 * "template" property of the view.
	 * 
	 * @extends Backbone.View
	 */
	Backbone.DView = Backbone.View.extend({

		/**
		 * DView constructor
		 * @constructor
		 */
		constructor: function( options ) {

			options = options || {};

			if (!options.templateName && !this.templateName) {
				throw new Error("DViews must be instantiated with a template name");
			}

			// Save a reference to the specified template name
			this.templateName = options.templateName || this.templateName;

			// Save a reference to the user-defined render method
			this._userRender = options.render || this.render;

			// Override the view's render method with the asynchronous-ready
			// implementation
			this.render = Backbone.DView.prototype.render;

			// Proceed with the default view initialization
			Backbone.View.apply(this, arguments);
		},

		/**
		 * Method for retrieving template strings, compiling them to JavaScript
		 * functions, and storing the compiled functions in the MyLibrary namespace
		 * for later re-use.
		 */
		getTemplate: function(name, render) {

			var path = MyLibrary.rootUrl + "templates/" + name + ".html";

			// If the template has already been defined (due to an earlier request
			// or due to being packaged with the script), return it directly.
			if (MyLibrary.templates[name]) {
				// Force this operation to be asynchronous for consistency
				setTimeout(function() {
					render(MyLibrary.templates[name]);
				}, 0);
				return;
			}

			$.ajax({
				url: path
			}).done(function(html) {
				MyLibrary.templates[name] = _.template(html);
				render(MyLibrary.templates[name]);
			});

		},

		/**
		 * Override the default Backbone render method with our own version
		 * which fetches the template before rendering.
		 */
		render: function() {

			var args = arguments;

			this.getTemplate(this.templateName, _.bind(function(template) {

				this.template = template;

				if (_.isFunction(this._userRender)) {
					this._userRender.apply(this, args);
				}
			}, this));

			// Return a reference to the view for chaining purposes
			return this;
		}
	});

}(window, window.MyLibrary));

The code above will add the DView to the MyLibrary copy of Backbone. Now, we can create new asynchronous views by extending the DView instead of the standard Backbone View. The view extending the DView will need to have a “templateName” property otherwise the initialisation of the view will fail. The extending view should also contain a render method which will be called once the template has been retrieved. If a render method is not specified then the default Backbone View render method will be called.

Now, in order to get the code above to work you will need to modify the MyLibrary.rootUrl property to your root URL. The templates themselves should be placed in the “templates” folder located under the rootUrl and all the templates should have “.html” extensions. Note: when setting the templateName value, you do not need to add the “.html” extension.
 

3. Using the DView

I guess the best way to explain the paragraphs above is to provide you with an example view which extends the DView. The first thing to do is to create your template inside the templates folder. Below is an example hello world template which will be saved as “myTemplate.html” in the templates folder:

<div><%= saySomething %></div>

Now that we have a template, the next step is to create our custom view which uses the template above to say something on screen:

var MyView = MyLibrary.Backbone.DView.extend({
	templateName: 'myTemplate',
	initialize: function( options ) {
		// initialise things
	},
	render: function() {
		this.$el.html(this.template({
			saySomething: "Hello World!"
		}));
		return this;
	}
});

You can noew check that the template is being fetched remotely by using the developer tools in Chrome or Firebug in Firefox.
 

4. Conclusion

This approach is useful for development and for separating out the logic. However, when implementing this in a live environment, the feeling you will get is that the application loses responsiveness (as it needs to go off and fetch each template). For those cases, I would highly recommend using a build tool such as Grunt to package up all the templates into the MyLibrary.templates namespace and providing them from the start. And that is how you would asynchronously load backbone view templates.
I might update this blog later with details on how to do that.

Alessandro

Alessandro

Serial Entrepreneur with a passion for code, sales and coffee. Always on the look out for new interesting projects. You can find me working on Mambo.IO (http://mambo.io) or doing freelance projects.
Alessandro