Backbone w/D8

Todd Zebert @toddzebert

Lead Web Dev

Miles @meetmiles

v1.0.1

Backbone.js

Backbone.js gives structure to web applications by providing models with key-value binding and custom events, collections with a rich API of enumerable functions, views with declarative event handling, and connects it all to your existing API over a RESTful JSON interface.

Backbone Dependencies

  • Underscore.js >= 1.8.3: a hard requirement
  • jQuery >= 1.11.0: likely will need*

* "only" for RESTful persistence and DOM manipulation with Backbone.View

Backbone and Drupal

Backbone can be used to create seamless, richly interactive pages or sections, for admins or end users, or even a whole end-user web app.

It is lightweight (only 22.5KB min.), unopinionated, and high customizeable - so much so that's is reasonably considered a library VS being a framework. Yet, it's quite useful!

Bb is also easy to implement: no ES6/transpiling, no weird new syntax, no build steps - it just works.

Backbone and D7

Of course you can use Backbone with Drupal 7: just include backbone.js (and it's dependencies) in your code.

There's a Drupal 7 Backbone module that "provides a foundation for the development of Backbone.js apps with Drupal" and although it hasn't been updated since Sep '13, it might be worth looking into.

Past, Present & Future

Initially released Oct '10 by Jeremy Ashkenas, it builds on Underscore.

Backbone v1.2.3 (9/3/15) is included with D8, and only slightly out of date (v1.3.3, 4/5/16, is current as of this writting).

Backbone is still a thriving, in-demand skill* on its own (although probably trending downward long term).

* still a top-5 front-end skill in SF, LA, SD...

Extensions & Plugins

Backbone is highly extensible and interoperable, supporting a huge ecosystem of hundreds of various models, relations, collections, views, controllers, templates, events, widgets, and frameworks including (next page):

Extensions & Plugins List

  • Backbone.Drupal
  • react-backbone
  • Backbone.localStorage
  • backbone.couchdb.js
  • Backbone.ioBind (socket.io)
  • Backbone-websql
  • Backbone.SharePoint
  • Backbone.SAP
  • Backbone.Offline
  • Backbone-mongodb
  • Backbone.React.Component
  • react-backbone.glue
  • Knockback.js
  • Backbone.Marionette.Export
  • outback.js
  • Backrub
  • Backbone.Marionette
  • ngBackbone
  • Backbone.WS
  • Backbone.Analytics
  • Backbone.CrossDomain
  • Backbone.jFeed
  • Backbone-FSM
  • Backbone.GoogleMaps

Extensions & Plugins, selected

  • Backbone.Marionette: "Marionette simplifies your Backbone application code with robust views and architecture solutions" and is still getting commits and releases.
  • backbone.drupal: "provides Drupal compatible using Services module" for D7 and D8, although it hasn't been updated in ~2 years.

Backbone Overview

Logical Structure

Backbone has an MV* stucture.*

  • Models
  • Views
  • Collections (a group of models)
  • Events
  • Routers (client-side navigation)
  • Sync (server interaction)

* While previous versions did include a distinct Controller, this isn't the case anymore - it's split between Routers and Views.

The Approach

Backbone apps derive much of their functionality by first extending built-in Bb objects, and then instantiating them as needed. Within those objects various specific methods and properties customize and configure the object's overall function.

Important JS Concepts

  • new: In Bb, "Prototypes are instantiated with the new keyword". new creates a new object based on the object given, via the constructor.
  • .extend(): Shallow copy all the sources to the destination. In most cases we're extending built-in Bb objects.
  • this: is a JS keyword that references an object defining the context at invocation of a function.

The Model

Get your truth out of the DOM.
-Ashkenas

Bb Models contain the data (model attributes) and also logic concerning the data.

Think of a Model as somewhat like a database "row": a thing that has one or more properties (values). Examples would be a "person", "tweet", "addrees", etc.

Defining a Model

Like most things in Backbone, you start by extending a base object. Here we'll extend Bb's Model:


var MyModel = Backbone.Model.extend({ /* properties */ });
						

Later, will fill in the properties object to customize our model.

This (capital M) Model is like the blueprint (or schema, to continue to Db analogy), and it's used to create instances of (small m) models.

Instantiating a Model

Use the new keyword to create (instantiate) a specific instance of a model, and optionally provide a hash of attribute values.


var MyModel = Backbone.Model.extend({ /* hash */ });

var someModel = new MyModel();

var anotherModel = new MyModel({ myVar: 2 });
						

initialize method

The initialize()* method is called when a new model instance is created


var MyModel = Backbone.Model.extend({
  constructor: function() {
    /* some code */
    console.log('my model is being initialize');
  },
});
						

We'll see examples of constructor code later on.

In Bb the base constructor function can be extended using initialize() or it's synonym constructor().

defaults property

defaults, a hash of default values that are assigned the to model object's attributes.


var MyModel = Backbone.Model.extend({
  defaults: {
    myItem:  "my value",
  }
});
						

.set()

In Backbone you don't access or set property values directly, but rather use the interface.

Sets a model attribute to a given value, or a hash of property & value pairs.


var MyModel = Backbone.Model.extend({ /* ... */});
var model = new MyModel();

model.set("myItem", "my value");

model.set({
  myItem: "my value",
  x: 2
});
						

.get() and .escape()

Returns a specified attribute of the model


var MyModel = Backbone.Model.extend({
  defaults: {
    myItem:  "my value",
  }
});
var model = new MyModel();

model.get('myItem');
"my value"
						

.escape() is get() but HTML escaped, to prevent XSS attacks and similar.

.toJSON()

A slightly confusingly named method: it returns the entire model state (all of the attributes) in an object (as a shallow copy), like a "getAll".


var MyModel = Backbone.Model.extend({
  defaults: {
    myItem: "my value",
    x: 2
  }
});
var model = new MyModel();

model.toJSON();
{ "myItem": "my value", "x": 2 }
						

.toJSON() can be overridden, so be careful if you're inheriting code.

.attributes Property

The .attributes property is a hash of the model state - often it's the same as .toJSON().

Sometimes used to pass the state to a template in a View, especially if .toJSON() is overridden.

Do not write or modify .attributes as you may bypass/break Bb's functionality.

Model Events

Events can be used to listen to changes in your model.


var MyModel = Backbone.Model.extend({
  defaults: {
    myItem: "my value",
  }
  constructor: function() {
    this.on('change', function () {
      console.log('Something in the model changed.');
    });
    this.on('change:myItem', function () {
      console.log('"myItem" changed.")
    });
  },
});
						

There is a whole syntax to how events are defined, which will be described in the Events section.

More...

There's more to Model so see backbonejs.org/#Model .

Collections

Groups of models instances are collections. This allows you to treat them in aggregate.

If a model is a "row", then a Collection is a "table", and a collection is a table of data.

Bb Collections are Underscore Collections, so essentially all of it's Collection methods apply.

Defining a Collection

You extend the base Bb Collection:


MyCollection = Backbone.Collection.extend({ /* properties */ })
						

The properties object can be used to customize the Collection.

Instantiating a Collection

new Backbone.Collection([models], [options])

Use the new keyword to create (instantiate) a specific instance of the Collection


MyCollection = Backbone.Collection.extend({ /* ... */ })

collection = new MyCollection(
  [modelInstance1, modelInstance2, modelInstance3...]
);
						

While you can pass in model instances directly (the [modelInstance1, modelInstance2, modelInstance3...]) there's other ways to do so.

You can pass in a constructor() method in source but probably won't have to.

model Property

The model property is used to define the Model of those objects in the Collection.


MyCollection = Backbone.Collection.extend({
  model: MyModel
})
						

If you don't include model some collection methods (like add, create, etc) won't accept raw attribute objects, so it's generally easiest/best to define model.

model may also be defined in a constructor function to enable polymorphic collections.

.toJSON()

collection.toJSON([options])

Like Model.toJSON() but returns the entire collection state (all of the attributes of all the models) as an array of objects.


collection.toJSON();
[
  { /* model 1 */ },
  { /* model 2 */ },
  { /* model 3 */ }
]
						

Unlike model there's no similar .attributes property.

.add()

collection.add(models, [options])

Add model(s) to the collection. Models may be a single object, or an array of objects.

Also takes an options object that changes the behavior.


MyCollection = Backbone.Collection.extend({ /* ... */ })
collection = new MyCollection();

collection.add(modelInstance1);
collection.add([modelInstance2, modelInstance3...])
						

.set() is similar but it is "smart" as it automatically adds, merges & removes models. It's behavior can be altered.

.remove()

collection.remove(models, [options])

Removes model(s) from the collection, but not the server. Model may be an id string, instance, or object.

Also takes an options object that changes the behavior.


collection = new Backbone.Collection([
  { id: 10, word: 'go' },
  { id: 22, word: 'try' },
  { id: 119, word: 'backbone' }
]);

collection.remove(10);
collection.toJSON();
// no "go" model
						

id, cid, and instance

There's various ways to distinctly identify a model in a collection:

  • id - a "special" unique property used by Bb, if set as part of the model*
  • cid - a Bb assigned attribute of the model object
  • model - an instance of Model

These are often used in .get, .remove, etc.

*If the server data "model" doesn't include a unique id attribute, you can map whatever unique attribute to id using the idAttribute property.

.get()

collection.get(id)

Returns a model from the collection. The model may be specified by id, by model instance, or otherwise.


collection = new Backbone.Collection(
  [
    { id: 10, word: 'go', order: 1 },
    { id: 22, word: 'try', order: 2 },
    { id: 119, word: 'backbone', order: 3 }
  ]
);

collection.get(collection.where({order: 3})[0]);
{ id: 119, word: 'backbone', order: 3 }
						

.where returns an array of models matching (see Underscore).

push, pop, shift, unshift, slice methods, and length property

All work as expected. Some take an options object that changes their behavior.

Events

Models and Collections "share" events:

  • Collection level events trigger model level events
  • Model level events bubble-up to Collections

Of course not all events go either way.

More...

There's more to Collections so see backbonejs.org/#Collection

Views

A view renders content to the screen that represents the model, and is what the user interacts with.

Bb views includes part of traditional "controller" logic.

Views may use a template. Backbone by default comes with _'s .template(), but is agnostic.

Views of Models and Collections are different.

You can have multiple separate Views of the same Model or Collection.

Defining a View

Extend the base View object:


var MyView = Backbone.View.extend({ /* properties */ });
						

Later, we'll fill in the properties object to customize our View.

Instantiating a View

new View([options])

Use the new keyword to create a specific instance of a View, which optionally takes a configuration object.


var MyView = Backbone.View.extend({ /* properties */ });

var view = new MyView();
						

Instantiating, cont.

These properties may to be be part of the constructor object passed in during instantiation.

  • model
  • collection
  • el
  • id
  • className
  • tagName
  • attributes
  • events

See examples of each in the next slides.

You can also pass in a constructor() method as part of properties.

el and $el

View instances always have an el set from the moment they are instantiated.

el defaults to div unless some properties are set that el will be composed from, or if it is set explicitely.

$el is the same as el except it's wrapped in a jQuery object, so view.$el === $(view.el) .


var MyView = Backbone.View.extend({ /* properties */ });

var anotherView = new MyView({ el: $('#my-element') });
						

el Tag Attributes*

tagName, id and className are optional; may be a string, or a function that returns one.


var MyView = Backbone.View.extend({
  tagName: 'span',
  id: 'my-element',
  className: 'my-ele-class'
});

var view = new MyView();
console.log(view.el)


						

* Not to be confused with .attributes

render() Method

view.render()

A way to render the model data into the view. Often used with templates. First update $el.html() with the html.

It's best practice to return this so the function can be chained/used as sub-view.*

* skipped in examples for brevity.

render() & el()

Some code we'll use in the next few slides.

Minimal HTML:


Our model:


var thing = new (Backbone.Model.extend(
  { defaults: { name: 'World' }}
));
						

This "collapsed" structure is mostly just for convenience.

The render() and el usage below are independent.

render() & el(), cont

Example of creating then inserting into DOM:


var ThingView = Backbone.View.extend({
  className: 'my-ele-class',
  initialize: function() { this.render(); },
  render: function() {
    this.$el.html('hello ' + this.model.get('name'));
  }
});

var thingView = new ThingView({ model: thing });
$('#my-view').html(thingView.el);
						

hello World

By making render part of initialize I avoid having an individual render step.

render() & el(), cont

Example of inserting into existing DOM element:


var ThingView = Backbone.View.extend({
  render: function() {
    this.$el.html('hello ' + this.model.get('name'));
  }
});

var thingView = new ThingView({ model: thing, el: '#my-view' });
thingView.render();
						

hello World

Notice "hello World" is not double-div wrapped. Also, if I was to have a className (or similar) it would be ignored.

remove Method

view.remove()

Removes the View (along with it's el) from the DOM.

It also does stopListen on all previously registered listeners.

If you remove a view be sure to take into consideration any other view or model that might be "tied" to it.

template Method

The Bb template method doesn't handle templating directly, but rather passes it on to a templating engine, such as Underscore's _.template.


var MyView = Backbone.View.extend({
  template: _.template('
<%= itemProp %>
'), render: function() { // model not collection* this.$el.html(this.template(this.model.attributes)); return this; } });

The template literal above is trivial and can be done easier otherwise.

* see below for a render function for a collection

events Property

View events are different from Model and Collection events, because these are jQuery events.

Use an object hash (or a function that returns such) to define event & selector strings (detailed below) & callback function name string pairs.

These functions are actually methods defined within the view.

events, cont.


// this is a partial of a view definition
events: {
  'mouseover': "moEvent",
  'click .my-button': "myButtonClickEvent"
}

moEvent: function() { this.$el.addClass('isMousedOver'); }
myButtonClickEvent: function() { /* some code */ }
						

Under the hood, jQuery's .on() is used, so the events are those used by jQuery, such as keypress, click, mouseenter, etc.

The event & selector string is in the format "event selector" where selector is optional; if omitted the event is bound to this.el.

Collections and Views

For Views of Collections, you need a render method for the collection, which then iterates over each model in the collection, passing it to a render function.

The model's render function may be custom or may call an existing model render method.


// view partial
  render: function() {
    this.collection.each(this.doSingleView, this);
    return this; }
  doSingleView: function() {
    var singleView = new modelView({ model: myModel });
    this.$el.append(singleView.render().el); } // jQ append

$('#my-collection').html(myCollectionView.render().el);
						

Events

Backbone supports two types of events: the internal ones focused on Models, Collections & Routes, and the jQuery events in Views.

This section deals only with the former.

You can get some idea of jQuery's events api.jquery.com/category/events.

.On and .Off Methods

.on(event, callback, [context])

.off(event, [callback], [context])

on attaches an event listener to an object, and off removes it.

listenTo & stopListening method

listenTo(other, event, callback)

stopListening([other], [event], [callback])

listenTo has an object listen for events originating from the target object. Whereas on affects the target object by adding callbacks back to the listening object.

listenTo, cont

The issue with on is that often a view is listening for events originating from a model. If the model is removed without the event listener being removed with an off the view will persist and creates a memory leak.

Whereas when a view is removed it calls stopListening by default and avoids this issue.

So it's often best to use listenTo.

Events Syntax

events can either be a string or map of event-string: callback pairs.

Each event string itself can be:

  • A simple event name: "add"
  • Some an event name and option: "change:myAttribute"
  • A space deliminted list of above: "add change:myAttribute"

The Events

Bb has a full array of built-in events, but you're free to create your own.

  • add
  • remove
  • update
  • reset
  • sort
  • change:[attribute]
  • destroy
  • request
  • synce
  • error
  • invalid
  • route:[name]
  • all

Each one has it's own object types it works on and functional definition so be sure to check the docs.

Others

There are more methods such as trigger, once, etc.

See backbonejs.org/#Events

Router & History

A way of handling client-side "routing" of the URL.

While every implementation of Backbone will most likely use Models, Collections & Views, using the Router is optional.

Bb Router handles both the "old-style" hash fragments*, and the new History API support for URL paths.

For most uses you'll want to use the History API.

* Note that using the has fragments for routing will break use of hashtag "jumping" to id's.

Defining the Router

As always, start by extending the built-in Router object


var MyRouter = Backbone.Router.extend({/* properties */});
						

The properties object will get filled in with configuration properties and methods.

Instantiating the Router

new Router([options])

Routes may be added during instantiation, or later.


var myRouter = new MyRouter();
						

Unlike Models and Collections, it's unlikely you'll have multiple routers.

routes Hash

The routes property object hash (or a method that returns an object) pairs routes to "action" callbacks.

  • key: URL pattern
  • value: callback function name string (quoted)

Routes are matched in order so be sure keep that in mind.

routes Hash, cont.

The URL pattern syntax, usually delimited by /

  • myroute text that matches exactly
  • :param partials which are passed to the callback
  • (optional) optional parts
  • -partial allows partial delimination by -
  • *splat match remaining

routes Hash, cont.

Example of routes hash:


// partial object
'' : 'defaultRoute', // aka "home" route
'path' : 'someRouteFunction',
'path/:id' : 'someOtherRouter',
'path/:id(/:page)': 'someRouter', // page is optional (in parens)
'*path': 'defaultRouter',
':first-:second' : 'partialRouter', // partial dash deliminited
'path/:id(/)' : 'someOtherRouter', // optional trailing slash
						

route Method

.route(route, name, [callback])

Routes are usually set during instantiation, but to add programmatic routes, or those using regex, use .route()

As compared to the routes hash, this method also includes name which is the name of a event to trigger.


// instantiation object partial
initialize: function() {
  this.route(/^path\/(\d+)$/, 'regexroute', 'someRouter');
}
						

navigate Method

.navigate(fragment, [options])

navigate is used to update the URL, and optionally call routing callback.


router.navigate("dashboard",
  { trigger: true }
) // to change url and invoke the route callback

router.navigate("dashboard/page-2")
  // to change url, but no route cb invocation
						

History Object

Backbone.History handles URL hash changes and pushState.

Backbone dynamically "upgrades" newer browsers to use pushState and downgrades old ones to use hash URL fragments.

history.start Method

Backbone.history.start([options])

The last step after defining your routing is calling history.start() which starts monitoring URL events. Options:

  • pushState: boolean : true to enable pushState
  • hashChange: boolean : false to disable hash changes
  • root: "some path" : if not from URL root
  • silent: boolean: true to disable inititial route cb

More

There's obviously more to routing backbonejs.org/#Router and history backbonejs.org/#History

Sync and Server Related

This section will cover the Backbone.sync and related methods, and all the Model and View methods that depend on it.

Backbone.sync is the interface Backbone uses for all server communications. All other server related Backbone methods use it at some point.

Backbone.sync Method

sync(method, model, [options])

This is an "internal" method you probably won't be calling directly.

sync handles RESTful JSON interactions via jQuery.ajax.

  • CRUD method: create (POST), read (GET), update (PUT), patch (PATCH), delete (DELETE)
  • "model": a model (writing) | a collection (reading)
  • options: a object of callbacks and other .ajax options.

sync() Props & Methods

In case you need to alter the default behavior:

  • .ajax : a method to override jQuery.ajax
  • emulateHTTP : true to use header method instead of HTTP method
  • emulateJSON : true to use x-www-form-urlencoded MIME type instead of JSON

Model and Collection Methods

Most of the following methods have an options parameter derived from Backbone.sync's options.

Option methods include success and error callbacks.

Callbacks are passed (collection, response, options) or (model, response, options) respectively.

Collection.fetch() Method

collection.fetch([options])

Get a collection of models from the server.

options include jQuery.ajax options, and things like pagination or other criteria that "filter" the result set.

If possible, pre-load ("bootstrap") the initial collection so the app doesn't have to wait for an initial async call.

.fetch, cont

fetch depends on the url property being set during collection definition. Once the data is received, the data is passed to collection.set().


// partial object
  url: '/things'
						

things.fetch({ page: 2 });
						

create() Method

collection.create(attributes, [options])

A convenience method that instantiates, save() & add() a model all in one.


things.create({ name: 'Drupal' });
// callback code omitted
thingsView.render();

Hello World
Hello Drupal
						

Model.fetch() Method

model.fetch([options])

Get a model from the server

Generally, in Bb you get collections from the server, but create or update models on the server.

.save() Method

model.save([attributes], [options])

Save a model to the server.

First it calls .validate() if one exists, and will return false if it fails.

attributes is a hash of attributes that override what's in the model.

.destroy() Method

model.destroy([options])

Delete the model from the server, and from the collection.

Other Sync-related Methods

These exist if needed, for both Model and Collection:

.parse(response, options) overrides parsing of the raw AJAX response object; common if the object isn't "flat".

.sync(method, collection, [options]) overrides Backbone.sync behavior entirely.

It's not the end but the beginning of your Backbone journey.