08Dec

Backbone.js: Introducing The Backbone Store!

Posted by Elf Sternberg as javascript, programming

Note: This tutorial is out of date! There are now two updates: A Javascript, HTML, and CSS Version, and a Coffeescript, HAML, and Stylus Version.

Introduction

I’ve been playing with Backbone.js, a small but nifty Javascript library that provides a small Model-View-Controller framework where Models can generate events that trigger View changes, and vice versa, along with a Collections models so groups of models can cause view-level events, and a Sync library that provides a basic REST architecture for propagating client-made changes back to the server.

There are a number of good tutorials for Backbone, such as: Meta Cloud, &Yet’s Tutorial, Backbone Mobile (which is written in Coffee), and 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 The JsonStore.

In the spirit of The JSON Store, I present The Backbone Store.

Literate Program

A note: this article was written with the Literate Programming toolkit Noweb. Where you see something that looks like <this>, it’s a placeholder for code defined 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->) indicates that the code you’re seeing is defined here but used later in the document, and (<-U) indicates it was used earlier but is being defined here.

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.

Let’s start by showing you the HTML that we’re going to be exploiting. As you can see, the shopping cart’s primary display is already present, with zero items showing. DOM ID “main” is empty. We’ll fill it with templated data later.

HTML

<index.html>=
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />

        <title>The Backbone Store</title>
        <link rel="stylesheet" href="jsonstore.css" type="text/css" media="screen" charset="utf-8" />

        <product list template>

        <product template>

    </head>
    <body>
        <div id="container">
            <div id="header">
                <h1>
                    The Backbone Store
                </h1>

                <div class="cart-info">
                    My Cart (<span class="cart-items">0</span> items)
                </div>
            </div>

            <div id="main">
            </div>
        </div>
        <script src="jquery-1.4.4.min.js" type="text/javascript"></script>
        <script src="jquery.tmpl.min.js" type="text/javascript"></script>
        <script src="underscore.js" type="text/javascript"></script>
        <script src="backbone.js" type="text/javascript"></script>
        <script src="store.js" type="text/javascript"></script>
    </body>
</html>

This is taken, more or less, straight from The JSON Store. I’ve included one extra thing, aside from jQuery and Backbone, and that’s the jQuery Templates kit. There is also a simplified JSON file that comes in the download; it contains six record albums that the store sells. (Unlike the JSON store, these albums don’t exist; the covers were generated during a round of The Album Cover Game.)

The Program

And here’s the skeleton of the program we’re going to be writing:

<store.js>=
<product models>

<product list view>

<shopping cart models>

<shopping cart view>

<product view>

<application>

Products and Product List View

To start, I have a list of products. The basic product is just a model, with nothing to show for it; the list of products is a Backbone.Collection, with one feature, the comparator, which sorts the albums in order by album title.

<product models>= (<-U)
var Product = Backbone.Model.extend({});

var ProductCollection = Backbone.Collection.extend({
    model: Product,
    comparator: function(item) {
        return item.get('title');
    }
});

The ProductCollection is what we want to show when the user isn’t looking at a specific product. I create a Backbone.View object class ProductListView:

<product list view>= (<-U)
var ProductListView = Backbone.View.extend({
    el: $('#main'),
    indexTemplate: $("#indexTmpl").template(),

    render: function() {
        var sg = this;
        this.el.fadeOut('fast', function() {
            sg.el.empty();
            $.tmpl(sg.indexTemplate, sg.model.toArray()).appendTo(sg.el);
            sg.el.fadeIn('fast');
        });
        return this;
    }

});

Here, we’ve told this view that it’s principle element is the DOM ID “main”, allocated an indexTemplate using the jQuery template complire, and created a render function that fades out “main”, replaces its content with a rendered template, and fades it back in.

The template looks like this:

<product list template>= (<-U)
        <script id="indexTmpl" type="text/x-jquery-tmpl">
        <div class="item">
         <div class="item-image">
             <a href="#item/${cid}"><img src="${attributes.image}" alt="${attributes.title}" /></a>
         </div>
         <div class="item-artist">${attributes.artist}</div>
            <div class="item-title">${attributes.title}</div>
            <div class="item-price">$${attributes.price}</div>
        </div>
        </script>

There’s some Demeter violations going on here, in that I have to know about the attributes of a Backbone model, something that’s normally hidden within the class. But this is good enough for our purposes. The above is a jQuery template, and the \$\{\} syntax is what’s used to dereference variables within a template.

(As an aside, I think that the set and get methods of Backbone.Model are a poor access mechanism. I understand why they’re there, and I can only hope that someday Javascript Getter and Setters become so well-established as to make set and get irrelevant.)

The Shopping Cart

Before I move on to the product view, I want to go over the shopping cart.

A little rocket science here: A Cart contains CartItems. Each “item” represents a quantity of a Product. (I know, that always struck me as odd, but that’s how most online stores do it.) CartItem has an update method that allows you to add more (but not remove any– hey, the Sammy store wasn’t any smarter, and this is For Demonstration Purposes Only), and we use the set method to make sure that a “change” event is triggered.

The Cart, in turn, has a method, getByPid (“Product ID”), which is meant to assist other objects in finding the CartItem associated with a specific product. Here, I’m just using the Backbone default client id. Once we’ve found that item, we can just call update() on it.

<shopping cart models>= (<-U)
var CartItem = Backbone.Model.extend({
    update: function(amount) {
        this.set({'quantity': this.get('quantity') + amount});
    }
});

var Cart = Backbone.Collection.extend({
    model: CartItem,
    getByPid: function(pid) {
        return this.detect(function(obj) { return (obj.get('product').cid == pid); });
    },
});

The cart is represented by a little tag in the upper right-hand corner of the view; it never goes away, and its count is always the total number of Products (not CartItems) ordered. So the CartView needs to update whenever a CartItem is added or updated. And we want a nifty little animation to go with it:

<shopping cart view>= (<-U)
var CartView = Backbone.View.extend({
    el: $('.cart-info'),

    initialize: function() {
        this.model.bind('change', _.bind(this.render, this));
    },

    render: function() {
        var sum = this.model.reduce(function(m, n) { return m + n.get('quantity'); }, 0);
        this.el
            .find('.cart-items').text(sum).end()
            .animate({paddingTop: '30px'})
            .animate({paddingTop: '10px'});
    }
});

A couple of things here: the render is rebound to this to make sure it renders in the context of the view. I found that that was not always happening. Note the use of reduce, a nifty method from underscore.js that allows you to build a result out an array using an anonymous function. This reduce, obviously, sums up the total quantity of items in the cart. Also, jQuery enthusiasts could learn (I certainly did!) from the .find() and .end() methods, which push a child object onto the stack to be modified, and then pop it off after the operation has been applied.

One of the big things this illustrates is that a Backbone.View is not a full-page event; it’s a mini-application for drawing its own little universe, that may be part of a larger universe. It’s entirely possible to have lots of Views on a page.

Also, this cart does not have a template associated with it: we’re changing a single textual item on the page and animating another one that is always present.

The Product Detail View

So now we’re down to the ProductView. This is slightly more complicated. First, let me show you a skeleton of the view, similar to the one we saw for the ProductListView:

<product view>= (<-U)
var ProductView = Backbone.View.extend({
    el: $('#main'),
    itemTemplate: $("#itemTmpl").template(),

<product events>

<product view initialization>

<update product>

<render product>
});

The reason the ProductView is complex is because it’s going to interact with the shopping cart. We need to keep track of the cart. There are two ways of dealing with this: Have the ProductView track down its cart item every time, or keep a reference to an individual track item having found it once. I’m going with the first option.

<product view initialization>= (<-U)
    initialize: function(options) {
        this.cart = options.cart;
    },

Rendering is exactly the same as that for the ProductListView. In fact, it’s so similar, I’m thinking maybe I should have made this an abstract function and mixed it in afteward:

<render product>= (<-U)
    render: function() {
        var sg = this;
        this.el.fadeOut('fast', function() {
            sg.el.empty();
            $.tmpl(sg.itemTemplate, sg.model).appendTo(sg.el);
            sg.el.fadeIn('fast');
        });
        return this;
    }

The template for a ProductView, however, has some interesting qualities:

<product template>= (<-U)
        <script id="itemTmpl" type="text/x-jquery-tmpl">
        <div class="item-detail">
          <div class="item-image"><img src="${attributes.large_image}" alt="${attributes.title}" /></div>
          <div class="item-info">
            <div class="item-artist">${attributes.artist}</div>
            <div class="item-title">${attributes.title}</div>
            <div class="item-price">$${attributes.price}</div>
            <div class="item-form">
              <form action="#/cart" method="post">
                <input type="hidden" name="item_id" value="${cid}" />
                <p>
                  <label>Quantity:</label>
                  <input type="text" size="2" name="quantity" value="1" class="uqf" />
                </p>
                <p><input type="submit" value="Add to Cart" class="uq" /></p>
              </form>
            </div>
            <div class="item-link"><a href="${attributes.url}">Buy this item on Amazon</a></div>
            <div class="back-link"><a href="#">&laquo; Back to Items</a></div>
          </div>
        </div>
        </script>

Note the octothorpe used as the target link for “Home”. I kept thinking an empty link or just “/” would be appropriate, but no, it’s an octothorpe.

Also note that it has a form. (Again, note the Demeter violations.) What we want is to update the shopping cart whenever the user enters a number into the input box and either presses “Add To Cart” or the ENTER button. That gives us our methods: We’re in a view for a specific product; we must see if the customer has a CartItem for that product in the Cart, and add or update it as needed. Like so:

<update product>= (<-U)
    update: function(e) {
        e.preventDefault();
        var cart_item = this.cart.getByPid(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);
        }
    },

But how to do these events get triggered? Go back to the ProductView skeleton above; there’s a placeholder for “product events”, which looks like this:

<product events>= (<-U)
    events: {
        "keypress .uqf" : "updateOnEnter",
        "click .uq"     : "update",
    },

Backbone uses a curious definition of an event with an “event selector”, followed by a target method of the View class. Backbone is also limited about what events can be used here, as the following events cannot be wrapped by jQuery’s delegate method and do not work: “focus”, “blur”, “change”, “submit”, and “reset”.

We preventDefault to keep the traditional meaning of the submit button from triggering. When the CartItem is updated, it triggers a “change” event, and the CartView will update itself automatically. I added the “silent” option to keep the “change” event from triggering twice when adding a new CartItem to the Cart.

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.

Here’s the skeleton of our router, along with its instantiation:

<application>= (<-U)
var Workspace = Backbone.Controller.extend({
<application variables>

<routes>

<initialization>

<index render call>

<product render call>
});

workspace = new Workspace();
Backbone.history.start();

There are two routes that we want to present: the index (our list of products) and the item (a product detail). So, using Backbone.Controller, we’re going to route the following:

<routes>= (<-U)
    routes: {
        "": "index",
        "item/:id": "item",
    },

There are a few things I want to track: the index view, the individual product views, and the shopping cart.

<application variables>= (<-U)
    _index: null,
    _products: null,
    _cart :null,

Now, we can render the index view:

<index render call>= (<-U)
    index: function() {
        this._index.render();
    },

There are two things left in our workspace, that we haven’t defined. The intialization, and the product render.

Initialization consists of getting our product list, creating a shopping cart to hold “desired” products (and in quantity!), and creating the product list view.

<initialization>= (<-U)
    initialize: function() {
        var ws = this;
        if (this._index === null) {
            $.ajax({
                url: 'data/items.json',
                dataType: 'json',
                data: {},
                success: function(data) {
                    ws._cart = new Cart();
                    new CartView({model: ws._cart});
                    ws._products = new ProductCollection(data);
                    ws._index = new ProductListView({model: ws._products});
                    Backbone.history.loadUrl();
                }
            });
            return this;
        }
        return this;
    },

Here, I load the data, and upon success create a new ProductCollection from the data, a new shopping cart, and a new ProductListView for the product collection. I then call Backbone.history.loadUrl(), which then routes us to the correct view.

Thanks to this, users can bookmark places in your site other than the home page. Yes, the bookmark will be funny and have at least one octothorpe in it, but it will work.

And now I’m down to one last thing. I haven’t defined that product render call in the application controller. The one thing I don’t want to do is have ProductViews for every product, if I don’t need them. So I want to build them as-needed, but keep them, and associate them with the local Product, so they can be recalled whenever we want. The underscore function isUndefined is excellent for this.

<product render call>= (<-U)
    item: function(id) {
        if (_.isUndefined(this._products.getByCid(id)._view)) {
            this._products.getByCid(id)._view = new ProductView({model: this._products.getByCid(id),
                                                                 cart: this._cart});
        }
        this._products.getByCid(id)._view.render();
    }

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 The Backbone Store.

Index of code references:

Postscript:  Someone pointed out to me that using the toJSON() method that Backbone provides for models and collections would provide better results than the Demeter violations I complained about.  The problem with that is that I’m using the CID as my primary key, and toJSON doesn’t include the CID in the JSON object.  I suppose I could override the toJSON method and add it, or write an inline to decorate the JSON product with the CID, but after all that it doesn’t seem worth the effort.  For more canonical work, though, it’s something to keep in mind.

19 Responses to Backbone.js: Introducing The Backbone Store!

Pallavi Kaushik

December 9th, 2010 at 6:41 pm

Loved this tutorial! Thanks!

JavaScript Magazine Blog for JSMag » Blog Archive » News roundup: Crankshaft, WebSockets disabled, 3d Christmas tree

December 10th, 2010 at 10:59 am

[...] Blog DailyJS: Node Tutorial Part 5 High Performance Web Sites :: Evolution of Script Loading Backbone.js: Introducing The Backbone Store! by Elf Sternberg HTML5 Script Execution Changes in Firefox 4 Beta 7 sprite.js: a framework that lets you create [...]

Rafi B.

December 11th, 2010 at 1:08 am

Great tutorial! thank you. Backbone looks great.

Murph

December 16th, 2010 at 8:38 am

Really helps a beginner start to understand how Backbone.js works. Thanks.

One question: doesn’t Underscore already have templating functionality? Is there a specific reason you chose the jQuery templating instead?

Elf Sternberg

December 16th, 2010 at 9:05 am

Familiarity, mostly. I knew the jQuery templating language already and had used it; it’s become part of my regular toolkit. Also, I think it’s more robust, in that it has both list comprehensions and remote invocation capabilities that, while I didn’t use them here, I greatly appreciate.

Marisol Perry

December 21st, 2010 at 7:04 pm

Loved this tutorial! Thanks!

Latoya Bridges

December 22nd, 2010 at 1:46 am

[...] Blog DailyJS: Node Tutorial Part 5 High Performance Web Sites :: Evolution of Script Loading Backbone.js: Introducing The Backbone Store! by Elf Sternberg HTML5 Script Execution Changes in Firefox 4 Beta 7 sprite.js: a framework that lets you create [...]

Building Single Page Applications With jQuery’s Best Friends

February 13th, 2011 at 11:34 pm

[...] [3] The Backbone.js Store (recommended reading. my initial inspiration for writing about Backbone further) http://www.elfsternberg.com/2010/12/08/backbonejs-introducing-backbone-store/ [...]

Mickey

March 20th, 2011 at 2:25 am

Hello! Nice tutorial, thank you.

I had one issue with it, though…

The Workspace would call “index” route before the AJAX request from “initialize” would return and set “_index” with new View. This caused an error “Unable to get value of the property ‘render’: object is null or undefined” when I refreshed the page (or first opened it).

So I added “async: false” to the AJAX call at first.

But then I just wrapped “this._index.render();” in “index” function with “if”:

if (this._index) {
this._index.render();
}

I am still not feeling comfortable with Backbone.js, but your tutorial definitely helped with my progress in it.

Thank You.

Kickass Labs » Blog Archive » Yet Another Backbone.js Tutorial – Part 1 – Backbone.js Philosophy

April 16th, 2011 at 7:18 pm

[...] Elf Sternberg’s backbone tutorial [...]

Ole S

May 3rd, 2011 at 12:42 am

Thanks for the example. I wonder though, every example I find it’s always many views/models and only one (1) controller. I this becourse it’s a limitation in the backbone framework, or why?
I would not be using backbone in some larger webapplications if it is limited to only using one controller per application.

Thomas Davis

May 12th, 2011 at 8:50 pm

I am working on a community based resource for Backbone.js tutorials.

Anyone looking for more beginner tutorials can visit;

http://backbonetutorials.com

Backbone.js: Where can I find a simple tutorial for backbone.js that uses a collection, a router, models and views? - Quora

August 30th, 2011 at 4:22 am

[...] them all work together.   Rui Tang Please checkhttp://duganchen.ca/backbone-js-…http://www.elfsternberg.com/2010…This answer .Please specify the necessary improvements. Edit Link Text Show answer summary [...]

Paul

January 23rd, 2012 at 11:28 pm

The most practical and well written Backbone tutorial i seen so far!! Made me understand the confusing structure of BackboneJS. Thanks.

Elf Sternberg

January 25th, 2012 at 10:17 am

Thanks, but there are some grievous sins in this tutorial, mostly because I attach views to models. While I get away with it, it’s still not very good practice. I recommend you look to The Backbone Store 2.0 tutorial, where you can see the changes I’ve made. If you search the site, there’s also a version of The Backbone Store done entirely in Coffeescript, and a short entry on doing deep namespacing with the Backbone Relational ORM.

Backbone.js: Where can I find a simple tutorial for backbone.js that uses a collection, a router, models and views? - Quora

June 20th, 2012 at 2:37 am

[...] Answers  Rui Tang 1 vote by Leo RomanovskyPlease checkhttp://duganchen.ca/backbone-js-…http://www.elfsternberg.com/2010…Comment Loading… • Post • Aug 30, 2011   Zack Bonebrake, please move [...]

Baby steps to Backbone.js: Exploring collections | Tu Ngoc Man

April 10th, 2013 at 3:34 am

[...] Backbone.js: Introducing The Backbone Store http://www.elfsternberg.com/2010/12/08/backbonejs-introducing-backbone-store/ [...]

buuu

September 1st, 2013 at 10:50 am

thanks, the LICENSE is not MIT –> I’m not trusting you.

small mind –> strict license

Elf Sternberg

September 2nd, 2013 at 11:53 am

Dude, the license is BSD Classic. The only difference between it and MIT is that you have to include the copyright notice and attribution with every distribution. If your requirement for an “open-minded” license is that you can steal it and rub off the original creator’s identity, your brains have fallen out.

Comment Form

Recent Comments