Introducing the Backbone Module for Drupal
Let's dig in.
A bit of Backbone Background
I won't spend too much time on Backbone coding specifically, since there are plenty of great resources available online. I'd particuarly recommend this open-source Backbone book and a quick review of the Backbone documentation. Because Backbone is minimal and lightweight, the docs are a quick read, and even the annotated source code is a useful and quick read.
As mentioned above, the main components of a Backbone app are Backbone.Model, Backbone.Collection and Backbone.View. Backbone models are much like models in MVC web frameworks like Ruby on Rails and Symfony. They manage the state of our data objects, describe data properties and take care of validating, loading and saving objects. In the case of Backbone, however, loading and saving means sending and receiving JSON data from the server.
Backbone collections are analogous to Drupal views: they are sets of data objects that are usually presented together. While this is not a common pattern for server MVC's, other JS client-side frameworks (like EmberJS) frequently have something similar. Collections allow us to more easily manage the state of listing displays, handle batch operations, etc. Backbone collections are, in fact, a great compliment to Drupal views.
Backbone views should not be confused with Drupal views. Backbone.View objects serve two functions: on the one hand they manage the rendering of all model and collection data, transforming our JS objects into the HTML that makes the user interface. They also are responsible for binding and handling user interaction events, such as clicking a link or dragging an object. As such they are similar to MVC routers and controllers, managing what code gets executed in response to various inputs. Generally, Backbone apps have a number of lightweight views for rendering model data, and one "Application" view that handles much of the event delegation.
Just remember Backbone collections correspond to Drupal views, Backbone views are totally different from Drupal views.
All of these objects are meant to be easily extended. Making a database of backcountry waypoints? You'll probably create a Waypoint object using Waypoint = Backbone.Model.extend({...})
and a TrailWaypoints collection via TrailWaypoints = Backbone.Collection.extend({ model: Waypoint, ...})
(2/24: Updated to fix the syntax error Tim Branyen spotted, thanks!)
Drupal.Backbone
The Backbone module for Drupal uses this inheritence pattern to create a number of child prototypes that take care of most of the Drupal-specific parts of building a Backbone app in Drupal. It provides three main parent objects extending the core Backbone ones: Drupal.Backbone.Model, Drupal.Backbone.Collection and Drupal.Backbone.View, and a number of children of those objects with specific functionality for integrating with nodes, views, search results, etc. You can check out the annotated source code to see everything in detail, but here's a quick overview:
Drupal.Backbone.Model overrides Backbones default url scheme to use the REST endpoint provided by the Services module. The easiest way to get this working is to use the Backbone Base feature bundled with the Backbone module, which sets up an endpoint at the default "backbone/rest" path. If you want to use a different endpoint for some reason, you can change this on the configuration page for the Backbone module.
Drupal.Backbone.Collection also adapts Backbone's default expected path scheme to work with Drupal, while additionally providing a layer to manage the parameters for collection load requests, tracking the state of collections that hold results for specific search results or views arguments, for instance.
While Backbone.View is agnostic in terms of templating library, Drupal.Backbone.View sets up a number of default rendering settings. It provides functions for automatically extracting and compiling Underscore.js templates from the code, as well as rendering them. Using a standard method for managing client-side templating allows us to do something really cool: integrate Drupal's theme system with Backbone's templates. On the server-side, modules building on the Backbone module use theme functions to generate all client templates, and "backbone_add_template" to include those templates in the page in a standard way. This allows themers to change the look and feel of Backbone applications without needing modify the Backbone App's JavaScript at all...something Backbone can't do on its own.
The current version of the Backbone module also includes a few more specific objects: Drupal.Backbone.NodeModel takes care of loading and saving nodes easily via Backbone, Drpual.Backbone.NodeViewCollection handles loading and managing of Views, via the Services Views module, and Drupal.Backbone.NodeIndexCollection loads views via the Service modules "index" listing of Nodes, a simple listing provider that functions independent of views. We plan to release many more of these in the upcoming weeks, to cover the array of [objects provided by the current Services API][drupanium].
It's worth noting that the Backbone module also has pretty full test coverage via the Drupal QUnit module. The testing code includes some tricks for testing asynchronous functions which we'll cover is a later post, but you can check it out here.
A Quick Example
The Backbone module also includes a short example Backbone app to demonstrate some of these functions. You can see the example module code and example js online to get the full picture, but here's a quick rundown. The core of our application, is the following, super-short page callback function:
function backbone_example_admin_page() {
// Add templates to page.
$nodetpl = theme('backbone_example_node_template');
backbone_add_template('backbone-example-node-template', $nodetpl);
$apptpl = theme('backbone_example_app_template');
backbone_add_template('backbone-example-app-template', $apptpl);
// Add app js.
drupal_add_js(drupal_get_path('module', 'backbone_example') .
'/js/backbone_example_app.js');
// Return a container div for the app to anchor itself to.
return array(
'#type' => 'html_tag',
'#tag' => 'div',
'#attributes' => array(
'id' => 'backbone-example-app',
),
);
}
This function is all we need to take care of setting up our entire app. It attaches two templates to the page via the backbone_add_template function (managing both templates through the theme), adds our applications js file to the page, and returns just a single div tag, which our app will use to attach itself to the page. The other functions in the module are mainly responsible for providing a menu callback, a hook_theme implementation, etc. to our page.
Similarly, the application's js has a fairly compact main function:
initialize: function() {
Drupal.Backbone.View.prototype.initialize.apply(this);
_.bindAll(this, 'doLoadNode');
this.nodeModel = new Drupal.Backbone.NodeModel();
this.nodeView = new NodeView({model: this.nodeModel});
$('#backbone-example-app').append(this.render().el);
this.$('#backbone-example-node-container').append(this.nodeView.render().el);
}
This code executes the Drupal.Backbone.View object's parent constructor to compile the required templates, etc., then creates a model and attendant view for the data we'll receive from the server. Once that's done, the application view renders itself and attaches itself to the div we made for it on the page. There are a few other lines of code specifying the other views and objects used, but all in all we're able to create a JS app that loads and renders arbitrary nodes from the server via JSON in about 40 lines of code. Pretty nifty, eh?
So, that's why I'm excited. We've got a lot planned for this module, and you can see more on our project page. While I certainly wouldn't recommend using it in production yet, I do hope you'll download the module, give it a whirl, and help out with testing, development and feedback, and building some awesome showcases so we can turn the Drupal UI world upside down!
This module is a collaboration between our efforts here at EchoDitto and the work of Cormac McGuire and some of the folks at Studio Rua. Cormac and I created parallel modules at almost the same time, and we are currently working to merge the Backbone API project's code into the Backbone module.Update Feb. 26: the module has been updated with support for user and comment CRUD...more to come!