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
@ -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>
</li>
<div class="item-artist"><%= p.artist %></div> <div class="item-artist"><%= p.artist %></div>
<div class="item-title"><%= p.title %></div> <div class="item-title"><%= p.title %></div>
<div class="item-price">$<%= p.price %></div> <div class="item-price">$<%= p.price %></div>
</li>
<% } %> <% } %>
</ul> </ul>
</script> </script>
@ -50,6 +50,7 @@
<p>Items: <%= count %> ($<%= cost %>)</p> <p>Items: <%= count %> ($<%= cost %>)</p>
</script> </script>
</head> </head>
</head>
<body> <body>
<div id="container"> <div id="container">
<div id="header"> <div id="header">

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();

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