% -*- Mode: noweb; noweb-code-mode: javascript-mode ; noweb-doc-mode: latex-mode -*- \documentclass{article} \usepackage{noweb} \usepackage[T1]{fontenc} \usepackage{hyperref} \begin{document} % Generate code and documentation with: % % noweave -filter l2h -delay -x -html backbonestore.nw | htmltoc > backbonestore.html % notangle -Rstore.js backbonestore.nw > store.js % notangle -Rindex.html backbonestore.nw > index.html \section{Introduction} \nwanchorto{http://documentcloud.github.com/backbone/}{Backbone.js} is a popular Model-View-Controller (MVC) library that provides a framework by which models generate events and views reflect those events. The models represent data and ways in which that data can be chnaged. The nifty features of backbone are (1) its event-driven architecture, which separate a complex, working model of \textbf{objects} and their relationships, and the way those things and their relationships are presented to the viewer, and (2) its router, which allows developers to create bookmark-ready URLs for specialized views. Backbone also provides a Sync library which will RESTfully shuttle objects back and forth between the browser and the client. There are a number of good tutorials for Backbone (See: \nwanchorto{http://www.plexical.com/blog/2010/11/18/backbone-js-tutorial/}{Meta Cloud}, \nwanchorto{http://andyet.net/blog/2010/oct/29/building-a-single-page-app-with-backbonejs-undersc/?utm_source=twitterfeed&utm_medium=twitter}{\&Yet's Tutorial}, \nwanchorto{http://bennolan.com/2010/11/24/backbone-jquery-demo.html}{Backbone Mobile} (which is written in \nwanchorto{http://jashkenas.github.com/coffee-script/}{Coffee}), and \nwanchorto{http://joshbohde.com/2010/11/25/backbonejs-and-django/}{Backbone and Django}. However, a couple of months ago I was attempting to learn Sammy.js, a library very similar to Backbone, and they had a nifty tutorial called \nwanchorto{http://code.quirkey.com/sammy/tutorials/json_store_part1.html}{The JsonStore}. In the spirit of The JSON Store, I present The Backbone Store. \subsection{Literate Program} A note: this article was written with the \nwanchorto{http://en.wikipedia.org/wiki/Literate_programming}{Literate Programming} toolkit \nwanchorto{http://www.cs.tufts.edu/~nr/noweb/}{Noweb}. Where you see something that looks like \textless \textless this \textgreater \textgreater, it's a placeholder for code described elsewhere in the document. Placeholders with an equal sign at the end of them indicate the place where that code is defined. The link (U-\textgreater) indicates that the code you're seeing is used later in the document, and (\textless-U) indicates it was used earlier but is being defined here. \subsection{Revision} This is version 2.0 of \textit{The Backbone Store}. It includes changes to the store based upon a better understanding of what Backbone.js can do. This version uses jQuery 1.6.2 and Backbone 0.5.2. \subsection{The Store} The store has three features: A list of products, a product detail page, and a ``shopping cart'' that does nothing but tally up the number of products total that you might wish to order. The main viewport flips between a list of products and a product detail; the shopping cart quantity tally is always visible. We will be creating a store for music albums. There will be: (1) The catalog of products, (2) A detail page for a specific product from the catalog, (3) A ``checkout page'' where users can add/change/delete items from their shopping cart, and (4) a shopping cart ``widget'' that is visible on every page, and allows the user to see how many items are in the cart and how much money those items cost. This is taken, more or less, straight from The JSON Store. We will be getting our data from a simplified JSON file that comes in the download; it contains six record albums that the store sells. (Unlike the JSON store, these albums do not exist; the covers were generated during a round of \nwanchorto{http://elfs.livejournal.com/756709.html}{The Album Cover Game}, a meme one popular with graphic designers.) Under the covers, we have two essential objects: a \textbf{Product} that we're selling, and a shopping cart \textbf{Item} into which we put a reference to a Product and a count of the number of that product that we're selling. In the Backbone idiom, we will be callling the cart an \textbf{ItemCollection} that the user wants to buy, and the Products will be kept in a \textbf{ProductCollection} In backbone's parlance, Product and Item are \textbf{Models}, and Cart and Catalog are \textbf{Collections}. The idiom is that models are named for what they represent, and collections are model names suffixed with the word ``collection.'' The pages ``catalog,'' ``product detail,'' and ``checkout'' are \textbf{Routable Views}, while the shopping cart widget is just a \textbf{View}. There's no programmatic difference internally between the two kinds of views; instead, the difference is in how they're accessed. \subsection{Models} The first version of this tutorial concentrated on the HTML. In this version, we're going to start logically, with the models. The first model is \textbf{Product}, that is, the thing we're selling in our store. We will create Products by inheriting from Backbone's \textbf{Model}. Backbone models use the methods [[get()]] and [[set()]] to access the attributes of the model. When you want to change a model's attribute, you must do so through those methods. Any other object that has even a fleeting reference to the model can then subscribe to the \textbf{change} event on that model, and whenever [[set()]] is called, those other objects can react in some way. This is one of the most important features of Backbone, and you'll see why shortly. Because a Backbone model maintains its attributes as a javascript object, it is schema-free. So the Product model is ridiculously simple: <>= var Product = Backbone.Model.extend({}) @ And we said before, the products are kept in a catalog. Backbone's ``list of models'' feature is called a \textbf{Collection}, and to stay in Backbone's idioms, rather than call it ``Catalog'', we'll call it a \textbf{ProductCollection}: <>= var ProductCollection = Backbone.Collection.extend({ model: Product, comparator: function(item) { return item.get('title'); } }); @ Collections have a reference to the Product constructor; if you call [[Collection.add()]] with a JSON object, it will use that constructor to create the associated Backbone model object. The other novel thing here is the comparator; Backbone uses it define the default ordering for the collection. If not defined, calling [[sort()]] on the collection raises an exception. Shopping carts have always seemed a bit strange to me, because each item isn't a one-to-one with a product, but a reference to the product and a quantity. For our (simple) purpose, I'm just going to have an item that you can add amounts to, that get stored as a 'quantity'. <>= var Item = Backbone.Model.extend({ update: function(amount) { this.set({'quantity': this.get('quantity') + amount}); } }); @ The other feature is that, for the collection, I will want to find the CartItem not by its ID, but by the product it contains, and I want the Cart to be able to host any product, even it it has none of those, So I have added the method [[getOrCreateItemForProduct]]. The [[detect()]] and [[reduce()]] methods ares provided by Backbone's one major dependency, a wonderful utility library called \texttt{Underscore}. [[detect()]] returns the first object for which the anonymous function return [[true]]. The [[reduce()]] functions take an intitial value and a means of calculating a per-object value, and reduce all that to a final value for all objects in the collection. <>= var ItemCollection = Backbone.Collection.extend({ model: Item, getOrCreateItemForProduct: function(product) { var i, pid = product.get('id'), o = this.detect(function(obj) { return (obj.get('product').get('id') == pid); }); if (o) { return o; } i = new Item({'product': product, 'quantity': 0}) this.add(i, {silent: true}) return i; }, getTotalCount: function() { return this.reduce(function(memo, obj) { return obj.get('quantity') + memo; }, 0); } getTotalCost: function() { return this.reduce(function(memo, obj) { return (obj.get('product').get('price') * obj.get('quantity')) + memo; }, 0); } }); @ \subsection {Views} Now that we have the structure for our catalog and our shopping cart laid out, let's show you how those are organized visually. I'd like to say that it's possible to completely separate View and their descriptions of how to interact with the DOM with DOM development, but we must have some preliminary plans for dealing with the display. The plan is to have a one-page display for everything. We will have an area of the screen allocated for our major, routable views (the product list display, the product detail display, and the checkout display), and a small area of the screen allocated for our shopping cart. Let's put the shopping cart link in the upper-right-hand corner; everybody does. As an additional feature, we want the views to transition elegantly, using the jQuery [[fadeIn()]] and [[fadeOut()]] animations. Backbone Views are simple policy objects. They often have a root element, the contents of which they manipulate, a model or collection they represent within that root element, events that may occur within that root element that they monitor and consequently act on. Views are not rigid; it's just Javascript and the DOM, and you can hook external events as needed. (This can be useful, for example, when doing drag-and-drop with jQueryUI to highlight valid drop zones.) More importantly, it is sensitive to events \textit{within its model or collection}, and can respond to changes automatically, without having to manually invoke the view. A Backbone view can be either an existing DOM element, or it can generate one automatically at (client-side) run time. In the previous version of the tutorial, I used existing DOM elements, but for this one, almost everything will be generated at run time. To achieve the animations and enforce consistency, we're going to engage in classic object-oriented programming. We're going to create a base class that contains knowledge about the main area into which all views are rendered, and that manages these transitions. With this technique, you can do lots of navigation-related tricks: you can highlight where the user is in breadcrumb-style navigation; you can change the class and highlight an entry on a nav bar; you can add and remove tabs from the top of the viewport as needed. <>= var _BaseView = Backbone.View.extend({ parent: '#main', className: 'viewport', @ The above says that we're creating a class called \texttt{BaseView} and defining two fields. The first, 'parent', will be used by all child views to identify in which DOM object the view will be rendered. The second defines a common class we will use for the purpose of identifying these views to jQuery. Backbone automatically creates a new [[DIV]] object with the class 'viewport' when a view constructor is called. It will be our job to attach that [[DIV]] to the DOM. <>= initialize: function() { this.el = $(this.el); //$ this.el.hide(); this.parent.append(this.el); return this. }, @ The method above ensures that the element is rendered, but not visible, and contained within the [[DIV#main]]. Note also that the element is not a sacrosanct object; the Backbone.View is more a collection of standards than a mechanism of enforcement, and so defining it from a raw DOM object to a jQuery object will not break anything. Next, we will define the hide and show functions: <>= hide: function() { if (this.el.is(":visible") === false) { return null; } promise = $.Deferred(function(dfd) { //$ this.el.fadeOut('fast', dfd.resolve) }).promise(); this.trigger('hide', this); return promise; }, show: function() { if (this.el.is(':visible')) { return; } promise = $.Deferred(function(dfd) { //$ this.el.fadeIn('fast', dfd.resolve) }).promise(); this.trigger('show', this); return promise; } @ \textbf{Deferred} is a new feature of jQuery. It is a different mechanism for invoking callbacks by attaching attributes and behavior to the callback function. By using this, we can say thing like ``\textit{When} everything is hidden (when every deferred returned from \textbf{hide} has been resolved), \textit{then} show the appropriate viewport.'' Deferreds are incredibly powerful, and this is a small taste of what can be done with them. Before we move on, let's take a look at the HTML we're going to use for our one-page application: <>= The Backbone Store <> <> <>
@ It's not much to look at, but already you can see where that [[DIV#main]] goes, as well as where we are putting our templates. The [[DIV#main]] will host a number of viewports, only one of which will be visible at any given time. Our first view is going to be the product list view, named, well, guess. Or just look down a few lines. This gives us a chance to discuss one of the big confusions new Backbone users frequently have: \textit{What is \texttt{render()} for?}. Render is not there to show or hide the view. \texttt{Render()} is there to \textit{change the view when the underlying data changes}. It renders the data into a view. In our functionality, we use the parent class's \texttt{show()} and \texttt{hide()} methods to actually show the view. <>= var ProductListView = _BaseView.extend({ id: 'productlistview', indexTemplate: $("#store_index_template").template(), //$ render: function() { self.el.html(_.template(this.template, {'products': this.model.toJSON()})) return this; } }); @ That \texttt{\_.template()} method is provided by undescore.js, and is a fairly powerful templating method. It's not the fastest or the most feature-complete, but it is more than adequate for our purposes and it means we don't have to import another library. It vaguely resembles ERB from Rails, so if you are familiar with that, you should understand this fairly easily. And here is the HTML: <>= @ %$ One of the most complicated objects in our ecosystem is the product view. It actually does something! The prefix ought to be familiar, but note that we are again using [[#main]] as our target; we will be showing and hiding the various [[DIV]] objects in [[#main]] again and again. The only trickiness here is twofold: the (rather hideous) means by which one calls the method of a parnt class from a child class via Backbone's class heirarchy (this is most definitely \textbf{not} Javascript standard), and keeping track of the itemcollection object, so we can add and change items as needed. <>= var ProductListView = _BaseView.extend({ id: 'productlistview', indexTemplate: $("#store_item_template").template(), //$ initialize: function(options) { this.constructor.__super__.initialize.apply(this, [options]) this.itemcollection = options.itemcollection; return this; }, @ We want to update the cart as needed. Remember the way Backbone is supposed to work: when we update the cart, it will send out a signal automatically, and subscribers (in this case, that little widget in the upper right hand corner we mentioned earlier) will show the changes. These are the events in which we're interested: keypresses and clicks on the update button and the quantity form. (Okay, ``UQ'' isn't the best for ``update quantity''. I admit that.) Note the peculiar syntax of ``EVENT SELECTOR'': ``methodByName'' for each event. Backbone tells us that the only events it can track by itself are those that jQuery's ``delegate'' understands. As of 1.5, that seems to be just about all of them. <>= events: { "keypress .uqf" : "updateOnEnter", "click .uq" : "update", }, @ And now we will deal with the update. This code ought to be fairly readable: the only specialness is that it's receiving an event, and we're ``silencing'' the call to [[cart.add()]], which means that the cart collection will not publish any events. There are only events when the item has more than zero, and that gets called on [[cart_item.update()]]. In the original tutorial, this code had a lot of responsibility for managing the shopping cart, looking into it and seeing if it had an item for this product, and there was lots of accessing the model to get its id and so forth. All of that has been put into the shopping cart model, which is where it belongs: \textit{knowledge about items and each item's relationship to its collection belongs in the collection}. %' <>= update: function(e) { e.preventDefault(); var item = this.itemcollection.getOrCreateItemProduct(this.model); item.update(parseInt($('.uqf').val())); }, updateOnEnter: function(e) { if (e.keyCode == 13) { return this.update(e); } }, @ %$ So, let's talk about that shopping cart thing. We've been making the point that when it changes, automatically you should see just how many \section{The Program} And here's the skeleton of the program we're going to be writing: <>= (function() { <> <> <> <> <> <> <> }).call(this); @ \section{Views} Backbone Views are simple policy objects. They often have a root element, the contents of which they manipulate, a model or collection they represent within that root element, events that may occur within that root element that they monitor and consequently act on. Views are not rigid; it's just Javascript and the DOM, and you can hook external events as needed. (This can be useful, for example, when doing drag-and-drop with jQueryUI to highlight valid drop zones.) More importantly, it is sensitive to events \textit{within its model or collection}, and can respond to changes automatically, without having to manually invoke the view. There are three views here: the CartView, the ProductListView, and a single ProductView. The [[CartView]] lives in the upper-right-hand corner of our screen, and just shows the quantity of items in our shopping cart. It has a default [[el]], where it will draw its quantity. This view illustrates the binding to its collection: whenever the collection is updated in some way, the [[CartView]] automagically updates itself. The programmer is now relieved of any responsibility of remembering to update the view, which is a huge win. The [[\_.bind()]] method associates the [[render]] with the instance of the [[CartView]]. The [[render()]] method is the conventional name for rendering the elements. Nothing in Backbone calls [[render()]] directly; it's up to the developer to decide how and when an object should should be rendered. This also illustrates the use of jQuery animations in Backbone. <>= var CartView = Backbone.View.extend({ el: $('.cart-info'), initialize: function() { this.collection.bind('change', _.bind(this.render, this)); }, render: function() { var sum = this.collection.reduce(function(m, n) { return m + n.get('quantity'); }, 0); this.el .find('.cart-items').text(sum).end() .animate({paddingTop: '30px'}) .animate({paddingTop: '10px'}); } }); @ %$ The [[ProductListView]] again has a root element, this time the [[#main]] DIV of our HTML, into which we're going to draw a jQuery template list of our record albums. The only tricks here are the compilation of the jQuery template when the View is instantiated, and the use of an enclosured (is that a word?) [[self]] variable to provide a hard context for the [[this]] variable within inner jQuery calls. <>= var ProductListView = Backbone.View.extend({ el: $('#main'), indexTemplate: $("#indexTmpl").template(), render: function() { var self = this; this.el.fadeOut('fast', function() { self.el.html($.tmpl(self.indexTemplate, self.model.toJSON())); self.el.fadeIn('fast'); }); return this; } }); @ The view uses a jQuery template. This is a simple, repeatable template that jQuery.Template, upon encountering an array, repeats until the array is exhausted. Note the presence of [[\${cid}]]. <>= @ %$ The most complicated object . <>= var ProductView = Backbone.View.extend({ el: $('#main'), itemTemplate: $("#itemTmpl").template(), initialize: function(options) { this.cart = options.cart; return this; }, @ We want to update the cart as needed. Remember that when we update the cart item, the CartView will be notified automagically. Later, I'll show how when we initialize and route to a product view, we pass in the model associated with it. This code ought to be fairly readable: the only specialness is that it's receiving an event, and we're ``silencing'' the call to [[cart.add()]], which means that the cart collection will not publish any events. There are only events when the item has more than zero, and that gets called on [[cart_item.update()]]. <>= update: function(e) { e.preventDefault(); var cart_item = this.cart.getByProductId(this.model.cid); if (_.isUndefined(cart_item)) { cart_item = new CartItem({product: this.model, quantity: 0}); this.cart.add(cart_item, {silent: true}); } cart_item.update(parseInt($('.uqf').val())); }, updateOnEnter: function(e) { if (e.keyCode == 13) { return this.update(e); } }, @ %$ These are the events in which we're interested: keypresses and clicks on the update button and the quantity form. (Okay, ``UQ'' isn't the best for ``update quantity''. I admit that.) Note the peculiar syntax of ``EVENT SELECTOR'': ``methodByName'' for each event. Backbone tells us that the only events it can track by itself are those that jQuery's ``delegate'' understands. As of 1.5, that seems to be just about all of them. One thing that I was not aware of until recently: if you remove and replace the [[el]] object during the lifespan of your view (including in [[initialize()]]), you must then call [[delegateEvents()]] again on the new object for these events to work. <>= events: { "keypress .uqf" : "updateOnEnter", "click .uq" : "update", }, @ And finally the render. There is no rocket science here. You've seen this before. %' <>= render: function() { var self = this; this.el.fadeOut('fast', function() { self.el.html($.tmpl(self.itemTemplate, self.model.toJSON())); self.el.fadeIn('fast'); }); return this; } }); @ The template for a ProductView is straightforward. It contains the form with the [[uq]] objects, the actions of which we intercept and operate on internally. Backbone does this automatically using jQuery's [[delegate]] method. <>= @ %' \section{The Router} The router is a fairly straightforward component. It's purpose is to pay attention to the ``\#hash'' portion of your URL and, when it changes, do something. Anything, really. [[Backbone.History]] is the event listener for the hash, so it has to be activated after the application. In many ways, a Backbone ``Controller'' is just a big View with authority over the entire Viewport. To begin with, I'm going to keep track of the ``three'' views I care about: the CartView, the ProductListView, and the ProductView. I'm going to cheat by attaching the ProductViews to their individual products, and invoke that view as necessary. <>= var BackboneStore = Backbone.Controller.extend({ _index: null, _products: null, _cart :null, @ %$ There are only two routes: home, and item: <>= routes: { "": "index", "item/:id": "item", }, @ Here's where things get interesting. There are two schools of thought over the Controller; one, that the Controller ought to be able to get all the data it needs, and two, that the Controller ought to begin with enough data to do the job sensibly. I fall into the second camp. I'm going to pass in to the [[initialize()]] method an array of objects representing all the products in the system. <>= initialize: function(data) { this._cart = new Cart(); new CartView({collection: this._cart}); this._products = new ProductCollection(data); this._index = new ProductListView({model: this._products}); return this; }, @ When we're routed to the [[index]] method, all we need to do is render the index: <>= index: function() { this._index.render(); }, @ When we are routed to a product, we need to find that product, get its view if it has one or create one if it doesn't, then call render: <>= item: function(id) { var product = this._products.getByCid(id); if (_.isUndefined(product._view)) { product._view = new ProductView({model: product, cart: this._cart}); } product._view.render(); } }); @ And that's the entirety of the application. \section{Initialization} Initialization for most single-page applications happens when the DOM is ready. So I'll do exactly that. This should be obvious, except what the Hell is that when/then construct? That's a new feature of jQuery 1.5 called Deferreds (also known as Promises or Futures). All jQuery 1.5 ajax calls are Deferreds that return data when you dereference them; [[when()]] is an instruction to wait until the ajax call is done, and [[then()]] is a chained instruction on what to do next. This is a trivial example, but when you have multiple streams of data coming in (say, you're loading independent schemas, or you have multiple, orthagonal data sets in your application, each with their own URL as per the Richardson Maturity Model), you can pass the array of ajax objects to [[when()]] and [[then()]] won't fire until they're all done. Automagic synchronization is a miracle. <>= $(document).ready(function() { var fetch_items = function() { return $.ajax({ url: 'data/items.json', data: {}, contentType: "application/json; charset=utf-8", dataType: "json" }); }; $.when(fetch_items()).then(function(data) { new BackboneStore(data); Backbone.history.start(); }); }); @ And that's it. Put it all together, and you've got yourself a working Backbone Store. This code is available at my github at \nwanchorto{https://github.com/elfsternberg/The-Backbone-Store}{The Backbone Store}. \end{document}