• Share
  • Email
  • Embed
  • Like
  • Save
  • Private Content
Building a Single-Page App: Backbone, Node.js, and Beyond
 

Building a Single-Page App: Backbone, Node.js, and Beyond

on

  • 16,881 views

 

Statistics

Views

Total Views
16,881
Views on SlideShare
12,734
Embed Views
4,147

Actions

Likes
49
Downloads
307
Comments
0

18 Embeds 4,147

http://www.scoop.it 2137
http://mobicon.tistory.com 1648
http://ccambo.blogspot.kr 142
http://ccambo.blogspot.com 61
https://twitter.com 41
http://www.linkedin.com 36
http://beta.gogmat.com 33
http://zweitag.posterous.com 30
http://twitter.com 6
http://webcache.googleusercontent.com 3
https://www.linkedin.com 2
https://t.co 2
http://pinterest.com 1
https://www.evernote.com 1
https://www.google.co.kr 1
http://ccambo.blogspot.ca 1
http://lms.loc 1
http://nodeslide.herokuapp.com 1
More...

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

    Building a Single-Page App: Backbone, Node.js, and Beyond Building a Single-Page App: Backbone, Node.js, and Beyond Presentation Transcript

    • Building a Single-Page App: Backbone, Node.js, and Beyond Spike Brehm, Front End Engineer spike@airbnb.com @spikebrehm September 12, 2012Thursday, September 13, 12
    • Past: Why Single-Page Apps Present: How we built Wish Lists Future: In pursuit of the Holy GrailThursday, September 13, 12
    • Past Why Single-Page AppsThursday, September 13, 12
    • Thursday, September 13, 12
    • Airbedandbreakfast.comThursday, September 13, 12
    • Airbedandbreakfast.com • Started in 2008 as a Rails 2.x appThursday, September 13, 12
    • Airbedandbreakfast.com • Started in 2008 as a Rails 2.x app • Now Rails 3.0Thursday, September 13, 12
    • Airbedandbreakfast.com • Started in 2008 as a Rails 2.x app • Now Rails 3.0 • Still stuck in old, page-based paradigmThursday, September 13, 12
    • What is a Single-Page App?Thursday, September 13, 12
    • What is a Single-Page App?Thursday, September 13, 12
    • What is a Single-Page App?Thursday, September 13, 12
    • What is a Single-Page App? • Navigate in the app without page refreshThursday, September 13, 12
    • What is a Single-Page App? • Navigate in the app without page refresh • Application logic in the clientThursday, September 13, 12
    • What is a Single-Page App? • Navigate in the app without page refresh • Application logic in the client • Fetch data on demandThursday, September 13, 12
    • Why Single-Page Apps?Thursday, September 13, 12
    • Why Single-Page Apps? • Faster JavaScript runtimesThursday, September 13, 12
    • Why Single-Page Apps? • Faster JavaScript runtimes • New browser features (pushState, localStorage, etc.)Thursday, September 13, 12
    • Why Single-Page Apps? • Faster JavaScript runtimes • New browser features (pushState, localStorage, etc.) • Heightened user expectationsThursday, September 13, 12
    • Two Approaches The Easy Way The Hard Way aka “The Holy Grail”Thursday, September 13, 12
    • The Easy WayThursday, September 13, 12
    • The Easy WayThursday, September 13, 12
    • The Easy Way • JavaScript app runs entirely in clientThursday, September 13, 12
    • The Easy Way • JavaScript app runs entirely in client • Server technology agnosticThursday, September 13, 12
    • The Easy Way • JavaScript app runs entirely in client • Server technology agnostic • Can use Backbone to structure appThursday, September 13, 12
    • The Easy Way • JavaScript app runs entirely in client • Server technology agnostic • Can use Backbone to structure app • Poor SEO -- not crawlableThursday, September 13, 12
    • The Easy Way • JavaScript app runs entirely in client • Server technology agnostic • Can use Backbone to structure app • Poor SEO -- not crawlable • Performance hit to download & evaluate JS before renderingThursday, September 13, 12
    • The Easy Way • JavaScript app runs entirely in client • Server technology agnostic • Can use Backbone to structure app • Poor SEO -- not crawlable • Performance hit to download & evaluate JS before rendering • Good for apps behind login, or toolsThursday, September 13, 12
    • The Hard Way aka “The Holy Grail”Thursday, September 13, 12
    • The Hard WayThursday, September 13, 12
    • The Hard Way • Routing, templating, application logic, utilities run on client and serverThursday, September 13, 12
    • The Hard Way • Routing, templating, application logic, utilities run on client and server • Navigate to any page, HTML rendered in client -- hit refresh, serves up HTMLThursday, September 13, 12
    • The Hard Way • Routing, templating, application logic, utilities run on client and server • Navigate to any page, HTML rendered in client -- hit refresh, serves up HTML • Must render full page of HTML without access to DOM (or find a faster DOM implementation)Thursday, September 13, 12
    • The Hard Way • Routing, templating, application logic, utilities run on client and server • Navigate to any page, HTML rendered in client -- hit refresh, serves up HTML • Must render full page of HTML without access to DOM (or find a faster DOM implementation) • Requires JavaScript runtime on the server (or DSL that compiles down to JavaScript -- think GWT)Thursday, September 13, 12
    • The Hard Way • Routing, templating, application logic, utilities run on client and server • Navigate to any page, HTML rendered in client -- hit refresh, serves up HTML • Must render full page of HTML without access to DOM (or find a faster DOM implementation) • Requires JavaScript runtime on the server (or DSL that compiles down to JavaScript -- think GWT) • Backbone not a good fitThursday, September 13, 12
    • The Hard Way • Routing, templating, application logic, utilities run on client and server • Navigate to any page, HTML rendered in client -- hit refresh, serves up HTML • Must render full page of HTML without access to DOM (or find a faster DOM implementation) • Requires JavaScript runtime on the server (or DSL that compiles down to JavaScript -- think GWT) • Backbone not a good fit • Provides good SEOThursday, September 13, 12
    • The Hard Way • Routing, templating, application logic, utilities run on client and server • Navigate to any page, HTML rendered in client -- hit refresh, serves up HTML • Must render full page of HTML without access to DOM (or find a faster DOM implementation) • Requires JavaScript runtime on the server (or DSL that compiles down to JavaScript -- think GWT) • Backbone not a good fit • Provides good SEO • Better performanceThursday, September 13, 12
    • Performance Improving performance on Twitter.com http://engineering.twitter.com/2012/05/improving-performance-on- twittercom.html “Time to first tweet”Thursday, September 13, 12
    • Stops and StartsThursday, September 13, 12
    • Stops and Starts • mustache.rb: code duplicationThursday, September 13, 12
    • Stops and Starts • mustache.rb: code duplication • therubyracer: performance, stabilityThursday, September 13, 12
    • Stops and Starts • mustache.rb: code duplication • therubyracer: performance, stability • PhantomJS: slow, overly complicatedThursday, September 13, 12
    • Present How we built Wish ListsThursday, September 13, 12
    • Thursday, September 13, 12
    • TechnologiesThursday, September 13, 12
    • Technologies • MV*: Backbone.jsThursday, September 13, 12
    • Technologies • MV*: Backbone.js • Templating: HandlebarsThursday, September 13, 12
    • Technologies • MV*: Backbone.js • Templating: Handlebars • UI & Layout: Oxygen (Airbnb’s Bootstrap)Thursday, September 13, 12
    • Technologies • MV*: Backbone.js • Templating: Handlebars • UI & Layout: Oxygen (Airbnb’s Bootstrap) • CoffeeScriptThursday, September 13, 12
    • Technologies • MV*: Backbone.js • Templating: Handlebars • UI & Layout: Oxygen (Airbnb’s Bootstrap) • CoffeeScript • HTML5 pushStateThursday, September 13, 12
    • Technologies • MV*: Backbone.js • Templating: Handlebars • UI & Layout: Oxygen (Airbnb’s Bootstrap) • CoffeeScript • HTML5 pushState • api.airbnb.comThursday, September 13, 12
    • Rails-Backbone interface: index.html.erb <div class=”app_view”></div> <script> !function(){ I18n.extend(<%= @phrases.to_json.html_safe %>); Airbnb.Api.config(<%= @api_config.to_json.html_safe %>); window.WishlistsApp = new AIR.Apps.Wishlists( <%= @init_data.to_json.html_safe %> ); }(); </script>Thursday, September 13, 12
    • Rails-Backbone interface: index.html.erb <div class=”app_view”></div> <script> !function(){ I18n.extend(<%= @phrases.to_json.html_safe %>); Airbnb.Api.config(<%= @api_config.to_json.html_safe %>); window.WishlistsApp = new AIR.Apps.Wishlists( <%= @init_data.to_json.html_safe %> ); }(); </script>Thursday, September 13, 12
    • Rails-Backbone interface: index.html.erb <div class=”app_view”></div> <script> !function(){ I18n.extend(<%= @phrases.to_json.html_safe %>); Airbnb.Api.config(<%= @api_config.to_json.html_safe %>); window.WishlistsApp = new AIR.Apps.Wishlists( <%= @init_data.to_json.html_safe %> ); }(); </script>Thursday, September 13, 12
    • Rails-Backbone interface: index.html.erb <div class=”app_view”></div> <script> !function(){ I18n.extend(<%= @phrases.to_json.html_safe %>); Airbnb.Api.config(<%= @api_config.to_json.html_safe %>); window.WishlistsApp = new AIR.Apps.Wishlists( <%= @init_data.to_json.html_safe %> ); }(); </script>Thursday, September 13, 12
    • Rails-Backbone interface: index.html.erb <div class=”app_view”></div> <script> !function(){ I18n.extend(<%= @phrases.to_json.html_safe %>); Airbnb.Api.config(<%= @api_config.to_json.html_safe %>); window.WishlistsApp = new AIR.Apps.Wishlists( <%= @init_data.to_json.html_safe %> ); }(); </script>Thursday, September 13, 12
    • Rails-Backbone interface: index.html.erb <div class=”app_view”></div> <script> !function(){ I18n.extend(<%= @phrases.to_json.html_safe %>); Airbnb.Api.config(<%= @api_config.to_json.html_safe %>); window.WishlistsApp = new AIR.Apps.Wishlists( <%= @init_data.to_json.html_safe %> ); }(); </script>Thursday, September 13, 12
    • Rails-Backbone interface: index.html.erb <div class=”app_view”></div> <script> !function(){ I18n.extend(<%= @phrases.to_json.html_safe %>); Airbnb.Api.config(<%= @api_config.to_json.html_safe %>); window.WishlistsApp = new AIR.Apps.Wishlists( <%= @init_data.to_json.html_safe %> ); }(); </script>Thursday, September 13, 12
    • Bootstrapping the app window.WishlistsApp = new AIR.Apps.Wishlists({ “listings”: [...], “wishlists”: [...], ... });Thursday, September 13, 12
    • Bootstrapping the app window.WishlistsApp = new AIR.Apps.Wishlists({ “listings”: [...], “wishlists”: [...], ... }); WishlistsApp.get(‘wishlists’) => [Object, Object, Object, ...]Thursday, September 13, 12
    • Bootstrapping the appThursday, September 13, 12
    • Bootstrapping the app • Each action bootstraps whatever data needed on first pageloadThursday, September 13, 12
    • Bootstrapping the app • Each action bootstraps whatever data needed on first pageload • Subsequent data is requested on- demandThursday, September 13, 12
    • App Initialize class AIR.Apps.Wishlists extends Backbone.Model initialize: => @wishlists = new AIR.Collections.Wishlists @get(wishlists) @listings = new AIR.Collections.Listings @get(listings) ... new AIR.Routers.Wishlists({app: @})Thursday, September 13, 12
    • App Initialize class AIR.Apps.Wishlists extends Backbone.Model initialize: => @wishlists = new AIR.Collections.Wishlists @get(wishlists) @listings = new AIR.Collections.Listings @get(listings) ... new AIR.Routers.Wishlists({app: @}) WishlistsApp.wishlists => Wishlists _byCid: Object _byId: Object length: 11 models: Array[11] __proto__: ctorThursday, September 13, 12
    • Backbone RouterThursday, September 13, 12
    • Backbone Router • Translates URL changes to method callsThursday, September 13, 12
    • Backbone Router • Translates URL changes to method calls • Source of global app stateThursday, September 13, 12
    • Backbone Router • Translates URL changes to method calls • Source of global app state • Keep state out of viewsThursday, September 13, 12
    • Backbone Router • Translates URL changes to method calls • Source of global app state • Keep state out of views • Idempotent view renderingThursday, September 13, 12
    • Backbone Router class AIR.Routers.Wishlists extends Backbone.Router routes: wishlists/:id : show wishlists/:id/edit : edit ... show: (id) -> @app.fetchWishlist id, (model) => view = new AIR.Views.Wishlists.ShowView {@app, model} @updateContent(view)Thursday, September 13, 12
    • Backbone Router class AIR.Routers.Wishlists extends Backbone.Router routes: wishlists/:id : show wishlists/:id/edit : edit ... show: (id) -> @app.fetchWishlist id, (model) => view = new AIR.Views.Wishlists.ShowView {@app, model} @updateContent(view)Thursday, September 13, 12
    • Backbone Router class AIR.Routers.Wishlists extends Backbone.Router routes: wishlists/:id : show wishlists/:id/edit : edit ... show: (id) -> @app.fetchWishlist id, (model) => view = new AIR.Views.Wishlists.ShowView {@app, model} @updateContent(view)Thursday, September 13, 12
    • Backbone Router class AIR.Routers.Wishlists extends Backbone.Router routes: wishlists/:id : show wishlists/:id/edit : edit ... show: (id) -> @app.fetchWishlist id, (model) => view = new AIR.Views.Wishlists.ShowView {@app, model} @updateContent(view)Thursday, September 13, 12
    • Data-on-demandThursday, September 13, 12
    • Data-on-demand # AIR.Apps.Wishlists fetchWishlist: (id, callback) -> model = @wishlists.get(id) if model? callback(model) else @appView.setLoading(true) @wishlists.fetchById id, (model) => @appView.setLoading(false) callback(model)Thursday, September 13, 12
    • Data-on-demand # AIR.Apps.Wishlists fetchWishlist: (id, callback) -> model = @wishlists.get(id) if model? callback(model) else @appView.setLoading(true) @wishlists.fetchById id, (model) => @appView.setLoading(false) callback(model)Thursday, September 13, 12
    • Data-on-demand # AIR.Apps.Wishlists fetchWishlist: (id, callback) -> model = @wishlists.get(id) if model? callback(model) else @appView.setLoading(true) @wishlists.fetchById id, (model) => @appView.setLoading(false) callback(model)Thursday, September 13, 12
    • Data-on-demand # AIR.Apps.Wishlists fetchWishlist: (id, callback) -> model = @wishlists.get(id) if model? callback(model) else @appView.setLoading(true) @wishlists.fetchById id, (model) => @appView.setLoading(false) callback(model)Thursday, September 13, 12
    • Data-on-demand # AIR.Apps.Wishlists fetchWishlist: (id, callback) -> model = @wishlists.get(id) if model? callback(model) else @appView.setLoading(true) @wishlists.fetchById id, (model) => @appView.setLoading(false) callback(model)Thursday, September 13, 12
    • Data-on-demand # AIR.Apps.Wishlists fetchWishlist: (id, callback) -> model = @wishlists.get(id) if model? callback(model) else @appView.setLoading(true) @wishlists.fetchById id, (model) => @appView.setLoading(false) callback(model)Thursday, September 13, 12
    • api.airbnb.comThursday, September 13, 12
    • api.airbnb.com • Used by iOS, Android, Mobile Web clientsThursday, September 13, 12
    • api.airbnb.com • Used by iOS, Android, Mobile Web clients • No Cross-Domain XHRThursday, September 13, 12
    • api.airbnb.com • Used by iOS, Android, Mobile Web clients • No Cross-Domain XHR • JSONP for GET; but no POST, PUT, DELETEThursday, September 13, 12
    • api.airbnb.com • Used by iOS, Android, Mobile Web clients • No Cross-Domain XHR • JSONP for GET; but no POST, PUT, DELETE • Added CORS support in API to allow requests coming from valid Airbnb domain (*.airbnb.com, *.airbnb.co.uk, *.airbnb.de...)Thursday, September 13, 12
    • Accessing API from Backbone Airbnb.Api.getUrl(‘/v1/users/1234’)Thursday, September 13, 12
    • Accessing API from Backbone Airbnb.Api.getUrl(‘/v1/users/1234’) => "https://api.airbnb.com/v1/users/1234?currency=USD&locale=en& key=...&oauth_token=..."Thursday, September 13, 12
    • Accessing API from Backbone class AIR.Models.WishlistUser extends Backbone.Model jsonKey: user apiPath: -> "/v1/users/#{@id}" ... _.extend AIR.Models.WishlistUser.prototype, AIR.Mixins.ApiResourceThursday, September 13, 12
    • Accessing API from Backbone AIR.Mixins.ApiResource = url: (options = {}) -> apiPath = options.apiPath || @apiPath if _.isFunction(apiPath) apiPath = apiPath.call(@) Airbnb.Api.getUrl(apiPath) sync: (method, model, options) -> options = _.defaults options, url: @url(options) Backbone.sync method, model, optionsThursday, September 13, 12
    • Accessing API from Backbone AIR.Mixins.ApiResource = url: (options = {}) -> apiPath = options.apiPath || @apiPath if _.isFunction(apiPath) apiPath = apiPath.call(@) Airbnb.Api.getUrl(apiPath) sync: (method, model, options) -> options = _.defaults options, url: @url(options) Backbone.sync method, model, optionsThursday, September 13, 12
    • Accessing API from Backbone AIR.Mixins.ApiResource = url: (options = {}) -> apiPath = options.apiPath || @apiPath if _.isFunction(apiPath) apiPath = apiPath.call(@) Airbnb.Api.getUrl(apiPath) sync: (method, model, options) -> options = _.defaults options, url: @url(options) Backbone.sync method, model, optionsThursday, September 13, 12
    • Accessing API from Backbone AIR.Mixins.ApiResource = url: (options = {}) -> apiPath = options.apiPath || @apiPath if _.isFunction(apiPath) apiPath = apiPath.call(@) Airbnb.Api.getUrl(apiPath) sync: (method, model, options) -> options = _.defaults options, url: @url(options) Backbone.sync method, model, optionsThursday, September 13, 12
    • AIR.Views.BaseView class AIR.Views.BaseView extends Backbone.View postInitialize: -> postRender: -> getRenderData: -> cleanup: -> ...Thursday, September 13, 12
    • Before class WishlistIndexView extends Backbone.View template: wishlists/wishlist_index_view render: -> @$el.html JST[@template](@model.toJSON()) @renderSomeThing() @ renderSomeThing: -> ...Thursday, September 13, 12
    • Before class WishlistIndexView extends Backbone.View template: wishlists/wishlist_index_view render: -> @$el.html JST[@template](@model.toJSON()) @renderSomeThing() @ renderSomeThing: -> ...Thursday, September 13, 12
    • Before class WishlistIndexView extends Backbone.View template: wishlists/wishlist_index_view render: -> @$el.html JST[@template](@model.toJSON()) @renderSomeThing() @ renderSomeThing: -> ...Thursday, September 13, 12
    • Before class WishlistIndexView extends Backbone.View template: wishlists/wishlist_index_view render: -> @$el.html JST[@template](@model.toJSON()) @renderSomeThing() @ renderSomeThing: -> ...Thursday, September 13, 12
    • Before class WishlistIndexView extends Backbone.View template: wishlists/wishlist_index_view render: -> @$el.html JST[@template](@model.toJSON()) @renderSomeThing() @ renderSomeThing: -> ...Thursday, September 13, 12
    • Before class WishlistIndexView extends Backbone.View template: wishlists/wishlist_index_view render: -> @$el.html JST[@template](@model.toJSON()) @renderSomeThing() @ renderSomeThing: -> ...Thursday, September 13, 12
    • After class WishlistIndexView extends AIR.Views.BaseView template: wishlists/wishlist_index_view postRender: -> @renderSomeThing() renderSomeThing: -> ...Thursday, September 13, 12
    • After class WishlistIndexView extends AIR.Views.BaseView template: wishlists/wishlist_index_view postRender: -> @renderSomeThing() renderSomeThing: -> ...Thursday, September 13, 12
    • After class WishlistIndexView extends AIR.Views.BaseView template: wishlists/wishlist_index_view postRender: -> @renderSomeThing() renderSomeThing: -> ...Thursday, September 13, 12
    • Before, Part II class WishlistIndexView extends Backbone.View ... render: -> @$el.html JST[@template](@model.toJSON()) @Thursday, September 13, 12
    • Before, Part II class WishlistIndexView extends Backbone.View ... render: -> data = _.extend @model.toJSON(), show_share_button: @options.show_share_button @$el.html JST[@template](data) @Thursday, September 13, 12
    • Before, Part II class WishlistIndexView extends Backbone.View ... render: -> @$el.html JST[@template](@getRenderData()) @ getRenderData: -> _.extend @model.toJSON(), show_share_button: @options.show_share_buttonThursday, September 13, 12
    • After, Part II class WishlistIndexView extends AIR.Views.BaseView ... getRenderData: -> _.extend super, show_share_button: @options.show_share_buttonThursday, September 13, 12
    • After, Part II class WishlistIndexView extends AIR.Views.BaseView ... getRenderData: -> _.extend super, show_share_button: @options.show_share_buttonThursday, September 13, 12
    • cleanup() class AIR.Views.BaseView extends Backbone.View ... cleanup: -> @undelegateEvents() @model?.off(null, null, @) @remove()Thursday, September 13, 12
    • cleanup() class WishlistIndexView extends AIR.Views.BaseView ... cleanup: -> super @someChildView.cleanup() clearInterval(@interval)Thursday, September 13, 12
    • cleanup() Backbone 0.9.2 adds new method: Backbone.View.prototype.dispose()Thursday, September 13, 12
    • Modular, DRY viewsThursday, September 13, 12
    • Modular, DRY views • Re-usable bits of markup and behaviorThursday, September 13, 12
    • Modular, DRY views • (screenshot)Thursday, September 13, 12
    • Modular, DRY views • (screenshot)Thursday, September 13, 12
    • Subview initialization class EditView extends AIR.Views.BaseView ... postRender: -> @renderPrivacyDropdown() renderPrivacyDropdown: -> view = new AIR.Views.Shared.PrivacyDropdownView private: @model.get(private) @$(data-privacy-dropdown).replaceWith view.render().elThursday, September 13, 12
    • Subview initialization class EditView extends AIR.Views.BaseView ... postRender: -> @renderPrivacyDropdown() renderPrivacyDropdown: -> view = new AIR.Views.Shared.PrivacyDropdownView private: @model.get(private) @$(data-privacy-dropdown).replaceWith view.render().elThursday, September 13, 12
    • Subview initialization class EditView extends AIR.Views.BaseView ... postRender: -> @renderPrivacyDropdown() renderPrivacyDropdown: -> view = new AIR.Views.Shared.PrivacyDropdownView private: @model.get(private) @$(data-privacy-dropdown).replaceWith view.render().elThursday, September 13, 12
    • Subview initialization class EditView extends AIR.Views.BaseView ... postRender: -> @renderPrivacyDropdown() renderPrivacyDropdown: -> view = new AIR.Views.Shared.PrivacyDropdownView private: @model.get(private) @$(data-privacy-dropdown).replaceWith view.render().elThursday, September 13, 12
    • Subview initialization class EditView extends AIR.Views.BaseView ... postRender: -> @renderPrivacyDropdown() renderPrivacyDropdown: -> view = new AIR.Views.Shared.PrivacyDropdownView private: @model.get(private) view.on ‘private-changed’, (isPrivate) => # do something console.log(isPrivate) @$(data-privacy-dropdown).replaceWith view.render().elThursday, September 13, 12
    • What goes into a view?Thursday, September 13, 12
    • What goes into a view?• app/assets/coffeescripts/views/shared/privacy_dropdown_view.coffeeThursday, September 13, 12
    • What goes into a view?• app/assets/coffeescripts/views/shared/privacy_dropdown_view.coffee• app/assets/templates/views/shared/privacy_dropdown_view.hbsThursday, September 13, 12
    • What goes into a view?• app/assets/coffeescripts/views/shared/privacy_dropdown_view.coffee• app/assets/templates/views/shared/privacy_dropdown_view.hbs• app/assets/stylesheets/partials/_ privacy_dropdown_view.scssThursday, September 13, 12
    • What goes into a view?• app/assets/coffeescripts/views/shared/privacy_dropdown_view.coffee• app/assets/templates/views/shared/privacy_dropdown_view.hbs• app/assets/stylesheets/partials/_ privacy_dropdown_view.scss• lib/phrase_bundles/privacy_dropdown_view.rbThursday, September 13, 12
    • Rdio’s Backbone-based View Component Framework Justin Tulloss, @justin_tulloss http://www.youtube.com/watch?v=TB-l2nF67iUThursday, September 13, 12
    • Modular, DRY views • (screenshot)Thursday, September 13, 12
    • http://airbnb.github.com/infinity Infinity.js • (screenshot)Thursday, September 13, 12
    • I18n.jsThursday, September 13, 12
    • I18n.js • 192 countriesThursday, September 13, 12
    • I18n.js • 192 countries • 31 localesThursday, September 13, 12
    • I18n.js • 192 countries • 31 locales • Client-slide translation libraryThursday, September 13, 12
    • I18n.t() I18n.t(edit_wish_list);Thursday, September 13, 12
    • I18n.t() I18n.t(edit_wish_list); "Edit Wish List"Thursday, September 13, 12
    • I18n.t() I18n.t(edit_wish_list); "Edit Wish List" <h1>{{t "edit_wish_list"}}</h1>Thursday, September 13, 12
    • I18n.t() I18n.t(edit_wish_list); "Edit Wish List" <h1>{{t "edit_wish_list"}}</h1> <h1>Edit Wish List</h1>Thursday, September 13, 12
    • Interpolation I18n.t(owners_wish_list, {name: name});Thursday, September 13, 12
    • Interpolation I18n.t(owners_wish_list, {name: name}); "Spike’s Wish List"Thursday, September 13, 12
    • Interpolation I18n.t(owners_wish_list, {name: name}); "Spike’s Wish List" <h1>{{t "owners_wish_list" name=name}}</h1>Thursday, September 13, 12
    • Interpolation I18n.t(owners_wish_list, {name: name}); "Spike’s Wish List" <h1>{{t "owners_wish_list" name=name}}</h1> <h1>Spike’s Wish List</h1>Thursday, September 13, 12
    • I18n.extend() I18n.extend({ "edit_wish_list": "Edit Wish List", "owners_wish_list": "%{name}’s Wish List", ... });Thursday, September 13, 12
    • I18n.pluralize() I18n.pluralize("Listing", listings);Thursday, September 13, 12
    • I18n.pluralize() I18n.pluralize("Listing", listings); 3 ListingsThursday, September 13, 12
    • I18n.pluralize() I18n.pluralize("Listing", listings); 3 Listings <span>{{t_pluralize "Listing" count=listings}}</span>Thursday, September 13, 12
    • I18n.pluralize() I18n.pluralize("Listing", listings); 3 Listings <span>{{t_pluralize "Listing" count=listings}}</span> <span>3 Listings</span>Thursday, September 13, 12
    • pluralize() just calls t() { "pluralize.Listing.zero": "%{count} Listings", "pluralize.Listing.one": "%{count} Listing", "pluralize.Listing.many": "%{count} Listings" }Thursday, September 13, 12
    • PhraseBundleThursday, September 13, 12
    • PhraseBundle • Composable bundles of I18n phrasesThursday, September 13, 12
    • PhraseBundle • Composable bundles of I18n phrases • Keep phrases DRYThursday, September 13, 12
    • PhraseBundle • Composable bundles of I18n phrases • Keep phrases DRY • Separation of concerns: treat phrases as data sourceThursday, September 13, 12
    • PhraseBundle I18n.extend(<%= { map_view => t(wishlists.Map View, :default => Map View), list_view => t(wishlists.List View, :default => List View), ... }.to_json.html_safe %>);Thursday, September 13, 12
    • PhraseBundle I18n.extend(<%= { map_view => t(wishlists.Map View, :default => Map View), list_view => t(wishlists.List View, :default => List View), ... }.to_json.html_safe %>); I18n.extend(<%= PhraseBundles::Wishlists.new.to_json.html_safe %>);Thursday, September 13, 12
    • PhraseBundle module PhraseBundles class Wishlists < PhraseBundle includes :privacy_dropdown, :share_dropdown, :wishlists_modal def phrases { map_view => t(wishlists.Map View, :default => Map View), list_view => t(wishlists.List View, :default => List View), ... } end end endThursday, September 13, 12
    • PhraseBundle module PhraseBundles class Wishlists < PhraseBundle includes :privacy_dropdown, :share_dropdown, :wishlists_modal def phrases { map_view => t(wishlists.Map View, :default => Map View), list_view => t(wishlists.List View, :default => List View), ... } end end endThursday, September 13, 12
    • PhraseBundle module PhraseBundles class Wishlists < PhraseBundle includes :privacy_dropdown, :share_dropdown, :wishlists_modal def phrases { map_view => t(wishlists.Map View, :default => Map View), list_view => t(wishlists.List View, :default => List View), ... } end end endThursday, September 13, 12
    • CDN Asset URLs • Image paths need to go through SprocketsThursday, September 13, 12
    • CDN Asset URLs • Image paths need to go through Sprockets Development: https://localhost.airbnb.com:3001 /static/icons/facebook.pngThursday, September 13, 12
    • CDN Asset URLs • Image paths need to go through Sprockets Development: https://localhost.airbnb.com:3001 /static/icons/facebook.png https://a0.muscache.com/airbnb Production: /static/icons/facebook- e04e8c0c43e40ff7a277a3a7a734ed52.pngThursday, September 13, 12
    • CDN Asset URLsThursday, September 13, 12
    • CDN Asset URLs window.ImagePaths = <%= map_image_paths([ icons/facebook.png, ... ]).to_json.html_safe %>;Thursday, September 13, 12
    • CDN Asset URLs window.ImagePaths = <%= map_image_paths([ icons/facebook.png, ... ]).to_json.html_safe %>; ImagePaths[icons/facebook.png]; => “https://a0.muscache.com/airbnb /static/icons/facebook- e04e8c0c43e40ff7a277a3a7a734ed52.png”Thursday, September 13, 12
    • CDN Asset URLs window.ImagePaths = <%= map_image_paths([ icons/facebook.png, ... ]).to_json.html_safe %>; ImagePaths[icons/facebook.png]; => “https://a0.muscache.com/airbnb /static/icons/facebook- e04e8c0c43e40ff7a277a3a7a734ed52.png” <img src=”{{image_path “icons/facebook.png”}}” ...>Thursday, September 13, 12
    • Future In pursuit of the Holy GrailThursday, September 13, 12
    • Backbone.js is just a stopgapThursday, September 13, 12
    • Backbone.js is just a stopgap • Backbone.View is DOM-centricThursday, September 13, 12
    • Backbone.js is just a stopgap • Backbone.View is DOM-centric • Backbone.History is window-centricThursday, September 13, 12
    • Backbone.js is just a stopgap • Backbone.View is DOM-centric • Backbone.History is window-centric • Backbone.Model and Backbone.Collection are more portable (with override of Backbone.sync)Thursday, September 13, 12
    • It’s a great time to be a JavaScript hacker.Thursday, September 13, 12
    • It’s a great time to be a JavaScript hacker. But not a great time to build modern, plug-and-play web apps.Thursday, September 13, 12
    • Testing the Node.js WatersThursday, September 13, 12
    • Testing the Node.js Waters We are refactoring m.airbnb.com with a Node backend instead of Rails.Thursday, September 13, 12
    • Testing the Node.js Waters We are refactoring m.airbnb.com with a Node backend instead of Rails. Primary goal is to learn how to productionize a Node app.Thursday, September 13, 12
    • Testing the Node.js Waters We are refactoring m.airbnb.com with a Node backend instead of Rails. Primary goal is to learn how to productionize a Node app. Secondary goal is to prototype a new way of building web apps.Thursday, September 13, 12
    • Testing the Node.js WatersThursday, September 13, 12
    • Node FrameworksThursday, September 13, 12
    • Node Frameworks Geddy, Tower Rails-inspired. Not utilizing Node’s strengths.Thursday, September 13, 12
    • Node Frameworks Geddy, Tower Rails-inspired. Not utilizing Node’s strengths. SocketStream Modular, real-time, but optimized for The Easy Way.Thursday, September 13, 12
    • Node Frameworks Geddy, Tower Rails-inspired. Not utilizing Node’s strengths. SocketStream Modular, real-time, but optimized for The Easy Way. Meteor Solves for The Hard Way, but all-or-nothing. Alpha.Thursday, September 13, 12
    • Node Frameworks Geddy, Tower Rails-inspired. Not utilizing Node’s strengths. SocketStream Modular, real-time, but optimized for The Easy Way. Meteor Solves for The Hard Way, but all-or-nothing. Alpha. Derby Solves for The Hard Way, but not very modular. Alpha.Thursday, September 13, 12
    • Node Frameworks Active authors. Active mailing list. Small, if messy, codebase. Derby Solves for The Hard Way, but not very modular. Alpha.Thursday, September 13, 12
    • Node Frameworks Derby Active authors. Active mailing list. Small, if messy, codebase. Solves for The Hard Way, but not very modular. Alpha.Thursday, September 13, 12
    • Node Frameworks Derby Active authors. Active mailing list. Small, if messy, codebase. Solves for The Hard Way, but not very modular. Alpha.Thursday, September 13, 12
    • Other Resources Single Page App Book, by Mikito Takada http://singlepageappbook.com/ view.json, by Mikito Takada http://mixu.net/view.json/ Building The Next SoundCloud http://backstage.soundcloud.com/2012/06/building-the-next- soundcloud/ Sean McBride, Bridging the Client-Server Divide http://seanmcb.com/client-server-divide/ NodeUp Podcast http://nodeup.com/Thursday, September 13, 12
    • res.end()Thursday, September 13, 12
    • Let’s chat @spikebrehmThursday, September 13, 12