3
// (c) 2010-2011 Jeremy Ashkenas, DocumentCloud Inc.
4
// (c) 2011-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
3
// (c) 2010-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
5
4
// Backbone may be freely distributed under the MIT license.
6
5
// For all details and documentation:
7
6
// http://backbonejs.org
8
(function(root, factory) {
10
// Set up Backbone appropriately for the environment. Start with AMD.
11
if (typeof define === 'function' && define.amd) {
12
define(['underscore', 'jquery', 'exports'], function(_, $, exports) {
13
// Export global even in AMD case in case this script is loaded with
14
// others that may still expect a global Backbone.
15
root.Backbone = factory(root, exports, _, $);
18
// Next for Node.js or CommonJS. jQuery may not be needed as a module.
19
} else if (typeof exports !== 'undefined') {
20
var _ = require('underscore');
21
factory(root, exports, _);
23
// Finally, as a browser global.
25
root.Backbone = factory(root, {}, root._, (root.jQuery || root.Zepto || root.ender || root.$));
28
}(this, function(root, Backbone, _, $) {
14
// Save a reference to the global object (`window` in the browser, `exports`
18
33
// Save the previous value of the `Backbone` variable, so that it can be
19
34
// restored later on, if `noConflict` is used.
20
35
var previousBackbone = root.Backbone;
25
40
var slice = array.slice;
26
41
var splice = array.splice;
28
// The top-level namespace. All public Backbone classes and modules will
29
// be attached to this. Exported for both the browser and the server.
31
if (typeof exports !== 'undefined') {
34
Backbone = root.Backbone = {};
37
43
// Current version of the library. Keep in sync with `package.json`.
38
Backbone.VERSION = '1.1.0';
40
// Require Underscore, if we're on the server, and it's not already present.
42
if (!_ && (typeof require !== 'undefined')) _ = require('underscore');
44
Backbone.VERSION = '1.1.2';
44
46
// For Backbone's purposes, jQuery, Zepto, Ender, or My Library (kidding) owns
45
47
// the `$` variable.
46
Backbone.$ = root.jQuery || root.Zepto || root.ender || root.$;
48
50
// Runs Backbone.js in *noConflict* mode, returning the `Backbone` variable
49
51
// to its previous owner. Returns a reference to this Backbone object.
109
111
var retain, ev, events, names, i, l, j, k;
110
112
if (!this._events || !eventsApi(this, 'off', name, [callback, context])) return this;
111
113
if (!name && !callback && !context) {
114
this._events = void 0;
115
117
names = name ? [name] : _.keys(this._events);
205
207
case 1: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1); return;
206
208
case 2: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2); return;
207
209
case 3: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2, a3); return;
208
default: while (++i < l) (ev = events[i]).callback.apply(ev.ctx, args);
210
default: while (++i < l) (ev = events[i]).callback.apply(ev.ctx, args); return;
351
353
// Trigger all relevant attribute changes.
353
if (changes.length) this._pending = true;
355
if (changes.length) this._pending = options;
354
356
for (var i = 0, l = changes.length; i < l; i++) {
355
357
this.trigger('change:' + changes[i], this, current[changes[i]], options);
528
531
// using Backbone's restful methods, override this to change the endpoint
529
532
// that will be called.
530
533
url: function() {
531
var base = _.result(this, 'urlRoot') || _.result(this.collection, 'url') || urlError();
535
_.result(this, 'urlRoot') ||
536
_.result(this.collection, 'url') ||
532
538
if (this.isNew()) return base;
533
return base + (base.charAt(base.length - 1) === '/' ? '' : '/') + encodeURIComponent(this.id);
539
return base.replace(/([^\/])$/, '$1/') + encodeURIComponent(this.id);
536
542
// **parse** converts a response into the hash of attributes to be `set` on
650
656
options.index = index;
651
657
model.trigger('remove', model, this, options);
653
this._removeReference(model);
659
this._removeReference(model, options);
655
661
return singular ? models[0] : models;
676
682
// Turn bare objects into model references, and prevent invalid models
677
683
// from being added.
678
684
for (i = 0, l = models.length; i < l; i++) {
685
attrs = models[i] || {};
680
686
if (attrs instanceof Model) {
681
687
id = model = attrs;
683
id = attrs[targetModel.prototype.idAttribute];
689
id = attrs[targetModel.prototype.idAttribute || 'id'];
686
692
// If a duplicate is found, prevent it from being added and
700
706
model = models[i] = this._prepareModel(attrs, options);
701
707
if (!model) continue;
702
708
toAdd.push(model);
704
// Listen to added models' events, and index models for lookup by
705
// `id` and by `cid`.
706
model.on('all', this._onModelEvent, this);
707
this._byId[model.cid] = model;
708
if (model.id != null) this._byId[model.id] = model;
709
this._addReference(model, options);
710
if (order) order.push(existing || model);
712
// Do not add multiple models with the same `id`.
713
model = existing || model;
714
if (order && (model.isNew() || !modelMap[model.id])) order.push(model);
715
modelMap[model.id] = true;
713
718
// Remove nonexistent models if appropriate.
757
762
reset: function(models, options) {
758
763
options || (options = {});
759
764
for (var i = 0, l = this.models.length; i < l; i++) {
760
this._removeReference(this.models[i]);
765
this._removeReference(this.models[i], options);
762
767
options.previousModels = this.models;
798
803
// Get a model from the set by id.
799
804
get: function(obj) {
800
805
if (obj == null) return void 0;
801
return this._byId[obj.id] || this._byId[obj.cid] || this._byId[obj];
806
return this._byId[obj] || this._byId[obj.id] || this._byId[obj.cid];
804
809
// Get the model at the given index.
874
879
if (!options.wait) this.add(model, options);
875
880
var collection = this;
876
881
var success = options.success;
877
options.success = function(model, resp, options) {
882
options.success = function(model, resp) {
878
883
if (options.wait) collection.add(model, options);
879
884
if (success) success(model, resp, options);
904
909
// Prepare a hash of attributes (or other model) to be added to this
906
911
_prepareModel: function(attrs, options) {
907
if (attrs instanceof Model) {
908
if (!attrs.collection) attrs.collection = this;
912
if (attrs instanceof Model) return attrs;
911
913
options = options ? _.clone(options) : {};
912
914
options.collection = this;
913
915
var model = new this.model(attrs, options);
921
// Internal method to create a model's ties to a collection.
922
_addReference: function(model, options) {
923
this._byId[model.cid] = model;
924
if (model.id != null) this._byId[model.id] = model;
925
if (!model.collection) model.collection = this;
926
model.on('all', this._onModelEvent, this);
919
929
// Internal method to sever a model's ties to a collection.
920
_removeReference: function(model) {
930
_removeReference: function(model, options) {
921
931
if (this === model.collection) delete model.collection;
922
932
model.off('all', this._onModelEvent, this);
946
956
'reject', 'every', 'all', 'some', 'any', 'include', 'contains', 'invoke',
947
957
'max', 'min', 'toArray', 'size', 'first', 'head', 'take', 'initial', 'rest',
948
958
'tail', 'drop', 'last', 'without', 'difference', 'indexOf', 'shuffle',
949
'lastIndexOf', 'isEmpty', 'chain'];
959
'lastIndexOf', 'isEmpty', 'chain', 'sample'];
951
961
// Mix in each Underscore method as a proxy to `Collection#models`.
952
962
_.each(methods, function(method) {
960
970
// Underscore methods that take a property name as an argument.
961
var attributeMethods = ['groupBy', 'countBy', 'sortBy'];
971
var attributeMethods = ['groupBy', 'countBy', 'sortBy', 'indexBy'];
963
973
// Use attributes instead of properties.
964
974
_.each(attributeMethods, function(method) {
1183
var noXhrPatch = typeof window !== 'undefined' && !!window.ActiveXObject && !(window.XMLHttpRequest && (new XMLHttpRequest).dispatchEvent);
1194
typeof window !== 'undefined' && !!window.ActiveXObject &&
1195
!(window.XMLHttpRequest && (new XMLHttpRequest).dispatchEvent);
1185
1197
// Map from CRUD to HTTP for our default `Backbone.sync` implementation.
1186
1198
var methodMap = {
1239
1251
var router = this;
1240
1252
Backbone.history.route(route, function(fragment) {
1241
1253
var args = router._extractParameters(route, fragment);
1242
callback && callback.apply(router, args);
1254
router.execute(callback, args);
1243
1255
router.trigger.apply(router, ['route:' + name].concat(args));
1244
1256
router.trigger('route', name, args);
1245
1257
Backbone.history.trigger('route', router, name, args);
1262
// Execute a route handler with the provided parameters. This is an
1263
// excellent place to do pre-route setup or post-route cleanup.
1264
execute: function(callback, args) {
1265
if (callback) callback.apply(this, args);
1250
1268
// Simple proxy to `Backbone.history` to save a fragment into the history.
1251
1269
navigate: function(fragment, options) {
1252
1270
Backbone.history.navigate(fragment, options);
1271
1289
route = route.replace(escapeRegExp, '\\$&')
1272
1290
.replace(optionalParam, '(?:$1)?')
1273
1291
.replace(namedParam, function(match, optional) {
1274
return optional ? match : '([^\/]+)';
1292
return optional ? match : '([^/?]+)';
1276
.replace(splatParam, '(.*?)');
1277
return new RegExp('^' + route + '$');
1294
.replace(splatParam, '([^?]*?)');
1295
return new RegExp('^' + route + '(?:\\?([\\s\\S]*))?$');
1280
1298
// Given a route, and a URL fragment that it matches, return the array of
1282
1300
// treated as `null` to normalize cross-browser behavior.
1283
1301
_extractParameters: function(route, fragment) {
1284
1302
var params = route.exec(fragment).slice(1);
1285
return _.map(params, function(param) {
1303
return _.map(params, function(param, i) {
1304
// Don't decode the search params.
1305
if (i === params.length - 1) return param || null;
1286
1306
return param ? decodeURIComponent(param) : null;
1320
1340
// Cached regex for removing a trailing slash.
1321
1341
var trailingSlash = /\/$/;
1323
// Cached regex for stripping urls of hash and query.
1324
var pathStripper = /[?#].*$/;
1343
// Cached regex for stripping urls of hash.
1344
var pathStripper = /#.*$/;
1326
1346
// Has the history handling already been started?
1327
1347
History.started = false;
1333
1353
// twenty times a second.
1356
// Are we at the app root?
1357
atRoot: function() {
1358
return this.location.pathname.replace(/[^\/]$/, '$&/') === this.root;
1336
1361
// Gets the true hash value. Cannot use location.hash directly due to bug
1337
1362
// in Firefox where location.hash will always be decoded.
1338
1363
getHash: function(window) {
1345
1370
getFragment: function(fragment, forcePushState) {
1346
1371
if (fragment == null) {
1347
1372
if (this._hasPushState || !this._wantsHashChange || forcePushState) {
1348
fragment = this.location.pathname;
1373
fragment = decodeURI(this.location.pathname + this.location.search);
1349
1374
var root = this.root.replace(trailingSlash, '');
1350
1375
if (!fragment.indexOf(root)) fragment = fragment.slice(root.length);
1376
1401
this.root = ('/' + this.root + '/').replace(rootStripper, '/');
1378
1403
if (oldIE && this._wantsHashChange) {
1379
this.iframe = Backbone.$('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo('body')[0].contentWindow;
1404
var frame = Backbone.$('<iframe src="javascript:0" tabindex="-1">');
1405
this.iframe = frame.hide().appendTo('body')[0].contentWindow;
1380
1406
this.navigate(fragment);
1394
1420
// opened by a non-pushState browser.
1395
1421
this.fragment = fragment;
1396
1422
var loc = this.location;
1397
var atRoot = loc.pathname.replace(/[^\/]$/, '$&/') === this.root;
1399
1424
// Transition from hashChange to pushState or vice versa if both are
1403
1428
// If we've started off with a route from a `pushState`-enabled
1404
1429
// browser, but we're currently in a browser that doesn't support it...
1405
if (!this._hasPushState && !atRoot) {
1430
if (!this._hasPushState && !this.atRoot()) {
1406
1431
this.fragment = this.getFragment(null, true);
1407
this.location.replace(this.root + this.location.search + '#' + this.fragment);
1432
this.location.replace(this.root + '#' + this.fragment);
1408
1433
// Return immediately as browser will do redirect to new url
1411
1436
// Or if we've started out with a hash-based route, but we're currently
1412
1437
// in a browser where it could be `pushState`-based instead...
1413
} else if (this._hasPushState && atRoot && loc.hash) {
1438
} else if (this._hasPushState && this.atRoot() && loc.hash) {
1414
1439
this.fragment = this.getHash().replace(routeStripper, '');
1415
this.history.replaceState({}, document.title, this.root + this.fragment + loc.search);
1440
this.history.replaceState({}, document.title, this.root + this.fragment);
1424
1449
// but possibly useful for unit testing Routers.
1425
1450
stop: function() {
1426
1451
Backbone.$(window).off('popstate', this.checkUrl).off('hashchange', this.checkUrl);
1427
clearInterval(this._checkUrlInterval);
1452
if (this._checkUrlInterval) clearInterval(this._checkUrlInterval);
1428
1453
History.started = false;
1473
1498
var url = this.root + (fragment = this.getFragment(fragment || ''));
1475
// Strip the fragment of the query and hash for matching.
1500
// Strip the hash for matching.
1476
1501
fragment = fragment.replace(pathStripper, '');
1478
1503
if (this.fragment === fragment) return;