184
Building a Single-Page App: Backbone, Node.js, and Beyond Spike Brehm, Front End Engineer [email protected] @spikebrehm September 12, 2012 Thursday, September 13, 12

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

Embed Size (px)

DESCRIPTION

 

Citation preview

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

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

Spike Brehm, Front End [email protected]

@spikebrehmSeptember 12, 2012

Thursday, September 13, 12

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

Past: Why Single-Page Apps

Present: How we built Wish Lists

Future: In pursuit of the Holy Grail

Thursday, September 13, 12

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

PastWhy Single-Page Apps

Thursday, September 13, 12

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

Thursday, September 13, 12

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

Airbedandbreakfast.com

Thursday, September 13, 12

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

Airbedandbreakfast.com

• Started in 2008 as a Rails 2.x app

Thursday, September 13, 12

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

Airbedandbreakfast.com

• Started in 2008 as a Rails 2.x app

• Now Rails 3.0

Thursday, September 13, 12

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

Airbedandbreakfast.com

• Started in 2008 as a Rails 2.x app

• Now Rails 3.0

• Still stuck in old, page-based paradigm

Thursday, September 13, 12

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

What is a Single-Page App?

Thursday, September 13, 12

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

What is a Single-Page App?

Thursday, September 13, 12

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

What is a Single-Page App?

Thursday, September 13, 12

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

What is a Single-Page App?

• Navigate in the app without page refresh

Thursday, September 13, 12

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

What is a Single-Page App?

• Navigate in the app without page refresh

• Application logic in the client

Thursday, September 13, 12

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

What is a Single-Page App?

• Navigate in the app without page refresh

• Application logic in the client

• Fetch data on demand

Thursday, September 13, 12

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

Why Single-Page Apps?

Thursday, September 13, 12

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

Why Single-Page Apps?

• Faster JavaScript runtimes

Thursday, September 13, 12

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

Why Single-Page Apps?

• Faster JavaScript runtimes

• New browser features (pushState, localStorage, etc.)

Thursday, September 13, 12

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

Why Single-Page Apps?

• Faster JavaScript runtimes

• New browser features (pushState, localStorage, etc.)

• Heightened user expectations

Thursday, September 13, 12

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

Two Approaches

The Easy Way

The Hard Wayaka “The Holy Grail”

Thursday, September 13, 12

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

The Easy Way

Thursday, September 13, 12

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

The Easy Way

Thursday, September 13, 12

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

The Easy Way

• JavaScript app runs entirely in client

Thursday, September 13, 12

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

The Easy Way

• JavaScript app runs entirely in client

• Server technology agnostic

Thursday, September 13, 12

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

The Easy Way

• JavaScript app runs entirely in client

• Server technology agnostic

• Can use Backbone to structure app

Thursday, September 13, 12

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

The Easy Way

• JavaScript app runs entirely in client

• Server technology agnostic

• Can use Backbone to structure app

• Poor SEO -- not crawlable

Thursday, September 13, 12

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

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

Thursday, September 13, 12

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

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 tools

Thursday, September 13, 12

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

The Hard Wayaka “The Holy Grail”

Thursday, September 13, 12

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

The Hard Way

Thursday, September 13, 12

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

The Hard Way• Routing, templating, application logic, utilities run on

client and server

Thursday, September 13, 12

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

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

Thursday, September 13, 12

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

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

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

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

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

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

Thursday, September 13, 12

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

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

Thursday, September 13, 12

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

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 performance

Thursday, September 13, 12

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

Stops and Starts

Thursday, September 13, 12

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

Stops and Starts

• mustache.rb: code duplication

Thursday, September 13, 12

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

Stops and Starts

• mustache.rb: code duplication

• therubyracer: performance, stability

Thursday, September 13, 12

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

Stops and Starts

• mustache.rb: code duplication

• therubyracer: performance, stability

• PhantomJS: slow, overly complicated

Thursday, September 13, 12

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

PresentHow we built Wish Lists

Thursday, September 13, 12

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

Thursday, September 13, 12

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

Technologies

Thursday, September 13, 12

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

Technologies• MV*: Backbone.js

Thursday, September 13, 12

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

Technologies• MV*: Backbone.js

• Templating: Handlebars

Thursday, September 13, 12

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

Technologies• MV*: Backbone.js

• Templating: Handlebars

• UI & Layout: Oxygen (Airbnb’s Bootstrap)

Thursday, September 13, 12

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

Technologies• MV*: Backbone.js

• Templating: Handlebars

• UI & Layout: Oxygen (Airbnb’s Bootstrap)

• CoffeeScript

Thursday, September 13, 12

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

Technologies• MV*: Backbone.js

• Templating: Handlebars

• UI & Layout: Oxygen (Airbnb’s Bootstrap)

• CoffeeScript

• HTML5 pushState

Thursday, September 13, 12

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

Technologies• MV*: Backbone.js

• Templating: Handlebars

• UI & Layout: Oxygen (Airbnb’s Bootstrap)

• CoffeeScript

• HTML5 pushState

• api.airbnb.com

Thursday, September 13, 12

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

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

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

<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>

Rails-Backbone interface: index.html.erb

Thursday, September 13, 12

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

<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>

Rails-Backbone interface: index.html.erb

Thursday, September 13, 12

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

<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>

Rails-Backbone interface: index.html.erb

Thursday, September 13, 12

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

<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>

Rails-Backbone interface: index.html.erb

Thursday, September 13, 12

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

<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>

Rails-Backbone interface: index.html.erb

Thursday, September 13, 12

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

<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>

Rails-Backbone interface: index.html.erb

Thursday, September 13, 12

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

Bootstrapping the app window.WishlistsApp = new AIR.Apps.Wishlists({ “listings”: [...], “wishlists”: [...], ... });

Thursday, September 13, 12

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

Bootstrapping the app window.WishlistsApp = new AIR.Apps.Wishlists({ “listings”: [...], “wishlists”: [...], ... });

WishlistsApp.get(‘wishlists’)

=> [Object, Object, Object, ...]

Thursday, September 13, 12

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

Bootstrapping the app

Thursday, September 13, 12

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

• Each action bootstraps whatever data needed on first pageload

Bootstrapping the app

Thursday, September 13, 12

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

• Each action bootstraps whatever data needed on first pageload

• Subsequent data is requested on-demand

Bootstrapping the app

Thursday, September 13, 12

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

App Initializeclass 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

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

App Initializeclass 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__: ctor

Thursday, September 13, 12

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

Backbone Router

Thursday, September 13, 12

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

Backbone Router• Translates URL changes to method

calls

Thursday, September 13, 12

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

Backbone Router• Translates URL changes to method

calls

• Source of global app state

Thursday, September 13, 12

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

Backbone Router• Translates URL changes to method

calls

• Source of global app state

• Keep state out of views

Thursday, September 13, 12

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

Backbone Router• Translates URL changes to method

calls

• Source of global app state

• Keep state out of views

• Idempotent view rendering

Thursday, September 13, 12

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

Backbone Routerclass 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

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

Backbone Routerclass 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

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

Backbone Routerclass 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

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

Backbone Routerclass 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

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

Data-on-demand

Thursday, September 13, 12

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

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

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

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

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

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

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

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

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

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

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

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

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

api.airbnb.com

Thursday, September 13, 12

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

api.airbnb.com• Used by iOS, Android, Mobile Web clients

Thursday, September 13, 12

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

api.airbnb.com• Used by iOS, Android, Mobile Web clients

• No Cross-Domain XHR

Thursday, September 13, 12

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

api.airbnb.com• Used by iOS, Android, Mobile Web clients

• No Cross-Domain XHR

• JSONP for GET; but no POST, PUT, DELETE

Thursday, September 13, 12

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

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

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

Accessing API from Backbone

Airbnb.Api.getUrl(‘/v1/users/1234’)

Thursday, September 13, 12

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

Accessing API from Backbone

class AIR.Models.WishlistUser extends Backbone.Model jsonKey: 'user' apiPath: -> "/v1/users/#{@id}" ...

_.extend AIR.Models.WishlistUser.prototype, AIR.Mixins.ApiResource

Thursday, September 13, 12

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

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, options

Thursday, September 13, 12

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

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, options

Thursday, September 13, 12

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

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, options

Thursday, September 13, 12

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

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, options

Thursday, September 13, 12

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

AIR.Views.BaseViewclass AIR.Views.BaseView extends Backbone.View

postInitialize: ->

postRender: ->

getRenderData: ->

cleanup: ->

...

Thursday, September 13, 12

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

Beforeclass WishlistIndexView extends Backbone.View

template: 'wishlists/wishlist_index_view'

render: ->

@$el.html JST[@template](@model.toJSON())

@renderSomeThing()

@

renderSomeThing: -> ...

Thursday, September 13, 12

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

Beforeclass WishlistIndexView extends Backbone.View

template: 'wishlists/wishlist_index_view'

render: ->

@$el.html JST[@template](@model.toJSON())

@renderSomeThing()

@

renderSomeThing: -> ...

Thursday, September 13, 12

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

Beforeclass WishlistIndexView extends Backbone.View

template: 'wishlists/wishlist_index_view'

render: ->

@$el.html JST[@template](@model.toJSON())

@renderSomeThing()

@

renderSomeThing: -> ...

Thursday, September 13, 12

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

Beforeclass WishlistIndexView extends Backbone.View

template: 'wishlists/wishlist_index_view'

render: ->

@$el.html JST[@template](@model.toJSON())

@renderSomeThing()

@

renderSomeThing: -> ...

Thursday, September 13, 12

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

Beforeclass WishlistIndexView extends Backbone.View

template: 'wishlists/wishlist_index_view'

render: ->

@$el.html JST[@template](@model.toJSON())

@renderSomeThing()

@

renderSomeThing: -> ...

Thursday, September 13, 12

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

Beforeclass WishlistIndexView extends Backbone.View

template: 'wishlists/wishlist_index_view'

render: ->

@$el.html JST[@template](@model.toJSON())

@renderSomeThing()

@

renderSomeThing: -> ...

Thursday, September 13, 12

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

Afterclass WishlistIndexView extends AIR.Views.BaseView

template: 'wishlists/wishlist_index_view'

postRender: ->

@renderSomeThing()

renderSomeThing: -> ...

Thursday, September 13, 12

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

Afterclass WishlistIndexView extends AIR.Views.BaseView

template: 'wishlists/wishlist_index_view'

postRender: ->

@renderSomeThing()

renderSomeThing: -> ...

Thursday, September 13, 12

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

Afterclass WishlistIndexView extends AIR.Views.BaseView

template: 'wishlists/wishlist_index_view'

postRender: ->

@renderSomeThing()

renderSomeThing: -> ...

Thursday, September 13, 12

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

Before, Part IIclass WishlistIndexView extends Backbone.View

...

render: ->

@$el.html JST[@template](@model.toJSON())

@

Thursday, September 13, 12

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

Before, Part IIclass 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

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

Before, Part IIclass WishlistIndexView extends Backbone.View

...

render: ->

@$el.html JST[@template](@getRenderData())

@

getRenderData: ->

_.extend @model.toJSON(),

show_share_button: @options.show_share_button

Thursday, September 13, 12

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

After, Part IIclass WishlistIndexView extends AIR.Views.BaseView

...

getRenderData: ->

_.extend super,

show_share_button: @options.show_share_button

Thursday, September 13, 12

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

After, Part IIclass WishlistIndexView extends AIR.Views.BaseView

...

getRenderData: ->

_.extend super,

show_share_button: @options.show_share_button

Thursday, September 13, 12

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

cleanup()class AIR.Views.BaseView extends Backbone.View

...

cleanup: ->

@undelegateEvents()

@model?.off(null, null, @)

@remove()

Thursday, September 13, 12

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

cleanup()class WishlistIndexView extends AIR.Views.BaseView

...

cleanup: ->

super

@someChildView.cleanup()

clearInterval(@interval)

Thursday, September 13, 12

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

cleanup()

Backbone 0.9.2 adds new method: Backbone.View.prototype.dispose()

Thursday, September 13, 12

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

Modular, DRY views

Thursday, September 13, 12

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

Modular, DRY views

• Re-usable bits of markup and behavior

Thursday, September 13, 12

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

Modular, DRY views

• (screenshot)

Thursday, September 13, 12

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

Modular, DRY views

• (screenshot)

Thursday, September 13, 12

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

Subview initializationclass EditView extends AIR.Views.BaseView

...

postRender: ->

@renderPrivacyDropdown()

renderPrivacyDropdown: ->

view = new AIR.Views.Shared.PrivacyDropdownView

'private': @model.get('private')

@$('data-privacy-dropdown').replaceWith view.render().el

Thursday, September 13, 12

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

Subview initializationclass EditView extends AIR.Views.BaseView

...

postRender: ->

@renderPrivacyDropdown()

renderPrivacyDropdown: ->

view = new AIR.Views.Shared.PrivacyDropdownView

'private': @model.get('private')

@$('data-privacy-dropdown').replaceWith view.render().el

Thursday, September 13, 12

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

Subview initializationclass EditView extends AIR.Views.BaseView

...

postRender: ->

@renderPrivacyDropdown()

renderPrivacyDropdown: ->

view = new AIR.Views.Shared.PrivacyDropdownView

'private': @model.get('private')

@$('data-privacy-dropdown').replaceWith view.render().el

Thursday, September 13, 12

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

Subview initializationclass EditView extends AIR.Views.BaseView

...

postRender: ->

@renderPrivacyDropdown()

renderPrivacyDropdown: ->

view = new AIR.Views.Shared.PrivacyDropdownView

'private': @model.get('private')

@$('data-privacy-dropdown').replaceWith view.render().el

Thursday, September 13, 12

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

Subview initializationclass 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().el

Thursday, September 13, 12

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

What goes into a view?

Thursday, September 13, 12

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

What goes into a view?

• app/assets/coffeescripts/views/shared/privacy_dropdown_view.coffee

Thursday, September 13, 12

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

What goes into a view?

• app/assets/coffeescripts/views/shared/privacy_dropdown_view.coffee

• app/assets/templates/views/shared/privacy_dropdown_view.hbs

Thursday, September 13, 12

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

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

Thursday, September 13, 12

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

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.rb

Thursday, September 13, 12

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

Rdio’s Backbone-based View Component FrameworkJustin Tulloss, @justin_tullosshttp://www.youtube.com/watch?v=TB-l2nF67iU

Thursday, September 13, 12

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

Modular, DRY views

• (screenshot)

Thursday, September 13, 12

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

Infinity.js

• (screenshot)

http://airbnb.github.com/infinity

Thursday, September 13, 12

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

I18n.js

Thursday, September 13, 12

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

I18n.js

• 192 countries

Thursday, September 13, 12

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

I18n.js

• 192 countries

• 31 locales

Thursday, September 13, 12

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

I18n.js

• 192 countries

• 31 locales

• Client-slide translation library

Thursday, September 13, 12

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

I18n.t()I18n.t('edit_wish_list');

Thursday, September 13, 12

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

I18n.t()I18n.t('edit_wish_list');

"Edit Wish List"

Thursday, September 13, 12

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

I18n.t()I18n.t('edit_wish_list');

<h1>{{t "edit_wish_list"}}</h1>

"Edit Wish List"

Thursday, September 13, 12

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

I18n.t()I18n.t('edit_wish_list');

<h1>Edit Wish List</h1>

<h1>{{t "edit_wish_list"}}</h1>

"Edit Wish List"

Thursday, September 13, 12

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

InterpolationI18n.t('owners_wish_list', {name: name});

Thursday, September 13, 12

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

InterpolationI18n.t('owners_wish_list', {name: name});

"Spike’s Wish List"

Thursday, September 13, 12

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

Interpolation

<h1>{{t "owners_wish_list" name=name}}</h1>

I18n.t('owners_wish_list', {name: name});

"Spike’s Wish List"

Thursday, September 13, 12

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

Interpolation

<h1>{{t "owners_wish_list" name=name}}</h1>

<h1>Spike’s Wish List</h1>

I18n.t('owners_wish_list', {name: name});

"Spike’s Wish List"

Thursday, September 13, 12

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

I18n.extend()

I18n.extend({ "edit_wish_list": "Edit Wish List", "owners_wish_list": "%{name}’s Wish List", ...});

Thursday, September 13, 12

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

I18n.pluralize()I18n.pluralize("Listing", listings);

Thursday, September 13, 12

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

I18n.pluralize()I18n.pluralize("Listing", listings);

3 Listings

Thursday, September 13, 12

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

I18n.pluralize()

<span>{{t_pluralize "Listing" count=listings}}</span>

I18n.pluralize("Listing", listings);

3 Listings

Thursday, September 13, 12

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

I18n.pluralize()

<span>{{t_pluralize "Listing" count=listings}}</span>

<span>3 Listings</span>

I18n.pluralize("Listing", listings);

3 Listings

Thursday, September 13, 12

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

pluralize() just calls t(){

"pluralize.Listing.zero": "%{count} Listings",

"pluralize.Listing.one": "%{count} Listing",

"pluralize.Listing.many": "%{count} Listings"

}

Thursday, September 13, 12

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

PhraseBundle

Thursday, September 13, 12

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

PhraseBundle• Composable bundles of I18n phrases

Thursday, September 13, 12

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

PhraseBundle• Composable bundles of I18n phrases

• Keep phrases DRY

Thursday, September 13, 12

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

PhraseBundle• Composable bundles of I18n phrases

• Keep phrases DRY

• Separation of concerns: treat phrases as data source

Thursday, September 13, 12

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

PhraseBundleI18n.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

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

PhraseBundleI18n.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

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

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 endend

PhraseBundle

Thursday, September 13, 12

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

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 endend

PhraseBundle

Thursday, September 13, 12

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

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 endend

PhraseBundle

Thursday, September 13, 12

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

CDN Asset URLs• Image paths need to go through Sprockets

Thursday, September 13, 12

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

CDN Asset URLs

https://localhost.airbnb.com:3001/static/icons/facebook.png

• Image paths need to go through Sprockets

Development:

Thursday, September 13, 12

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

CDN Asset URLs

Thursday, September 13, 12

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

CDN Asset URLswindow.ImagePaths = <%= map_image_paths([ 'icons/facebook.png', ...]).to_json.html_safe %>;

Thursday, September 13, 12

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

CDN Asset URLswindow.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

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

CDN Asset URLswindow.ImagePaths = <%= map_image_paths([ 'icons/facebook.png', ...]).to_json.html_safe %>;

<img src=”{{image_path “icons/facebook.png”}}” ...>

ImagePaths['icons/facebook.png'];=> “https://a0.muscache.com/airbnb

/static/icons/facebook-e04e8c0c43e40ff7a277a3a7a734ed52.png”

Thursday, September 13, 12

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

FutureIn pursuit of the Holy Grail

Thursday, September 13, 12

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

Backbone.js is just a stopgap

Thursday, September 13, 12

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

Backbone.js is just a stopgap

• Backbone.View is DOM-centric

Thursday, September 13, 12

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

Backbone.js is just a stopgap

• Backbone.View is DOM-centric

• Backbone.History is window-centric

Thursday, September 13, 12

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

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

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

It’s a great time to be a JavaScript hacker.

Thursday, September 13, 12

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

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

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

Testing the Node.js Waters

Thursday, September 13, 12

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

Testing the Node.js WatersWe are refactoring m.airbnb.com with a Node backend instead of Rails.

Thursday, September 13, 12

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

Testing the Node.js WatersWe 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

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

Testing the Node.js WatersWe 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

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

Testing the Node.js Waters

Thursday, September 13, 12

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

Node Frameworks

Thursday, September 13, 12

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

Node FrameworksGeddy, TowerRails-inspired. Not utilizing Node’s strengths.

Thursday, September 13, 12

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

Node FrameworksGeddy, TowerRails-inspired. Not utilizing Node’s strengths.

SocketStreamModular, real-time, but optimized for The Easy Way.

Thursday, September 13, 12

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

Node FrameworksGeddy, TowerRails-inspired. Not utilizing Node’s strengths.

SocketStreamModular, real-time, but optimized for The Easy Way.

MeteorSolves for The Hard Way, but all-or-nothing. Alpha.

Thursday, September 13, 12

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

Node FrameworksGeddy, TowerRails-inspired. Not utilizing Node’s strengths.

SocketStreamModular, real-time, but optimized for The Easy Way.

MeteorSolves for The Hard Way, but all-or-nothing. Alpha.

DerbySolves for The Hard Way, but not very modular. Alpha.

Thursday, September 13, 12

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

Node Frameworks

Solves for The Hard Way, but not very modular. Alpha.Derby

Active authors.Active mailing list.Small, if messy, codebase.

Thursday, September 13, 12

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

Node Frameworks

Solves for The Hard Way, but not very modular. Alpha.

DerbyActive authors.Active mailing list.Small, if messy, codebase.

Thursday, September 13, 12

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

Node Frameworks

Solves for The Hard Way, but not very modular. Alpha.

DerbyActive authors.Active mailing list.Small, if messy, codebase.

Thursday, September 13, 12

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

Other ResourcesSingle Page App Book, by Mikito Takadahttp://singlepageappbook.com/

view.json, by Mikito Takadahttp://mixu.net/view.json/

Building The Next SoundCloudhttp://backstage.soundcloud.com/2012/06/building-the-next-soundcloud/

Sean McBride, Bridging the Client-Server Dividehttp://seanmcb.com/client-server-divide/

NodeUp Podcasthttp://nodeup.com/

Thursday, September 13, 12

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

res.end()

Thursday, September 13, 12

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

Let’s chat@spikebrehm

Thursday, September 13, 12