3
// (c) 2010-2013 Jeremy Ashkenas, DocumentCloud Inc.
4
// Backbone may be freely distributed under the MIT license.
5
// For all details and documentation:
6
// http://backbonejs.org
13
// Save a reference to the global object (`window` in the browser, `exports`
17
// Save the previous value of the `Backbone` variable, so that it can be
18
// restored later on, if `noConflict` is used.
19
var previousBackbone = root.Backbone;
21
// Create local references to array methods we'll want to use later.
23
var push = array.push;
24
var slice = array.slice;
25
var splice = array.splice;
27
// The top-level namespace. All public Backbone classes and modules will
28
// be attached to this. Exported for both the browser and the server.
30
if (typeof exports !== 'undefined') {
33
Backbone = root.Backbone = {};
36
// Current version of the library. Keep in sync with `package.json`.
37
Backbone.VERSION = '1.0.0';
39
// Require Underscore, if we're on the server, and it's not already present.
41
if (!_ && (typeof require !== 'undefined')) _ = require('underscore');
43
// For Backbone's purposes, jQuery, Zepto, Ender, or My Library (kidding) owns
45
Backbone.$ = root.jQuery || root.Zepto || root.ender || root.$;
47
// Runs Backbone.js in *noConflict* mode, returning the `Backbone` variable
48
// to its previous owner. Returns a reference to this Backbone object.
49
Backbone.noConflict = function() {
50
root.Backbone = previousBackbone;
54
// Turn on `emulateHTTP` to support legacy HTTP servers. Setting this option
55
// will fake `"PUT"` and `"DELETE"` requests via the `_method` parameter and
56
// set a `X-Http-Method-Override` header.
57
Backbone.emulateHTTP = false;
59
// Turn on `emulateJSON` to support legacy servers that can't deal with direct
60
// `application/json` requests ... will encode the body as
61
// `application/x-www-form-urlencoded` instead and will send the model in a
62
// form param named `model`.
63
Backbone.emulateJSON = false;
68
// A module that can be mixed in to *any object* in order to provide it with
69
// custom events. You may bind with `on` or remove with `off` callback
70
// functions to an event; `trigger`-ing an event fires all callbacks in
74
// _.extend(object, Backbone.Events);
75
// object.on('expand', function(){ alert('expanded'); });
76
// object.trigger('expand');
78
var Events = Backbone.Events = {
80
// Bind an event to a `callback` function. Passing `"all"` will bind
81
// the callback to all events fired.
82
on: function(name, callback, context) {
83
if (!eventsApi(this, 'on', name, [callback, context]) || !callback) return this;
84
this._events || (this._events = {});
85
var events = this._events[name] || (this._events[name] = []);
86
events.push({callback: callback, context: context, ctx: context || this});
90
// Bind an event to only be triggered a single time. After the first time
91
// the callback is invoked, it will be removed.
92
once: function(name, callback, context) {
93
if (!eventsApi(this, 'once', name, [callback, context]) || !callback) return this;
95
var once = _.once(function() {
97
callback.apply(this, arguments);
99
once._callback = callback;
100
return this.on(name, once, context);
103
// Remove one or many callbacks. If `context` is null, removes all
104
// callbacks with that function. If `callback` is null, removes all
105
// callbacks for the event. If `name` is null, removes all bound
106
// callbacks for all events.
107
off: function(name, callback, context) {
108
var retain, ev, events, names, i, l, j, k;
109
if (!this._events || !eventsApi(this, 'off', name, [callback, context])) return this;
110
if (!name && !callback && !context) {
115
names = name ? [name] : _.keys(this._events);
116
for (i = 0, l = names.length; i < l; i++) {
118
if (events = this._events[name]) {
119
this._events[name] = retain = [];
120
if (callback || context) {
121
for (j = 0, k = events.length; j < k; j++) {
123
if ((callback && callback !== ev.callback && callback !== ev.callback._callback) ||
124
(context && context !== ev.context)) {
129
if (!retain.length) delete this._events[name];
136
// Trigger one or many events, firing all bound callbacks. Callbacks are
137
// passed the same arguments as `trigger` is, apart from the event name
138
// (unless you're listening on `"all"`, which will cause your callback to
139
// receive the true name of the event as the first argument).
140
trigger: function(name) {
141
if (!this._events) return this;
142
var args = slice.call(arguments, 1);
143
if (!eventsApi(this, 'trigger', name, args)) return this;
144
var events = this._events[name];
145
var allEvents = this._events.all;
146
if (events) triggerEvents(events, args);
147
if (allEvents) triggerEvents(allEvents, arguments);
151
// Tell this object to stop listening to either specific events ... or
152
// to every object it's currently listening to.
153
stopListening: function(obj, name, callback) {
154
var listeners = this._listeners;
155
if (!listeners) return this;
156
var deleteListener = !name && !callback;
157
if (typeof name === 'object') callback = this;
158
if (obj) (listeners = {})[obj._listenerId] = obj;
159
for (var id in listeners) {
160
listeners[id].off(name, callback, this);
161
if (deleteListener) delete this._listeners[id];
168
// Regular expression used to split event strings.
169
var eventSplitter = /\s+/;
171
// Implement fancy features of the Events API such as multiple event
172
// names `"change blur"` and jQuery-style event maps `{change: action}`
173
// in terms of the existing API.
174
var eventsApi = function(obj, action, name, rest) {
175
if (!name) return true;
177
// Handle event maps.
178
if (typeof name === 'object') {
179
for (var key in name) {
180
obj[action].apply(obj, [key, name[key]].concat(rest));
185
// Handle space separated event names.
186
if (eventSplitter.test(name)) {
187
var names = name.split(eventSplitter);
188
for (var i = 0, l = names.length; i < l; i++) {
189
obj[action].apply(obj, [names[i]].concat(rest));
197
// A difficult-to-believe, but optimized internal dispatch function for
198
// triggering events. Tries to keep the usual cases speedy (most internal
199
// Backbone events have 3 arguments).
200
var triggerEvents = function(events, args) {
201
var ev, i = -1, l = events.length, a1 = args[0], a2 = args[1], a3 = args[2];
202
switch (args.length) {
203
case 0: while (++i < l) (ev = events[i]).callback.call(ev.ctx); return;
204
case 1: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1); return;
205
case 2: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2); return;
206
case 3: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2, a3); return;
207
default: while (++i < l) (ev = events[i]).callback.apply(ev.ctx, args);
211
var listenMethods = {listenTo: 'on', listenToOnce: 'once'};
213
// Inversion-of-control versions of `on` and `once`. Tell *this* object to
214
// listen to an event in another object ... keeping track of what it's
216
_.each(listenMethods, function(implementation, method) {
217
Events[method] = function(obj, name, callback) {
218
var listeners = this._listeners || (this._listeners = {});
219
var id = obj._listenerId || (obj._listenerId = _.uniqueId('l'));
221
if (typeof name === 'object') callback = this;
222
obj[implementation](name, callback, this);
227
// Aliases for backwards compatibility.
228
Events.bind = Events.on;
229
Events.unbind = Events.off;
231
// Allow the `Backbone` object to serve as a global event bus, for folks who
232
// want global "pubsub" in a convenient place.
233
_.extend(Backbone, Events);
238
// Backbone **Models** are the basic data object in the framework --
239
// frequently representing a row in a table in a database on your server.
240
// A discrete chunk of data and a bunch of useful, related methods for
241
// performing computations and transformations on that data.
243
// Create a new model with the specified attributes. A client id (`cid`)
244
// is automatically generated and assigned for you.
245
var Model = Backbone.Model = function(attributes, options) {
247
var attrs = attributes || {};
248
options || (options = {});
249
this.cid = _.uniqueId('c');
250
this.attributes = {};
251
_.extend(this, _.pick(options, modelOptions));
252
if (options.parse) attrs = this.parse(attrs, options) || {};
253
if (defaults = _.result(this, 'defaults')) {
254
attrs = _.defaults({}, attrs, defaults);
256
this.set(attrs, options);
258
this.initialize.apply(this, arguments);
261
// A list of options to be attached directly to the model, if provided.
262
var modelOptions = ['url', 'urlRoot', 'collection'];
264
// Attach all inheritable methods to the Model prototype.
265
_.extend(Model.prototype, Events, {
267
// A hash of attributes whose current and previous value differ.
270
// The value returned during the last failed validation.
271
validationError: null,
273
// The default name for the JSON `id` attribute is `"id"`. MongoDB and
274
// CouchDB users may want to set this to `"_id"`.
277
// Initialize is an empty function by default. Override it with your own
278
// initialization logic.
279
initialize: function(){},
281
// Return a copy of the model's `attributes` object.
282
toJSON: function(options) {
283
return _.clone(this.attributes);
286
// Proxy `Backbone.sync` by default -- but override this if you need
287
// custom syncing semantics for *this* particular model.
289
return Backbone.sync.apply(this, arguments);
292
// Get the value of an attribute.
293
get: function(attr) {
294
return this.attributes[attr];
297
// Get the HTML-escaped value of an attribute.
298
escape: function(attr) {
299
return _.escape(this.get(attr));
302
// Returns `true` if the attribute contains a value that is not null
304
has: function(attr) {
305
return this.get(attr) != null;
308
// Set a hash of model attributes on the object, firing `"change"`. This is
309
// the core primitive operation of a model, updating the data and notifying
310
// anyone who needs to know about the change in state. The heart of the beast.
311
set: function(key, val, options) {
312
var attr, attrs, unset, changes, silent, changing, prev, current;
313
if (key == null) return this;
315
// Handle both `"key", value` and `{key: value}` -style arguments.
316
if (typeof key === 'object') {
320
(attrs = {})[key] = val;
323
options || (options = {});
326
if (!this._validate(attrs, options)) return false;
328
// Extract attributes and options.
329
unset = options.unset;
330
silent = options.silent;
332
changing = this._changing;
333
this._changing = true;
336
this._previousAttributes = _.clone(this.attributes);
339
current = this.attributes, prev = this._previousAttributes;
341
// Check for changes of `id`.
342
if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
344
// For each `set` attribute, update or delete the current value.
345
for (attr in attrs) {
347
if (!_.isEqual(current[attr], val)) changes.push(attr);
348
if (!_.isEqual(prev[attr], val)) {
349
this.changed[attr] = val;
351
delete this.changed[attr];
353
unset ? delete current[attr] : current[attr] = val;
356
// Trigger all relevant attribute changes.
358
if (changes.length) this._pending = true;
359
for (var i = 0, l = changes.length; i < l; i++) {
360
this.trigger('change:' + changes[i], this, current[changes[i]], options);
364
// You might be wondering why there's a `while` loop here. Changes can
365
// be recursively nested within `"change"` events.
366
if (changing) return this;
368
while (this._pending) {
369
this._pending = false;
370
this.trigger('change', this, options);
373
this._pending = false;
374
this._changing = false;
378
// Remove an attribute from the model, firing `"change"`. `unset` is a noop
379
// if the attribute doesn't exist.
380
unset: function(attr, options) {
381
return this.set(attr, void 0, _.extend({}, options, {unset: true}));
384
// Clear all attributes on the model, firing `"change"`.
385
clear: function(options) {
387
for (var key in this.attributes) attrs[key] = void 0;
388
return this.set(attrs, _.extend({}, options, {unset: true}));
391
// Determine if the model has changed since the last `"change"` event.
392
// If you specify an attribute name, determine if that attribute has changed.
393
hasChanged: function(attr) {
394
if (attr == null) return !_.isEmpty(this.changed);
395
return _.has(this.changed, attr);
398
// Return an object containing all the attributes that have changed, or
399
// false if there are no changed attributes. Useful for determining what
400
// parts of a view need to be updated and/or what attributes need to be
401
// persisted to the server. Unset attributes will be set to undefined.
402
// You can also pass an attributes object to diff against the model,
403
// determining if there *would be* a change.
404
changedAttributes: function(diff) {
405
if (!diff) return this.hasChanged() ? _.clone(this.changed) : false;
406
var val, changed = false;
407
var old = this._changing ? this._previousAttributes : this.attributes;
408
for (var attr in diff) {
409
if (_.isEqual(old[attr], (val = diff[attr]))) continue;
410
(changed || (changed = {}))[attr] = val;
415
// Get the previous value of an attribute, recorded at the time the last
416
// `"change"` event was fired.
417
previous: function(attr) {
418
if (attr == null || !this._previousAttributes) return null;
419
return this._previousAttributes[attr];
422
// Get all of the attributes of the model at the time of the previous
424
previousAttributes: function() {
425
return _.clone(this._previousAttributes);
428
// Fetch the model from the server. If the server's representation of the
429
// model differs from its current attributes, they will be overridden,
430
// triggering a `"change"` event.
431
fetch: function(options) {
432
options = options ? _.clone(options) : {};
433
if (options.parse === void 0) options.parse = true;
435
var success = options.success;
436
options.success = function(resp) {
437
if (!model.set(model.parse(resp, options), options)) return false;
438
if (success) success(model, resp, options);
439
model.trigger('sync', model, resp, options);
441
wrapError(this, options);
442
return this.sync('read', this, options);
445
// Set a hash of model attributes, and sync the model to the server.
446
// If the server returns an attributes hash that differs, the model's
447
// state will be `set` again.
448
save: function(key, val, options) {
449
var attrs, method, xhr, attributes = this.attributes;
451
// Handle both `"key", value` and `{key: value}` -style arguments.
452
if (key == null || typeof key === 'object') {
456
(attrs = {})[key] = val;
459
// If we're not waiting and attributes exist, save acts as `set(attr).save(null, opts)`.
460
if (attrs && (!options || !options.wait) && !this.set(attrs, options)) return false;
462
options = _.extend({validate: true}, options);
464
// Do not persist invalid models.
465
if (!this._validate(attrs, options)) return false;
467
// Set temporary attributes if `{wait: true}`.
468
if (attrs && options.wait) {
469
this.attributes = _.extend({}, attributes, attrs);
472
// After a successful server-side save, the client is (optionally)
473
// updated with the server-side state.
474
if (options.parse === void 0) options.parse = true;
476
var success = options.success;
477
options.success = function(resp) {
478
// Ensure attributes are restored during synchronous saves.
479
model.attributes = attributes;
480
var serverAttrs = model.parse(resp, options);
481
if (options.wait) serverAttrs = _.extend(attrs || {}, serverAttrs);
482
if (_.isObject(serverAttrs) && !model.set(serverAttrs, options)) {
485
if (success) success(model, resp, options);
486
model.trigger('sync', model, resp, options);
488
wrapError(this, options);
490
method = this.isNew() ? 'create' : (options.patch ? 'patch' : 'update');
491
if (method === 'patch') options.attrs = attrs;
492
xhr = this.sync(method, this, options);
494
// Restore attributes.
495
if (attrs && options.wait) this.attributes = attributes;
500
// Destroy this model on the server if it was already persisted.
501
// Optimistically removes the model from its collection, if it has one.
502
// If `wait: true` is passed, waits for the server to respond before removal.
503
destroy: function(options) {
504
options = options ? _.clone(options) : {};
506
var success = options.success;
508
var destroy = function() {
509
model.trigger('destroy', model, model.collection, options);
512
options.success = function(resp) {
513
if (options.wait || model.isNew()) destroy();
514
if (success) success(model, resp, options);
515
if (!model.isNew()) model.trigger('sync', model, resp, options);
522
wrapError(this, options);
524
var xhr = this.sync('delete', this, options);
525
if (!options.wait) destroy();
529
// Default URL for the model's representation on the server -- if you're
530
// using Backbone's restful methods, override this to change the endpoint
531
// that will be called.
533
var base = _.result(this, 'urlRoot') || _.result(this.collection, 'url') || urlError();
534
if (this.isNew()) return base;
535
return base + (base.charAt(base.length - 1) === '/' ? '' : '/') + encodeURIComponent(this.id);
538
// **parse** converts a response into the hash of attributes to be `set` on
539
// the model. The default implementation is just to pass the response along.
540
parse: function(resp, options) {
544
// Create a new model with identical attributes to this one.
546
return new this.constructor(this.attributes);
549
// A model is new if it has never been saved to the server, and lacks an id.
551
return this.id == null;
554
// Check if the model is currently in a valid state.
555
isValid: function(options) {
556
return this._validate({}, _.extend(options || {}, { validate: true }));
559
// Run validation against the next complete set of model attributes,
560
// returning `true` if all is well. Otherwise, fire an `"invalid"` event.
561
_validate: function(attrs, options) {
562
if (!options.validate || !this.validate) return true;
563
attrs = _.extend({}, this.attributes, attrs);
564
var error = this.validationError = this.validate(attrs, options) || null;
565
if (!error) return true;
566
this.trigger('invalid', this, error, _.extend(options || {}, {validationError: error}));
572
// Underscore methods that we want to implement on the Model.
573
var modelMethods = ['keys', 'values', 'pairs', 'invert', 'pick', 'omit'];
575
// Mix in each Underscore method as a proxy to `Model#attributes`.
576
_.each(modelMethods, function(method) {
577
Model.prototype[method] = function() {
578
var args = slice.call(arguments);
579
args.unshift(this.attributes);
580
return _[method].apply(_, args);
584
// Backbone.Collection
585
// -------------------
587
// If models tend to represent a single row of data, a Backbone Collection is
588
// more analagous to a table full of data ... or a small slice or page of that
589
// table, or a collection of rows that belong together for a particular reason
590
// -- all of the messages in this particular folder, all of the documents
591
// belonging to this particular author, and so on. Collections maintain
592
// indexes of their models, both in order, and for lookup by `id`.
594
// Create a new **Collection**, perhaps to contain a specific type of `model`.
595
// If a `comparator` is specified, the Collection will maintain
596
// its models in sort order, as they're added and removed.
597
var Collection = Backbone.Collection = function(models, options) {
598
options || (options = {});
599
if (options.url) this.url = options.url;
600
if (options.model) this.model = options.model;
601
if (options.comparator !== void 0) this.comparator = options.comparator;
603
this.initialize.apply(this, arguments);
604
if (models) this.reset(models, _.extend({silent: true}, options));
607
// Default options for `Collection#set`.
608
var setOptions = {add: true, remove: true, merge: true};
609
var addOptions = {add: true, merge: false, remove: false};
611
// Define the Collection's inheritable methods.
612
_.extend(Collection.prototype, Events, {
614
// The default model for a collection is just a **Backbone.Model**.
615
// This should be overridden in most cases.
618
// Initialize is an empty function by default. Override it with your own
619
// initialization logic.
620
initialize: function(){},
622
// The JSON representation of a Collection is an array of the
623
// models' attributes.
624
toJSON: function(options) {
625
return this.map(function(model){ return model.toJSON(options); });
628
// Proxy `Backbone.sync` by default.
630
return Backbone.sync.apply(this, arguments);
633
// Add a model, or list of models to the set.
634
add: function(models, options) {
635
return this.set(models, _.defaults(options || {}, addOptions));
638
// Remove a model, or a list of models from the set.
639
remove: function(models, options) {
640
models = _.isArray(models) ? models.slice() : [models];
641
options || (options = {});
642
var i, l, index, model;
643
for (i = 0, l = models.length; i < l; i++) {
644
model = this.get(models[i]);
645
if (!model) continue;
646
delete this._byId[model.id];
647
delete this._byId[model.cid];
648
index = this.indexOf(model);
649
this.models.splice(index, 1);
651
if (!options.silent) {
652
options.index = index;
653
model.trigger('remove', model, this, options);
655
this._removeReference(model);
660
// Update a collection by `set`-ing a new list of models, adding new ones,
661
// removing models that are no longer present, and merging models that
662
// already exist in the collection, as necessary. Similar to **Model#set**,
663
// the core operation for updating the data contained by the collection.
664
set: function(models, options) {
665
options = _.defaults(options || {}, setOptions);
666
if (options.parse) models = this.parse(models, options);
667
if (!_.isArray(models)) models = models ? [models] : [];
668
var i, l, model, attrs, existing, sort;
670
var sortable = this.comparator && (at == null) && options.sort !== false;
671
var sortAttr = _.isString(this.comparator) ? this.comparator : null;
672
var toAdd = [], toRemove = [], modelMap = {};
674
// Turn bare objects into model references, and prevent invalid models
676
for (i = 0, l = models.length; i < l; i++) {
677
if (!(model = this._prepareModel(models[i], options))) continue;
679
// If a duplicate is found, prevent it from being added and
680
// optionally merge it into the existing model.
681
if (existing = this.get(model)) {
682
if (options.remove) modelMap[existing.cid] = true;
684
existing.set(model.attributes, options);
685
if (sortable && !sort && existing.hasChanged(sortAttr)) sort = true;
688
// This is a new model, push it to the `toAdd` list.
689
} else if (options.add) {
692
// Listen to added models' events, and index models for lookup by
693
// `id` and by `cid`.
694
model.on('all', this._onModelEvent, this);
695
this._byId[model.cid] = model;
696
if (model.id != null) this._byId[model.id] = model;
700
// Remove nonexistent models if appropriate.
701
if (options.remove) {
702
for (i = 0, l = this.length; i < l; ++i) {
703
if (!modelMap[(model = this.models[i]).cid]) toRemove.push(model);
705
if (toRemove.length) this.remove(toRemove, options);
708
// See if sorting is needed, update `length` and splice in new models.
710
if (sortable) sort = true;
711
this.length += toAdd.length;
713
splice.apply(this.models, [at, 0].concat(toAdd));
715
push.apply(this.models, toAdd);
719
// Silently sort the collection if appropriate.
720
if (sort) this.sort({silent: true});
722
if (options.silent) return this;
724
// Trigger `add` events.
725
for (i = 0, l = toAdd.length; i < l; i++) {
726
(model = toAdd[i]).trigger('add', model, this, options);
729
// Trigger `sort` if the collection was sorted.
730
if (sort) this.trigger('sort', this, options);
734
// When you have more items than you want to add or remove individually,
735
// you can reset the entire set with a new list of models, without firing
736
// any granular `add` or `remove` events. Fires `reset` when finished.
737
// Useful for bulk operations and optimizations.
738
reset: function(models, options) {
739
options || (options = {});
740
for (var i = 0, l = this.models.length; i < l; i++) {
741
this._removeReference(this.models[i]);
743
options.previousModels = this.models;
745
this.add(models, _.extend({silent: true}, options));
746
if (!options.silent) this.trigger('reset', this, options);
750
// Add a model to the end of the collection.
751
push: function(model, options) {
752
model = this._prepareModel(model, options);
753
this.add(model, _.extend({at: this.length}, options));
757
// Remove a model from the end of the collection.
758
pop: function(options) {
759
var model = this.at(this.length - 1);
760
this.remove(model, options);
764
// Add a model to the beginning of the collection.
765
unshift: function(model, options) {
766
model = this._prepareModel(model, options);
767
this.add(model, _.extend({at: 0}, options));
771
// Remove a model from the beginning of the collection.
772
shift: function(options) {
773
var model = this.at(0);
774
this.remove(model, options);
778
// Slice out a sub-array of models from the collection.
779
slice: function(begin, end) {
780
return this.models.slice(begin, end);
783
// Get a model from the set by id.
785
if (obj == null) return void 0;
786
return this._byId[obj.id != null ? obj.id : obj.cid || obj];
789
// Get the model at the given index.
790
at: function(index) {
791
return this.models[index];
794
// Return models with matching attributes. Useful for simple cases of
796
where: function(attrs, first) {
797
if (_.isEmpty(attrs)) return first ? void 0 : [];
798
return this[first ? 'find' : 'filter'](function(model) {
799
for (var key in attrs) {
800
if (attrs[key] !== model.get(key)) return false;
806
// Return the first model with matching attributes. Useful for simple cases
808
findWhere: function(attrs) {
809
return this.where(attrs, true);
812
// Force the collection to re-sort itself. You don't need to call this under
813
// normal circumstances, as the set will maintain sort order as each item
815
sort: function(options) {
816
if (!this.comparator) throw new Error('Cannot sort a set without a comparator');
817
options || (options = {});
819
// Run sort based on type of `comparator`.
820
if (_.isString(this.comparator) || this.comparator.length === 1) {
821
this.models = this.sortBy(this.comparator, this);
823
this.models.sort(_.bind(this.comparator, this));
826
if (!options.silent) this.trigger('sort', this, options);
830
// Figure out the smallest index at which a model should be inserted so as
831
// to maintain order.
832
sortedIndex: function(model, value, context) {
833
value || (value = this.comparator);
834
var iterator = _.isFunction(value) ? value : function(model) {
835
return model.get(value);
837
return _.sortedIndex(this.models, model, iterator, context);
840
// Pluck an attribute from each model in the collection.
841
pluck: function(attr) {
842
return _.invoke(this.models, 'get', attr);
845
// Fetch the default set of models for this collection, resetting the
846
// collection when they arrive. If `reset: true` is passed, the response
847
// data will be passed through the `reset` method instead of `set`.
848
fetch: function(options) {
849
options = options ? _.clone(options) : {};
850
if (options.parse === void 0) options.parse = true;
851
var success = options.success;
852
var collection = this;
853
options.success = function(resp) {
854
var method = options.reset ? 'reset' : 'set';
855
collection[method](resp, options);
856
if (success) success(collection, resp, options);
857
collection.trigger('sync', collection, resp, options);
859
wrapError(this, options);
860
return this.sync('read', this, options);
863
// Create a new instance of a model in this collection. Add the model to the
864
// collection immediately, unless `wait: true` is passed, in which case we
865
// wait for the server to agree.
866
create: function(model, options) {
867
options = options ? _.clone(options) : {};
868
if (!(model = this._prepareModel(model, options))) return false;
869
if (!options.wait) this.add(model, options);
870
var collection = this;
871
var success = options.success;
872
options.success = function(resp) {
873
if (options.wait) collection.add(model, options);
874
if (success) success(model, resp, options);
876
model.save(null, options);
880
// **parse** converts a response into a list of models to be added to the
881
// collection. The default implementation is just to pass it through.
882
parse: function(resp, options) {
886
// Create a new collection with an identical list of models as this one.
888
return new this.constructor(this.models);
891
// Private method to reset all internal state. Called when the collection
892
// is first initialized or reset.
899
// Prepare a hash of attributes (or other model) to be added to this
901
_prepareModel: function(attrs, options) {
902
if (attrs instanceof Model) {
903
if (!attrs.collection) attrs.collection = this;
906
options || (options = {});
907
options.collection = this;
908
var model = new this.model(attrs, options);
909
if (!model._validate(attrs, options)) {
910
this.trigger('invalid', this, attrs, options);
916
// Internal method to sever a model's ties to a collection.
917
_removeReference: function(model) {
918
if (this === model.collection) delete model.collection;
919
model.off('all', this._onModelEvent, this);
922
// Internal method called every time a model in the set fires an event.
923
// Sets need to update their indexes when models change ids. All other
924
// events simply proxy through. "add" and "remove" events that originate
925
// in other collections are ignored.
926
_onModelEvent: function(event, model, collection, options) {
927
if ((event === 'add' || event === 'remove') && collection !== this) return;
928
if (event === 'destroy') this.remove(model, options);
929
if (model && event === 'change:' + model.idAttribute) {
930
delete this._byId[model.previous(model.idAttribute)];
931
if (model.id != null) this._byId[model.id] = model;
933
this.trigger.apply(this, arguments);
938
// Underscore methods that we want to implement on the Collection.
939
// 90% of the core usefulness of Backbone Collections is actually implemented
941
var methods = ['forEach', 'each', 'map', 'collect', 'reduce', 'foldl',
942
'inject', 'reduceRight', 'foldr', 'find', 'detect', 'filter', 'select',
943
'reject', 'every', 'all', 'some', 'any', 'include', 'contains', 'invoke',
944
'max', 'min', 'toArray', 'size', 'first', 'head', 'take', 'initial', 'rest',
945
'tail', 'drop', 'last', 'without', 'indexOf', 'shuffle', 'lastIndexOf',
948
// Mix in each Underscore method as a proxy to `Collection#models`.
949
_.each(methods, function(method) {
950
Collection.prototype[method] = function() {
951
var args = slice.call(arguments);
952
args.unshift(this.models);
953
return _[method].apply(_, args);
957
// Underscore methods that take a property name as an argument.
958
var attributeMethods = ['groupBy', 'countBy', 'sortBy'];
960
// Use attributes instead of properties.
961
_.each(attributeMethods, function(method) {
962
Collection.prototype[method] = function(value, context) {
963
var iterator = _.isFunction(value) ? value : function(model) {
964
return model.get(value);
966
return _[method](this.models, iterator, context);
973
// Backbone Views are almost more convention than they are actual code. A View
974
// is simply a JavaScript object that represents a logical chunk of UI in the
975
// DOM. This might be a single item, an entire list, a sidebar or panel, or
976
// even the surrounding frame which wraps your whole app. Defining a chunk of
977
// UI as a **View** allows you to define your DOM events declaratively, without
978
// having to worry about render order ... and makes it easy for the view to
979
// react to specific changes in the state of your models.
981
// Creating a Backbone.View creates its initial element outside of the DOM,
982
// if an existing element is not provided...
983
var View = Backbone.View = function(options) {
984
this.cid = _.uniqueId('view');
985
this._configure(options || {});
986
this._ensureElement();
987
this.initialize.apply(this, arguments);
988
this.delegateEvents();
991
// Cached regex to split keys for `delegate`.
992
var delegateEventSplitter = /^(\S+)\s*(.*)$/;
994
// List of view options to be merged as properties.
995
var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName', 'events'];
997
// Set up all inheritable **Backbone.View** properties and methods.
998
_.extend(View.prototype, Events, {
1000
// The default `tagName` of a View's element is `"div"`.
1003
// jQuery delegate for element lookup, scoped to DOM elements within the
1004
// current view. This should be prefered to global lookups where possible.
1005
$: function(selector) {
1006
return this.$el.find(selector);
1009
// Initialize is an empty function by default. Override it with your own
1010
// initialization logic.
1011
initialize: function(){},
1013
// **render** is the core function that your view should override, in order
1014
// to populate its element (`this.el`), with the appropriate HTML. The
1015
// convention is for **render** to always return `this`.
1016
render: function() {
1020
// Remove this view by taking the element out of the DOM, and removing any
1021
// applicable Backbone.Events listeners.
1022
remove: function() {
1024
this.stopListening();
1028
// Change the view's element (`this.el` property), including event
1030
setElement: function(element, delegate) {
1031
if (this.$el) this.undelegateEvents();
1032
this.$el = element instanceof Backbone.$ ? element : Backbone.$(element);
1033
this.el = this.$el[0];
1034
if (delegate !== false) this.delegateEvents();
1038
// Set callbacks, where `this.events` is a hash of
1040
// *{"event selector": "callback"}*
1043
// 'mousedown .title': 'edit',
1044
// 'click .button': 'save'
1045
// 'click .open': function(e) { ... }
1048
// pairs. Callbacks will be bound to the view, with `this` set properly.
1049
// Uses event delegation for efficiency.
1050
// Omitting the selector binds the event to `this.el`.
1051
// This only works for delegate-able events: not `focus`, `blur`, and
1052
// not `change`, `submit`, and `reset` in Internet Explorer.
1053
delegateEvents: function(events) {
1054
if (!(events || (events = _.result(this, 'events')))) return this;
1055
this.undelegateEvents();
1056
for (var key in events) {
1057
var method = events[key];
1058
if (!_.isFunction(method)) method = this[events[key]];
1059
if (!method) continue;
1061
var match = key.match(delegateEventSplitter);
1062
var eventName = match[1], selector = match[2];
1063
method = _.bind(method, this);
1064
eventName += '.delegateEvents' + this.cid;
1065
if (selector === '') {
1066
this.$el.on(eventName, method);
1068
this.$el.on(eventName, selector, method);
1074
// Clears all callbacks previously bound to the view with `delegateEvents`.
1075
// You usually don't need to use this, but may wish to if you have multiple
1076
// Backbone views attached to the same DOM element.
1077
undelegateEvents: function() {
1078
this.$el.off('.delegateEvents' + this.cid);
1082
// Performs the initial configuration of a View with a set of options.
1083
// Keys with special meaning *(e.g. model, collection, id, className)* are
1084
// attached directly to the view. See `viewOptions` for an exhaustive
1086
_configure: function(options) {
1087
if (this.options) options = _.extend({}, _.result(this, 'options'), options);
1088
_.extend(this, _.pick(options, viewOptions));
1089
this.options = options;
1092
// Ensure that the View has a DOM element to render into.
1093
// If `this.el` is a string, pass it through `$()`, take the first
1094
// matching element, and re-assign it to `el`. Otherwise, create
1095
// an element from the `id`, `className` and `tagName` properties.
1096
_ensureElement: function() {
1098
var attrs = _.extend({}, _.result(this, 'attributes'));
1099
if (this.id) attrs.id = _.result(this, 'id');
1100
if (this.className) attrs['class'] = _.result(this, 'className');
1101
var $el = Backbone.$('<' + _.result(this, 'tagName') + '>').attr(attrs);
1102
this.setElement($el, false);
1104
this.setElement(_.result(this, 'el'), false);
1113
// Override this function to change the manner in which Backbone persists
1114
// models to the server. You will be passed the type of request, and the
1115
// model in question. By default, makes a RESTful Ajax request
1116
// to the model's `url()`. Some possible customizations could be:
1118
// * Use `setTimeout` to batch rapid-fire updates into a single request.
1119
// * Send up the models as XML instead of JSON.
1120
// * Persist models via WebSockets instead of Ajax.
1122
// Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests
1123
// as `POST`, with a `_method` parameter containing the true HTTP method,
1124
// as well as all requests with the body as `application/x-www-form-urlencoded`
1125
// instead of `application/json` with the model in a param named `model`.
1126
// Useful when interfacing with server-side languages like **PHP** that make
1127
// it difficult to read the body of `PUT` requests.
1128
Backbone.sync = function(method, model, options) {
1129
var type = methodMap[method];
1131
// Default options, unless specified.
1132
_.defaults(options || (options = {}), {
1133
emulateHTTP: Backbone.emulateHTTP,
1134
emulateJSON: Backbone.emulateJSON
1137
// Default JSON-request options.
1138
var params = {type: type, dataType: 'json'};
1140
// Ensure that we have a URL.
1142
params.url = _.result(model, 'url') || urlError();
1145
// Ensure that we have the appropriate request data.
1146
if (options.data == null && model && (method === 'create' || method === 'update' || method === 'patch')) {
1147
params.contentType = 'application/json';
1148
params.data = JSON.stringify(options.attrs || model.toJSON(options));
1151
// For older servers, emulate JSON by encoding the request into an HTML-form.
1152
if (options.emulateJSON) {
1153
params.contentType = 'application/x-www-form-urlencoded';
1154
params.data = params.data ? {model: params.data} : {};
1157
// For older servers, emulate HTTP by mimicking the HTTP method with `_method`
1158
// And an `X-HTTP-Method-Override` header.
1159
if (options.emulateHTTP && (type === 'PUT' || type === 'DELETE' || type === 'PATCH')) {
1160
params.type = 'POST';
1161
if (options.emulateJSON) params.data._method = type;
1162
var beforeSend = options.beforeSend;
1163
options.beforeSend = function(xhr) {
1164
xhr.setRequestHeader('X-HTTP-Method-Override', type);
1165
if (beforeSend) return beforeSend.apply(this, arguments);
1169
// Don't process data on a non-GET request.
1170
if (params.type !== 'GET' && !options.emulateJSON) {
1171
params.processData = false;
1174
// If we're sending a `PATCH` request, and we're in an old Internet Explorer
1175
// that still has ActiveX enabled by default, override jQuery to use that
1176
// for XHR instead. Remove this line when jQuery supports `PATCH` on IE8.
1177
if (params.type === 'PATCH' && window.ActiveXObject &&
1178
!(window.external && window.external.msActiveXFilteringEnabled)) {
1179
params.xhr = function() {
1180
return new ActiveXObject("Microsoft.XMLHTTP");
1184
// Make the request, allowing the user to override any Ajax options.
1185
var xhr = options.xhr = Backbone.ajax(_.extend(params, options));
1186
model.trigger('request', model, xhr, options);
1190
// Map from CRUD to HTTP for our default `Backbone.sync` implementation.
1199
// Set the default implementation of `Backbone.ajax` to proxy through to `$`.
1200
// Override this if you'd like to use a different library.
1201
Backbone.ajax = function() {
1202
return Backbone.$.ajax.apply(Backbone.$, arguments);
1208
// Routers map faux-URLs to actions, and fire events when routes are
1209
// matched. Creating a new one sets its `routes` hash, if not set statically.
1210
var Router = Backbone.Router = function(options) {
1211
options || (options = {});
1212
if (options.routes) this.routes = options.routes;
1214
this.initialize.apply(this, arguments);
1217
// Cached regular expressions for matching named param parts and splatted
1218
// parts of route strings.
1219
var optionalParam = /\((.*?)\)/g;
1220
var namedParam = /(\(\?)?:\w+/g;
1221
var splatParam = /\*\w+/g;
1222
var escapeRegExp = /[\-{}\[\]+?.,\\\^$|#\s]/g;
1224
// Set up all inheritable **Backbone.Router** properties and methods.
1225
_.extend(Router.prototype, Events, {
1227
// Initialize is an empty function by default. Override it with your own
1228
// initialization logic.
1229
initialize: function(){},
1231
// Manually bind a single named route to a callback. For example:
1233
// this.route('search/:query/p:num', 'search', function(query, num) {
1237
route: function(route, name, callback) {
1238
if (!_.isRegExp(route)) route = this._routeToRegExp(route);
1239
if (_.isFunction(name)) {
1243
if (!callback) callback = this[name];
1245
Backbone.history.route(route, function(fragment) {
1246
var args = router._extractParameters(route, fragment);
1247
callback && callback.apply(router, args);
1248
router.trigger.apply(router, ['route:' + name].concat(args));
1249
router.trigger('route', name, args);
1250
Backbone.history.trigger('route', router, name, args);
1255
// Simple proxy to `Backbone.history` to save a fragment into the history.
1256
navigate: function(fragment, options) {
1257
Backbone.history.navigate(fragment, options);
1261
// Bind all defined routes to `Backbone.history`. We have to reverse the
1262
// order of the routes here to support behavior where the most general
1263
// routes can be defined at the bottom of the route map.
1264
_bindRoutes: function() {
1265
if (!this.routes) return;
1266
this.routes = _.result(this, 'routes');
1267
var route, routes = _.keys(this.routes);
1268
while ((route = routes.pop()) != null) {
1269
this.route(route, this.routes[route]);
1273
// Convert a route string into a regular expression, suitable for matching
1274
// against the current location hash.
1275
_routeToRegExp: function(route) {
1276
route = route.replace(escapeRegExp, '\\$&')
1277
.replace(optionalParam, '(?:$1)?')
1278
.replace(namedParam, function(match, optional){
1279
return optional ? match : '([^\/]+)';
1281
.replace(splatParam, '(.*?)');
1282
return new RegExp('^' + route + '$');
1285
// Given a route, and a URL fragment that it matches, return the array of
1286
// extracted decoded parameters. Empty or unmatched parameters will be
1287
// treated as `null` to normalize cross-browser behavior.
1288
_extractParameters: function(route, fragment) {
1289
var params = route.exec(fragment).slice(1);
1290
return _.map(params, function(param) {
1291
return param ? decodeURIComponent(param) : null;
1300
// Handles cross-browser history management, based on either
1301
// [pushState](http://diveintohtml5.info/history.html) and real URLs, or
1302
// [onhashchange](https://developer.mozilla.org/en-US/docs/DOM/window.onhashchange)
1303
// and URL fragments. If the browser supports neither (old IE, natch),
1304
// falls back to polling.
1305
var History = Backbone.History = function() {
1307
_.bindAll(this, 'checkUrl');
1309
// Ensure that `History` can be used outside of the browser.
1310
if (typeof window !== 'undefined') {
1311
this.location = window.location;
1312
this.history = window.history;
1316
// Cached regex for stripping a leading hash/slash and trailing space.
1317
var routeStripper = /^[#\/]|\s+$/g;
1319
// Cached regex for stripping leading and trailing slashes.
1320
var rootStripper = /^\/+|\/+$/g;
1322
// Cached regex for detecting MSIE.
1323
var isExplorer = /msie [\w.]+/;
1325
// Cached regex for removing a trailing slash.
1326
var trailingSlash = /\/$/;
1328
// Has the history handling already been started?
1329
History.started = false;
1331
// Set up all inheritable **Backbone.History** properties and methods.
1332
_.extend(History.prototype, Events, {
1334
// The default interval to poll for hash changes, if necessary, is
1335
// twenty times a second.
1338
// Gets the true hash value. Cannot use location.hash directly due to bug
1339
// in Firefox where location.hash will always be decoded.
1340
getHash: function(window) {
1341
var match = (window || this).location.href.match(/#(.*)$/);
1342
return match ? match[1] : '';
1345
// Get the cross-browser normalized URL fragment, either from the URL,
1346
// the hash, or the override.
1347
getFragment: function(fragment, forcePushState) {
1348
if (fragment == null) {
1349
if (this._hasPushState || !this._wantsHashChange || forcePushState) {
1350
fragment = this.location.pathname;
1351
var root = this.root.replace(trailingSlash, '');
1352
if (!fragment.indexOf(root)) fragment = fragment.substr(root.length);
1354
fragment = this.getHash();
1357
return fragment.replace(routeStripper, '');
1360
// Start the hash change handling, returning `true` if the current URL matches
1361
// an existing route, and `false` otherwise.
1362
start: function(options) {
1363
if (History.started) throw new Error("Backbone.history has already been started");
1364
History.started = true;
1366
// Figure out the initial configuration. Do we need an iframe?
1367
// Is pushState desired ... is it available?
1368
this.options = _.extend({}, {root: '/'}, this.options, options);
1369
this.root = this.options.root;
1370
this._wantsHashChange = this.options.hashChange !== false;
1371
this._wantsPushState = !!this.options.pushState;
1372
this._hasPushState = !!(this.options.pushState && this.history && this.history.pushState);
1373
var fragment = this.getFragment();
1374
var docMode = document.documentMode;
1375
var oldIE = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7));
1377
// Normalize root to always include a leading and trailing slash.
1378
this.root = ('/' + this.root + '/').replace(rootStripper, '/');
1380
if (oldIE && this._wantsHashChange) {
1381
this.iframe = Backbone.$('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo('body')[0].contentWindow;
1382
this.navigate(fragment);
1385
// Depending on whether we're using pushState or hashes, and whether
1386
// 'onhashchange' is supported, determine how we check the URL state.
1387
if (this._hasPushState) {
1388
Backbone.$(window).on('popstate', this.checkUrl);
1389
} else if (this._wantsHashChange && ('onhashchange' in window) && !oldIE) {
1390
Backbone.$(window).on('hashchange', this.checkUrl);
1391
} else if (this._wantsHashChange) {
1392
this._checkUrlInterval = setInterval(this.checkUrl, this.interval);
1395
// Determine if we need to change the base url, for a pushState link
1396
// opened by a non-pushState browser.
1397
this.fragment = fragment;
1398
var loc = this.location;
1399
var atRoot = loc.pathname.replace(/[^\/]$/, '$&/') === this.root;
1401
// If we've started off with a route from a `pushState`-enabled browser,
1402
// but we're currently in a browser that doesn't support it...
1403
if (this._wantsHashChange && this._wantsPushState && !this._hasPushState && !atRoot) {
1404
this.fragment = this.getFragment(null, true);
1405
this.location.replace(this.root + this.location.search + '#' + this.fragment);
1406
// Return immediately as browser will do redirect to new url
1409
// Or if we've started out with a hash-based route, but we're currently
1410
// in a browser where it could be `pushState`-based instead...
1411
} else if (this._wantsPushState && this._hasPushState && atRoot && loc.hash) {
1412
this.fragment = this.getHash().replace(routeStripper, '');
1413
this.history.replaceState({}, document.title, this.root + this.fragment + loc.search);
1416
if (!this.options.silent) return this.loadUrl();
1419
// Disable Backbone.history, perhaps temporarily. Not useful in a real app,
1420
// but possibly useful for unit testing Routers.
1422
Backbone.$(window).off('popstate', this.checkUrl).off('hashchange', this.checkUrl);
1423
clearInterval(this._checkUrlInterval);
1424
History.started = false;
1427
// Add a route to be tested when the fragment changes. Routes added later
1428
// may override previous routes.
1429
route: function(route, callback) {
1430
this.handlers.unshift({route: route, callback: callback});
1433
// Checks the current URL to see if it has changed, and if it has,
1434
// calls `loadUrl`, normalizing across the hidden iframe.
1435
checkUrl: function(e) {
1436
var current = this.getFragment();
1437
if (current === this.fragment && this.iframe) {
1438
current = this.getFragment(this.getHash(this.iframe));
1440
if (current === this.fragment) return false;
1441
if (this.iframe) this.navigate(current);
1442
this.loadUrl() || this.loadUrl(this.getHash());
1445
// Attempt to load the current URL fragment. If a route succeeds with a
1446
// match, returns `true`. If no defined routes matches the fragment,
1448
loadUrl: function(fragmentOverride) {
1449
var fragment = this.fragment = this.getFragment(fragmentOverride);
1450
var matched = _.any(this.handlers, function(handler) {
1451
if (handler.route.test(fragment)) {
1452
handler.callback(fragment);
1459
// Save a fragment into the hash history, or replace the URL state if the
1460
// 'replace' option is passed. You are responsible for properly URL-encoding
1461
// the fragment in advance.
1463
// The options object can contain `trigger: true` if you wish to have the
1464
// route callback be fired (not usually desirable), or `replace: true`, if
1465
// you wish to modify the current URL without adding an entry to the history.
1466
navigate: function(fragment, options) {
1467
if (!History.started) return false;
1468
if (!options || options === true) options = {trigger: options};
1469
fragment = this.getFragment(fragment || '');
1470
if (this.fragment === fragment) return;
1471
this.fragment = fragment;
1472
var url = this.root + fragment;
1474
// If pushState is available, we use it to set the fragment as a real URL.
1475
if (this._hasPushState) {
1476
this.history[options.replace ? 'replaceState' : 'pushState']({}, document.title, url);
1478
// If hash changes haven't been explicitly disabled, update the hash
1479
// fragment to store history.
1480
} else if (this._wantsHashChange) {
1481
this._updateHash(this.location, fragment, options.replace);
1482
if (this.iframe && (fragment !== this.getFragment(this.getHash(this.iframe)))) {
1483
// Opening and closing the iframe tricks IE7 and earlier to push a
1484
// history entry on hash-tag change. When replace is true, we don't
1486
if(!options.replace) this.iframe.document.open().close();
1487
this._updateHash(this.iframe.location, fragment, options.replace);
1490
// If you've told us that you explicitly don't want fallback hashchange-
1491
// based history, then `navigate` becomes a page refresh.
1493
return this.location.assign(url);
1495
if (options.trigger) this.loadUrl(fragment);
1498
// Update the hash location, either replacing the current entry, or adding
1499
// a new one to the browser history.
1500
_updateHash: function(location, fragment, replace) {
1502
var href = location.href.replace(/(javascript:|#).*$/, '');
1503
location.replace(href + '#' + fragment);
1505
// Some browsers require that `hash` contains a leading #.
1506
location.hash = '#' + fragment;
1512
// Create the default Backbone.history.
1513
Backbone.history = new History;
1518
// Helper function to correctly set up the prototype chain, for subclasses.
1519
// Similar to `goog.inherits`, but uses a hash of prototype properties and
1520
// class properties to be extended.
1521
var extend = function(protoProps, staticProps) {
1525
// The constructor function for the new subclass is either defined by you
1526
// (the "constructor" property in your `extend` definition), or defaulted
1527
// by us to simply call the parent's constructor.
1528
if (protoProps && _.has(protoProps, 'constructor')) {
1529
child = protoProps.constructor;
1531
child = function(){ return parent.apply(this, arguments); };
1534
// Add static properties to the constructor function, if supplied.
1535
_.extend(child, parent, staticProps);
1537
// Set the prototype chain to inherit from `parent`, without calling
1538
// `parent`'s constructor function.
1539
var Surrogate = function(){ this.constructor = child; };
1540
Surrogate.prototype = parent.prototype;
1541
child.prototype = new Surrogate;
1543
// Add prototype properties (instance properties) to the subclass,
1545
if (protoProps) _.extend(child.prototype, protoProps);
1547
// Set a convenience property in case the parent's prototype is needed
1549
child.__super__ = parent.prototype;
1554
// Set up inheritance for the model, collection, router, view and history.
1555
Model.extend = Collection.extend = Router.extend = View.extend = History.extend = extend;
1557
// Throw an error when a URL is needed, and none is supplied.
1558
var urlError = function() {
1559
throw new Error('A "url" property or function must be specified');
1562
// Wrap an optional error callback with a fallback error event.
1563
var wrapError = function (model, options) {
1564
var error = options.error;
1565
options.error = function(resp) {
1566
if (error) error(model, resp, options);
1567
model.trigger('error', model, resp, options);