November 15, 2011

An MVC approach to the Legislators App

Recently, a couple of people pointed out to me that the architecture I was trying to explore in UI-RPC might lend itself to a more MVC approach. Since the Legislators app I created there makes a good "hello world" for testing javascript architectures, I'll use it again for this project.

Lately the Backbone.js project is getting a lot of traction as a neat way to build an MVC javascript architecture. It provides models, views, collections, and routers (REST URI mappings) and ties them all together with object and event bindings. It also uses Underscore.js, which among other things has a built-in templating function. Since dynamically creating markup was one of the things I need to solve for UI-RPC, it makes sense to give it a test drive here.

First things first:

Code: https://github.com/baspete/backbone
Live: http://dev.basdesign.com/backbone/

The guts of the app are in legislators.js. The app is surprisingly small, consisting of a Model and View for each individual legislator, a Collection of legislators, and a Model and View for the filter widget.

In this case, even though the models are the most fundamental objects here, they're not very interesting.

var Legislator = Backbone.Model.extend({
  initialize: function(){
    console.log("Created Legislator",this.get("legislator"));
  }
}); 

Models fundamentally exist to hold the data model (duh) for an object. They can be bound to Views, and the magic of backbone is that changes to a Model will automatically update its View.

Views are responsible for actually making something happen on the screen, and in this case what they mainly do is pass a JSON object to underscore.js for rendering:

var LegislatorView = Backbone.View.extend({
  template: _.template($('#legislator_template').html()),
  initialize: function(){
    this.render();
  },
  render: function() {
    var l = this.model.toJSON().legislator;
    $("#legislators").append(this.template(l));
    return this;
  }
});

Models, Views, Collections and Routers in backbone are constructors. In our example, a new legislator is created by instantiating its View and binding it to a Model, like so:

var view = new LegislatorView({
  model: Legislator
});

A Collection is a list of things, in our case a View for each legislator:

var LegislatorList = Backbone.Collection.extend({
  url: sunlightBaseUrl,
  initialize: function(){
    this.fetch({
      success: function(collection) {
        collection.each(function(Legislator) {
          var view = new LegislatorView({
            model: Legislator
          });
        });
       }
    })
  },
  sync: function(method, model, options){  
    options.cache    = true; // sunlightlabs needs this to return jsonp
    options.jsonp    = "jsonp"; // sunlightlabs needs this to return jsonp
    options.dataType = "jsonp";  // by tell backbone.js to use jsonp
    return Backbone.sync(method, model, options);  
  },
  parse: function(response){
    return(response.response.legislators); // just return the array, not the whole object
  }
});

The first issue I ran into was the same one raised in UI-RPC: how do you bind an action in one view to a different view or collection? I cheated:

var Filter = Backbone.Model.extend({
  change: function(){
    $("#legislators").empty();
    var params = this.toJSON();
    // sending empty query params breaks 
    for(var i in params){
      if(params[i] === "undefined" || params[i] === ""){
        delete params[i];
      }
    }
    // This is ghetto. How to bind here?
    legislators.url = sunlightBaseUrl + $.param(params);
    legislators.initialize();
  }
});

"legislators" is an instance of the Legislators collection, and one thing I'm certain of is that a Model shouldn't know anything about instances. Interestingly, when I researched this online I found a bunch of posts talking about using a controller to handle this event mapping. Hmmm... that's the same problem I ran into in UI-RPC.

At any rate, even though this is an incomplete version of the Legislators app, with only one data source (the sunlight labs API), it's an interesting start. I'll get it to the same level of functionality that UI-RPC is currently, then start exploring the two architectures in parallel.

No comments:

Post a Comment