Backbone, Underscore and Dust
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 %>°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}°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.
Trackbacks