Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Bridging the Client-side Server-side Divide

Bridging the Client-side Server-side Divide

Slides for my talk for the 2012 O'Reilly Fluent JavaScript Conference. This talk covers how Typekit built our new font browsing UI, taking advantage of MVVM (Model-View-ViewModel), declarative bindings, and client/server rendering to come up with a system that organized code across client and server and reduced duplication. Even though the server is in Ruby and the client code in JS, we were able to build an app-like client UI that degrades gracefully to a standard static webpages experience for clients that don't support JS.

Sean McBride

May 30, 2012
Tweet

More Decks by Sean McBride

Other Decks in Programming

Transcript

  1. Standley Chasm by Jon Wiley DIVIDE CLIENT-SIDE SERVER-SIDE Bridging the

    @smcbride #fluentconf #divided
  2. Sean McBride

  3. Sean McBride Engineer & Web Developer

  4. Sean McBride Engineer & Web Developer User Experience Web Developer

  5. empty conference room by MNicoleM

  6. ? empty conference room by MNicoleM

  7. DIVIDE? What do I mean by @smcbride #fluentconf #divided

  8. None
  9. None
  10. FAST FUN ACCESSIBLE

  11. CLIENT-SIDE SERVER-SIDE

  12. CLIENT-SIDE SERVER-SIDE Ruby jQuery Rails

  13. CLIENT-SIDE SERVER-SIDE Ruby ? Rails

  14. CLIENT-SIDE SERVER-SIDE

  15. CLIENT-SIDE SERVER-SIDE Java Python Ruby JS Application Behavior

  16. CLIENT-SIDE SERVER-SIDE Java Ruby Python JS Coffee Libraries API Application

  17. CLIENT-SIDE SERVER-SIDE Java Python Ruby JS Coffee Libs Application Application

  18. Application Application CLIENT-SIDE SERVER-SIDE Java Python Ruby JS Coffee Libs

    + SERVES JS AND NO-JS CLIENTS
  19. Application Application CLIENT-SIDE SERVER-SIDE Java Python Ruby JS Coffee Libs

    + GREATER UX FLEXIBILITY
  20. Application Application CLIENT-SIDE SERVER-SIDE Java Python Ruby JS Coffee Libs

    :( CODE DUPLICATION
  21. Application Application CLIENT-SIDE SERVER-SIDE Java Python Ruby JS Coffee Libs

    :( MOVING DATA BACK AND FORTH
  22. CLIENT-SIDE SERVER-SIDE Java Python Ruby JS Coffee Libs Application Application

  23. CLIENT-SIDE SERVER-SIDE JS Coffee Application Application

  24. CLIENT-SIDE SERVER-SIDE JS Coffee Node.js JS Coffee Node.js Application Application

  25. CLIENT-SIDE SERVER-SIDE Java Python Ruby JS Coffee Libs Application Application

  26. Application Application CLIENT-SIDE SERVER-SIDE Java Python Ruby JS Coffee Libs

    MVVM Model–View–ViewModel
  27. Application Application CLIENT-SIDE SERVER-SIDE Java Python Ruby JS Coffee Libs

    DECLARATIVE BINDINGS
  28. Application Application CLIENT-SIDE SERVER-SIDE Java Python Ruby JS Coffee Libs

    CLIENT/SERVER RENDERING
  29. None
  30. MVVM Better organization with @smcbride #fluentconf #divided

  31. JAVASCRIPT LIBRARIES

  32. Backbone.js SproutCore Sammy.js Spine.js Cappuccino Knockout.js Javascript MVC Google Web

    Toolkit Google Closure Ember.js Angular.js Batman.js Meteor Derby JAVASCRIPT LIBRARIES
  33. MVC MVP MVVM

  34. MVC Model + View + Controller MVP Model + View

    + Presenter MVVM Model + View + ViewModel
  35. MVC Model + View + Controller MVP Model + View

    + Presenter MVVM Model + View + ViewModel
  36. None
  37. CLIENT-SIDE SERVER-SIDE

  38. CLIENT-SIDE SERVER-SIDE MVC

  39. CLIENT-SIDE SERVER-SIDE MVC VVM M

  40. CLIENT-SIDE SERVER-SIDE MVC VVM

  41. CLIENT-SIDE SERVER-SIDE Model (ActiveRecord)

  42. CLIENT-SIDE SERVER-SIDE Model (ActiveRecord) Controller (ActionController)

  43. CLIENT-SIDE SERVER-SIDE Model (ActiveRecord) ViewModel (JS + Knockout) Controller (ActionController)

  44. CLIENT-SIDE SERVER-SIDE Model (ActiveRecord) ViewModel (JS + Knockout) ViewModel Adapter

    Controller (ActionController) (JSON)
  45. CLIENT-SIDE SERVER-SIDE Model (ActiveRecord) ViewModel (JS + Knockout) Template (HTML

    + Bindings) ViewModel Adapter Controller (ActionController) (JSON)
  46. Template (HTML + Bindings) CLIENT-SIDE SERVER-SIDE Model (ActiveRecord) ViewModel (JS

    + Knockout) ViewModel Adapter Controller (ActionController) (JSON)
  47. Ferrying data from server to client VIEWMODEL ADAPTERS

  48. Ferrying data from server to client VIEWMODEL ADAPTERS class User

    < ActiveRecord::Base validates_presence_of :billing_name end Server-side
  49. Ferrying data from server to client VIEWMODEL ADAPTERS class User

    < ActiveRecord::Base validates_presence_of :billing_name end module ViewModel::Person < ViewModel::Base def self.from_user(user) self.new( :name => user.billing_name ) end end Server-side
  50. Ferrying data from server to client VIEWMODEL ADAPTERS class User

    < ActiveRecord::Base validates_presence_of :billing_name end module ViewModel::Person < ViewModel::Base def self.from_user(user) self.new( :name => user.billing_name ) end end Server-side self.from_twitter self.from_vendor self.from_admin
  51. Ferrying data from server to client VIEWMODEL ADAPTERS user =

    User.find(1) person = ViewModel::Person.from_user(user) person.to_json # => {"name": "Sean McBride"} Server-side
  52. function Person(name) { this.name = ko.observable(name); } Client-side Ferrying data

    from server to client VIEWMODEL ADAPTERS
  53. function Person(name) { this.name = ko.observable(name); } Person.prototype.update = function(data)

    { this.name(data["name"]); } Client-side Ferrying data from server to client VIEWMODEL ADAPTERS
  54. The MVVM pattern helps to ORGANIZE CLIENT AND SERVER CODE

  55. The MVVM pattern helps to MOVE DATA BACK AND FORTH

  56. The MVVM pattern helps to REDUCE DUPLICATION

  57. The MVVM pattern helps to ORGANIZE DUPLICATION

  58. DECLARATIVE BINDINGS Easily build dynamic UIs with @smcbride #fluentconf #divided

  59. Backbone.js SproutCore Sammy.js Spine.js Cappuccino Knockout.js Javascript MVC Google Web

    Toolkit Google Closure Ember.js Angular.js Batman.js Meteor Derby DECLARATIVE BINDINGS
  60. Backbone.js SproutCore Sammy.js Spine.js Cappuccino Knockout.js Javascript MVC Google Web

    Toolkit Google Closure Ember.js Angular.js Batman.js Meteor Derby DECLARATIVE BINDINGS
  61. function Person(name) { this.name = ko.observable(name); } Client-side

  62. function Person(name) { this.name = ko.observable(name); } <dl id="person"> <dt>Full

    name:</dt> <dd data-bind="text: name"></dd> </dl> Client-side Template
  63. function Person(name) { this.name = ko.observable(name); } <dl id="person"> <dt>Full

    name:</dt> <dd data-bind="text: name"></dd> </dl> Full name: Client-side Template
  64. function Person(name) { this.name = ko.observable(name); } <dl id="person"> <dt>Full

    name:</dt> <dd data-bind="text: name"></dd> </dl> var me = new Person("Sean McBride"); ko.applyBindings(me, document.getElementById("person")); Full name: Client-side Template Client-side
  65. function Person(name) { this.name = ko.observable(name); } <dl id="person"> <dt>Full

    name:</dt> <dd data-bind="text: name"></dd> </dl> var me = new Person("Sean McBride"); ko.applyBindings(me, document.getElementById("person")); Full name: Sean McBride Client-side Template Client-side
  66. function Person(name) { this.name = ko.observable(name); } <dl id="person"> <dt>Full

    name:</dt> <dd data-bind="text: name"></dd> </dl> var me = new Person("Sean McBride"); ko.applyBindings(me, document.getElementById("person")); Full name: Sean McBride me.name("Oscar the Grouch"); Client-side Template Client-side
  67. function Person(name) { this.name = ko.observable(name); } <dl id="person"> <dt>Full

    name:</dt> <dd data-bind="text: name"></dd> </dl> var me = new Person("Sean McBride"); ko.applyBindings(me, document.getElementById("person")); Full name: Oscar the Grouch me.name("Oscar the Grouch"); Client-side Template Client-side
  68. <dl id="person"> <dt>Full name:</dt> <dd><input type="text" data-bind="value: name"></dd> <dt>Verify name:</dt>

    <dd data-bind="text: name"></dd> </dl> Template
  69. Full name: Verify name: Sean McBride <dl id="person"> <dt>Full name:</dt>

    <dd><input type="text" data-bind="value: name"></dd> <dt>Verify name:</dt> <dd data-bind="text: name"></dd> </dl> Sean McBride Template
  70. Full name: Verify name: <dl id="person"> <dt>Full name:</dt> <dd><input type="text"

    data-bind="value: name"></dd> <dt>Verify name:</dt> <dd data-bind="text: name"></dd> </dl> Template
  71. Full name: Verify name: <dl id="person"> <dt>Full name:</dt> <dd><input type="text"

    data-bind="value: name"></dd> <dt>Verify name:</dt> <dd data-bind="text: name"></dd> </dl> Oscar the Grouch Oscar the Grouch Template
  72. Declarative bindings mean TEMPLATES ARE JUST HTML

  73. Declarative bindings mean NO MORE MESSY RENDER METHODS

  74. Declarative bindings mean DON’T THINK ABOUT UPDATING

  75. CLIENT/SERVER RENDERING Reduce duplication with @smcbride #fluentconf #divided

  76. CLIENT-SIDE SERVER-SIDE Model (ActiveRecord) ViewModel (JS + Knockout) Template (HTML

    + Bindings) ViewModel Adapter Controller (ActionController) (JSON)
  77. Template (HTML + Bindings) CLIENT-SIDE SERVER-SIDE Model (ActiveRecord) ViewModel (JS

    + Knockout) ViewModel Adapter Controller (ActionController) (JSON)
  78. class ViewModel::Person < ViewModel::Base def self.from_user(user) self.new( :name => user.billing_name

    ) end end Server-side
  79. class ViewModel::Person < ViewModel::Base def self.from_user(user) self.new( :name => user.billing_name

    ) end end person = ViewModel::Person.from_user(User.get(1)) Server-side
  80. class ViewModel::Person < ViewModel::Base def self.from_user(user) self.new( :name => user.billing_name

    ) end end person = ViewModel::Person.from_user(User.get(1)) render_client_side_template "person", :object => person Server-side
  81. class ViewModel::Person < ViewModel::Base def self.from_user(user) self.new( :name => user.billing_name

    ) end end person = ViewModel::Person.from_user(User.get(1)) render_client_side_template "person", :object => person Server-side Our helper Template name ViewModel adapter
  82. How to render DECLARATIVE BINDINGS on the server?

  83. Bindings modify the DOM

  84. Bindings modify the DOM NOKOGIRI

  85. Bindings modify the DOM NOKOGIRI Bindings can have JS expressions

  86. Bindings modify the DOM NOKOGIRI Bindings can have JS expressions

    JOHNSON (SpiderMonkey)
  87. 1. Get the template source

  88. 1. Get the template source 2. Evaluate all bindings with

    ViewModel data
  89. 1. Get the template source 2. Evaluate all bindings with

    ViewModel data 3. Turn template into DOM fragment
  90. 1. Get the template source 2. Evaluate all bindings with

    ViewModel data 3. Turn template into DOM fragment 4. Execute each binding with values to modify DOM
  91. 1. Get the template source 2. Evaluate all bindings with

    ViewModel data 3. Turn template into DOM fragment 4. Execute each binding with values to modify DOM 5. Output HTML
  92. Using Johnson server-side to EVALUATE BINDINGS

  93. Using Johnson server-side to EVALUATE BINDINGS Server-side Johnson.evaluate <<JS (function()

    { // Data from viewmodel adapter var name = "Sean McBride"; // Binding string from template return { text: name }; })(); JS # => { "text" => "Sean McBride" }
  94. Using Nokogiri server-side to APPLY BINDINGS

  95. Using Nokogiri server-side to APPLY BINDINGS Client-side <span data-bind="text: name"></span>

  96. Using Nokogiri server-side to APPLY BINDINGS Client-side ko.bindingHandlers['text'] = {

    'init': function(element, valueAccessor) { // Set text initially }, 'update': function (element, valueAccessor) { // Update text when value changes } }; <span data-bind="text: name"></span>
  97. Using Nokogiri server-side to APPLY BINDINGS Client-side ko.bindingHandlers['text'] = {

    'init': function(element, valueAccessor) { // Set text initially }, 'update': function (element, valueAccessor) { // Update text when value changes } }; Update is used on the client, but we don’t need it on the server! <span data-bind="text: name"></span>
  98. Using Nokogiri server-side to APPLY BINDINGS Server-side bindings.register "text" do

    |element, value| element.content = value.to_s end <span data-bind="text: name"></span>
  99. Client/server rendering SERVES JS AND NO-JS CLIENTS

  100. Client/server rendering REDUCES DUPLICATION

  101. Client/server rendering CAN SLOW THINGS DOWN

  102. Client/server rendering CAN BE DISABLED FOR JS CLIENTS

  103. None
  104. PUTTING IT ALL TOGETHER Wrapping up by @smcbride #fluentconf #divided

  105. Template (HTML + Bindings) CLIENT-SIDE SERVER-SIDE Model (ActiveRecord) ViewModel (JS

    + Knockout) ViewModel Adapter Controller (ActionController) (JSON)
  106. CLIENT-SIDE SERVER-SIDE

  107. CLIENT-SIDE SERVER-SIDE u = User.find(1)

  108. CLIENT-SIDE SERVER-SIDE u = User.find(1) p = ViewModel::Person .from_user(u)

  109. CLIENT-SIDE SERVER-SIDE u = User.find(1) p = ViewModel::Person .from_user(u) render

    "person", :object => p
  110. CLIENT-SIDE SERVER-SIDE (JSON) u = User.find(1) p = ViewModel::Person .from_user(u)

    p.to_json var p = new Person(); p.update(jsonData); render "person", :object => p
  111. CLIENT-SIDE SERVER-SIDE (JSON) u = User.find(1) p = ViewModel::Person .from_user(u)

    p.to_json var p = new Person(); p.update(jsonData); ko.applyBindings(p, el); render "person", :object => p
  112. CLIENT-SIDE SERVER-SIDE MVC VM MV

  113. CLIENT-SIDE SERVER-SIDE MVC VM V

  114. CLIENT-SIDE SERVER-SIDE MVC VM

  115. FAST FUN ACCESSIBLE

  116. DIVIDE CLIENT-SIDE SERVER-SIDE Bridging the sean@typekit.com seanmcb.com/client-server-divide MVVM DECLARATIVE BINDINGS

    CLIENT/SERVER RENDERING @smcbride #fluentconf #divided