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 NOTANGLE= notangle
NOWEAVE= noweave NOWEAVE= noweave
ECHO= /bin/echo ECHO= /bin/echo
STYLUS= stylus
HAML= haml
COFFEE= coffee
all: index.html store.js all: index.html store.js jsonstore.css
.nw.html: index.html: index.haml
$(NOWEAVE) -filter l2h -delay -x -index -autodefs c -html $*.nw > $*.html $(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: .nw.tex:
$(NOWEAVE) -x -delay $*.nw > $*.tex #$ $(NOWEAVE) -x -delay $*.nw > $*.tex #$
@ -19,47 +37,6 @@ all: index.html store.js
xelatex *$.tex; \ xelatex *$.tex; \
done 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: clean:
- rm -f *.tex *.dvi *.aux *.toc *.log *.out *.html *.js - 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 for our one-page application. The code below compiles beautifully
into the same HTML seen in the original Backbone Store. into the same HTML seen in the original Backbone Store.
<<index.html>>= <<index.haml>>=
!!! 5 !!! 5
%html{:xmlns => "http://www.w3.org/1999/xhtml"} %html{:xmlns => "http://www.w3.org/1999/xhtml"}
%head %head
@ -688,7 +688,7 @@ $(document).ready () ->
Here's the entirety of the program. Coffeescript provides its own Here's the entirety of the program. Coffeescript provides its own
namespace wrapper: namespace wrapper:
<<store.js>>= <<store.coffee>>=
<<product models>> <<product models>>
<<cart models>> <<cart models>>
@ -779,11 +779,16 @@ body
img img
border: 0 border: 0
#productlistview
ul
list-style: none
.item .item
float:left float:left
width: 250px width: 250px
margin-right: 3px margin-right: 10px
padding: 2px margin-bottom: 10px
padding: 5px
rounded(5px) rounded(5px)
border: 1px solid #ccc border: 1px solid #ccc
text-align:center text-align:center

View File

@ -3,7 +3,6 @@
%head %head
%title The Backbone Store %title The Backbone Store
%link{:charset => "utf-8", :href => "jsonstore.css", :rel => "stylesheet", :type => "text/css"}/ %link{:charset => "utf-8", :href => "jsonstore.css", :rel => "stylesheet", :type => "text/css"}/
%script#store_index_template(type="text/x-underscore-tmplate") %script#store_index_template(type="text/x-underscore-tmplate")
%h1 Product Catalog %h1 Product Catalog
%ul %ul
@ -12,9 +11,9 @@
.item-image .item-image
%a{:href => "#item/<%= p.id %>"} %a{:href => "#item/<%= p.id %>"}
%img{:src => "<%= p.image %>", :alt => "<%= p.title %>"}/ %img{:src => "<%= p.image %>", :alt => "<%= p.title %>"}/
.item-artist <%= p.artist %> .item-artist <%= p.artist %>
.item-title <%= p.title %> .item-title <%= p.title %>
.item-price $<%= p.price %> .item-price $<%= p.price %>
<% } %> <% } %>
%script#store_item_template(type= "text/x-underscore-template") %script#store_item_template(type= "text/x-underscore-template")
@ -37,10 +36,10 @@
%a(href="<%= url %>") Buy this item on Amazon %a(href="<%= url %>") Buy this item on Amazon
.back-link .back-link
%a(href="#") &laquo; Back to Items %a(href="#") &laquo; Back to Items
%script#store_cart_template(type="text/x-underscore-template") %script#store_cart_template(type="text/x-underscore-template")
%p Items: <%= count %> ($<%= cost %>) %p Items: <%= count %> ($<%= cost %>)
</head>
%body %body
#container #container
#header #header

View File

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

View File

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

463
store.js
View File

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