Skip to content

Backbone, Underscore and Dust

October 12, 2012

Introduction

Lately I’ve been reading about how various Agile/Lean teams have been refactoring their sites. The goal was to implement MVC/MVVM using a framework such as Backbone or Spine. For templating, they use Underscore (bundled with Backbone) or Dust. That got me to wondering what a similar implementation would look like for weather.com. In this post I’ll describe what it takes to implement a simple weather observation module using Backbone and Dust.

To get your page prepared for using Backbone, you will want to load scripts for jQuery, Underscore, and Backbone in that order inside your HEAD tag:

<script src="http://code.jquery.com/jquery-1.7.1.min.js"></script>
<script src="http://underscorejs.org/underscore-min.js"></script>
<script src="http://backbonejs.org/backbone-min.js"></script>

Please note that in the example above I pointed to the original locations for those scripts. In a final product, you’d want to download those scripts and host them locally.

Next, create the HTML container that you’ll want to place the data in, and the template to do the placement:

<script type="text/template" id="weatherObservationModuleTemplate">
	<h1>Weather for <%= presName %></h1>
	<div><img src="http://s.imwx.com/img/wxicon/100/<%= Observation.wxIcon %>.png" alt="<%= Observation.text %>" width="100" height="100" id="myIcon"></div>
	<p><%= Observation.temp %>&deg;F and <%= Observation.text %></p>
</script>
<div id="weatherObservationModule"></div>

In the example above, there are several interesting things taking place. I placed the template inside a script tag in order to ensure that the browser does not render the text. I changed the script type to ‘text/template’ to further ensure that the browser does not try to parse the contents as a script. I then gave it an ID attribute so I could retrieve its contents. I then placed this template immediately above the target container so that they’re in close proximity. There is no technical reason they would need to be placed close together, but it is an intentional choice on my part to make it easier for anyone to maintain the module by keeping related functionality in a single location. The template contents merely use Underscore templating syntax to place variable data verbatim. Notice how it is visually similar to JSP syntax.

Now you’re ready to use Backbone to load the weather data and parse the template. Backbone has a Model class that we’ll extend to create a weatherModel, and a View class that we’ll use to create a weatherView. We’ll then create instances of both to kick off the process. First, instatiate the Model, then instantiate the view so you can pass it a reference to the Model instance. In the View instance’s initialization method, create a listener for the change event, which should fire when the Model instance’s data is populated, and then tell the View instance to render upon successful retrieval. Then tell the model to go fetch the data.

<script>
(function($){
	var weatherModel = Backbone.Model.extend({
		// Overriding one of the Model's built-in methods.
		// The initialize method is called automatically when the Model is instantiated.
		// The fetch method tells the Model to use jQuery to make a JSONP call 
		//     using the url method below.
		// Notice that the fetch success callback 
		//     is invoking the weatherView instance directly.
		initialize: function(){
			// Do nothing
        },
        // The default don't have to be set in this case, 
        //     but it helps indicate what the important properties are, and
        //     if the defaults are displayed, you know something went wrong.
		defaults: {
			'locid': '00000'
		},
		// This is a custom presentation logic method that creates the location name.
		presName: function(data){
			var _l = data.Location;
			return _l.name + ', ' + ((_l.state === '*') ? _l.countryCode : _l.state);
		},
		// Apply the presentation rules to the data before populating the Model.
		parse: function(data,xhr){
			data.presName = this.presName(data);
			return data;
		},
		// This is the URL that we'll use to retrieve the data
		url: function(){
			return 'http://wxdata.weather.com/wxdata/agg/' + this.get('locid') + '.js?cb=?&key=e88ca396-a740-102c-bafd-001321203584';
		}
	});
	var weatherView = Backbone.View.extend({
		initialize: function(){
			var _self = this;
			this.model.on('change',function(){
				if (_self.model.get('locid') !== '00000'){
					_self.render();
				}
			},this.model);
			this.model.fetch();
		},
		render: function(){
			// convert the Model data to JSON for templating
			var data = this.model.toJSON();
			// grab the innerHTML of the template, combine with JSON, and turn into HTML.
			var template = _.template($(this.options.tmpl).html(),data);
			// place the HTML into the target DIV
			this.$el.html(template);
			// return the instance to support chaining
			return this;
		}
	});
	var myWeatherModel = new weatherModel({
		locid: '90210'
	});
	var myWeatherView = new weatherView({
		el: '#weatherObservationModule',
		model: myWeatherModel,
		tmpl: '#weatherObservationModuleTemplate'
	});
})(jQuery);
</script>

View the complete working example.

Dust is a templating language that gives the developer more functionality and the ability to combine templates externally and to precompile them into JS functions for major speed gains. To convert our example from Underscore to Dust requires four small changes. One, add a reference to the Dust library immediately after Backbone. Two, modify the template to replace the JSP-style Underscore variable insertion instructions with curly-brace Dust variable insertions. Three, compile the Dust template in the (previously empty) weatherView initialize function. Four, replace the Underscore templating line and DOM placement of the rendered HTML with a call to the compiled Dust template while passing it a callback to render the result to the DOM.

<script src="dust-full-1.0.0.min.js"></script>
...
<script type="text/template" id="weatherObservationModuleTemplate">
	<h1>Weather for {presName}</h1>
	<div><img src="http://s.imwx.com/img/wxicon/100/{Observation.wxIcon}.png" alt="{Observation.text}" width="100" height="100" id="myIcon"></div>
	<p>{Observation.temp}&deg;F and {Observation.text}</p>
</script>
...
	var weatherView = Backbone.View.extend({
		// read the template from the DOM and compile to a JS function when initializing the view
		initialize: function(){
			var _self = this;
			this.myCompiledTemplateKey = 'weatherTmpl';
			dust.loadSource(dust.compile($(this.options.tmpl).html(),this.myCompiledTemplateKey));
			this.model.on('change',function(){
				if (_self.model.get('locid') !== '00000'){
					_self.render();
				}
			},this.model);
			this.model.fetch();
		},
		render: function(){
			// convert the Model data to JSON for templating
			var data = this.model.toJSON();
			// pass Dust the cache key for the compiled template, 
			//     combine with JSON, 
			//     and add a callback that gets passed the rendered HTML fragment as 'out'.
			var _self = this;
			dust.render(this.myCompiledTemplateKey, data, function(err, out) {
				// place the HTML into the target DIV
				_self.$el.html(out);
			});
			// return the instance to support chaining
			return this;
		}
	});

View the second example.

About these ads

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 508 other followers

%d bloggers like this: