Saturday, August 3, 2013

Extending BackboneJS to Build Better Web Applications

I was reading Cody Lindley's Backbone.js Deconstructed and he pointed out that
... a framework calls your code, while a library is code that you call ...
And makes the statement, given the above definition, that Backbone is simply a library not a framework. That got me thinking about what truly defines a framework. Some people might include development workflow tools, visual components, and, various other helpful technologies. My definition boils down to simply these two concepts: modularization and life-cycle management.

Motivation


Before digging into anything real, I took a step back and tried to reason through what really defines these two concepts and only loosely tie these back to Backbone. I am interested in how these ideas relate to browser-side application development and thinking in terms of larger single page applications. I'm really interested in going beyond "hello world" and todo lists and want to find ways to factor out common functionality. In the context of building data-driven applications, I need to present the data to the user, allow them to interact with it, and then persist any changes along the way without having to write reams of code to accomplish it.

Modularization

There are several facets to modularization as it relates to web systems. First, a framework must help the developer segment and encapsulate parts of the system in a natural and consistent manner. The purpose of doing this is to enable building reusable, loosely-coupled components that define basic public interfaces for managing data, manipulating state, and monitoring key events. These basic concepts are central to the MVC pattern and Backbone gets you started down this path. Second, the framework should be able to limit access directly to the object prototypes by using factory methods to create instances. One of the challenges of building a browser application is managing parts of the DOM in tandem with the logic behind it. Finding ways to effectively connect and manage those relationships in a consistent manner is something a framework can provide by being cognizant of all the players involved. Finally, there must be a means to establish communication between components and define the relationship to each other such that a system of implied permissions can be created to properly route messages between components. By doing this, the framework enables tracking of relationships for life-cycle management and mediating transfer of control between application components.

Life-Cycle Management

Just like a book, an application has a beginning, middle, and end. However, while an author of a book must write an original beginning and ending, there's really no reason why a developer should program one for every application they write. What happens in the middle defines the application, the beginning and ending are generally common between applications. I think this is where Cody's definition really comes into play. A real framework handles all the beginning and ending bits and "calls" your application modules at appropriate times. You work from a template to define the interesting parts of your application while the framework deals with the details of loading modules, attaching instances, and cleaning up when things are done. In the context of building browser-based applications, frameworks that help manage binding logic to the DOM and cleaning up those instances once the nodes in the DOM are removed can save a developer a lot of redundant coding plus add a layer of consistency for connecting and disconnecting components to the DOM throughout their life-cycle.

Digging Deeper


Ok, less generalizations and more specifics. Let's evaluate the current abilities of a Backbone view in terms of it promotes modular pattens and life-cycle management. A typical view will flow through these stages during its life-cycle:

Loading -> Creating -> Rendering <-> Changing -> Destroying

Let's investigate each state and see what Backbone does and examine areas where improvements could be made and begin identifying solutions.

Loading

Backbone does not provide a means to dynamically load views. Its something that can be accomplished with RequireJS but to add callbacks, events, and deferred promises requires some extra effort to support in the context of Backbone views. Looking outside the Backbone eco-system, Durandal, which provides modularization and dynamic loading for Knockout, introduces an interesting approach to defining your layout in a modular fashion. Even if you're not a fan of MVVM solutions, there are still concepts worth examining that can be applied to a Backbone-based solution that will enhance the modularization of application components. Its hard to argue with the expressive declarative semantics available in a MVVM library and Durandal does a nice job extending the concept further to match templates with there associated view models and dynamically load everything via RequireJS. By using RequireJS, you automatically get an opinionated approach to defining functionality that decouples the physical location and references to application components. Durandal leverages these features to provide a mechanism to automatically load functionality and bind it to the DOM.

So typically, you might define the following binding in Knockout:


First name:

Last name:

function AppViewModel() { this.firstName = "Bert"; this.lastName = "Bertington"; } ko.applyBindings( new AppViewModel(), document.getElementById('name-goes-here') );


With Durandal, you implicitly apply the binding by first defining the template in a file:

name.html:

   

First name:

Last name:



with the same name as the view model which is wrapped in a RequireJS define function:

name.js:
define(function() {

   return function AppViewModel() {
       this.firstName = "Bert";
       this.lastName = "Bertington";
   }

});



And, somewhere in your application, you'd use the compose binding to tell Durandal that you want it to load both the template and view model, inject the content, and apply the bindings:


   


Nice and easy, right? In Backbone, it might be interesting to find a way to similarly declare a view/template binding in the DOM such that you can use RequireJS to define the view, load in the corresponding template file, compile it, and make it available to the render function:

   


A little framework magic will load everything, create the view, and render it using the template into the point defined in the DOM. Obviously, there's more required to get here but I like to sketch out what I'd like and see how I can make it possible.

Creating

Our typical view creation starts with the new keyword somewhere outside of the view. Sometimes this is another view or some other kind of mediator object like a controller. At a minimum, you need to define a render function, and almost always, an initialize function:


var AppView = Backbone.View.extend({
    
    initialize: function( options ) {
       this.template = _.template( $('#tmpl-name') );
       this.listenTo( this.model, 'change', this.render );
    },

    render: function() {      
       this.$el.html( this.template( this.model.toJSON() ) );
    }
});

var v = new AppView(...);



However, we're trying to create a framework that will be capable automatically loading the view object and its template definition together and handle these two common steps for us. What we really need to do is hide the initialization process and factor out template compilation and basic rendering. To achieve this, my boilerplate view would probably look something like this:


define(function() {

    return Backbone.View.extend({
          
       setup: function( options ) {
          // Initialize instance variables
       },

       beforeRender: function() {
          // Modify stuff; cancel rendering
       },

       afterRender: function() {
          // Initialize widgets, if any
          // Cache DOM references
       },

       events: {
          // Data and state changes
       },

       destroy: function() {
          // Delete instance variables, if needed
       }
   });
});



Behind the scenes, the extended framework will set the compiled template, create an instance, and render the view into the element that declared the data-view attribute. Everything above is completely optional. What's not entirely obvious is how you get data into the view and access the instance externally for coordinating multiple views within the application. As I was wandering the Internet to find ideas for a solution, I found Backbone.Giraffe which helps to define relationships between views by maintaining references within the views to their children and parent plus annotating those connection points in the DOM via custom attributes. The approach mimics certain features of Knockout but within a Backbone context. The view relationship was what I was most interested in and how those relationships paralleled with the hierarchy in the DOM. Since views really are the logic behind the presentation, it makes sense to structure them in a similar fashion. You can visualize the organization of the views in an application as starting from the body down into regions for the navigation, main content, and footer. Those further break apart into menus, toolbars, forms, tables, etc.


Rendering

We took a peek at what the framework's view definition would look like. However, we need to understand how it affects the rendering of the view. The creation step happens one time per instance. However, rendering can happen many times during the lifetime of a view and is triggered by any number of state changes (data, user, or system initiated). The most common activity here is to call the compiled template function with data (generally serialized from the collection or model) and replace anything inside the containing element. Once the content is rendered, you can attach other functionality to the elements (like jQueryUI widgets). Let's go back to a typical Backbone view definition:


var AppView = Backbone.View.extend({
    
    initialize: function( options ) {
       this.template = _.template( $('#tmpl-name') );
    },

    render: function() {      
       this.$el.html( this.template( this.model.toJSON() ) );
    }
});

var v = new AppView( { model: new Backbone.Model({ 
        firstName: 'Bert',
        lastName: 'Bertington'
   })
});

$('#myelement').append( v.render().$el );



Behind the scenes, Backbone will create a container element where you can render your template. You can override the defaults by setting certain attributes in the view definition:


var AppView = Backbone.View.extend({
   
   tag: 'ul',
   id: 'mylistofstuff',
   className: 'a-list',
   attributes: {
      ...
   }

   ...
});



Or avoid the creation of a new element by setting the "el" key in the options hash during creation of the view:


var v = new AppView({ el: $('#myelement') });
v.render();



What I did here is changed how the view received the point in the DOM from being generated internally to explicitly from the calling code. It changes what would have happened in the original definition from:



where v.$el references the UL tag to:



where v.$el references the DIV tag. Its much flatter, removes markup being defined in the view, and enables the framework to set the views node automatically as it finds data-view elements in the DOM.

Changing

Depending on the view, it most likely will experience various state changes. If you are listening to a model or collection, it will probably be tied to the render function so each update refreshes the contents. If you are attached to events in the DOM, user interaction will additionally affect the visible content. Either way, after creation and until destruction, the view will cycle through some form of rendering/updating the DOM and/or collection/model. Backbone's strength is its ability to easily create a one-way binding from the data to the DOM. The convenient events hash enables binding to DOM events to either push data back to a model or changing pieces of the DOM without a full render cycle. However, there is no built-in help to manage binding data in the other direction or maintaining state. Additionally, wiring up events between views is tedious and using the global event object doesn't provide any hierarchical filtering between children and their parents. Since my view instances are connected together in a way that mirrors the DOM, why not push events out on the DOM and listen for them this way. We can leverage jQuery custom events which comes with built-in namespacing. This snippet from my base view demonstrates how its done:


      trigger: function( topic, data ) {

         if ( !this.$el.trigger( topic+'.'+this.instanceOf, { view: this, data: data || {} } ) )
            this._superStop();
      },



Now, I can mix normal DOM events with view events anywhere along the hierarchy of views connected to the DOM:

   ...
   events: {

      'click li': function( event ) {
      
      },

      'change.nav': function( event, ui ) {
       
      },

      'select.content': function( event, ui ) {
      
      },
   }
   ...



The namespace is the module id defined through using RequireJS and the "ui" object contains the data from the event plus a reference to the view instance that generated it. Additionally, the base view will emit "created", "rendering", "rendered", and "removed" events that can be subscribed to by parent views and utilized to trigger certain functionality. My goal here is to try to define my view in terms of states and the triggers that cause those states to change. The framework provides the structure and helpers to make this possible so I can focus on what's suppose to happen at these points in the process and less about how it happens.

Destroying

At some point, a view will not be needed. Backbone has a remove function that will cleanup event bindings and remove the element from the DOM. However, it is not aware of any views that might be bound to a child element and doesn't do any work to clean those "children" views in tandem. There is still a burden on the developer to ensure everything cleans up in an orderly fashion to avoid memory leaks. This is an area that a solid framework will protect us from having to manage. Alex MacCaw's Memory Management in JS Web Apps gave me an idea on how to leverage jQuery to deal with cleaning up the views as elements were removed from the DOM. Since we're already identifying both the view definition and its instance on the element, we can listen for those nodes to be removed and subsequently clean up the view:


   $.cleanData = function( elems ) {
      for ( var i = 0, elem;
      (elem = elems[i]) !== undefined; i++ ) {
         // Trigger the destroyed event
         $(elem).triggerHandler("destroyed");
      }
      // Call the old jQuery.cleanData
      oldClean(elems);
   };



Now, each view needs to listen for this event and clean itself up:


      initialize: function( options ) {

         this.$el.on( 'destroyed', _.bind( this.remove, this ) );
         
         ...
      },



Now, if you have a main content section view with various children views attached deeper in the DOM, you can safely render new content using $.html() and know that each view will be notified that its DOM element is being removed and given a chance to clean up. Using the parent/child relationships were defined in the creation stage, its possible to navigate through the view hierarchy and automatically remove all the references along the way. Once the rendering is complete, there won't be any phantom objects listening to events or eating memory.

A Solution


I eluded to certain implementation details as I covered each part of the view life-cycle. Each stage will have functionality handled by the framework with hooks or events at critical points to enable defining relevant application logic. At this point, I'd call what I'm working on very experimental and subject to change wildly as I tinker with more use cases and find alternative approaches to add more consistency and define smarter patterns for using it. I have the code out on GitHub and called it Backbone.Fiber and built a simple movie explorer that consumes data from the Rotten Tomatoes API. This post has already gotten too long so I'll cover different parts of how that mini-app works later. However, if you read this far and are not yelling at me through your computer because of my ideas and comments, then you must think their some value in what I've proposed. So, for now, I'll conclude with what I like about what I've built so far:

  • Dynamic, asynchronous loading of views using RequireJS to create modules. The template is automatically loaded and compiled with the view during the loading process
  • Declarative binding of views to the DOM. Instances are automatically created and inserted into a parent/child relationship that mirrors the structure of the DOM
  • DOM, model, and view events are bound through the same events hash for consistency and hierarchical filtering
  • All clean up is automatic regardless of how you remove something


With just a little refactoring and a few new features, I think this brings Backbone closer to what I need in a framework for building applications. I'm sure opinions will vary, but, maybe that's a credit to Backbone. You're not locked into any one way to solve a problem. You can easily morph it into what you need to achieve your development goals.