diff --git a/index.haml b/index.haml new file mode 100644 index 0000000..6db9d82 --- /dev/null +++ b/index.haml @@ -0,0 +1,54 @@ +!!! 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 + %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 %>) + + %body + #container + #header + %h1 + The Backbone Store + .cart-info + #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"} + %script{:src => "store.js", :type => "text/javascript"} diff --git a/store.coffee b/store.coffee new file mode 100644 index 0000000..455ae3a --- /dev/null +++ b/store.coffee @@ -0,0 +1,164 @@ +class Product extends Backbone.Model + +class ProductCollection extends Backbone.Collection + model: Product + + initialize: (models, options) -> + @url = options.url + @ + + comparator: (item) -> + item.get('title') + + +class Item extends Backbone.Model + update: (amount) -> + @set {quantity: @get('quantity') + amount} + +class ItemCollection extends Backbone.Collection + model: Item + + getOrCreateItemForProduct: (product) -> + pid = product.get('id') + i = this.detect (obj) -> (obj.get('product').get('id') == pid) + if (i) + return i + i = new Item + product: product + quantity: 0 + @add i, {silent: true} + i + + getTotalCount: () -> + addup = (memo, obj) -> obj.get('quantity') + memo + @reduce addup, 0 + + getTotalCost: () -> + addup = (memo, obj) -> + (obj.get('product').get('price') * + obj.get('quantity')) + memo + @reduce addup, 0 + +class _BaseView extends Backbone.View + parent: $('#main') + className: 'viewport' + + initialize: () -> + @el = $(@el) + @el.hide() + @parent.append(@el) + @ + + 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: () -> + if @el.is(':visible') + return + + promise = $.Deferred (dfd) => @el.fadeIn('fast', dfd.resolve) + @trigger 'show', @ + promise.promise() + + +class ProductListView extends _BaseView + id: 'productlistview' + template: $("#store_index_template").html() + + initialize: (options) -> + @constructor.__super__.initialize.apply @, [options] + @collection.bind 'reset', _.bind(@render, @) + + render: () -> + @el.html(_.template(@template, {'products': @collection.toJSON()})) + @ + +class ProductView extends _BaseView + id: 'productitemview' + template: $("#store_item_template").html() + initialize: (options) -> + @constructor.__super__.initialize.apply @, [options] + @itemcollection = options.itemcollection + @item = @itemcollection.getOrCreateItemForProduct @model + @ + + events: + "keypress .uqf" : "updateOnEnter" + "click .uq" : "update" + + update: (e) -> + e.preventDefault() + @item.update parseInt($('.uqf').val()) + + updateOnEnter: (e) -> + if (e.keyCode == 13) + @update e + + render: () -> + @el.html(_.template(@template, @model.toJSON())); + @ + +class CartWidget extends Backbone.View + el: $('.cart-info') + template: $('#store_cart_template').html() + + initialize: () -> + @collection.bind('change', _.bind(@render, @)); + + 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(@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();