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}
|
77
index.html
77
index.html
|
@ -2,53 +2,51 @@
|
|||
"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 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" />
|
||||
|
||||
<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>
|
||||
|
||||
<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>
|
||||
|
||||
<title>
|
||||
The Backbone Store
|
||||
</title>
|
||||
<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">
|
||||
<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>
|
||||
|
||||
<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>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<div id="container">
|
||||
<div id="header">
|
||||
<h1>
|
||||
The JSON Store
|
||||
The Backbone Store
|
||||
</h1>
|
||||
|
||||
<div class="cart-info">
|
||||
|
@ -63,7 +61,6 @@
|
|||
<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="backbone-localstorage.js" type="text/javascript"></script>
|
||||
<script src="store.js" type="text/javascript"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
5
store.js
5
store.js
|
@ -7,7 +7,6 @@ var ProductCollection = Backbone.Collection.extend({
|
|||
}
|
||||
});
|
||||
|
||||
|
||||
var CartItem = Backbone.Model.extend({
|
||||
update: function(amount) {
|
||||
this.set({'quantity': this.get('quantity') + amount});
|
||||
|
@ -22,7 +21,6 @@ var Cart = Backbone.Collection.extend({
|
|||
},
|
||||
});
|
||||
|
||||
|
||||
var CartView = Backbone.View.extend({
|
||||
el: $('.cart-info'),
|
||||
|
||||
|
@ -39,7 +37,6 @@ var CartView = Backbone.View.extend({
|
|||
}
|
||||
});
|
||||
|
||||
|
||||
var ProductView = Backbone.View.extend({
|
||||
el: $('#main'),
|
||||
itemTemplate: $("#itemTmpl").template(),
|
||||
|
@ -97,8 +94,6 @@ var IndexView = Backbone.View.extend({
|
|||
|
||||
});
|
||||
|
||||
|
||||
|
||||
var Workspace = Backbone.Controller.extend({
|
||||
_index: null,
|
||||
_products: null,
|
||||
|
|
Loading…
Reference in New Issue