Now generating source code through Noweb, and including commentary. Woot!
This commit is contained in:
parent
bedb20bb0b
commit
66586d8aad
|
@ -0,0 +1,467 @@
|
||||||
|
\documentclass{article}
|
||||||
|
\usepackage{noweb}
|
||||||
|
\usepackage{hyperref}
|
||||||
|
\begin{document}
|
||||||
|
|
||||||
|
I've been learning how to use \nwanchorto{http://documentcloud.github.com/backbone/}{Backbone.js}, a nifty little library for
|
||||||
|
organizing your client-side Javascript into a classic
|
||||||
|
Model-View-Controller paradigm while trying (and to some extent,
|
||||||
|
succeeding) in trying to burden you, the user, with as little
|
||||||
|
additional learning as possible. I consider this a good thing; the
|
||||||
|
overhead of learning a library and its accompaning DSL represent
|
||||||
|
additional cognitive loads that developers can better use elsewhere.
|
||||||
|
Keeping as much as possible within familiar paradigms is not just
|
||||||
|
useful, it's necessary as our programs get bigger.
|
||||||
|
|
||||||
|
The tutorial for Backbone is woefully lacking in specifics, and the
|
||||||
|
example program, Todo, doesn't really have much chops in teaching you
|
||||||
|
the ins and outs of Backbone, especially not its new Controller and
|
||||||
|
History modules. But in the announcement for Backbone.Controller,
|
||||||
|
Jeremy Ashkenas hid a clue: There's another library, Sammy.js, that
|
||||||
|
does something similar, and they do have a 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, and
|
||||||
|
online store written entirely in JSON and operating entirely within a
|
||||||
|
single page.
|
||||||
|
|
||||||
|
Let's start by showing you the HTML that we're going to be exploiting:
|
||||||
|
|
||||||
|
<<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" />
|
||||||
|
|
||||||
|
<<index 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 \nwanchorto{https://github.com/jquery/jquery-tmpl}{jQuery Templates kit}. We'll discuss those in a minute. There's 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 \nwanchorto{http://elfs.livejournal.com/756709.html}{The Album Cover
|
||||||
|
Game}.)
|
||||||
|
|
||||||
|
There are two views, the index and the item. So, using
|
||||||
|
[[Backbone.Controller]], we're going to route the following:
|
||||||
|
|
||||||
|
<<routes>>=
|
||||||
|
routes: {
|
||||||
|
"": "index",
|
||||||
|
"item/:id": "item",
|
||||||
|
},
|
||||||
|
@
|
||||||
|
|
||||||
|
Unlike Sammy, Backbone mostly only routes GET commands. Routes are to
|
||||||
|
routes to views; everything else happens more or less under the
|
||||||
|
covers.
|
||||||
|
|
||||||
|
As Backbone is running, the [[Backbone.History]] module is listening to
|
||||||
|
the hash object, waiting for it to change so that it can trigger a
|
||||||
|
``route'' event, in which case the function named as the value in the
|
||||||
|
route is called.
|
||||||
|
|
||||||
|
There are a few things I want to track: the index view, the individual
|
||||||
|
product views, and the shopping cart.
|
||||||
|
|
||||||
|
<<application variables>>=
|
||||||
|
_index: null,
|
||||||
|
_products: null,
|
||||||
|
_cart :null,
|
||||||
|
@
|
||||||
|
|
||||||
|
Using backbone, I have a list of products. So, I should declare
|
||||||
|
those. 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>>=
|
||||||
|
var Product = Backbone.Model.extend({});
|
||||||
|
|
||||||
|
var ProductCollection = Backbone.Collection.extend({
|
||||||
|
model: Product,
|
||||||
|
comparator: function(item) {
|
||||||
|
return item.get('title');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
@
|
||||||
|
|
||||||
|
That's not very exciting. Let's show this list, with a View. Let's
|
||||||
|
call it IndexView:
|
||||||
|
|
||||||
|
<<index view>>=
|
||||||
|
var IndexView = 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
@
|
||||||
|
|
||||||
|
This code defines a [[Backbone.View]] object, in which the parent element
|
||||||
|
is #main, the render function fades out the existing elements in that
|
||||||
|
position and replaces them with the contents of a rendered jQuery
|
||||||
|
template, and then fades the element back in.
|
||||||
|
|
||||||
|
The index template looks like this:
|
||||||
|
|
||||||
|
<<index template>>=
|
||||||
|
<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
|
||||||
|
\nwanchorto{http://en.wikipedia.org/wiki/Law_of_Demeter}{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
|
||||||
|
\nwanchorto{http://ejohn.org/blog/javascript-getters-and-setters/}{Javascript
|
||||||
|
Getter and Setters} become so well-established as to make [[set]]
|
||||||
|
and [[get]] irrelevant.)
|
||||||
|
|
||||||
|
Now, we can render the index view:
|
||||||
|
|
||||||
|
<<index render call>>=
|
||||||
|
index: function() {
|
||||||
|
this._index.render();
|
||||||
|
},
|
||||||
|
@
|
||||||
|
|
||||||
|
At this point, well, we need an application. A controller. And we
|
||||||
|
need to initialize it, and call it. Here's what it looks like (some
|
||||||
|
of this, you've already seen):
|
||||||
|
|
||||||
|
<<workspace>>=
|
||||||
|
var Workspace = Backbone.Controller.extend({
|
||||||
|
<<application variables>>
|
||||||
|
|
||||||
|
<<routes>>
|
||||||
|
|
||||||
|
<<initialization>>
|
||||||
|
|
||||||
|
<<index render call>>
|
||||||
|
|
||||||
|
<<product render call>>
|
||||||
|
});
|
||||||
|
|
||||||
|
workspace = new Workspace();
|
||||||
|
Backbone.history.start();
|
||||||
|
@
|
||||||
|
|
||||||
|
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 index view. (Product views, we'll discuss in a moment).
|
||||||
|
|
||||||
|
<<initialization>>=
|
||||||
|
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 IndexView({model: ws._products});
|
||||||
|
Backbone.history.loadUrl();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
@
|
||||||
|
|
||||||
|
We haven't defined the Cart yet, but that's all right. We'll get to
|
||||||
|
it.) But here you see a lot of what's already existent being used: we
|
||||||
|
get a ProductCollection, and an IndexView.
|
||||||
|
|
||||||
|
That last line is curious. It's an instruction to Backbone to look at
|
||||||
|
the URL; if the user navigated to something other than the home page,
|
||||||
|
it's to use the routes defined to go there. Users can now bookmark
|
||||||
|
places in your site other than the home page. Yes, the bookmark will
|
||||||
|
be funny and have at least one
|
||||||
|
\nwanchorto{http://en.wiktionary.org/wiki/octothorpe}{octothorpe} in it, but
|
||||||
|
it will work.
|
||||||
|
|
||||||
|
Let's deal with the shopping cart:
|
||||||
|
|
||||||
|
<<shopping cart models>>=
|
||||||
|
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); });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
@
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
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 [[CartItem]]s) 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>>=
|
||||||
|
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 animated, and then pop it off
|
||||||
|
after the operation has been applied.
|
||||||
|
|
||||||
|
The biggest thing left is the [[ProductView]]. It's skeleton looks
|
||||||
|
like this:
|
||||||
|
|
||||||
|
<<product view>>=
|
||||||
|
var ProductView = Backbone.View.extend({
|
||||||
|
el: $('#main'),
|
||||||
|
itemTemplate: $("#itemTmpl").template(),
|
||||||
|
|
||||||
|
<<product events>>
|
||||||
|
|
||||||
|
initialize: function(options) {
|
||||||
|
this.cart = options.cart;
|
||||||
|
},
|
||||||
|
|
||||||
|
<<update product>>
|
||||||
|
|
||||||
|
<<render product>>
|
||||||
|
});
|
||||||
|
|
||||||
|
@
|
||||||
|
|
||||||
|
First, we find the element we're going to work with, and the template.
|
||||||
|
I expect the ProductView to be where we'll add items to the cart, so
|
||||||
|
the initializer here expects to have a handle on the cart.
|
||||||
|
|
||||||
|
And the template:
|
||||||
|
|
||||||
|
<<product template>>=
|
||||||
|
<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="#">« Back to Items</a></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</script>
|
||||||
|
@
|
||||||
|
|
||||||
|
One extra item: 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.
|
||||||
|
|
||||||
|
Rendering the product is not difficult:
|
||||||
|
|
||||||
|
<<render product>>=
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
@
|
||||||
|
|
||||||
|
That looks familiar.
|
||||||
|
|
||||||
|
Updating the product, however, is a whole 'nother story. Note that
|
||||||
|
each product has a form associated with it. We need to intercept any
|
||||||
|
form update events and manipulate our shopping cart. We have two
|
||||||
|
objects that can do that: the input field, and the submit button. I
|
||||||
|
need to intercept those events:
|
||||||
|
|
||||||
|
<<product events>>=
|
||||||
|
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''.
|
||||||
|
|
||||||
|
The update then becomes straightforward. 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>>=
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
@
|
||||||
|
|
||||||
|
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]].
|
||||||
|
|
||||||
|
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>>=
|
||||||
|
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 now my store looks like
|
||||||
|
|
||||||
|
<<store.js>>=
|
||||||
|
<<product models>>
|
||||||
|
|
||||||
|
<<shopping cart models>>
|
||||||
|
|
||||||
|
<<shopping cart view>>
|
||||||
|
|
||||||
|
<<product view>>
|
||||||
|
|
||||||
|
<<index view>>
|
||||||
|
|
||||||
|
<<workspace>>
|
||||||
|
@
|
||||||
|
|
||||||
|
As always, this code is available at github.
|
||||||
|
|
||||||
|
\end{document}
|
11
index.html
11
index.html
|
@ -2,9 +2,11 @@
|
||||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta name="generator" content="HTML Tidy for Linux/x86 (vers 25 March 2009), see www.w3.org" />
|
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
<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" />
|
||||||
|
|
||||||
<script id="indexTmpl" type="text/x-jquery-tmpl">
|
<script id="indexTmpl" type="text/x-jquery-tmpl">
|
||||||
<div class="item">
|
<div class="item">
|
||||||
<div class="item-image">
|
<div class="item-image">
|
||||||
|
@ -39,16 +41,12 @@
|
||||||
</div>
|
</div>
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<title>
|
|
||||||
The Backbone Store
|
|
||||||
</title>
|
|
||||||
<link rel="stylesheet" href="jsonstore.css" type="text/css" media="screen" charset="utf-8" />
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="container">
|
<div id="container">
|
||||||
<div id="header">
|
<div id="header">
|
||||||
<h1>
|
<h1>
|
||||||
The JSON Store
|
The Backbone Store
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<div class="cart-info">
|
<div class="cart-info">
|
||||||
|
@ -63,7 +61,6 @@
|
||||||
<script src="jquery.tmpl.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="underscore.js" type="text/javascript"></script>
|
||||||
<script src="backbone.js" type="text/javascript"></script>
|
<script src="backbone.js" type="text/javascript"></script>
|
||||||
<script src="backbone-localstorage.js" type="text/javascript"></script>
|
|
||||||
<script src="store.js" type="text/javascript"></script>
|
<script src="store.js" type="text/javascript"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
5
store.js
5
store.js
|
@ -7,7 +7,6 @@ var ProductCollection = Backbone.Collection.extend({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
var CartItem = Backbone.Model.extend({
|
var CartItem = Backbone.Model.extend({
|
||||||
update: function(amount) {
|
update: function(amount) {
|
||||||
this.set({'quantity': this.get('quantity') + amount});
|
this.set({'quantity': this.get('quantity') + amount});
|
||||||
|
@ -22,7 +21,6 @@ var Cart = Backbone.Collection.extend({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
var CartView = Backbone.View.extend({
|
var CartView = Backbone.View.extend({
|
||||||
el: $('.cart-info'),
|
el: $('.cart-info'),
|
||||||
|
|
||||||
|
@ -39,7 +37,6 @@ var CartView = Backbone.View.extend({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
var ProductView = Backbone.View.extend({
|
var ProductView = Backbone.View.extend({
|
||||||
el: $('#main'),
|
el: $('#main'),
|
||||||
itemTemplate: $("#itemTmpl").template(),
|
itemTemplate: $("#itemTmpl").template(),
|
||||||
|
@ -97,8 +94,6 @@ var IndexView = Backbone.View.extend({
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
var Workspace = Backbone.Controller.extend({
|
var Workspace = Backbone.Controller.extend({
|
||||||
_index: null,
|
_index: null,
|
||||||
_products: null,
|
_products: null,
|
||||||
|
|
Loading…
Reference in New Issue