Scalable JavaScript

Backbone and Marionette

Kaleb Fulgham

App Lead at PROS

I love coffee!

I love the JavaScripts;

Web applications

large & complex

Solid architecture

backend

client

essential

Large apps

small parts

UI Views

Data & Business Logic

jQuery

DOM manipulation

$.ajax

plugins...

Hacking together jQuery

will make code soup

Soup != App

Let's focus...

Solve a Business Problem

Data, Views, Interactions

Backbone.js

MVC, MVP, MV* ...

  1. MODELS with key-value binding
  2. COLLECTIONS to enumerate
  3. VIEWS with declarative event handling
  4. ROUTERS provide browser history support
  5. EVENTS galore!

MV* library
not a framework

annotated source code

app backbone

abstractions & building blocks

Embedded widgets

Massive composite apps

Backbone.Events

var object = {};

_.extend(object, Backbone.Events);

object.on("alert", function(msg) {
  alert("Triggered " + msg);
});

object.trigger("alert", "an event!");

Event dispatcher

var window.App = {};

// ... amazing app code ...

App.dispatcher = _.clone(Backbone.Events);

Venue JSON

{
  "id": "508b5225e4b0c9eb71763651",
  "name": "Southside Espresso",
  "twitter": "@SouthsideHOU",
  "categories": [
    {
      "id": "4bf58dd8d48988d1e0931735",
      "name": "Coffee Shop",
      "icon": "http://t.co/coffeeshop_bg_32.png"
    }
  ],
  "location": {
    "address": "904-C Westheimer Rd.",
    "postalCode": "77006",
    "city": "Houston"
  }
}

Backbone.Models

var Venue = Backbone.Model.extend({
  urlRoot: '/venues'
});

var venue = new Venue({
  name: "Southside Espresso",
  twitter: "@SouthsideHOU"
});

venue.get('name');
// Southside Espresso

venue.set('open', false);
venue.hasChanged(); // true

Models + Events

/* Listen to any change */
venue.on('change', function(){
  console.log('Changed something');
});
venue.set('name', 'The South Side');
// Changed something


// Listen to `open` changes
venue.on('change:open', function(){
  console.log('Notify Kaleb');
});
venue.set('open', true);
// Notify Kaleb
// Changed something

Collections

Server Syncing

var Venue = Backbone.Model.extend({
  urlRoot: '/venues'
});

var venue = new Venue({ id : 123 });
venue.fetch(); // $.ajax `GET /venues/123`

venue.set('open', true);
venue.save(); // $.ajax `PUT /venues/123`

helpful utilities

_.each();
_.filter();
_.indexBy();
_.last();
...
_.map();
_.pluck();
_.reduce();
_.where();
...

Backbone.View

Logical views

Backed by Models

Update independently

Let's dive into the

Example Foursquare App

Check the
vibrant community!

Backbone.View boilerplate?

marionette.js

Marionette.js

  1. Reduction of View Boilerplates
  2. Memory management (cleaning up zombie views)
  3. Promotes modular, event-driven architecture
  4. Sensible defaults
  5. Use only what you need!

Let's go back to the code

Before Marionette

var ItemView = Backbone.View.extend({
  tagName: 'li',
  template: _.template($('#item-template').html()),
  initialize: function() {
    this.model.on('change',this.render,this);
    this.model.on('destroy',this.remove,this);
  },
  render: function() {
    this.$el.html(this.template(this.model.toJSON()));
    return this;
  }
});

Before Marionette

var ListView = Backbone.View.extend({
  tagName: 'ui',
  initialize: function() {
    this.collection.on('add',this.addItem,this);
    this.collection.on('remove',this.removeItem,this);
    this.collection.on('reset',this.render,this);
  },
  // addItem: -> Add item to model-to-view map
  // removeItem: -> # Remove item from above map
  render: function() {
    this.$el.html('')
    this.collection.each(function(model){
      view = new ItemView({model: model})
      this.$el.append(view.render().$el)
    });
  }
});

With Marionette

var MItemView = Marionette.ItemView.extend({
  tagName: 'li',
  template: '#item-template'
});

var MListView = Marionette.CollectionView.extend({
  itemView: MItemView
});

Zombie Views

var MyView = Backbone.View.extend({
  initialize: function() {
    // Bind to change event to call re-render
    this.model.on('change', this.render, this);
  },
  render: function() {
    alert('Rendering the view '+this.cid);
  }
});
var venue = new Backbone.Model();
var view = new MyView({ model: venue});
// Zombie created
view = new MyView({ model: venue});
// 2 alerts will fire
venue.set({ name: 'Southside Espresso' });

Killing the Zombie Views

var MyView = Backbone.View.extend({
  initialize: function() {
    // Bind to change event to call re-render
    this.listenTo(this.model, 'change', this.render);
  },
  close: function() { this.stopListening(); },
  render: function() {
    alert('Rendering the view '+this.cid);
  }
});
var venue = new Backbone.Model();
var view = new MyView({ model: venue});
view.close(); // Double-tap. Kill the zombie!
view = new MyView({ model: venue });
// Only 1 alert will fire
venue.set({ name: 'Southside Espresso' });

Keep it clean & simple

var AppLayout = Marionette.Layout.extend({
  regions: {
    menu: "#menu",
    main: "#main"
  }
});
var layout = new AppLayout();
layout.render();
layout.main.show(new SummaryView());

// ... user interactions ...

layout.main.show(new DetailedView());
// SummaryView `close` is called.

Composite Event-Driven

Eventing, App, Modules

shoulders of giants

PROS, Disqus, Rdio

Trello, USA Today, Walmart

Games & Interactive Magazines

Construction, Business, & Pricing

Composite & Modular design

Backbone.js with Marionette.js

Thank You!

QUESTIONS?

We @ PROS are hiring UI Engineers!

Kaleb Fulgham
kfulgham@pros.com

@kalebdf