[★] Added built products.
There's no reason other people need to replicate my idiosyncratic toolchain.
This commit is contained in:
parent
645d8650c4
commit
366dc6159d
|
@ -7,8 +7,7 @@
|
||||||
node_modules/*
|
node_modules/*
|
||||||
bower_components/*
|
bower_components/*
|
||||||
npm-debug.log
|
npm-debug.log
|
||||||
work/*
|
docs/*.html
|
||||||
htdocs/*.*
|
docs/*.tex
|
||||||
docs/*
|
|
||||||
htdocs/lib
|
htdocs/lib
|
||||||
package.yml
|
package.yml
|
||||||
|
|
Binary file not shown.
|
@ -0,0 +1,71 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||||
|
<head>
|
||||||
|
<title>The Backbone Store</title>
|
||||||
|
<link charset="utf-8" href="jsonstore.css" rel="stylesheet" type="text/css">
|
||||||
|
<script id="store_index_template" type="text/x-underscore-tmplate">
|
||||||
|
<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.id %>">
|
||||||
|
<img alt="<%= p.title %>" src="<%= p.image %>">
|
||||||
|
</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>
|
||||||
|
<script id="store_item_template" type="text/x-underscore-template">
|
||||||
|
<div class="item-detail">
|
||||||
|
<div class="item-image">
|
||||||
|
<img alt="<%= title %>" src="<%= large_image %>">
|
||||||
|
</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"></div>
|
||||||
|
<form action="#/cart" method="post">
|
||||||
|
<p>
|
||||||
|
<label>Quantity:</label>
|
||||||
|
<input class="uqf" name="quantity" size="2" type="text" value="1">
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<input class="uq" type="submit" value="Add to Cart">
|
||||||
|
</p>
|
||||||
|
</form>
|
||||||
|
<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>
|
||||||
|
<script id="store_cart_template" type="text/x-underscore-template">
|
||||||
|
<p>Items: <%= count %> ($<%= cost %>)</p>
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
</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="lib/jquery.js" type="text/javascript"></script>
|
||||||
|
<script src="lib/underscore.js" type="text/javascript"></script>
|
||||||
|
<script src="lib/backbone.js" type="text/javascript"></script>
|
||||||
|
<script src="store.js" type="text/javascript"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,91 @@
|
||||||
|
body {
|
||||||
|
font-family: "Lucida Grande", Lucida, Helvetica, Arial, sans-serif;
|
||||||
|
background: #fff;
|
||||||
|
color: #333;
|
||||||
|
margin: 0px;
|
||||||
|
padding: 0px;
|
||||||
|
}
|
||||||
|
#main {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
#header {
|
||||||
|
background: #999;
|
||||||
|
background: -webkit-gradient(linear, left top, left bottom, from(#adadad), to(#7a7a7a));
|
||||||
|
background: -moz-linear-gradient(top, #adadad, #7a7a7a);
|
||||||
|
margin: 0px;
|
||||||
|
padding: 20px;
|
||||||
|
border-bottom: 1px solid #ccc;
|
||||||
|
}
|
||||||
|
#header h1 {
|
||||||
|
font-family: Inconsolata, Monaco, Courier, mono;
|
||||||
|
color: #fff;
|
||||||
|
margin: 0px;
|
||||||
|
}
|
||||||
|
#header .cart-info {
|
||||||
|
position: absolute;
|
||||||
|
top: 0px;
|
||||||
|
right: 0px;
|
||||||
|
text-align: right;
|
||||||
|
padding: 10px;
|
||||||
|
background: #555;
|
||||||
|
background: -webkit-gradient(linear, left top, left bottom, from(#777), to(#444));
|
||||||
|
background: -moz-linear-gradient(top, #777, #444);
|
||||||
|
color: #fff;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
img {
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
.productitemview {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
#productlistview {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
#productlistview ul {
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
.item {
|
||||||
|
float: left;
|
||||||
|
width: 250px;
|
||||||
|
margin-right: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
padding: 5px;
|
||||||
|
-moz-border-radius-topleft: 5px;
|
||||||
|
-moz-border-radius-topright: 5px;
|
||||||
|
-moz-border-radius-bottomleft: 5px;
|
||||||
|
-moz-border-radius-bottomright: 5px;
|
||||||
|
-webkit-border-bottom-right-radius: 5px;
|
||||||
|
-webkit-border-top-left-radius: 5px;
|
||||||
|
-webkit-border-top-right-radius: 5px;
|
||||||
|
-webkit-border-bottom-left-radius: 5px;
|
||||||
|
border-bottom-right-radius: 5px;
|
||||||
|
border-top-left-radius: 5px;
|
||||||
|
border-top-right-radius: 5px;
|
||||||
|
border-bottom-left-radius: 5px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.item-title {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.item-artist {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
.item-detail {
|
||||||
|
margin: 10px 0 0 10px;
|
||||||
|
}
|
||||||
|
.item-detail .item-image {
|
||||||
|
float: left;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
.item-detail .item-info {
|
||||||
|
padding: 100px 10px 0px 10px;
|
||||||
|
}
|
|
@ -0,0 +1,337 @@
|
||||||
|
// Generated by CoffeeScript 1.10.0
|
||||||
|
(function() {
|
||||||
|
var BackboneStore, CartWidget, Item, ItemCollection, Product, ProductCollection, ProductListView, ProductView, _BaseView,
|
||||||
|
extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
|
||||||
|
hasProp = {}.hasOwnProperty;
|
||||||
|
|
||||||
|
Product = (function(superClass) {
|
||||||
|
extend(Product, superClass);
|
||||||
|
|
||||||
|
function Product() {
|
||||||
|
return Product.__super__.constructor.apply(this, arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Product;
|
||||||
|
|
||||||
|
})(Backbone.Model);
|
||||||
|
|
||||||
|
Item = (function(superClass) {
|
||||||
|
extend(Item, superClass);
|
||||||
|
|
||||||
|
function Item() {
|
||||||
|
return Item.__super__.constructor.apply(this, arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
Item.prototype.update = function(amount) {
|
||||||
|
if (amount === this.get('quantity')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.set({
|
||||||
|
quantity: amount
|
||||||
|
}, {
|
||||||
|
silent: true
|
||||||
|
});
|
||||||
|
return this.collection.trigger('update', this);
|
||||||
|
};
|
||||||
|
|
||||||
|
Item.prototype.price = function() {
|
||||||
|
return this.get('product').get('price') * this.get('quantity');
|
||||||
|
};
|
||||||
|
|
||||||
|
return Item;
|
||||||
|
|
||||||
|
})(Backbone.Model);
|
||||||
|
|
||||||
|
ProductCollection = (function(superClass) {
|
||||||
|
extend(ProductCollection, superClass);
|
||||||
|
|
||||||
|
function ProductCollection() {
|
||||||
|
return ProductCollection.__super__.constructor.apply(this, arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
ProductCollection.prototype.model = Product;
|
||||||
|
|
||||||
|
ProductCollection.prototype.initialize = function(models, options) {
|
||||||
|
return this.url = options.url;
|
||||||
|
};
|
||||||
|
|
||||||
|
ProductCollection.prototype.comparator = function(item) {
|
||||||
|
return item.get('title');
|
||||||
|
};
|
||||||
|
|
||||||
|
return ProductCollection;
|
||||||
|
|
||||||
|
})(Backbone.Collection);
|
||||||
|
|
||||||
|
ItemCollection = (function(superClass) {
|
||||||
|
extend(ItemCollection, superClass);
|
||||||
|
|
||||||
|
function ItemCollection() {
|
||||||
|
return ItemCollection.__super__.constructor.apply(this, arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemCollection.prototype.model = Item;
|
||||||
|
|
||||||
|
ItemCollection.prototype.updateItemForProduct = function(product, amount) {
|
||||||
|
var i, pid;
|
||||||
|
amount = amount != null ? amount : 0;
|
||||||
|
pid = product.get('id');
|
||||||
|
i = this.detect(function(obj) {
|
||||||
|
return obj.get('product').get('id') === pid;
|
||||||
|
});
|
||||||
|
if (i) {
|
||||||
|
i.update(amount);
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
return this.add({
|
||||||
|
product: product,
|
||||||
|
quantity: amount
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
ItemCollection.prototype.getTotalCount = function() {
|
||||||
|
var addup;
|
||||||
|
addup = function(memo, obj) {
|
||||||
|
return memo + obj.get('quantity');
|
||||||
|
};
|
||||||
|
return this.reduce(addup, 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
ItemCollection.prototype.getTotalCost = function() {
|
||||||
|
var addup;
|
||||||
|
addup = function(memo, obj) {
|
||||||
|
return memo + obj.price();
|
||||||
|
};
|
||||||
|
return this.reduce(addup, 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
return ItemCollection;
|
||||||
|
|
||||||
|
})(Backbone.Collection);
|
||||||
|
|
||||||
|
_BaseView = (function(superClass) {
|
||||||
|
extend(_BaseView, superClass);
|
||||||
|
|
||||||
|
function _BaseView() {
|
||||||
|
return _BaseView.__super__.constructor.apply(this, arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
_BaseView.prototype.parent = $('#main');
|
||||||
|
|
||||||
|
_BaseView.prototype.className = 'viewport';
|
||||||
|
|
||||||
|
_BaseView.prototype.initialize = function() {
|
||||||
|
this.el = $(this.el);
|
||||||
|
this.el.hide();
|
||||||
|
this.parent.append(this.el);
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
_BaseView.prototype.hide = function() {
|
||||||
|
var dfd;
|
||||||
|
dfd = $.Deferred();
|
||||||
|
if (!this.el.is(':visible')) {
|
||||||
|
return dfd.resolve();
|
||||||
|
}
|
||||||
|
this.el.fadeOut('fast', function() {
|
||||||
|
return dfd.resolve();
|
||||||
|
});
|
||||||
|
return dfd.promise();
|
||||||
|
};
|
||||||
|
|
||||||
|
_BaseView.prototype.show = function() {
|
||||||
|
var dfd;
|
||||||
|
dfd = $.Deferred();
|
||||||
|
if (this.el.is(':visible')) {
|
||||||
|
return dfd.resolve();
|
||||||
|
}
|
||||||
|
this.el.fadeIn('fast', function() {
|
||||||
|
return dfd.resolve();
|
||||||
|
});
|
||||||
|
return dfd.promise();
|
||||||
|
};
|
||||||
|
|
||||||
|
return _BaseView;
|
||||||
|
|
||||||
|
})(Backbone.View);
|
||||||
|
|
||||||
|
ProductListView = (function(superClass) {
|
||||||
|
extend(ProductListView, superClass);
|
||||||
|
|
||||||
|
function ProductListView() {
|
||||||
|
return ProductListView.__super__.constructor.apply(this, arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
ProductListView.prototype.id = 'productlistview';
|
||||||
|
|
||||||
|
ProductListView.prototype.template = $("#store_index_template").html();
|
||||||
|
|
||||||
|
ProductListView.prototype.initialize = function(options) {
|
||||||
|
_BaseView.prototype.initialize.apply(this, [options]);
|
||||||
|
return this.collection.bind('reset', this.render.bind(this));
|
||||||
|
};
|
||||||
|
|
||||||
|
ProductListView.prototype.render = function() {
|
||||||
|
this.el.html(_.template(this.template)({
|
||||||
|
'products': this.collection.toJSON()
|
||||||
|
}));
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
return ProductListView;
|
||||||
|
|
||||||
|
})(_BaseView);
|
||||||
|
|
||||||
|
ProductView = (function(superClass) {
|
||||||
|
extend(ProductView, superClass);
|
||||||
|
|
||||||
|
function ProductView() {
|
||||||
|
return ProductView.__super__.constructor.apply(this, arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
ProductView.prototype.className = 'productitemview';
|
||||||
|
|
||||||
|
ProductView.prototype.template = $("#store_item_template").html();
|
||||||
|
|
||||||
|
ProductView.prototype.initialize = function(options) {
|
||||||
|
_BaseView.prototype.initialize.apply(this, [options]);
|
||||||
|
return this.itemcollection = options.itemcollection;
|
||||||
|
};
|
||||||
|
|
||||||
|
ProductView.prototype.events = {
|
||||||
|
"keypress .uqf": "updateOnEnter",
|
||||||
|
"click .uq": "update"
|
||||||
|
};
|
||||||
|
|
||||||
|
ProductView.prototype.update = function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
return this.itemcollection.updateItemForProduct(this.model, parseInt(this.$('.uqf').val()));
|
||||||
|
};
|
||||||
|
|
||||||
|
ProductView.prototype.updateOnEnter = function(e) {
|
||||||
|
if (e.keyCode === 13) {
|
||||||
|
return this.update(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ProductView.prototype.render = function() {
|
||||||
|
this.el.html(_.template(this.template)(this.model.toJSON()));
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
return ProductView;
|
||||||
|
|
||||||
|
})(_BaseView);
|
||||||
|
|
||||||
|
CartWidget = (function(superClass) {
|
||||||
|
extend(CartWidget, superClass);
|
||||||
|
|
||||||
|
function CartWidget() {
|
||||||
|
return CartWidget.__super__.constructor.apply(this, arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
CartWidget.prototype.el = $('.cart-info');
|
||||||
|
|
||||||
|
CartWidget.prototype.template = $('#store_cart_template').html();
|
||||||
|
|
||||||
|
CartWidget.prototype.initialize = function() {
|
||||||
|
return this.collection.bind('update', this.render.bind(this));
|
||||||
|
};
|
||||||
|
|
||||||
|
CartWidget.prototype.render = function() {
|
||||||
|
var tel;
|
||||||
|
tel = this.$el.html(_.template(this.template)({
|
||||||
|
'count': this.collection.getTotalCount(),
|
||||||
|
'cost': this.collection.getTotalCost()
|
||||||
|
}));
|
||||||
|
tel.animate({
|
||||||
|
paddingTop: '30px'
|
||||||
|
}).animate({
|
||||||
|
paddingTop: '10px'
|
||||||
|
});
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
return CartWidget;
|
||||||
|
|
||||||
|
})(Backbone.View);
|
||||||
|
|
||||||
|
BackboneStore = (function(superClass) {
|
||||||
|
extend(BackboneStore, superClass);
|
||||||
|
|
||||||
|
function BackboneStore() {
|
||||||
|
return BackboneStore.__super__.constructor.apply(this, arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
BackboneStore.prototype.views = {};
|
||||||
|
|
||||||
|
BackboneStore.prototype.products = null;
|
||||||
|
|
||||||
|
BackboneStore.prototype.cart = null;
|
||||||
|
|
||||||
|
BackboneStore.prototype.routes = {
|
||||||
|
"": "index",
|
||||||
|
"item/:id": "product"
|
||||||
|
};
|
||||||
|
|
||||||
|
BackboneStore.prototype.initialize = function(data) {
|
||||||
|
this.cart = new ItemCollection();
|
||||||
|
new CartWidget({
|
||||||
|
collection: this.cart
|
||||||
|
});
|
||||||
|
this.products = new ProductCollection([], {
|
||||||
|
url: 'data/items.json'
|
||||||
|
});
|
||||||
|
this.views = {
|
||||||
|
'_index': new ProductListView({
|
||||||
|
collection: this.products
|
||||||
|
})
|
||||||
|
};
|
||||||
|
$.when(this.products.fetch({
|
||||||
|
reset: true
|
||||||
|
})).then(function() {
|
||||||
|
return window.location.hash = '';
|
||||||
|
});
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
BackboneStore.prototype.hideAllViews = function() {
|
||||||
|
return _.select(_.map(this.views, function(v) {
|
||||||
|
return v.hide();
|
||||||
|
}), function(t) {
|
||||||
|
return t !== null;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
BackboneStore.prototype.index = function() {
|
||||||
|
var view;
|
||||||
|
view = this.views['_index'];
|
||||||
|
return $.when.apply($, this.hideAllViews()).then(function() {
|
||||||
|
return view.show();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
BackboneStore.prototype.product = function(id) {
|
||||||
|
var base, name, product, view;
|
||||||
|
product = this.products.detect(function(p) {
|
||||||
|
return p.get('id') === id;
|
||||||
|
});
|
||||||
|
view = ((base = this.views)[name = 'item.' + id] || (base[name] = new ProductView({
|
||||||
|
model: product,
|
||||||
|
itemcollection: this.cart
|
||||||
|
}).render()));
|
||||||
|
return $.when(this.hideAllViews()).then(function() {
|
||||||
|
return view.show();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return BackboneStore;
|
||||||
|
|
||||||
|
})(Backbone.Router);
|
||||||
|
|
||||||
|
$(document).ready(function() {
|
||||||
|
new BackboneStore();
|
||||||
|
return Backbone.history.start();
|
||||||
|
});
|
||||||
|
|
||||||
|
}).call(this);
|
|
@ -0,0 +1,53 @@
|
||||||
|
!!! 5
|
||||||
|
%html{:xmlns => "http://www.w3.org/1999/xhtml"}
|
||||||
|
%head
|
||||||
|
%title The Backbone Store
|
||||||
|
%link{:charset => "utf-8", :href => "jsonstore.css", :rel => "stylesheet", :type => "text/css"}/
|
||||||
|
%script#store_index_template(type="text/x-underscore-tmplate")
|
||||||
|
%h1 Product Catalog
|
||||||
|
%ul
|
||||||
|
<% for(i=0,l=products.length;i<l;++i) { p = products[i]; %>
|
||||||
|
%li.item
|
||||||
|
.item-image
|
||||||
|
%a{:href => "#item/<%= p.id %>"}
|
||||||
|
%img{:src => "<%= p.image %>", :alt => "<%= p.title %>"}/
|
||||||
|
.item-artist <%= p.artist %>
|
||||||
|
.item-title <%= p.title %>
|
||||||
|
.item-price $<%= p.price %>
|
||||||
|
<% } %>
|
||||||
|
|
||||||
|
%script#store_item_template(type= "text/x-underscore-template")
|
||||||
|
.item-detail
|
||||||
|
.item-image
|
||||||
|
%img(src="<%= large_image %>" alt="<%= title %>")/
|
||||||
|
.item-info
|
||||||
|
.item-artist <%= artist %>
|
||||||
|
.item-title <%= title %>
|
||||||
|
.item-price $<%= price %>
|
||||||
|
.item-form
|
||||||
|
%form(action="#/cart" method="post")
|
||||||
|
%p
|
||||||
|
%label Quantity:
|
||||||
|
%input(type="text" size="2" name="quantity" value="1" class="uqf")/
|
||||||
|
%p
|
||||||
|
%input(type="submit" value="Add to Cart" class="uq")/
|
||||||
|
|
||||||
|
.item-link
|
||||||
|
%a(href="<%= url %>") Buy this item on Amazon
|
||||||
|
.back-link
|
||||||
|
%a(href="#") « Back to Items
|
||||||
|
%script#store_cart_template(type="text/x-underscore-template")
|
||||||
|
%p Items: <%= count %> ($<%= cost %>)
|
||||||
|
|
||||||
|
</head>
|
||||||
|
%body
|
||||||
|
#container
|
||||||
|
#header
|
||||||
|
%h1
|
||||||
|
The Backbone Store
|
||||||
|
.cart-info
|
||||||
|
#main
|
||||||
|
%script{:src => "lib/jquery.js", :type => "text/javascript"}
|
||||||
|
%script{:src => "lib/underscore.js", :type => "text/javascript"}
|
||||||
|
%script{:src => "lib/backbone.js", :type => "text/javascript"}
|
||||||
|
%script{:src => "store.js", :type => "text/javascript"}
|
|
@ -0,0 +1,96 @@
|
||||||
|
rounded(radius)
|
||||||
|
-moz-border-radius-topleft: radius
|
||||||
|
-moz-border-radius-topright: radius
|
||||||
|
-moz-border-radius-bottomleft: radius
|
||||||
|
-moz-border-radius-bottomright: radius
|
||||||
|
-webkit-border-bottom-right-radius: radius
|
||||||
|
-webkit-border-top-left-radius: radius
|
||||||
|
-webkit-border-top-right-radius: radius
|
||||||
|
-webkit-border-bottom-left-radius: radius
|
||||||
|
border-bottom-right-radius: radius
|
||||||
|
border-top-left-radius: radius
|
||||||
|
border-top-right-radius: radius
|
||||||
|
border-bottom-left-radius: radius
|
||||||
|
|
||||||
|
background_gradient(base)
|
||||||
|
background: base
|
||||||
|
background: -webkit-gradient(linear, left top, left bottom, from(lighten(base, 20%)), to(darken(base, 20%)))
|
||||||
|
background: -moz-linear-gradient(top, lighten(base, 20%), darken(base, 20%))
|
||||||
|
|
||||||
|
body
|
||||||
|
font-family: "Lucida Grande", Lucida, Helvetica, Arial, sans-serif
|
||||||
|
background: #FFF
|
||||||
|
color: #333
|
||||||
|
margin: 0px
|
||||||
|
padding: 0px
|
||||||
|
|
||||||
|
|
||||||
|
#main
|
||||||
|
position: relative
|
||||||
|
|
||||||
|
#header
|
||||||
|
background_gradient(#999)
|
||||||
|
margin: 0px
|
||||||
|
padding: 20px
|
||||||
|
border-bottom: 1px solid #ccc
|
||||||
|
|
||||||
|
h1
|
||||||
|
font-family: Inconsolata, Monaco, Courier, mono
|
||||||
|
color: #FFF
|
||||||
|
margin: 0px
|
||||||
|
|
||||||
|
.cart-info
|
||||||
|
position: absolute
|
||||||
|
top: 0px
|
||||||
|
right: 0px
|
||||||
|
text-align: right
|
||||||
|
padding: 10px
|
||||||
|
background_gradient(#555)
|
||||||
|
color: #FFF
|
||||||
|
font-size: 12px
|
||||||
|
font-weight: bold
|
||||||
|
|
||||||
|
img
|
||||||
|
border: 0
|
||||||
|
|
||||||
|
.productitemview
|
||||||
|
position: absolute
|
||||||
|
top: 0
|
||||||
|
left: 0
|
||||||
|
|
||||||
|
#productlistview
|
||||||
|
position: absolute
|
||||||
|
top: 0
|
||||||
|
left: 0
|
||||||
|
|
||||||
|
ul
|
||||||
|
list-style: none
|
||||||
|
|
||||||
|
.item
|
||||||
|
float:left
|
||||||
|
width: 250px
|
||||||
|
margin-right: 10px
|
||||||
|
margin-bottom: 10px
|
||||||
|
padding: 5px
|
||||||
|
rounded(5px)
|
||||||
|
border: 1px solid #ccc
|
||||||
|
text-align:center
|
||||||
|
font-size: 12px
|
||||||
|
|
||||||
|
.item-title
|
||||||
|
font-weight: bold
|
||||||
|
|
||||||
|
.item-artist
|
||||||
|
font-weight: bold
|
||||||
|
font-size: 14px
|
||||||
|
|
||||||
|
.item-detail
|
||||||
|
margin: 10px 0 0 10px
|
||||||
|
|
||||||
|
.item-image
|
||||||
|
float:left
|
||||||
|
margin-right: 10px
|
||||||
|
|
||||||
|
.item-info
|
||||||
|
padding: 100px 10px 0px 10px
|
||||||
|
|
|
@ -0,0 +1,164 @@
|
||||||
|
class Product extends Backbone.Model
|
||||||
|
|
||||||
|
class Item extends Backbone.Model
|
||||||
|
update: (amount) ->
|
||||||
|
return if amount == @get 'quantity'
|
||||||
|
@set {quantity: amount}, {silent: true}
|
||||||
|
@collection.trigger 'update', @
|
||||||
|
|
||||||
|
price: () ->
|
||||||
|
@get('product').get('price') * @get('quantity')
|
||||||
|
|
||||||
|
class ProductCollection extends Backbone.Collection
|
||||||
|
model: Product
|
||||||
|
|
||||||
|
initialize: (models, options) ->
|
||||||
|
@url = options.url
|
||||||
|
|
||||||
|
comparator: (item) ->
|
||||||
|
item.get 'title'
|
||||||
|
|
||||||
|
class ItemCollection extends Backbone.Collection
|
||||||
|
model: Item
|
||||||
|
|
||||||
|
updateItemForProduct: (product, amount) ->
|
||||||
|
amount = if amount? then amount else 0
|
||||||
|
pid = product.get 'id'
|
||||||
|
i = this.detect (obj) -> (obj.get('product').get('id') == pid)
|
||||||
|
if i
|
||||||
|
i.update(amount)
|
||||||
|
return i
|
||||||
|
|
||||||
|
@add {product: product, quantity: amount}
|
||||||
|
|
||||||
|
getTotalCount: () ->
|
||||||
|
addup = (memo, obj) -> memo + obj.get 'quantity'
|
||||||
|
@reduce addup, 0
|
||||||
|
|
||||||
|
getTotalCost: () ->
|
||||||
|
addup = (memo, obj) -> memo + obj.price()
|
||||||
|
@reduce addup, 0
|
||||||
|
|
||||||
|
|
||||||
|
class _BaseView extends Backbone.View
|
||||||
|
parent: $('#main')
|
||||||
|
className: 'viewport'
|
||||||
|
|
||||||
|
initialize: () ->
|
||||||
|
@el = $(@el)
|
||||||
|
@el.hide()
|
||||||
|
@parent.append(@el)
|
||||||
|
@
|
||||||
|
|
||||||
|
hide: () ->
|
||||||
|
dfd = $.Deferred()
|
||||||
|
if not @el.is(':visible')
|
||||||
|
return dfd.resolve()
|
||||||
|
@el.fadeOut('fast', () -> dfd.resolve())
|
||||||
|
dfd.promise()
|
||||||
|
|
||||||
|
show: () ->
|
||||||
|
dfd = $.Deferred()
|
||||||
|
if @el.is(':visible')
|
||||||
|
return dfd.resolve()
|
||||||
|
@el.fadeIn('fast', () -> dfd.resolve())
|
||||||
|
dfd.promise()
|
||||||
|
|
||||||
|
|
||||||
|
class ProductListView extends _BaseView
|
||||||
|
id: 'productlistview'
|
||||||
|
template: $("#store_index_template").html()
|
||||||
|
|
||||||
|
initialize: (options) ->
|
||||||
|
_BaseView.prototype.initialize.apply @, [options]
|
||||||
|
@collection.bind 'reset', @render.bind @
|
||||||
|
|
||||||
|
render: () ->
|
||||||
|
@el.html(_.template(@template)({'products': @collection.toJSON()}))
|
||||||
|
@
|
||||||
|
|
||||||
|
|
||||||
|
class ProductView extends _BaseView
|
||||||
|
className: 'productitemview'
|
||||||
|
template: $("#store_item_template").html()
|
||||||
|
initialize: (options) ->
|
||||||
|
_BaseView.prototype.initialize.apply @, [options]
|
||||||
|
@itemcollection = options.itemcollection
|
||||||
|
|
||||||
|
events:
|
||||||
|
"keypress .uqf" : "updateOnEnter"
|
||||||
|
"click .uq" : "update"
|
||||||
|
|
||||||
|
update: (e) ->
|
||||||
|
e.preventDefault()
|
||||||
|
@itemcollection.updateItemForProduct @model, parseInt(@$('.uqf').val())
|
||||||
|
|
||||||
|
updateOnEnter: (e) ->
|
||||||
|
@update(e) if e.keyCode == 13
|
||||||
|
|
||||||
|
render: () ->
|
||||||
|
@el.html(_.template(@template)(@model.toJSON()));
|
||||||
|
@
|
||||||
|
|
||||||
|
|
||||||
|
class CartWidget extends Backbone.View
|
||||||
|
el: $('.cart-info')
|
||||||
|
template: $('#store_cart_template').html()
|
||||||
|
|
||||||
|
initialize: () ->
|
||||||
|
@collection.bind 'update', @render.bind @
|
||||||
|
|
||||||
|
render: () ->
|
||||||
|
tel = @$el.html _.template(@template)({
|
||||||
|
'count': @collection.getTotalCount()
|
||||||
|
'cost': @collection.getTotalCost()
|
||||||
|
})
|
||||||
|
tel.animate({paddingTop: '30px'}).animate({paddingTop: '10px'})
|
||||||
|
@
|
||||||
|
|
||||||
|
|
||||||
|
class BackboneStore extends Backbone.Router
|
||||||
|
views: {}
|
||||||
|
products: null
|
||||||
|
cart: null
|
||||||
|
|
||||||
|
routes:
|
||||||
|
"": "index"
|
||||||
|
"item/:id": "product"
|
||||||
|
|
||||||
|
initialize: (data) ->
|
||||||
|
@cart = new ItemCollection()
|
||||||
|
new CartWidget
|
||||||
|
collection: @cart
|
||||||
|
|
||||||
|
@products = new ProductCollection [],
|
||||||
|
url: 'data/items.json'
|
||||||
|
@views =
|
||||||
|
'_index': new ProductListView
|
||||||
|
collection: @products
|
||||||
|
$.when(@products.fetch({reset: true}))
|
||||||
|
.then(() -> window.location.hash = '')
|
||||||
|
@
|
||||||
|
|
||||||
|
hideAllViews: () ->
|
||||||
|
_.select(_.map(@views, (v) -> return v.hide()),
|
||||||
|
(t) -> t != null)
|
||||||
|
|
||||||
|
|
||||||
|
index: () ->
|
||||||
|
view = @views['_index']
|
||||||
|
$.when.apply($, @hideAllViews()).then(() -> view.show())
|
||||||
|
|
||||||
|
product: (id) ->
|
||||||
|
product = @products.detect (p) -> p.get('id') == (id)
|
||||||
|
view = (@views['item.' + id] ||= new ProductView(
|
||||||
|
model: product,
|
||||||
|
itemcollection: @cart
|
||||||
|
).render())
|
||||||
|
$.when(@hideAllViews()).then(
|
||||||
|
() -> view.show())
|
||||||
|
|
||||||
|
|
||||||
|
$(document).ready () ->
|
||||||
|
new BackboneStore();
|
||||||
|
Backbone.history.start();
|
Loading…
Reference in New Issue