$30 off During Our Annual Pro Sale. View Details »

SpreeConf Europe: Backbone.js & Spree

SpreeConf Europe: Backbone.js & Spree

A look a hypothetical Backbone.js front-end replacement for Spree Commerce, with sever-side EJS rendering.

Brian Quinn

August 23, 2012
Tweet

More Decks by Brian Quinn

Other Decks in Technology

Transcript

  1. View Slide

  2. Brian D. Quinn
    CTO & Co-founder Spree Commerce, Inc.
    github.com/bdq
    twitter.com/briandq

    View Slide

  3. BRUG

    View Slide

  4. Breakfast Roll User Group

    View Slide

  5. View Slide

  6. Using backbone.js
    with Spree

    View Slide

  7. Our Experience

    View Slide

  8. View Slide

  9. Theme Editor
    SpreeWorks.com

    View Slide

  10. Admin (Orders/Products)
    SpreeWorks.com

    View Slide

  11. View Slide

  12. Better API Needed

    View Slide

  13. Spree-API: improvements

    View Slide

  14. Overhauled Product & Order API’s
    Spree-API: improvements

    View Slide

  15. Overhauled Product & Order API’s
    Spree-API: improvements
    Included RABL for response templates

    View Slide

  16. Overhauled Product & Order API’s
    Spree-API: improvements
    Included RABL for response templates
    Swapped to Ransack for searching

    View Slide

  17. Overhauled Product & Order API’s
    Spree-API: improvements
    Included RABL for response templates
    Swapped to Ransack for searching
    Improved Authentication

    View Slide

  18. RABL
    https://github.com/nesquena/rabl

    View Slide

  19. 01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    class Spree::Api::BaseController < Spree::BaseController
    ...
    respond_to :json
    def index
    respond_with(@collection) do |format|
    format.json { render :json => @collection.to_json(serialization_options) }
    end
    end
    def show
    respond_with(@object) do |format|
    format.json { render :json => @object.to_json(serialization_options) }
    end
    end
    ...
    01. api/app/controllers/spree/api/base_controller.rb
    Spree-API: PRe-RABL

    View Slide

  20. 01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    class Spree::Api::BaseController < Spree::BaseController
    ...
    respond_to :json
    def index
    respond_with(@collection) do |format|
    format.json { render :json => @collection.to_json(serialization_options) }
    end
    end
    def show
    respond_with(@object) do |format|
    format.json { render :json => @object.to_json(serialization_options) }
    end
    end
    ...
    01. api/app/controllers/spree/api/base_controller.rb
    Spree-API: PRe-RABL

    View Slide

  21. 01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    object @product
    attributes *product_attributes
    child :variants_including_master => :variants do
    attributes *variant_attributes
    child :option_values => :option_values do
    attributes *option_value_attributes
    end
    end
    child :images => :images do
    extends "spree/api/v1/images/show"
    end
    child :option_types => :option_types do
    child :option_values => :option_values do
    attributes *option_value_attributes
    end
    end
    01. api/app/views/spree/api/v1/products/show.rabl
    Spree-API: With RABL

    View Slide

  22. 01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    object @product
    attributes *product_attributes
    child :variants_including_master => :variants do
    attributes *variant_attributes
    child :option_values => :option_values do
    attributes *option_value_attributes
    end
    end
    child :images => :images do
    extends "spree/api/v1/images/show"
    end
    child :option_types => :option_types do
    child :option_values => :option_values do
    attributes *option_value_attributes
    end
    end
    01. api/app/views/spree/api/v1/products/show.rabl
    Spree-API: With RABL

    View Slide

  23. 01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    object @product
    attributes *product_attributes
    child :variants_including_master => :variants do
    attributes *variant_attributes
    child :option_values => :option_values do
    attributes *option_value_attributes
    end
    end
    child :images => :images do
    extends "spree/api/v1/images/show"
    end
    child :option_types => :option_types do
    child :option_values => :option_values do
    attributes *option_value_attributes
    end
    end
    01. api/app/views/spree/api/v1/products/show.rabl
    Spree-API: With RABL

    View Slide

  24. 01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    object @product
    attributes *product_attributes
    child :variants_including_master => :variants do
    attributes *variant_attributes
    child :option_values => :option_values do
    attributes *option_value_attributes
    end
    end
    child :images => :images do
    extends "spree/api/v1/images/show"
    end
    child :option_types => :option_types do
    child :option_values => :option_values do
    attributes *option_value_attributes
    end
    end
    01. api/app/views/spree/api/v1/products/show.rabl
    Spree-API: With RABL

    View Slide

  25. Spree-API: Rabl Benefits

    View Slide

  26. Better control over JSON output
    Spree-API: Rabl Benefits

    View Slide

  27. Better control over JSON output
    Spree-API: Rabl Benefits
    Easily extendable by extensions / applications

    View Slide

  28. Better control over JSON output
    Spree-API: Rabl Benefits
    Easily extendable by extensions / applications
    Supports lots of formats

    View Slide

  29. Better control over JSON output
    Spree-API: Rabl Benefits
    Easily extendable by extensions / applications
    Supports lots of formats
    Great caching support

    View Slide

  30. Ransack
    https://github.com/nesquena/rabl

    View Slide

  31. Spree-API: RANSACK

    View Slide

  32. GET api/products/search?q[name_cont]=ruby
    Spree-API: RANSACK

    View Slide

  33. GET api/products/search?q[name_cont]=ruby
    Spree-API: RANSACK
    GET api/products/search?q[name_start]=Ruby

    View Slide

  34. GET api/products/search?q[name_cont]=ruby
    Spree-API: RANSACK
    GET api/products/search?q[master_price_lteq]=10.00
    GET api/products/search?q[name_start]=Ruby

    View Slide

  35. GET api/products/search?q[name_cont]=ruby
    Spree-API: RANSACK
    GET api/products/search?q[master_price_lteq]=10.00
    GET api/orders/search?q=[email_matches]=brian%25
    GET api/products/search?q[name_start]=Ruby

    View Slide

  36. GET api/products/search?q[name_cont]=ruby
    Spree-API: RANSACK
    GET api/products/search?q[master_price_lteq]=10.00
    GET api/orders/search?q=[email_matches]=brian%25
    GET api/orders/search?q=[completed_at_present]=1
    GET api/products/search?q[name_start]=Ruby

    View Slide

  37. GET api/products/search?q[name_cont]=ruby
    Spree-API: RANSACK
    GET api/products/search?q[master_price_lteq]=10.00
    GET api/orders/search?q=[email_matches]=brian%25
    GET api/orders/search?q=[completed_at_present]=1
    GET api/products/search?q[name_start]=Ruby
    GET api/products/search?q[taxons_id_eq]=12345

    View Slide

  38. GET api/products/search?q[name_cont]=ruby
    Spree-API: RANSACK
    GET api/products/search?q[master_price_lteq]=10.00
    GET api/orders/search?q=[email_matches]=brian%25
    GET api/orders/search?q=[completed_at_present]=1
    GET api/products/search?q[name_start]=Ruby
    GET api/products/search?q[taxons_id_eq]=12345
    GET api/products/search?q[variants_count_on_hand_gt]=5

    View Slide

  39. Spree 1.1

    View Slide

  40. BackBone.js
    &
    Core Admin

    View Slide

  41. Maybe?

    View Slide

  42. Sample Front End

    View Slide

  43. Target High traffic Pages
    Products
    Index
    Products
    Show
    Taxon
    Show
    Order
    Edit

    View Slide

  44. Target High traffic Pages
    Products
    Index
    Products
    Show
    Taxon
    Show
    Order
    Edit
    Checkout Log In/Out

    View Slide

  45. Target High traffic Pages
    Products
    Index
    Products
    Show
    Taxon
    Show
    Order
    Edit
    Checkout Log In/Out

    View Slide

  46. Models Collections Routers Views
    The BASICS: BACKbone.js
    Templates

    View Slide

  47. app/assets/javascripts/store
    The BASICS: Structure
    /models
    /collections
    /routers
    /views
    /products
    /templates
    /taxons
    /products
    /taxons
    /orders
    /orders

    View Slide

  48. The BASICS: MODELS

    View Slide

  49. The BASICS: MODELS
    Product

    View Slide

  50. The BASICS: MODELS
    Product Variant

    View Slide

  51. The BASICS: MODELS
    Product Variant
    Option
    Type

    View Slide

  52. The BASICS: MODELS
    Product Variant
    Option
    Type
    Option
    Value

    View Slide

  53. The BASICS: MODELS
    Product Variant Image
    Option
    Type
    Option
    Value

    View Slide

  54. The BASICS: MODELS
    Product Variant Image
    Option
    Type
    Option
    Value
    Taxonomy

    View Slide

  55. The BASICS: MODELS
    Product Variant Image
    Option
    Type
    Option
    Value
    Taxonomy Taxon

    View Slide

  56. The BASICS: MODELS
    Product Variant Image
    Option
    Type
    Option
    Value
    Taxonomy Taxon
    Order

    View Slide

  57. The BASICS: MODELS
    Product Variant Image
    Option
    Type
    Option
    Value
    Taxonomy Taxon
    Order LineItem

    View Slide

  58. The BASICS: MODELS
    Product Variant Image
    Option
    Type
    Option
    Value
    Taxonomy Taxon
    Order LineItem Adjustment

    View Slide

  59. The BASICS: MODELS
    Product Variant Image
    Option
    Type
    Option
    Value
    Taxonomy Taxon
    Order LineItem Adjustment Status

    View Slide

  60. 01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    Spree.Models.Product = Backbone.Model.extend({
    initialize: function(){
    this.build_associations();
    },
    build_associations: function(){
    this.images = new Spree.Collections.Images( this.get('images' ) );
    ...
    },
    ...
    });
    01. app/assets/javascripts/store/models/product.js
    The Basics: Models

    View Slide

  61. 01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    Spree.Models.Product = Backbone.Model.extend({
    initialize: function(){
    this.build_associations();
    },
    build_associations: function(){
    this.images = new Spree.Collections.Images( this.get('images' ) );
    ...
    },
    ...
    });
    01. app/assets/javascripts/store/models/product.js
    The Basics: Models

    View Slide

  62. 01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    Spree.Models.Product = Backbone.Model.extend({
    initialize: function(){
    this.build_associations();
    },
    build_associations: function(){
    this.images = new Spree.Collections.Images( this.get('images' ) );
    ...
    },
    ...
    });
    01. app/assets/javascripts/store/models/product.js
    The Basics: Models

    View Slide

  63. 01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    {
    id: 723959550,
    name: "Ruby on Rails Bag",
    description: "Lorem ipsum tincidunt egestas. Mauris nibh...",
    price: "22.99",
    available_on: "2012-06-05T13:02:05Z",
    permalink: "ruby-on-rails-bag",
    count_on_hand: 45,
    images: [{
    id: 3,
    position: 1,
    attachment_content_type: "image/jpeg",
    attachment_file_name: "ror_bag.jpeg",
    alt: null,
    viewable_type: "Spree::Variant",
    viewable_id: 504220342
    }]
    }
    01. Sample Products JSON Snippet
    The Basics: Models

    View Slide

  64. 01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    {
    id: 723959550,
    name: "Ruby on Rails Bag",
    description: "Lorem ipsum tincidunt egestas. Mauris nibh...",
    price: "22.99",
    available_on: "2012-06-05T13:02:05Z",
    permalink: "ruby-on-rails-bag",
    count_on_hand: 45,
    images: [{
    id: 3,
    position: 1,
    attachment_content_type: "image/jpeg",
    attachment_file_name: "ror_bag.jpeg",
    alt: null,
    viewable_type: "Spree::Variant",
    viewable_id: 504220342
    }]
    }
    01. Sample Products JSON Snippet
    The Basics: Models

    View Slide

  65. Product Variant Image
    Option
    Type
    Option
    Value
    Taxonomy Taxon
    Order LineItem Adjustment
    The BASICS: Collections

    View Slide

  66. Index Show
    Products =>
    The BASICS: Routers

    View Slide

  67. Index Show
    Products =>
    The BASICS: Routers
    Show
    Taxons =>

    View Slide

  68. Index Show
    Products =>
    Edit
    Orders =>
    The BASICS: Routers
    Show
    Taxons =>

    View Slide

  69. Index Show
    Products =>
    Edit
    Orders =>
    Show
    Taxons =>
    The BASICS: Views

    View Slide

  70. The BASICS: Templates
    ...

    View Slide

  71. 01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    //= require jquery
    //= require jquery_ujs
    //= require store/spree_core
    //= require store/spree_auth
    //= require store/spree_promo
    //= require jquery
    //= require underscore
    //= require backbone
    //= require store/spree
    //= require store/plumbing
    //= require store/paginated_collection
    //= require store/underscore_mixins
    //= require_tree ./models
    //= require_tree ./collections
    //= require_tree ./routers
    //= require_tree ./views
    //= require_tree ./templates
    01. app/assets/javascripts/store/all.js
    The Basics: all.js

    View Slide

  72. 01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    //= require jquery
    //= require jquery_ujs
    //= require store/spree_core
    //= require store/spree_auth
    //= require store/spree_promo
    //= require jquery
    //= require underscore
    //= require backbone
    //= require store/spree
    //= require store/plumbing
    //= require store/paginated_collection
    //= require store/underscore_mixins
    //= require_tree ./models
    //= require_tree ./collections
    //= require_tree ./routers
    //= require_tree ./views
    //= require_tree ./templates
    01. app/assets/javascripts/store/all.js
    The Basics: all.js

    View Slide

  73. 01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    //= require jquery
    //= require jquery_ujs
    //= require store/spree_core
    //= require store/spree_auth
    //= require store/spree_promo
    //= require jquery
    //= require underscore
    //= require backbone
    //= require store/spree
    //= require store/plumbing
    //= require store/paginated_collection
    //= require store/underscore_mixins
    //= require_tree ./models
    //= require_tree ./collections
    //= require_tree ./routers
    //= require_tree ./views
    //= require_tree ./templates
    01. app/assets/javascripts/store/all.js
    The Basics: all.js

    View Slide

  74. 01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    //= require jquery
    //= require jquery_ujs
    //= require store/spree_core
    //= require store/spree_auth
    //= require store/spree_promo
    //= require jquery
    //= require underscore
    //= require backbone
    //= require store/spree
    //= require store/plumbing
    //= require store/paginated_collection
    //= require store/underscore_mixins
    //= require_tree ./models
    //= require_tree ./collections
    //= require_tree ./routers
    //= require_tree ./views
    //= require_tree ./templates
    01. app/assets/javascripts/store/all.js
    The Basics: all.js

    View Slide

  75. 01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    //= require jquery
    //= require jquery_ujs
    //= require store/spree_core
    //= require store/spree_auth
    //= require store/spree_promo
    //= require jquery
    //= require underscore
    //= require backbone
    //= require store/spree
    //= require store/plumbing
    //= require store/paginated_collection
    //= require store/underscore_mixins
    //= require_tree ./models
    //= require_tree ./collections
    //= require_tree ./routers
    //= require_tree ./views
    //= require_tree ./templates
    01. app/assets/javascripts/store/all.js
    The Basics: all.js

    View Slide

  76. 01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    var Spree = {
    Views: { Shared: {}, Products: {}, Taxons: {} ,Orders: {} },
    Routers: { _active: {} },
    Models: {},
    Collections: {},
    Data: { _preload: {}, products: null, taxonomies: null },
    current_order: null,
    current_user: null,
    init: function(){
    ...
    }
    });
    01. app/assets/javascripts/store/spree.js
    The Basics: Spree Object

    View Slide

  77. 01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    var Spree = {
    Views: { Shared: {}, Products: {}, Taxons: {} ,Orders: {} },
    Routers: { _active: {} },
    Models: {},
    Collections: {},
    Data: { _preload: {}, products: null, taxonomies: null },
    current_order: null,
    current_user: null,
    init: function(){
    ...
    }
    });
    01. app/assets/javascripts/store/spree.js
    The Basics: Spree Object

    View Slide

  78. 01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    var Spree = {
    Views: { Shared: {}, Products: {}, Taxons: {} ,Orders: {} },
    Routers: { _active: {} },
    Models: {},
    Collections: {},
    Data: { _preload: {}, products: null, taxonomies: null },
    current_order: null,
    current_user: null,
    init: function(){
    ...
    }
    });
    01. app/assets/javascripts/store/spree.js
    The Basics: Spree Object

    View Slide

  79. 01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    var Spree = {
    Views: { Shared: {}, Products: {}, Taxons: {} ,Orders: {} },
    Routers: { _active: {} },
    Models: {},
    Collections: {},
    Data: { _preload: {}, products: null, taxonomies: null },
    current_order: null,
    current_user: null,
    init: function(){
    ...
    }
    });
    01. app/assets/javascripts/store/spree.js
    The Basics: Spree Object

    View Slide

  80. 01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    var Spree = {
    Views: { Shared: {}, Products: {}, Taxons: {} ,Orders: {} },
    Routers: { _active: {} },
    Models: {},
    Collections: {},
    Data: { _preload: {}, products: null, taxonomies: null },
    current_order: null,
    current_user: null,
    init: function(){
    ...
    }
    });
    01. app/assets/javascripts/store/spree.js
    The Basics: Spree Object

    View Slide

  81. 01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    var Spree = {
    Views: { Shared: {}, Products: {}, Taxons: {} ,Orders: {} },
    Routers: { _active: {} },
    Models: {},
    Collections: {},
    Data: { _preload: {}, products: null, taxonomies: null },
    current_order: null,
    current_user: null,
    init: function(){
    ...
    }
    });
    01. app/assets/javascripts/store/spree.js
    The Basics: Spree Object

    View Slide

  82. 01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    init: function(){
    if(this.Data._preload.products!=undefined){
    //preload products
    this.Data.products = new this.Collections.Products();
    this.Data.products.resetWithPagination(this.Data._preload.products);
    }
    this.Routers._active.products = new this.Routers.Products();
    this.Routers._active.taxons = new this.Routers.Taxons();
    this.Routers._active.orders = new this.Routers.Orders();
    Backbone.history.start({pushState: true})
    $('a[data-push-state]').click(this._navigate);
    this.status = new this.Models.Status()
    this.status.fetch({success: this.set_state});
    }
    01. app/assets/javascripts/store/spree.js
    The Basics: Spree Object

    View Slide

  83. 01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    init: function(){
    if(this.Data._preload.products!=undefined){
    //preload products
    this.Data.products = new this.Collections.Products();
    this.Data.products.resetWithPagination(this.Data._preload.products);
    }
    this.Routers._active.products = new this.Routers.Products();
    this.Routers._active.taxons = new this.Routers.Taxons();
    this.Routers._active.orders = new this.Routers.Orders();
    Backbone.history.start({pushState: true})
    $('a[data-push-state]').click(this._navigate);
    this.status = new this.Models.Status()
    this.status.fetch({success: this.set_state});
    }
    01. app/assets/javascripts/store/spree.js
    The Basics: Spree Object

    View Slide

  84. 01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    init: function(){
    if(this.Data._preload.products!=undefined){
    //preload products
    this.Data.products = new this.Collections.Products();
    this.Data.products.resetWithPagination(this.Data._preload.products);
    }
    this.Routers._active.products = new this.Routers.Products();
    this.Routers._active.taxons = new this.Routers.Taxons();
    this.Routers._active.orders = new this.Routers.Orders();
    Backbone.history.start({pushState: true})
    $('a[data-push-state]').click(this._navigate);
    this.status = new this.Models.Status()
    this.status.fetch({success: this.set_state});
    }
    01. app/assets/javascripts/store/spree.js
    The Basics: Spree Object

    View Slide

  85. 01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    init: function(){
    if(this.Data._preload.products!=undefined){
    //preload products
    this.Data.products = new this.Collections.Products();
    this.Data.products.resetWithPagination(this.Data._preload.products);
    }
    this.Routers._active.products = new this.Routers.Products();
    this.Routers._active.taxons = new this.Routers.Taxons();
    this.Routers._active.orders = new this.Routers.Orders();
    Backbone.history.start({pushState: true})
    $('a[data-push-state]').click(this._navigate);
    this.status = new this.Models.Status()
    this.status.fetch({success: this.set_state});
    }
    01. app/assets/javascripts/store/spree.js
    The Basics: Spree Object

    View Slide

  86. 01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    init: function(){
    if(this.Data._preload.products!=undefined){
    //preload products
    this.Data.products = new this.Collections.Products();
    this.Data.products.resetWithPagination(this.Data._preload.products);
    }
    this.Routers._active.products = new this.Routers.Products();
    this.Routers._active.taxons = new this.Routers.Taxons();
    this.Routers._active.orders = new this.Routers.Orders();
    Backbone.history.start({pushState: true})
    $('a[data-push-state]').click(this._navigate);
    this.status = new this.Models.Status()
    this.status.fetch({success: this.set_state});
    }
    01. app/assets/javascripts/store/spree.js
    The Basics: Spree Object

    View Slide

  87. The BASICS: push state
    /products/ruby-on-rails-mug
    RAILS URL:

    View Slide

  88. The BASICS: push state
    /products/ruby-on-rails-mug
    RAILS URL:
    #products/ruby-on-rails-mug
    HASH Anchor:

    View Slide

  89. The BASICS: push state
    /products/ruby-on-rails-mug
    RAILS URL:
    #products/ruby-on-rails-mug
    HASH Anchor:
    /products/ruby-on-rails-mug
    PUSH STATE:

    View Slide

  90. The BASICS: push state
    /products/ruby-on-rails-mug
    RAILS URL:
    #products/ruby-on-rails-mug
    HASH Anchor:
    /products/ruby-on-rails-mug
    PUSH STATE:

    View Slide

  91. 01
    02
    03
    04
    _navigate: function(evt){
    evt.preventDefault();
    Spree.Routers._active.products.navigate($(evt.currentTarget).attr('href'), true);
    }
    02. spree.js _navigate() function
    01
    02
    03
    04
    05
    init: function(){
    ...
    $('a[data-push-state]').click(this._navigate);
    ...
    }
    01. spree.js init() function
    The BASICS: push state

    View Slide

  92. 01
    02
    03
    04
    _navigate: function(evt){
    evt.preventDefault();
    Spree.Routers._active.products.navigate($(evt.currentTarget).attr('href'), true);
    }
    02. spree.js _navigate() function
    01
    02
    03
    04
    05
    init: function(){
    ...
    $('a[data-push-state]').click(this._navigate);
    ...
    }
    01. spree.js init() function
    The BASICS: push state

    View Slide

  93. 01
    02
    03
    04
    _navigate: function(evt){
    evt.preventDefault();
    Spree.Routers._active.products.navigate($(evt.currentTarget).attr('href'), true);
    }
    02. spree.js _navigate() function
    01
    02
    03
    04
    05
    init: function(){
    ...
    $('a[data-push-state]').click(this._navigate);
    ...
    }
    01. spree.js init() function
    The BASICS: push state

    View Slide

  94. Index Show
    Products =>
    The BASICS: Templates

    View Slide

  95. Index Show
    Products =>
    The BASICS: Templates
    Pagination

    View Slide

  96. Index Show
    Products =>
    The BASICS: Templates
    Pagination
    Show
    Taxons =>

    View Slide

  97. Index Show
    Edit
    Products =>
    Orders =>
    The BASICS: Templates
    Pagination
    Show
    Taxons =>

    View Slide

  98. Index Show
    Edit
    Products =>
    Orders =>
    The BASICS: Templates
    Pagination
    Cart
    Link
    Account
    Links
    Show
    Taxons =>

    View Slide

  99. EJS

    View Slide

  100. 01
    02
    03
    04
    05
    06
    07

    <% if variant.images.length == 0 %>
    <%= link_to small_image(variant.product), variant.product %>
    <% else %>
    <%= link_to image_tag(variant.images.first.attachment.url(:small)) ...
    <% end %>

    01. Original HTML.ERB Template
    01
    02
    03
    04
    05
    06
    07

    <% if(line_item.product.images.size()>0){ %>

    View Slide

  101. CODE SMELL?

    View Slide

  102. IDEA!

    View Slide

  103. Render EJS
    Server Side

    View Slide

  104. Rabbit Hole

    View Slide

  105. Rabbit Hole
    ME

    View Slide

  106. Scribble

    View Slide

  107. Scribble: Concept
    The Ruby Racer
    ActionController::Responder

    View Slide

  108. Scribble: REsponder
    Request

    View Slide

  109. Scribble: REsponder
    Request
    JST.EJS
    File Exists?

    View Slide

  110. Scribble: REsponder
    Request
    JST.EJS
    File Exists?
    Scribble
    JST.EJS
    YES

    View Slide

  111. Scribble: REsponder
    Request
    JST.EJS
    File Exists?
    Scribble
    JST.EJS
    YES
    Rails
    HTML.ERB
    No

    View Slide

  112. Scribble: REsponder
    Request Products => Index

    View Slide

  113. Scribble: REsponder
    Request Products => Index
    JST.EJS
    File Exists?
    app/views/spree/products/index.jst.ejs

    View Slide

  114. Scribble: REsponder
    Request
    Render
    Products => Index
    JST.EJS
    File Exists?
    app/views/spree/products/index.jst.ejs

    View Slide

  115. 01
    02
    03
    04
    05
    06
    07
    <% var products = new Spree.Collections.Products() %>
    <% products.resetWithPagination(JSON.parse(get('products_json'))) %>
    <%= JST['store/templates/products/index']({ products: products }) %>
    <br/>Spree.Data._preload.products = <%= get('products_json') %>;<br/>
    01. app/views/spree/products/index.jst.ejs
    Scribble: Example Server Side JST.EJS

    View Slide

  116. 01
    02
    03
    04
    05
    06
    07
    <% var products = new Spree.Collections.Products() %>
    <% products.resetWithPagination(JSON.parse(get('products_json'))) %>
    <%= JST['store/templates/products/index']({ products: products }) %>
    <br/>Spree.Data._preload.products = <%= get('products_json') %>;<br/>
    01. app/views/spree/products/index.jst.ejs
    Scribble: Example Server Side JST.EJS

    View Slide

  117. 01
    02
    03
    04
    05
    06
    07
    <% var products = new Spree.Collections.Products() %>
    <% products.resetWithPagination(JSON.parse(get('products_json'))) %>
    <%= JST['store/templates/products/index']({ products: products }) %>
    <br/>Spree.Data._preload.products = <%= get('products_json') %>;<br/>
    01. app/views/spree/products/index.jst.ejs
    Scribble: Example Server Side JST.EJS

    View Slide

  118. 01
    02
    03
    04
    05
    06
    07
    <% var products = new Spree.Collections.Products() %>
    <% products.resetWithPagination(JSON.parse(get('products_json'))) %>
    <%= JST['store/templates/products/index']({ products: products }) %>
    <br/>Spree.Data._preload.products = <%= get('products_json') %>;<br/>
    01. app/views/spree/products/index.jst.ejs
    Scribble: Example Server Side JST.EJS

    View Slide

  119. 01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    module Spree
    ProductsController.class_eval do
    helper Spree::Api::ApiHelpers
    def index
    @searcher = Config.searcher_class.new(params)
    @products = @searcher.retrieve_products
    @products_json = render_to_string(:file => 'spree/api/v1/products/index')
    respond_with(@products)
    end
    01. app/controllers/spree/products_contoller_decorator.rb
    Scribble: Getting JSON from Actions

    View Slide

  120. 01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    module Spree
    ProductsController.class_eval do
    helper Spree::Api::ApiHelpers
    def index
    @searcher = Config.searcher_class.new(params)
    @products = @searcher.retrieve_products
    @products_json = render_to_string(:file => 'spree/api/v1/products/index')
    respond_with(@products)
    end
    01. app/controllers/spree/products_contoller_decorator.rb
    Scribble: Getting JSON from Actions

    View Slide

  121. 01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    source:
    - vendor/assets/javascripts/underscore.js
    - vendor/assets/javascripts/backbone.js
    - app/assets/javascripts/store/spree.js
    - app/assets/javascripts/store/paginated_collection.js
    - app/assets/javascripts/store/underscore_mixins.js
    - app/assets/javascripts/store/models/product.js
    - app/assets/javascripts/store/models/variant.js
    - ...
    - app/assets/javascripts/store/models/adjustment.js
    - app/assets/javascripts/store/collections/products.js
    - app/assets/javascripts/store/collections/variants.js
    - ...
    - app/assets/javascripts/store/collections/images.js
    layout: store/templates/layouts/spree_application
    01. config/scribble.yaml
    Scribble: Configuration

    View Slide

  122. 01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    source:
    - vendor/assets/javascripts/underscore.js
    - vendor/assets/javascripts/backbone.js
    - app/assets/javascripts/store/spree.js
    - app/assets/javascripts/store/paginated_collection.js
    - app/assets/javascripts/store/underscore_mixins.js
    - app/assets/javascripts/store/models/product.js
    - app/assets/javascripts/store/models/variant.js
    - ...
    - app/assets/javascripts/store/models/adjustment.js
    - app/assets/javascripts/store/collections/products.js
    - app/assets/javascripts/store/collections/variants.js
    - ...
    - app/assets/javascripts/store/collections/images.js
    layout: store/templates/layouts/spree_application
    01. config/scribble.yaml
    Scribble: Configuration

    View Slide

  123. 01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    source:
    - vendor/assets/javascripts/underscore.js
    - vendor/assets/javascripts/backbone.js
    - app/assets/javascripts/store/spree.js
    - app/assets/javascripts/store/paginated_collection.js
    - app/assets/javascripts/store/underscore_mixins.js
    - app/assets/javascripts/store/models/product.js
    - app/assets/javascripts/store/models/variant.js
    - ...
    - app/assets/javascripts/store/models/adjustment.js
    - app/assets/javascripts/store/collections/products.js
    - app/assets/javascripts/store/collections/variants.js
    - ...
    - app/assets/javascripts/store/collections/images.js
    layout: store/templates/layouts/spree_application
    01. config/scribble.yaml
    Scribble: Configuration

    View Slide

  124. 01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    if template.exist?
    ctx = Scribble::Context.new(@controller)
    compile = ctx.load_templates
    compile << %Q{var _view = #{EJS.compile(Pathname.new(template).read)};
    var layout = JST['#{Scribble::Config.layout}'];
    layout()}
    @controller.response_body = ctx.js_context.eval(compile)
    else
    super #fallback to built rails method
    end
    01. lib/scribble/responder.rb
    Scribble: ActionController::responder

    View Slide

  125. 01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    if template.exist?
    ctx = Scribble::Context.new(@controller)
    compile = ctx.load_templates
    compile << %Q{var _view = #{EJS.compile(Pathname.new(template).read)};
    var layout = JST['#{Scribble::Config.layout}'];
    layout()}
    @controller.response_body = ctx.js_context.eval(compile)
    else
    super #fallback to built rails method
    end
    01. lib/scribble/responder.rb
    Scribble: ActionController::responder

    View Slide

  126. 01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    if template.exist?
    ctx = Scribble::Context.new(@controller)
    compile = ctx.load_templates
    compile << %Q{var _view = #{EJS.compile(Pathname.new(template).read)};
    var layout = JST['#{Scribble::Config.layout}'];
    layout()}
    @controller.response_body = ctx.js_context.eval(compile)
    else
    super #fallback to built rails method
    end
    01. lib/scribble/responder.rb
    Scribble: ActionController::responder

    View Slide

  127. 01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    if template.exist?
    ctx = Scribble::Context.new(@controller)
    compile = ctx.load_templates
    compile << %Q{var _view = #{EJS.compile(Pathname.new(template).read)};
    var layout = JST['#{Scribble::Config.layout}'];
    layout()}
    @controller.response_body = ctx.js_context.eval(compile)
    else
    super #fallback to built rails method
    end
    01. lib/scribble/responder.rb
    Scribble: ActionController::responder

    View Slide

  128. 01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    if template.exist?
    ctx = Scribble::Context.new(@controller)
    compile = ctx.load_templates
    compile << %Q{var _view = #{EJS.compile(Pathname.new(template).read)};
    var layout = JST['#{Scribble::Config.layout}'];
    layout()}
    @controller.response_body = ctx.js_context.eval(compile)
    else
    super #fallback to built rails method
    end
    01. lib/scribble/responder.rb
    Scribble: ActionController::responder

    View Slide

  129. 01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    if template.exist?
    ctx = Scribble::Context.new(@controller)
    compile = ctx.load_templates
    compile << %Q{var _view = #{EJS.compile(Pathname.new(template).read)};
    var layout = JST['#{Scribble::Config.layout}'];
    layout()}
    @controller.response_body = ctx.js_context.eval(compile)
    else
    super #fallback to built rails method
    end
    01. lib/scribble/responder.rb
    Scribble: ActionController::responder

    View Slide

  130. 01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20

    <%= get('title') %>
    <%= get('csrf_meta_tags') %>
    <%= get('javascript_include_tag', 'store/all') %>
    <br/>Spree.api_key = '<%= get('api_key') %>';<br/>$(function() { Spree.init(); });<br/>



    <%= JST['store/templates/shared/header']() %>

    <%= _view() %>

    <%= JST['store/templates/shared/footer']() %>


    01. app/assets/javascripts/store/templates/layouts/spree_application.jst.ejs
    Scribble: LAyout

    View Slide

  131. 01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20

    <%= get('title') %>
    <%= get('csrf_meta_tags') %>
    <%= get('javascript_include_tag', 'store/all') %>
    <br/>Spree.api_key = '<%= get('api_key') %>';<br/>$(function() { Spree.init(); });<br/>



    <%= JST['store/templates/shared/header']() %>

    <%= _view() %>

    <%= JST['store/templates/shared/footer']() %>


    01. app/assets/javascripts/store/templates/layouts/spree_application.jst.ejs
    Scribble: LAyout

    View Slide

  132. 01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20

    <%= get('title') %>
    <%= get('csrf_meta_tags') %>
    <%= get('javascript_include_tag', 'store/all') %>
    <br/>Spree.api_key = '<%= get('api_key') %>';<br/>$(function() { Spree.init(); });<br/>



    <%= JST['store/templates/shared/header']() %>

    <%= _view() %>

    <%= JST['store/templates/shared/footer']() %>


    01. app/assets/javascripts/store/templates/layouts/spree_application.jst.ejs
    Scribble: LAyout

    View Slide

  133. 01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20

    <%= get('title') %>
    <%= get('csrf_meta_tags') %>
    <%= get('javascript_include_tag', 'store/all') %>
    <br/>Spree.api_key = '<%= get('api_key') %>';<br/>$(function() { Spree.init(); });<br/>



    <%= JST['store/templates/shared/header']() %>

    <%= _view() %>

    <%= JST['store/templates/shared/footer']() %>


    01. app/assets/javascripts/store/templates/layouts/spree_application.jst.ejs
    Scribble: LAyout

    View Slide

  134. Demo time

    View Slide

  135. WHY?

    View Slide

  136. 1. DRY

    View Slide

  137. 2. SEO

    View Slide

  138. View Slide

  139. View Slide

  140. View Slide

  141. 3. Progressive Enhancement

    View Slide

  142. 4. Better User Experience

    View Slide

  143. 5. SCALABILITY

    View Slide

  144. 6. Because you can

    View Slide

  145. WHY NOT?

    View Slide

  146. 1. MEMORY HOG

    View Slide

  147. 2. MEMORY HOg

    View Slide

  148. 3. MEMORY
    HOG

    View Slide

  149. View Slide

  150. View Slide

  151. Workarounds

    View Slide

  152. Implementation

    View Slide

  153. Alternative JS Engines

    View Slide

  154. Server Configuration

    View Slide

  155. Page Caching

    View Slide

  156. Caching High traffic Pages
    Products
    Index
    Products
    Show
    Taxon
    Show
    Orders
    Edit

    View Slide

  157. The BASICS: MODELS
    Product Variant Image
    Option
    Type
    Option
    Value
    Taxonomy Taxon
    Order LineItem Adjustment Status

    View Slide

  158. 01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    {
    user: { id: 1 },
    order: { id: 1234,
    number: 'R716022188',
    item_total: '16.99',
    state: 'cart',
    ...
    line_items: [{...}]
    }
    }
    01. /status.json snippet
    Status: JSON

    View Slide

  159. 01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    {
    user: { id: 1 },
    order: { id: 1234,
    number: 'R716022188',
    item_total: '16.99',
    state: 'cart',
    ...
    line_items: [{...}]
    }
    }
    01. /status.json snippet
    Status: JSON

    View Slide

  160. 01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    {
    user: { id: 1 },
    order: { id: 1234,
    number: 'R716022188',
    item_total: '16.99',
    state: 'cart',
    ...
    line_items: [{...}]
    }
    }
    01. /status.json snippet
    Status: JSON

    View Slide

  161. 01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    {
    user: { id: 1 },
    order: { id: 1234,
    number: 'R716022188',
    item_total: '16.99',
    state: 'cart',
    ...
    line_items: [{...}]
    }
    }
    01. /status.json snippet
    Status: JSON

    View Slide

  162. View Slide

  163. View Slide

  164. View Slide

  165. View Slide

  166. View Slide

  167. aPI Caching

    View Slide

  168. 01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    object @taxon
    cache @taxon
    attributes *taxon_attributes
    node do |t|
    child t.children => :taxons do
    attributes *taxon_attributes
    end
    end
    01. /api/app/views/spree/v1/taxons/show.rabl
    API caching: Rabl

    View Slide

  169. 01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    object @taxon
    cache @taxon
    attributes *taxon_attributes
    node do |t|
    child t.children => :taxons do
    attributes *taxon_attributes
    end
    end
    01. /api/app/views/spree/v1/taxons/show.rabl
    API caching: Rabl

    View Slide

  170. 01
    02
    03
    Started GET "/api/products/search?q%5Btaxons_id_eq%5D=558398358&page=1" for
    127.0.0.1 at 2012-08-18 16:56:33 +0100
    Rendered spree/api/v1/products/index.rabl (3.2ms)
    02. With Cache
    01
    02
    03
    Started GET "/api/products/search?q%5Btaxons_id_eq%5D=558398358&page=1" for
    127.0.0.1 at 2012-08-18 16:56:05 +0100
    Rendered spree/api/v1/products/index.rabl (87.2ms)
    01. Without Cache
    Api Caching: performance

    View Slide

  171. 01
    02
    03
    Started GET "/api/products/search?q%5Btaxons_id_eq%5D=558398358&page=1" for
    127.0.0.1 at 2012-08-18 16:56:33 +0100
    Rendered spree/api/v1/products/index.rabl (3.2ms)
    02. With Cache
    01
    02
    03
    Started GET "/api/products/search?q%5Btaxons_id_eq%5D=558398358&page=1" for
    127.0.0.1 at 2012-08-18 16:56:05 +0100
    Rendered spree/api/v1/products/index.rabl (87.2ms)
    01. Without Cache
    Api Caching: performance

    View Slide

  172. Improvements

    View Slide

  173. Better Data Handling

    View Slide

  174. Cache Expiry

    View Slide

  175. ECO

    View Slide

  176. The Future

    View Slide

  177. Client Side Rendering

    View Slide

  178. View Slide

  179. JS

    View Slide

  180. JS

    View Slide

  181. JS

    View Slide

  182. JS

    View Slide

  183. JS

    View Slide

  184. JS

    View Slide

  185. polyglot

    View Slide

  186. Deface.js

    View Slide

  187. scribble.spree.mx

    View Slide

  188. github.com/bdq/ejs_demo

    View Slide

  189. ?

    View Slide