852 lines
31 KiB
Plaintext
852 lines
31 KiB
Plaintext
% -*- 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:
|
|
|
|
<<product models>>=
|
|
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}:
|
|
|
|
<<product models>>=
|
|
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'.
|
|
|
|
<<shopping cart models>>=
|
|
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.
|
|
|
|
<<shopping cart models>>=
|
|
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.
|
|
|
|
<<base view>>=
|
|
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.
|
|
|
|
<<base view>>=
|
|
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:
|
|
|
|
<<base view>>=
|
|
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:
|
|
|
|
<<index.html>>=
|
|
<!DOCTYPE html>
|
|
<html xmlns="http://www.w3.org/1999/xhtml">
|
|
<head>
|
|
<title>
|
|
The Backbone Store
|
|
</title>
|
|
<link rel="stylesheet" href="jsonstore.css" type="text/css">
|
|
<<product list template>>
|
|
<<product template>>
|
|
<<checkout template>>
|
|
</head>
|
|
<body>
|
|
<div id="container">
|
|
<div id="header">
|
|
<h1>
|
|
The Backbone Store
|
|
</h1>
|
|
|
|
<div class="cart-info">
|
|
</div>
|
|
</div>
|
|
|
|
<div id="main"> </div>
|
|
</div>
|
|
<script src="jquery-1.6.2.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>
|
|
@
|
|
|
|
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.
|
|
|
|
<<product list 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:
|
|
|
|
<<product list template>>=
|
|
<script id="store_index_template" type="text/x-jquery-tmpl">
|
|
<h1>Product Catalog</h1>
|
|
<ul>
|
|
<% for(i=0,l=products.length;i<l;++i) { p = products[i]; %>
|
|
<li class="item">
|
|
<div class="item-image">
|
|
<a href="#item/<%= p.cid%>">
|
|
<img src="<%= p.image %>" alt="<%= p.title %>" /></a>
|
|
</div>
|
|
<div class="item-artist"><%= p.artist %></div>
|
|
<div class="item-title"><%= p.title %></div>
|
|
<div class="item-price">$<%= p.price %></div>
|
|
</li>
|
|
<% } %>
|
|
</ul>
|
|
</script>
|
|
@
|
|
%$
|
|
|
|
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.
|
|
|
|
<<product detail view>>=
|
|
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.
|
|
|
|
<<product view>>=
|
|
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}.
|
|
%'
|
|
|
|
<<product view>>=
|
|
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:
|
|
|
|
<<store.js>>=
|
|
(function() {
|
|
|
|
<<product models>>
|
|
|
|
<<shopping cart models>>
|
|
|
|
<<shopping cart view>>
|
|
|
|
<<product list view>>
|
|
|
|
<<product view>>
|
|
|
|
<<application>>
|
|
|
|
<<initialization>>
|
|
|
|
}).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.
|
|
|
|
<<shopping cart view>>=
|
|
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.
|
|
|
|
<<product list view>>=
|
|
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}]].
|
|
|
|
<<product list template>>=
|
|
<script id="indexTmpl" type="text/x-jquery-tmpl">
|
|
<div class="item">
|
|
<div class="item-image">
|
|
<a href="#item/${cid}"><img src="${image}" alt="${title}" /></a>
|
|
</div>
|
|
<div class="item-artist">${artist}</div>
|
|
<div class="item-title">${title}</div>
|
|
<div class="item-price">$${price}</div>
|
|
</div>
|
|
</script>
|
|
@
|
|
%$
|
|
|
|
The most complicated object .
|
|
|
|
<<product view>>=
|
|
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()]].
|
|
|
|
<<product view>>=
|
|
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.
|
|
|
|
<<product view>>=
|
|
events: {
|
|
"keypress .uqf" : "updateOnEnter",
|
|
"click .uq" : "update",
|
|
},
|
|
|
|
@
|
|
|
|
And finally the render. There is no rocket science here. You've seen
|
|
this before.
|
|
|
|
%'
|
|
<<product view>>=
|
|
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.
|
|
|
|
<<product template>>=
|
|
<script id="itemTmpl" type="text/x-jquery-tmpl">
|
|
<div class="item-detail">
|
|
<div class="item-image"><img src="${large_image}" alt="${title}" /></div>
|
|
<div class="item-info">
|
|
<div class="item-artist">${artist}</div>
|
|
<div class="item-title">${title}</div>
|
|
<div class="item-price">$${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="${url}">Buy this item on Amazon</a></div>
|
|
<div class="back-link"><a href="#">« Back to Items</a></div>
|
|
</div>
|
|
</div>
|
|
</script>
|
|
@
|
|
|
|
%'
|
|
\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.
|
|
|
|
<<application>>=
|
|
var BackboneStore = Backbone.Controller.extend({
|
|
_index: null,
|
|
_products: null,
|
|
_cart :null,
|
|
@
|
|
%$
|
|
|
|
There are only two routes: home, and item:
|
|
|
|
<<application>>=
|
|
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.
|
|
|
|
<<application>>=
|
|
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:
|
|
|
|
<<application>>=
|
|
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:
|
|
|
|
<<application>>=
|
|
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.
|
|
|
|
<<initialization>>=
|
|
$(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}
|