Your browser doesn’t support the features needed to display this presentation.

A simplified version of this presentation follows.

For the best experience, please use one of the following browsers:

Single Page Applications with Backbone.js and Marionette.js


Tech Camp Memphis

11/1/2014

Josh W Lewis - @joshwlewis

Trade Informatics


joshwlewis.com/slides/backbone_marionette

Expectations for Web Applications are Changing

Traditional Web Applications

Reddit, Wikipedia, Hacker News

Single Page Application

Twitter, Facebook, Gmail

Advantages of an SPA

Rendering

Routing

Browser history

State Management

Search Engine Optimization

Browser Compatibility

API Integration

More Code

Client-side JavaScript Frameworks

Reduce the Complexity

A lightweight foundation to build on.

backbonejs.org

Common design patterns and components.

marionettejs.com


Beenz - JSON API

beenz.joshwlewis.comgithub.com/joshwlewis/beenz

MeowMeow - Single Page Application

meowmeow.joshwlewis.comgithub.com/joshwlewis/beenz

Data is persisted via JSON API

GET beenz.joshwlewis.com/ratings

[
  { "ratee_id": 4, "beenz": 4 },
  { "ratee_id": 1, "beenz": 2 },
  ...
]

POST beenz.joshwlewis.com/ratings

{ "ratee_id": 5, "beenz": 1 }

PUT beenz.joshwlewis.com/ratings/2

{ "beenz": 3 }

Backbone.Model

Encapsulates Data

// src/scripts/models/rating.js
Rating = Backbone.Model.extend({
  urlRoot: config.apiHost + '/ratings',
  defaults: {
    ratee_id: null,
    beenz: null
  }
});

Underscore Templates

Pre-configured html

<!-- src/templates/profile.html -->
<img class='avatar img-circle' src='{{ gravatar_url }}'>
<h3 class='name'>{{ name }}</h3>
<div class='beenz-line'></div>

Marionette.ItemView

Manages Model-DOM Relationship

// src/scripts/views/profile_view.js
ProfileView = Marionette.ItemView.extend({
  template: 'profile',
  className: 'profile-view',
  ui: {
    'beenz_line': '.beenz-line'
  },
  onRender: function() {
    var beenz = this.model.get('beenz');
    for (var been = 1; been <= beenz; been++ ) {
      var img = $('<img>')
                    .addClass('beenz beenz-' + beenz);
                    .attr('src', 'beenz-' + beenz + '.png');
      this.ui.beenz_line.append(img);
    }
  }
});

Backbone.Collection

Manages Model Lists

// src/scripts/collections/rating_collection.js
RatingCollection = Backbone.Collection.extend({
  url: config.apiHost + '/ratings',
  model: Rating
});

Marionette.CollectionView

Manages lists of ItemViews

// src/scripts/views/user_collection_view.js
UserCollectionView = Marionette.CollectionView.extend({
  tagName: 'ul',
  className: 'list user-collection-view',
  getChildView: function() {
    return UserItemView;
  }
});

The browser needs an initial DOM

<!-- src/static/index.html -->
<!doctype html>
<html lang="en">
  <head>
    <title>Meow Meow Beenz</title>
    <link rel="stylesheet" href="app.css">
  </head>
  <body>
    <header id='intro'>
      <h1 class='title'>Meow Meow Beenz</h1>
      <div id="header-container"></div>
    </header>
    <div id="main-container"></div>
    <script src="app.js"></script>
  </body>
</html>

Marionette.Application

Bootstraps the Environment

// src/scripts/app.js
app = new Marionette.Application();

app.addInitializer(function() {
  app.addRegions({
    header_container: '#header-container',
    main_container:   '#main-container'
  });
  app.controller = new AppController();
  app.router = new AppRouter({
    controller: app.controller
  });
  app.controller.showProfile();
  Backbone.history.start();
});

Marionette.Controller

Sets up Individual Components

AppController = Marionette.Controller.extend({
  listUsers: function() {
    var users = app.request('users');
    var view = new UserCollectionView({ collection: users });
    app.main_container.show(view);
  }
});

Marionette.AppRouter

Maps urls to Controller Methods

// src/scripts/routers/app_router.js
AppRouter = Marionette.AppRouter.extend({
  appRoutes: {
    '': 'listUsers'
  }
});

Backbone.Events

Allow loosely coupled object communicate messages

// src/scripts/models/session.js
Session = Backbone.Model.extend({
  urlRoot: config.apiHost + '/session',
  initialize: function() {
    this.on('change:token', function(model, token) {
      app.vent.trigger('change:token', token);
    });
  }
});
// src/scripts/app.js
app.vent.on("change:token", function(token) {
  localStorage.setItem('token', token);
  if (token) {
    app.request('profile').fetch();
  } else {
    app.request('profile').clear();
  }
});

Marionette.reqres

Application can manage global state

// src/scripts/app.js
app.reqres.setHandler('profile', function() {
  if (!app.profile) {
    app.profile = new Profile();
    app.profile.fetch();
  }
  return app.profile;
});
// src/scripts/views/user_item_view.js
UserItemView = Marionette.LayoutView.extend({
  showRating: function() {
    if (app.request('profile').id) {
      var rating = app.request('rating', this.model.id);
      view = new RatingView({ model: rating });
      this.rating_container.show(view);
    }
  }
});

Resources

This Presentation
joshwlewis.com/slides/backbone_marionette
Backbone.js
backbonejs.org
Marionette.js
marionettejs.com
MeowMeow (Client Application)
github.com/joshwlewis/meowmeow
Beenz (Rails JSON API)
github.com/joshwlewis/beenz