Todd Zebert @toddzebert
Lead Web Dev
Miles @meetmiles
v1.0.1
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.
* "only" for RESTful persistence and DOM manipulation with Backbone.View
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.
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.
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...
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):
Backbone has an MV* stucture.*
* While previous versions did include a distinct Controller, this isn't the case anymore - it's split between Routers and Views.
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.
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.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.
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.
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 });
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
, a hash of default values that are assigned the to model object's attributes.
var MyModel = Backbone.Model.extend({
defaults: {
myItem: "my value",
}
});
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
});
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.
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.
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.
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.
There's more to Model so see backbonejs.org/#Model .
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.
You extend the base Bb Collection:
MyCollection = Backbone.Collection.extend({ /* properties */ })
The properties object can be used to customize the 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.
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.
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.
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.
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
There's various ways to distinctly identify a model in a collection:
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.
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).
All work as expected. Some take an options object that changes their behavior.
Models and Collections "share" events:
Of course not all events go either way.
There's more to Collections so see backbonejs.org/#Collection
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.
Extend the base View object:
var MyView = Backbone.View.extend({ /* properties */ });
Later, we'll fill in the properties object to customize our 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();
These properties may to be be part of the constructor object passed in during instantiation.
See examples of each in the next slides.
You can also pass in a constructor()
method as part of properties.
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') });
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
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.
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.
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.
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.
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.
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
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.
// 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.
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);
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(event, callback, [context])
.off(event, [callback], [context])
on
attaches an event listener to an object, and off
removes it.
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.
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 can either be a string or map of event-string: callback pairs.
Each event string itself can be:
"add"
"change:myAttribute"
"add change:myAttribute"
Bb has a full array of built-in events, but you're free to create your own.
Each one has it's own object types it works on and functional definition so be sure to check the docs.
There are more methods such as trigger
, once
, etc.
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.
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.
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.
The routes property object hash (or a method that returns an object) pairs routes to "action" callbacks.
Routes are matched in order so be sure keep that in mind.
The URL pattern syntax, usually delimited by /
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(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(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
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.
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 pushStatehashChange: boolean
: false
to disable hash changesroot: "some path"
: if not from URL rootsilent: boolean
: true
to disable inititial route cbThere's obviously more to routing backbonejs.org/#Router and history backbonejs.org/#History
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.
sync(method, model, [options])
This is an "internal" method you probably won't be calling directly.
sync
handles RESTful JSON interactions via jQuery.ajax
.
.ajax
options.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 methodemulateJSON
: true
to use x-www-form-urlencoded
MIME type instead of JSONMost 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([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
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 });
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([options])
Get a model from the server
Generally, in Bb you get collections from the server, but create or update models on the server.
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
.
model.destroy([options])
Delete the model from the server, and from the collection.
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.