SlideShare a Scribd company logo
Introducing Rendr:
Run your Backbone.js apps
on the client and server

Spike Brehm
@spikebrehm

HTML5DevConf
April 1, 2013
2008
Introducing Rendr: Run your Backbone.js apps on the client and server
2013
Introducing Rendr: Run your Backbone.js apps on the client and server
Exciting times in the
world of web apps.


<your framework here>
Client-side
MVC
Introducing Rendr: Run your Backbone.js apps on the client and server
+
Poor SEO; not crawlable
Performance hit to download
and parse JS
Duplicated application logic
Context switching
It’s still a PITA to build
fast, maintainable
rich-client apps.
We started thinking...

What if our JavaScript
app could run on both
sides?
Client +
server
MVC
aka “The Holy Grail”
Provides SEO
Initial pageload is drastically
faster
Consolidated application logic
Has anyone already
done this?
Meteor: client/server, but no
server-side rendering; owns data
layer
Derby: client+server rendering,
but owns data layer
Mojito: client+server rendering,
but full stack, and... YUI.
Okay... how hard can
it be?
Introducing Rendr: Run your Backbone.js apps on the client and server
+
+
Rendr.
What it is.
What it is.
JavaScript MVC on client & server
Backbone & Handlebars
BaseView, BaseModel,
BaseCollection, BaseApp,
ClientRouter, ServerRouter...
Express middleware
Minimal glue between client & server
What it ain’t.
What it ain’t.

Batteries-included web framework
Finished
Design goals:
•   Write application logic agnostic to
    environment.
•   Library, not a framework.
•   Minimize   if (server) {...} else {...}.

•   Hide complexity in library.
•   Talk to RESTful API.
•   No server-side DOM.
•   Simple Express middleware.
Classes:
- BaseApp < Backbone.Model
- BaseModel < Backbone.Model
- BaseCollection < Backbone.Collection
- BaseView < Backbone.View
- AppView < BaseView
- ClientRouter < BaseRouter
- ServerRouter < BaseRouter
- ModelStore < MemoryStore
- CollectionStore < MemoryStore
- Fetcher
Rendr directory structure
|- client/
|- shared/   } Sent to client
|- server/
App directory structure
|- app/
|- public/
|- server/
App directory structure




                    }
|- app/
|--- collections/
|--- controllers/
|--- models/            Entire app dir
|--- templates/         gets sent to
|--- views/
|--- app.js
                        client
|--- router.js
|--- routes.js
|- public/
|- server/
CommonJS using Stitch

On the server:
var User = require(‘app/models/user’);
var BaseView = require(‘rendr/shared/base/view’);


On the client:
var User = require(‘app/models/user’);
var BaseView = require(‘rendr/shared/base/view’);
app/routes.js


 module.exports = function(match) {
    match('',          'home#index');
    match('users',     'users#index');
    match('users/:id', 'users#show');
 };
app/routes.js


 module.exports = function(match) {
    match('',          'home#index');
    match('users',     'users#index');
    match('users/:id', 'users#show');
 };


                     controller
app/routes.js


 module.exports = function(match) {
    match('',          'home#index');
    match('users',     'users#index');
    match('users/:id', 'users#show');
 };


                        action
Render lifecycle,
server.
•   On server startup, parse routes file and mount as
    Express routes
•   GET /users/1337
•   Router matches "/users/:id" to "users#show" with
    params = {"id": 1337}
•   Router finds controller:
    require("app/controllers/users_controller")
•   Router executes show action with params = {"id": 1337}
•   The show action says to fetch User#1337 and use
    UsersShowView view class
•   Router instantiates new UsersShowView with data
•   Router calls view.getHtml()
•   Hands HTML to Express, which decorates with layout
    and serves response
Render lifecycle,
client.
•   On page load, Router parses routes file and mounts
    Backbone routes
•   pushState /users/1337
•   Router matches "/users/:id" to "users#show" with
    params = {"id": 1337}
•   Router finds controller:
    require("app/controllers/users_controller")
•   Router executes show action with params = {"id": 1337}
•   The show action says to fetch User#1337 and use
    UsersShowView view class
•   Router instantiates new UsersShowView with data
•   Router calls view.render()
•   Insert into DOM
•   On page load, Router parses routes file and mounts
    Backbone routes
•   pushState /users/1337
•   Router matches "/users/:id" to "users#show" with
    params = {"id": 1337}
•   Router finds controller:
    require("app/controllers/users_controller")
•   Router executes show action with params = {"id": 1337}
•   The show action says to fetch User#1337 and use
    UsersShowView view class
•   Router instantiates new UsersShowView with data
•   Router calls view.render()
•   Insert into DOM
•   On page load, Router parses routes file and mounts
    Backbone routes
•   pushState /users/1337
•   Router matches "/users/:id" to "users#show" with
    params = {"id": 1337}
•   Router finds controller:
    require("app/controllers/users_controller")
•   Router executes show action with params = {"id": 1337}
•   The show action says to fetch User#1337 and use
    UsersShowView view class
•   Router instantiates new UsersShowView with data
•   Router calls view.render()
•   Insert into DOM
app/controllers/users_controller.js

module.exports = {
   show: function(params, callback) {
     callback(null, 'users_show_view');
   }
};
app/controllers/users_controller.js

module.exports = {
   show: function(params, callback) {
     callback(null, 'users_show_view');
   }
};
app/controllers/users_controller.js

module.exports = {
   show: function(params, callback) {
     callback(null, 'users_show_view');
   }
};
app/controllers/users_controller.js

module.exports = {
   show: function(params, callback) {
     callback(null, 'users_show_view');
   }
};
app/controllers/users_controller.js

module.exports = {
   show: function(params, callback) {
     callback(null, 'users_show_view');
   }
};




Most simple case: no fetching of
data.
app/controllers/users_controller.js

module.exports = {
   show: function(params, callback) {
     callback(null, 'users_show_view');
   }
};




But we want to fetch the user.
app/controllers/users_controller.js

module.exports = {
   show: function(params, callback) {
     var spec = {
        model: {model: ‘User’, params: params}
     };
     this.app.fetch(spec, function(err, results) {
        callback(err, 'users_show_view', results);
     });
   }
};
app/controllers/users_controller.js

module.exports = {
   show: function(params, callback) {
     var spec = {
        model: {model: ‘User’, params: params}
     };
     this.app.fetch(spec, function(err, results) {
        callback(err, 'users_show_view', results);
     });
   }
};
app/controllers/users_controller.js

module.exports = {
   show: function(params, callback) {
     var spec = {
        model: {model: ‘User’, params: params}
     };
     this.app.fetch(spec, function(err, results) {
        callback(err, 'users_show_view', results);
     });
   }
};
app/controllers/users_controller.js

module.exports = {
   show: function(params, callback) {
     var spec = {
        model: {model: ‘User’, params: params}
     };
     this.app.fetch(spec, function(err, results) {
        callback(err, 'users_show_view', results);
     });
   }
};
app/views/users_show_view.js

var BaseView = require('rendr/shared/base/view');

module.exports = BaseView.extend({
  className: 'users_show_view'
});
module.exports.id = 'users_show_view';
app/views/users_show_view.js

var BaseView = require('rendr/shared/base/view');

module.exports = BaseView.extend({
  className: 'users_show_view'
});
module.exports.id = 'users_show_view';
app/views/users_show_view.js

var BaseView = require('rendr/shared/base/view');

module.exports = BaseView.extend({
  className: 'users_show_view'
});
module.exports.id = 'users_show_view';
app/views/users_show_view.js

var BaseView = require('rendr/shared/base/view');

module.exports = BaseView.extend({
  className: 'users_show_view'
});
module.exports.id = 'users_show_view';
app/views/users_show_view.js

var BaseView = require('rendr/shared/base/view');

module.exports = BaseView.extend({
  className: 'users_show_view',

  events: {
   'click p': 'handleClick'
  },

  handleClick: function() {...}
});
module.exports.id = 'users_show_view';
app/templates/users_show_view.hbs



<h1>User: {{name}}</h1>

<p>From {{city}}.</p>
Rendered HTML

<div class="users_show_view"
     data-view="users_show_view"
     data-model_name="user"
     data-model_id="1337">

  <h1>User: Spike</h1>

  <p>From San Francisco.</p>
</div>
Rendered HTML

<div class="users_show_view"
     data-view="users_show_view"
     data-model_name="user"
     data-model_id="1337">

  <h1>User: Spike</h1>

  <p>From San Francisco.</p>
</div>
Rendered HTML

<div class="users_show_view"
     data-view="users_show_view"
     data-model_name="user"
     data-model_id="1337">

  <h1>User: Spike</h1>

  <p>From San Francisco.</p>
</div>
Rendered HTML

<div class="users_show_view"
     data-view="users_show_view"
     data-model_name="user"
     data-model_id="1337">

  <h1>User: Spike</h1>

  <p>From San Francisco.</p>
</div>
Where’d the data come from?
Where’s the render method?
How do I customize what gets
passed to the template?
Sensible defaults.
app/views/users_show_view.js
var BaseView = require('rendr/shared/base/view');

module.exports = BaseView.extend({
  className: 'users_show_view',

  getTemplateData: function() {

  }
});
app/views/users_show_view.js
var BaseView = require('rendr/shared/base/view');

module.exports = BaseView.extend({
  className: 'users_show_view',

  getTemplateData: function() {
    var data = BaseView.prototype.getTemplateData 
      .call(this);
  }
});
app/views/users_show_view.js
var BaseView = require('rendr/shared/base/view');

module.exports = BaseView.extend({
  className: 'users_show_view',

  getTemplateData: function() {
    var data = BaseView.prototype.getTemplateData 
      .call(this);
    // `data` is equivalent to this.model.toJSON()

  }
});
app/views/users_show_view.js
var BaseView = require('rendr/shared/base/view');

module.exports = BaseView.extend({
  className: 'users_show_view',

  getTemplateData: function() {
    var data = BaseView.prototype.getTemplateData 
      .call(this);
    // `data` is equivalent to this.model.toJSON()
    return _.extend(data, {
      nameUppercase: data.name.toUpperCase()
    });
  }
});
app/templates/users_show_view.hbs



<h1>User: {{nameUppercase}}</h1>

<p>From {{city}}.</p>
Rendered HTML


<div class="users_show_view" data-...>
  <h1>User: SPIKE</h1>

  <p>From San Francisco.</p>
</div>
Rendered HTML with layout
<!doctype html>
<html lang="en">
<head>...</head>

<body>
<div id="content">
  <div class="users_show_view" data-view="users_show_view"
       data-model_name="user" data-model_id="1337">

    <h1>User: SPIKE</h1>

    <p>From San Francisco.</p>
  </div>
</div>

<script>
(function() {
var App = window.App = new (require('app/app'));
App.bootstrapData({
  "model":{"summary":{"model":"user","id":1337},
  "data":{"name":"Spike","city":"San Francisco", ...}
});
App.start();
})();
</script>
</body></html>
Rendered HTML with layout
<!doctype html>
<html lang="en">
<head>...</head>

<body>
<div id="content">
  <div class="users_show_view" data-view="users_show_view"
       data-model_name="user" data-model_id="1337">

    <h1>User: SPIKE</h1>

    <p>From San Francisco.</p>
  </div>
</div>

<script>
(function() {
var App = window.App = new (require('app/app'));
App.bootstrapData({
  "model":{"summary":{"model":"user","id":"wycats"},
  "data":{"name":"Spike","city":"San Francisco", ...}
});
App.start();
})();
</script>
</body></html>
View hydration.
1. Find DOM els with data-view attribute.
var viewEls = $("[data-view]");
2. Determine model or collection based on
data-model_name, data-model_id, etc.

var modelName = viewEl.data(‘model_name’),
    modelId   = viewEl.data(‘model_id’);

console.log(modelName, modelId);
 => “user” 1337
3. Fetch model/collection data from
ModelStore/CollectionStore.
var model = modelStore.get(modelName,
              modelId);
4. Find view class.
var viewName, ViewClass;
viewName = viewEl.data(‘view’);
ViewClass = require('app/views/' + viewName);
5. Instantiate view instance with model.
var view = new ViewClass({
  model: model,
  ...
});
6. Attach to DOM el.
view.setElement(viewEl);
7. Delegate events.
view.delegateEvents();
8. Profit!
alert(“That wasn’t so hard, right?”)
Rendr is available
starting today.
github.com/airbnb/rendr
TODO

•   Share routing logic between client &
    server.

•   Lazy load views, templates, etc as needed.

•   Support other templating languages.

•   Break down into smaller modules.

•   Rewrite in vanilla JavaScript.

•   much more...
Hackers
wanted.
Thanks!
@spikebrehm
  @rendrjs
@AirbnbNerds

More Related Content

PDF
Hastening React SSR - Web Performance San Diego
PDF
Modular applications with montage components
PPT
Creating the interfaces of the future with the APIs of today
PPTX
IndexedDB - Querying and Performance
PDF
[FEConf Korea 2017]Angular 컴포넌트 대화법
PDF
Sane Async Patterns
PDF
Workshop 27: Isomorphic web apps with ReactJS
PDF
Backbone js
Hastening React SSR - Web Performance San Diego
Modular applications with montage components
Creating the interfaces of the future with the APIs of today
IndexedDB - Querying and Performance
[FEConf Korea 2017]Angular 컴포넌트 대화법
Sane Async Patterns
Workshop 27: Isomorphic web apps with ReactJS
Backbone js

What's hot (20)

PDF
Vue, vue router, vuex
PPTX
Angular js
PDF
Angular를 활용한 웹 프론트단 개발과 2.0에서 달라진점
PPTX
How to Build SPA with Vue Router 2.0
PDF
Modern Web Application Development Workflow - EclipseCon Europe 2014
DOC
No Coding Necessary: Building Confluence User Macros Cheat Sheet - Atlassian ...
PPT
Managing JavaScript Dependencies With RequireJS
PDF
No Coding Necessary: Building User Macros and Dynamic Reports Inside Confluen...
PDF
Refactoring Large Web Applications with Backbone.js
PDF
An Introduction To Testing In AngularJS Applications
PPTX
Introduction to Angularjs
PPTX
Planbox Backbone MVC
KEY
Javascript Frameworks for Well Architected, Immersive Web Apps
PDF
Workshop 13: AngularJS Parte II
PPTX
Getting Started with Angular JS
PDF
AngularJS 101 - Everything you need to know to get started
KEY
MVC on the server and on the client
PPTX
Spring MVC
PPTX
Vue business first
Vue, vue router, vuex
Angular js
Angular를 활용한 웹 프론트단 개발과 2.0에서 달라진점
How to Build SPA with Vue Router 2.0
Modern Web Application Development Workflow - EclipseCon Europe 2014
No Coding Necessary: Building Confluence User Macros Cheat Sheet - Atlassian ...
Managing JavaScript Dependencies With RequireJS
No Coding Necessary: Building User Macros and Dynamic Reports Inside Confluen...
Refactoring Large Web Applications with Backbone.js
An Introduction To Testing In AngularJS Applications
Introduction to Angularjs
Planbox Backbone MVC
Javascript Frameworks for Well Architected, Immersive Web Apps
Workshop 13: AngularJS Parte II
Getting Started with Angular JS
AngularJS 101 - Everything you need to know to get started
MVC on the server and on the client
Spring MVC
Vue business first
Ad

Similar to Introducing Rendr: Run your Backbone.js apps on the client and server (20)

PPTX
Backbonejs for beginners
PDF
Backbone.js
PPTX
Writing HTML5 Web Apps using Backbone.js and GAE
PDF
Director x Backbone = :)
KEY
Single Page Web Apps with Backbone.js and Rails
KEY
Prateek dayal backbonerails-110528024926-phpapp02
PDF
Viking academy backbone.js
KEY
Single Page Web Applications with CoffeeScript, Backbone and Jasmine
PDF
Introduction to Backbone.js for Rails developers
PDF
Aplicacoes dinamicas Rails com Backbone
PDF
Server and client rendering of single page apps
KEY
How and why i roll my own node.js framework
PDF
ParisJS #10 : RequireJS
ODP
Javascript frameworks: Backbone.js
PPTX
Javascript Frameworks Comparison
PPT
Backbone js
PDF
Developing maintainable Cordova applications
PDF
An approach to responsive, realtime with Backbone.js and WebSockets
PDF
Backbone
PDF
Unit Testing in JavaScript with MVC and QUnit
Backbonejs for beginners
Backbone.js
Writing HTML5 Web Apps using Backbone.js and GAE
Director x Backbone = :)
Single Page Web Apps with Backbone.js and Rails
Prateek dayal backbonerails-110528024926-phpapp02
Viking academy backbone.js
Single Page Web Applications with CoffeeScript, Backbone and Jasmine
Introduction to Backbone.js for Rails developers
Aplicacoes dinamicas Rails com Backbone
Server and client rendering of single page apps
How and why i roll my own node.js framework
ParisJS #10 : RequireJS
Javascript frameworks: Backbone.js
Javascript Frameworks Comparison
Backbone js
Developing maintainable Cordova applications
An approach to responsive, realtime with Backbone.js and WebSockets
Backbone
Unit Testing in JavaScript with MVC and QUnit
Ad

More from Spike Brehm (11)

PDF
Managing Through Chaos (w/ presenter notes)
PDF
Managing Through Chaos
PDF
The Evolution of Airbnb's Frontend
PDF
Integrating Browserify with Sprockets
PDF
Building Isomorphic Apps (JSConf.Asia 2014)
PDF
JSConf US 2014: Building Isomorphic Apps
PDF
In Pursuit of the Holy Grail: Building Isomorphic JavaScript Apps
PDF
General Assembly Workshop: Advanced JavaScript
PDF
Isomorphic JavaScript: #DevBeat Master Class
PDF
Building a Single-Page App: Backbone, Node.js, and Beyond
PPTX
Extending Apostrophe to build a variable-based CMS for rendering PDF brochures
Managing Through Chaos (w/ presenter notes)
Managing Through Chaos
The Evolution of Airbnb's Frontend
Integrating Browserify with Sprockets
Building Isomorphic Apps (JSConf.Asia 2014)
JSConf US 2014: Building Isomorphic Apps
In Pursuit of the Holy Grail: Building Isomorphic JavaScript Apps
General Assembly Workshop: Advanced JavaScript
Isomorphic JavaScript: #DevBeat Master Class
Building a Single-Page App: Backbone, Node.js, and Beyond
Extending Apostrophe to build a variable-based CMS for rendering PDF brochures

Recently uploaded (20)

PDF
The Rise and Fall of 3GPP – Time for a Sabbatical?
PDF
NewMind AI Weekly Chronicles - August'25-Week II
PPTX
Group 1 Presentation -Planning and Decision Making .pptx
PDF
Blue Purple Modern Animated Computer Science Presentation.pdf.pdf
PDF
Agricultural_Statistics_at_a_Glance_2022_0.pdf
PDF
MIND Revenue Release Quarter 2 2025 Press Release
PPTX
Big Data Technologies - Introduction.pptx
PPTX
KOM of Painting work and Equipment Insulation REV00 update 25-dec.pptx
PPTX
Digital-Transformation-Roadmap-for-Companies.pptx
PDF
gpt5_lecture_notes_comprehensive_20250812015547.pdf
PPTX
1. Introduction to Computer Programming.pptx
PPTX
Programs and apps: productivity, graphics, security and other tools
PPT
Teaching material agriculture food technology
PDF
Profit Center Accounting in SAP S/4HANA, S4F28 Col11
PDF
cuic standard and advanced reporting.pdf
PPTX
MYSQL Presentation for SQL database connectivity
PDF
Encapsulation theory and applications.pdf
PDF
Per capita expenditure prediction using model stacking based on satellite ima...
PPTX
A Presentation on Artificial Intelligence
PDF
Accuracy of neural networks in brain wave diagnosis of schizophrenia
The Rise and Fall of 3GPP – Time for a Sabbatical?
NewMind AI Weekly Chronicles - August'25-Week II
Group 1 Presentation -Planning and Decision Making .pptx
Blue Purple Modern Animated Computer Science Presentation.pdf.pdf
Agricultural_Statistics_at_a_Glance_2022_0.pdf
MIND Revenue Release Quarter 2 2025 Press Release
Big Data Technologies - Introduction.pptx
KOM of Painting work and Equipment Insulation REV00 update 25-dec.pptx
Digital-Transformation-Roadmap-for-Companies.pptx
gpt5_lecture_notes_comprehensive_20250812015547.pdf
1. Introduction to Computer Programming.pptx
Programs and apps: productivity, graphics, security and other tools
Teaching material agriculture food technology
Profit Center Accounting in SAP S/4HANA, S4F28 Col11
cuic standard and advanced reporting.pdf
MYSQL Presentation for SQL database connectivity
Encapsulation theory and applications.pdf
Per capita expenditure prediction using model stacking based on satellite ima...
A Presentation on Artificial Intelligence
Accuracy of neural networks in brain wave diagnosis of schizophrenia

Introducing Rendr: Run your Backbone.js apps on the client and server

  • 1. Introducing Rendr: Run your Backbone.js apps on the client and server Spike Brehm @spikebrehm HTML5DevConf April 1, 2013
  • 6. Exciting times in the world of web apps. <your framework here>
  • 9. +
  • 10. Poor SEO; not crawlable Performance hit to download and parse JS Duplicated application logic Context switching
  • 11. It’s still a PITA to build fast, maintainable rich-client apps.
  • 12. We started thinking... What if our JavaScript app could run on both sides?
  • 14. Provides SEO Initial pageload is drastically faster Consolidated application logic
  • 16. Meteor: client/server, but no server-side rendering; owns data layer Derby: client+server rendering, but owns data layer Mojito: client+server rendering, but full stack, and... YUI.
  • 17. Okay... how hard can it be?
  • 19. +
  • 20. +
  • 23. What it is. JavaScript MVC on client & server Backbone & Handlebars BaseView, BaseModel, BaseCollection, BaseApp, ClientRouter, ServerRouter... Express middleware Minimal glue between client & server
  • 25. What it ain’t. Batteries-included web framework Finished
  • 26. Design goals: • Write application logic agnostic to environment. • Library, not a framework. • Minimize if (server) {...} else {...}. • Hide complexity in library. • Talk to RESTful API. • No server-side DOM. • Simple Express middleware.
  • 27. Classes: - BaseApp < Backbone.Model - BaseModel < Backbone.Model - BaseCollection < Backbone.Collection - BaseView < Backbone.View - AppView < BaseView - ClientRouter < BaseRouter - ServerRouter < BaseRouter - ModelStore < MemoryStore - CollectionStore < MemoryStore - Fetcher
  • 28. Rendr directory structure |- client/ |- shared/ } Sent to client |- server/
  • 29. App directory structure |- app/ |- public/ |- server/
  • 30. App directory structure } |- app/ |--- collections/ |--- controllers/ |--- models/ Entire app dir |--- templates/ gets sent to |--- views/ |--- app.js client |--- router.js |--- routes.js |- public/ |- server/
  • 31. CommonJS using Stitch On the server: var User = require(‘app/models/user’); var BaseView = require(‘rendr/shared/base/view’); On the client: var User = require(‘app/models/user’); var BaseView = require(‘rendr/shared/base/view’);
  • 32. app/routes.js module.exports = function(match) { match('', 'home#index'); match('users', 'users#index'); match('users/:id', 'users#show'); };
  • 33. app/routes.js module.exports = function(match) { match('', 'home#index'); match('users', 'users#index'); match('users/:id', 'users#show'); }; controller
  • 34. app/routes.js module.exports = function(match) { match('', 'home#index'); match('users', 'users#index'); match('users/:id', 'users#show'); }; action
  • 36. On server startup, parse routes file and mount as Express routes • GET /users/1337 • Router matches "/users/:id" to "users#show" with params = {"id": 1337} • Router finds controller: require("app/controllers/users_controller") • Router executes show action with params = {"id": 1337} • The show action says to fetch User#1337 and use UsersShowView view class • Router instantiates new UsersShowView with data • Router calls view.getHtml() • Hands HTML to Express, which decorates with layout and serves response
  • 38. On page load, Router parses routes file and mounts Backbone routes • pushState /users/1337 • Router matches "/users/:id" to "users#show" with params = {"id": 1337} • Router finds controller: require("app/controllers/users_controller") • Router executes show action with params = {"id": 1337} • The show action says to fetch User#1337 and use UsersShowView view class • Router instantiates new UsersShowView with data • Router calls view.render() • Insert into DOM
  • 39. On page load, Router parses routes file and mounts Backbone routes • pushState /users/1337 • Router matches "/users/:id" to "users#show" with params = {"id": 1337} • Router finds controller: require("app/controllers/users_controller") • Router executes show action with params = {"id": 1337} • The show action says to fetch User#1337 and use UsersShowView view class • Router instantiates new UsersShowView with data • Router calls view.render() • Insert into DOM
  • 40. On page load, Router parses routes file and mounts Backbone routes • pushState /users/1337 • Router matches "/users/:id" to "users#show" with params = {"id": 1337} • Router finds controller: require("app/controllers/users_controller") • Router executes show action with params = {"id": 1337} • The show action says to fetch User#1337 and use UsersShowView view class • Router instantiates new UsersShowView with data • Router calls view.render() • Insert into DOM
  • 41. app/controllers/users_controller.js module.exports = { show: function(params, callback) { callback(null, 'users_show_view'); } };
  • 42. app/controllers/users_controller.js module.exports = { show: function(params, callback) { callback(null, 'users_show_view'); } };
  • 43. app/controllers/users_controller.js module.exports = { show: function(params, callback) { callback(null, 'users_show_view'); } };
  • 44. app/controllers/users_controller.js module.exports = { show: function(params, callback) { callback(null, 'users_show_view'); } };
  • 45. app/controllers/users_controller.js module.exports = { show: function(params, callback) { callback(null, 'users_show_view'); } }; Most simple case: no fetching of data.
  • 46. app/controllers/users_controller.js module.exports = { show: function(params, callback) { callback(null, 'users_show_view'); } }; But we want to fetch the user.
  • 47. app/controllers/users_controller.js module.exports = { show: function(params, callback) { var spec = { model: {model: ‘User’, params: params} }; this.app.fetch(spec, function(err, results) { callback(err, 'users_show_view', results); }); } };
  • 48. app/controllers/users_controller.js module.exports = { show: function(params, callback) { var spec = { model: {model: ‘User’, params: params} }; this.app.fetch(spec, function(err, results) { callback(err, 'users_show_view', results); }); } };
  • 49. app/controllers/users_controller.js module.exports = { show: function(params, callback) { var spec = { model: {model: ‘User’, params: params} }; this.app.fetch(spec, function(err, results) { callback(err, 'users_show_view', results); }); } };
  • 50. app/controllers/users_controller.js module.exports = { show: function(params, callback) { var spec = { model: {model: ‘User’, params: params} }; this.app.fetch(spec, function(err, results) { callback(err, 'users_show_view', results); }); } };
  • 51. app/views/users_show_view.js var BaseView = require('rendr/shared/base/view'); module.exports = BaseView.extend({ className: 'users_show_view' }); module.exports.id = 'users_show_view';
  • 52. app/views/users_show_view.js var BaseView = require('rendr/shared/base/view'); module.exports = BaseView.extend({ className: 'users_show_view' }); module.exports.id = 'users_show_view';
  • 53. app/views/users_show_view.js var BaseView = require('rendr/shared/base/view'); module.exports = BaseView.extend({ className: 'users_show_view' }); module.exports.id = 'users_show_view';
  • 54. app/views/users_show_view.js var BaseView = require('rendr/shared/base/view'); module.exports = BaseView.extend({ className: 'users_show_view' }); module.exports.id = 'users_show_view';
  • 55. app/views/users_show_view.js var BaseView = require('rendr/shared/base/view'); module.exports = BaseView.extend({ className: 'users_show_view', events: { 'click p': 'handleClick' }, handleClick: function() {...} }); module.exports.id = 'users_show_view';
  • 57. Rendered HTML <div class="users_show_view" data-view="users_show_view" data-model_name="user" data-model_id="1337"> <h1>User: Spike</h1> <p>From San Francisco.</p> </div>
  • 58. Rendered HTML <div class="users_show_view" data-view="users_show_view" data-model_name="user" data-model_id="1337"> <h1>User: Spike</h1> <p>From San Francisco.</p> </div>
  • 59. Rendered HTML <div class="users_show_view" data-view="users_show_view" data-model_name="user" data-model_id="1337"> <h1>User: Spike</h1> <p>From San Francisco.</p> </div>
  • 60. Rendered HTML <div class="users_show_view" data-view="users_show_view" data-model_name="user" data-model_id="1337"> <h1>User: Spike</h1> <p>From San Francisco.</p> </div>
  • 61. Where’d the data come from? Where’s the render method? How do I customize what gets passed to the template? Sensible defaults.
  • 62. app/views/users_show_view.js var BaseView = require('rendr/shared/base/view'); module.exports = BaseView.extend({ className: 'users_show_view', getTemplateData: function() { } });
  • 63. app/views/users_show_view.js var BaseView = require('rendr/shared/base/view'); module.exports = BaseView.extend({ className: 'users_show_view', getTemplateData: function() { var data = BaseView.prototype.getTemplateData .call(this); } });
  • 64. app/views/users_show_view.js var BaseView = require('rendr/shared/base/view'); module.exports = BaseView.extend({ className: 'users_show_view', getTemplateData: function() { var data = BaseView.prototype.getTemplateData .call(this); // `data` is equivalent to this.model.toJSON() } });
  • 65. app/views/users_show_view.js var BaseView = require('rendr/shared/base/view'); module.exports = BaseView.extend({ className: 'users_show_view', getTemplateData: function() { var data = BaseView.prototype.getTemplateData .call(this); // `data` is equivalent to this.model.toJSON() return _.extend(data, { nameUppercase: data.name.toUpperCase() }); } });
  • 67. Rendered HTML <div class="users_show_view" data-...> <h1>User: SPIKE</h1> <p>From San Francisco.</p> </div>
  • 68. Rendered HTML with layout <!doctype html> <html lang="en"> <head>...</head> <body> <div id="content"> <div class="users_show_view" data-view="users_show_view" data-model_name="user" data-model_id="1337"> <h1>User: SPIKE</h1> <p>From San Francisco.</p> </div> </div> <script> (function() { var App = window.App = new (require('app/app')); App.bootstrapData({ "model":{"summary":{"model":"user","id":1337}, "data":{"name":"Spike","city":"San Francisco", ...} }); App.start(); })(); </script> </body></html>
  • 69. Rendered HTML with layout <!doctype html> <html lang="en"> <head>...</head> <body> <div id="content"> <div class="users_show_view" data-view="users_show_view" data-model_name="user" data-model_id="1337"> <h1>User: SPIKE</h1> <p>From San Francisco.</p> </div> </div> <script> (function() { var App = window.App = new (require('app/app')); App.bootstrapData({ "model":{"summary":{"model":"user","id":"wycats"}, "data":{"name":"Spike","city":"San Francisco", ...} }); App.start(); })(); </script> </body></html>
  • 71. 1. Find DOM els with data-view attribute. var viewEls = $("[data-view]");
  • 72. 2. Determine model or collection based on data-model_name, data-model_id, etc. var modelName = viewEl.data(‘model_name’), modelId = viewEl.data(‘model_id’); console.log(modelName, modelId); => “user” 1337
  • 73. 3. Fetch model/collection data from ModelStore/CollectionStore. var model = modelStore.get(modelName, modelId);
  • 74. 4. Find view class. var viewName, ViewClass; viewName = viewEl.data(‘view’); ViewClass = require('app/views/' + viewName);
  • 75. 5. Instantiate view instance with model. var view = new ViewClass({ model: model, ... });
  • 76. 6. Attach to DOM el. view.setElement(viewEl);
  • 78. 8. Profit! alert(“That wasn’t so hard, right?”)
  • 79. Rendr is available starting today. github.com/airbnb/rendr
  • 80. TODO • Share routing logic between client & server. • Lazy load views, templates, etc as needed. • Support other templating languages. • Break down into smaller modules. • Rewrite in vanilla JavaScript. • much more...