Everything comes out of the Noweb file now.

This commit is contained in:
Elf M. Sternberg 2011-08-07 21:18:47 -07:00
parent 40df6a023f
commit b7603b24f8
8 changed files with 401 additions and 276 deletions

View File

@ -1,13 +1,31 @@
.SUFFIXES: .nw .js .pdf .html .tex
.SUFFIXES: .nw .js .pdf .html .tex .haml .css .stylus
NOTANGLE= notangle
NOWEAVE= noweave
ECHO= /bin/echo
STYLUS= stylus
HAML= haml
COFFEE= coffee
all: index.html store.js
all: index.html store.js jsonstore.css
.nw.html:
$(NOWEAVE) -filter l2h -delay -x -index -autodefs c -html $*.nw > $*.html
index.html: index.haml
$(HAML) --unix-newlines --no-escape-attrs --double-quote-attribute $*.haml > $*.html
index.haml: backbonestore.nw
$(NOTANGLE) -c -R$@ $< > $*.haml
jsonstore.css: jsonstore.styl
$(STYLUS) $*.styl
jsonstore.styl: backbonestore.nw
$(NOTANGLE) -c -R$@ $< > $@
store.js: store.coffee
$(COFFEE) --compile $<
store.coffee: backbonestore.nw
$(NOTANGLE) -c -R$@ $< > $@
.nw.tex:
$(NOWEAVE) -x -delay $*.nw > $*.tex #$
@ -19,47 +37,6 @@ all: index.html store.js
xelatex *$.tex; \
done
.nw.js:
@ $(ECHO) $(NOTANGLE) -c -R$@ $<
@ - $(NOTANGLE) -c -R$@ $< > $*.nw-js-tmp
@ if [ -s "$*.nw-js-tmp" ]; then \
mv $*.nw-js-tmp $@; \
else \
echo "$@ not found in $<"; \
rm $*.nw-js-tmp; \
fi
jsonstore.styl: backbonestore.nw
@ $(ECHO) $(NOTANGLE) -c -R$@ $<
@ - $(NOTANGLE) -c -R$@ $< > $*.nw-styl-tmp
@ if [ -s "$*.nw-styl-tmp" ]; then \
mv $*.nw-styl-tmp $@; \
else \
echo "$@ not found in $<"; \
rm $*.nw-styl-tmp; \
fi
store.js: backbonestore.nw
@ $(ECHO) $(NOTANGLE) -c -R$@ $<
@ - $(NOTANGLE) -c -R$@ $< > $*.nw-html-tmp
@ if [ -s "$*.nw-html-tmp" ]; then \
mv $*.nw-html-tmp $@; \
else \
echo "$@ not found in $<"; \
rm $*.nw-tmp; \
fi
index.html:
@ $(ECHO) $(NOTANGLE) -c -R$@ $<
@ - $(NOTANGLE) -c -R$@ $< > $*.nw-html-tmp
@ if [ -s "$*.nw-html-tmp" ]; then \
mv $*.nw-html-tmp $@; \
else \
echo "$@ not found in $<"; \
rm $*.nw-tmp; \
fi
clean:
- rm -f *.tex *.dvi *.aux *.toc *.log *.out *.html *.js

View File

@ -319,7 +319,7 @@ Before we move on, let's take a look at the HAML we're going to use
for our one-page application. The code below compiles beautifully
into the same HTML seen in the original Backbone Store.
<<index.html>>=
<<index.haml>>=
!!! 5
%html{:xmlns => "http://www.w3.org/1999/xhtml"}
%head
@ -688,7 +688,7 @@ $(document).ready () ->
Here's the entirety of the program. Coffeescript provides its own
namespace wrapper:
<<store.js>>=
<<store.coffee>>=
<<product models>>
<<cart models>>
@ -779,11 +779,16 @@ body
img
border: 0
#productlistview
ul
list-style: none
.item
float:left
width: 250px
margin-right: 3px
padding: 2px
margin-right: 10px
margin-bottom: 10px
padding: 5px
rounded(5px)
border: 1px solid #ccc
text-align:center

View File

@ -3,7 +3,6 @@
%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
@ -12,20 +11,20 @@
.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 %>
.item-artist <%= p.artist %>
.item-title <%= p.title %>
.item-price $<%= p.price %>
<% } %>
%script#store_item_template(type= "text/x-underscore-template")
.item-detail
.item-detail
.item-image
%img(src="<%= large_image %>" alt="<%= title %>")/
.item-info
.item-info
.item-artist <%= artist %>
.item-title <%= title %>
.item-price $<%= price %>
.item-form
.item-form
%form(action="#/cart" method="post")
%p
%label Quantity:
@ -37,17 +36,17 @@
%a(href="<%= url %>") Buy this item on Amazon
.back-link
%a(href="#") &laquo; 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
#main
%script{:src => "jquery-1.6.2.min.js", :type => "text/javascript"}
%script{:src => "underscore.js", :type => "text/javascript"}
%script{:src => "backbone.js", :type => "text/javascript"}

View File

@ -13,10 +13,10 @@
<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>
<div class="item-artist"><%= p.artist %></div>
<div class="item-title"><%= p.title %></div>
<div class="item-price">$<%= p.price %></div>
<% } %>
</ul>
</script>
@ -49,6 +49,7 @@
<script id="store_cart_template" type="text/x-underscore-template">
<p>Items: <%= count %> ($<%= cost %>)</p>
</script>
</head>
</head>
<body>
<div id="container">

View File

@ -29,11 +29,15 @@ body {
img {
border: 0;
}
#productlistview ul {
list-style: none;
}
.item {
float: left;
width: 250px;
margin-right: 3px;
padding: 2px;
margin-right: 10px;
margin-bottom: 10px;
padding: 5px;
-moz-border-radius-topleft: 5px;
-moz-border-radius-topright: 5px;
-moz-border-radius-bottomleft: 5px;

76
jsonstore.styl Normal file
View File

@ -0,0 +1,76 @@
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
body
font-family: "Lucida Grande", Lucida, Helvetica, Arial, sans-serif
background: #FFF
color: #333
margin: 0px
padding: 0px
#header
background: #C97E41
margin: 0px
padding: 20px
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: #714625
color: #FFF
font-size: 12px
font-weight: bold
img
border: 0
#productlistview
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
.item-image
float:left
.item-info
padding: 100px 10px 0px 10px

View File

@ -10,10 +10,13 @@ class ProductCollection extends Backbone.Collection
comparator: (item) ->
item.get('title')
class Item extends Backbone.Model
update: (amount) ->
@set {quantity: @get('quantity') + amount}
@set
quantity: @get('quantity')
price: () ->
@get('product').get('price') * @get('quantity')
class ItemCollection extends Backbone.Collection
model: Item
@ -34,10 +37,9 @@ class ItemCollection extends Backbone.Collection
@reduce addup, 0
getTotalCost: () ->
addup = (memo, obj) ->
(obj.get('product').get('price') *
obj.get('quantity')) + memo
@reduce addup, 0
addup = (memo, obj) ->obj.price() + memo
@reduce(addup, 0);
class _BaseView extends Backbone.View
parent: $('#main')
@ -52,11 +54,7 @@ class _BaseView extends Backbone.View
hide: () ->
if not @el.is(':visible')
return null
# Make a note here about how the => operator replaces the need for
# _.bind()
promise = $.Deferred (dfd) => @el.fadeOut('fast', dfd.resolve)
@trigger 'hide', @
promise.promise()
show: () ->
@ -64,7 +62,6 @@ class _BaseView extends Backbone.View
return
promise = $.Deferred (dfd) => @el.fadeIn('fast', dfd.resolve)
@trigger 'show', @
promise.promise()
@ -80,6 +77,7 @@ class ProductListView extends _BaseView
@el.html(_.template(@template, {'products': @collection.toJSON()}))
@
class ProductView extends _BaseView
id: 'productitemview'
template: $("#store_item_template").html()
@ -95,7 +93,7 @@ class ProductView extends _BaseView
update: (e) ->
e.preventDefault()
@item.update parseInt($('.uqf').val())
@item.update parseInt(@$('.uqf').val())
updateOnEnter: (e) ->
if (e.keyCode == 13)
@ -105,6 +103,7 @@ class ProductView extends _BaseView
@el.html(_.template(@template, @model.toJSON()));
@
class CartWidget extends Backbone.View
el: $('.cart-info')
template: $('#store_cart_template').html()
@ -119,6 +118,7 @@ class CartWidget extends Backbone.View
tel.animate({paddingTop: '30px'}).animate({paddingTop: '10px'})
@
class BackboneStore extends Backbone.Router
views: {}
products: null
@ -146,6 +146,7 @@ class BackboneStore extends Backbone.Router
_.select(_.map(@views, (v) -> return v.hide()),
(t) -> t != null)
index: () ->
view = @views['_index']
$.when(@hideAllViews()).then(() -> view.show())
@ -159,6 +160,7 @@ class BackboneStore extends Backbone.Router
$.when(@hideAllViews()).then(
() -> view.show())
$(document).ready () ->
new BackboneStore();
Backbone.history.start();

463
store.js
View File

@ -1,203 +1,264 @@
(function() {
var Product = Backbone.Model.extend({})
var ProductCollection = Backbone.Collection.extend({
model: Product,
initialize: function(models, options) {
this.url = options.url;
},
comparator: function(item) {
return item.get('title');
}
});
var Item = Backbone.Model.extend({
update: function(amount) {
this.set({'quantity': this.get('quantity') + amount});
}
});
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);
}
});
var _BaseView = Backbone.View.extend({
parent: $('#main'),
className: 'viewport',
initialize: function() {
this.el = $(this.el);
this.el.hide();
this.parent.append(this.el);
return this;
},
hide: function() {
if (this.el.is(":visible") === false) {
return null;
}
promise = $.Deferred(_.bind(function(dfd) {
this.el.fadeOut('fast', dfd.resolve)}, this)).promise();
this.trigger('hide', this);
return promise;
},
show: function() {
if (this.el.is(':visible')) {
return;
}
promise = $.Deferred(_.bind(function(dfd) {
this.el.fadeIn('fast', dfd.resolve) }, this)).promise();
this.trigger('show', this);
return promise;
}
});
var ProductListView = _BaseView.extend({
id: 'productlistview',
template: $("#store_index_template").html(),
initialize: function(options) {
this.constructor.__super__.initialize.apply(this, [options])
this.collection.bind('reset', _.bind(this.render, this));
},
render: function() {
this.el.html(_.template(this.template,
{'products': this.collection.toJSON()}))
return this;
}
});
var ProductView = _BaseView.extend({
id: 'productitemview',
template: $("#store_item_template").html(),
initialize: function(options) {
this.constructor.__super__.initialize.apply(this, [options])
this.itemcollection = options.itemcollection;
this.item = this.itemcollection.getOrCreateItemForProduct(this.model);
return this;
},
events: {
"keypress .uqf" : "updateOnEnter",
"click .uq" : "update",
},
update: function(e) {
e.preventDefault();
this.item.update(parseInt($('.uqf').val()));
},
updateOnEnter: function(e) {
if (e.keyCode == 13) {
return this.update(e);
}
},
render: function() {
this.el.html(_.template(this.template, this.model.toJSON()));
return this;
}
});
var CartWidget = Backbone.View.extend({
el: $('.cart-info'),
template: $('#store_cart_template').html(),
initialize: function() {
this.collection.bind('change', _.bind(this.render, this));
},
render: function() {
console.log(arguments);
this.el.html(
_.template(this.template, {
'count': this.collection.getTotalCount(),
'cost': this.collection.getTotalCost()
})).animate({paddingTop: '30px'})
.animate({paddingTop: '10px'});
}
});
var BackboneStore = Backbone.Router.extend({
views: {},
products: null,
cart: null,
routes: {
"": "index",
"item/:id": "product",
},
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() { window.location.hash = ''; });
return this;
},
hideAllViews: function () {
return _.select(
_.map(this.views, function(v) { return v.hide(); }),
function (t) { return t != null });
},
index: function() {
var view = this.views['_index'];
$.when(this.hideAllViews()).then(
function() { return view.show(); });
},
product: function(id) {
var product, v, view;
product = this.products.detect(function(p) { return p.get('id') == (id); })
view = ((v = this.views)['item.' + id]) || (v['item.' + id] = (
new ProductView({model: product,
itemcollection: this.cart}).render()));
$.when(this.hideAllViews()).then(
function() { view.show(); });
}
});
$(document).ready(function() {
new BackboneStore();
Backbone.history.start();
});
var BackboneStore, CartWidget, Item, ItemCollection, Product, ProductCollection, ProductListView, ProductView, _BaseView;
var __hasProp = Object.prototype.hasOwnProperty, __extends = 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;
}, __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
Product = (function() {
__extends(Product, Backbone.Model);
function Product() {
Product.__super__.constructor.apply(this, arguments);
}
return Product;
})();
ProductCollection = (function() {
__extends(ProductCollection, Backbone.Collection);
function ProductCollection() {
ProductCollection.__super__.constructor.apply(this, arguments);
}
ProductCollection.prototype.model = Product;
ProductCollection.prototype.initialize = function(models, options) {
this.url = options.url;
return this;
};
ProductCollection.prototype.comparator = function(item) {
return item.get('title');
};
return ProductCollection;
})();
Item = (function() {
__extends(Item, Backbone.Model);
function Item() {
Item.__super__.constructor.apply(this, arguments);
}
Item.prototype.update = function(amount) {
return this.set({
quantity: this.get('quantity')
});
};
Item.prototype.price = function() {
return this.get('product').get('price') * this.get('quantity');
};
return Item;
})();
ItemCollection = (function() {
__extends(ItemCollection, Backbone.Collection);
function ItemCollection() {
ItemCollection.__super__.constructor.apply(this, arguments);
}
ItemCollection.prototype.model = Item;
ItemCollection.prototype.getOrCreateItemForProduct = function(product) {
var i, pid;
pid = product.get('id');
i = this.detect(function(obj) {
return obj.get('product').get('id') === pid;
});
if (i) {
return i;
}
i = new Item({
product: product,
quantity: 0
});
this.add(i, {
silent: true
});
return i;
};
ItemCollection.prototype.getTotalCount = function() {
var addup;
addup = function(memo, obj) {
return obj.get('quantity') + memo;
};
return this.reduce(addup, 0);
};
ItemCollection.prototype.getTotalCost = function() {
var addup;
addup = function(memo, obj) {
return obj.price() + memo;
};
return this.reduce(addup, 0);
};
return ItemCollection;
})();
_BaseView = (function() {
__extends(_BaseView, Backbone.View);
function _BaseView() {
_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 promise;
if (!this.el.is(':visible')) {
return null;
}
promise = $.Deferred(__bind(function(dfd) {
return this.el.fadeOut('fast', dfd.resolve);
}, this));
return promise.promise();
};
_BaseView.prototype.show = function() {
var promise;
if (this.el.is(':visible')) {
return;
}
promise = $.Deferred(__bind(function(dfd) {
return this.el.fadeIn('fast', dfd.resolve);
}, this));
return promise.promise();
};
return _BaseView;
})();
ProductListView = (function() {
__extends(ProductListView, _BaseView);
function ProductListView() {
ProductListView.__super__.constructor.apply(this, arguments);
}
ProductListView.prototype.id = 'productlistview';
ProductListView.prototype.template = $("#store_index_template").html();
ProductListView.prototype.initialize = function(options) {
this.constructor.__super__.initialize.apply(this, [options]);
return this.collection.bind('reset', _.bind(this.render, this));
};
ProductListView.prototype.render = function() {
this.el.html(_.template(this.template, {
'products': this.collection.toJSON()
}));
return this;
};
return ProductListView;
})();
ProductView = (function() {
__extends(ProductView, _BaseView);
function ProductView() {
ProductView.__super__.constructor.apply(this, arguments);
}
ProductView.prototype.id = 'productitemview';
ProductView.prototype.template = $("#store_item_template").html();
ProductView.prototype.initialize = function(options) {
this.constructor.__super__.initialize.apply(this, [options]);
this.itemcollection = options.itemcollection;
this.item = this.itemcollection.getOrCreateItemForProduct(this.model);
return this;
};
ProductView.prototype.events = {
"keypress .uqf": "updateOnEnter",
"click .uq": "update"
};
ProductView.prototype.update = function(e) {
e.preventDefault();
return this.item.update(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;
})();
CartWidget = (function() {
__extends(CartWidget, Backbone.View);
function CartWidget() {
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('change', _.bind(this.render, 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;
})();
BackboneStore = (function() {
__extends(BackboneStore, Backbone.Router);
function BackboneStore() {
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(this.hideAllViews()).then(function() {
return view.show();
});
};
BackboneStore.prototype.product = function(id) {
var product, view, _base, _name;
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;
})();
$(document).ready(function() {
new BackboneStore();
return Backbone.history.start();
});
}).call(this);