vendor/assets/javascripts/backbone.js in character-1.0.2 vs vendor/assets/javascripts/backbone.js in character-1.0.3
- old
+ new
@@ -1,51 +1,53 @@
-// Backbone.js 1.1.0
+// Backbone.js 1.1.2
-// (c) 2010-2011 Jeremy Ashkenas, DocumentCloud Inc.
-// (c) 2011-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+// (c) 2010-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
// Backbone may be freely distributed under the MIT license.
// For all details and documentation:
// http://backbonejs.org
-(function(){
+(function(root, factory) {
+ // Set up Backbone appropriately for the environment. Start with AMD.
+ if (typeof define === 'function' && define.amd) {
+ define(['underscore', 'jquery', 'exports'], function(_, $, exports) {
+ // Export global even in AMD case in case this script is loaded with
+ // others that may still expect a global Backbone.
+ root.Backbone = factory(root, exports, _, $);
+ });
+
+ // Next for Node.js or CommonJS. jQuery may not be needed as a module.
+ } else if (typeof exports !== 'undefined') {
+ var _ = require('underscore');
+ factory(root, exports, _);
+
+ // Finally, as a browser global.
+ } else {
+ root.Backbone = factory(root, {}, root._, (root.jQuery || root.Zepto || root.ender || root.$));
+ }
+
+}(this, function(root, Backbone, _, $) {
+
// Initial Setup
// -------------
- // Save a reference to the global object (`window` in the browser, `exports`
- // on the server).
- var root = this;
-
// Save the previous value of the `Backbone` variable, so that it can be
// restored later on, if `noConflict` is used.
var previousBackbone = root.Backbone;
// Create local references to array methods we'll want to use later.
var array = [];
var push = array.push;
var slice = array.slice;
var splice = array.splice;
- // The top-level namespace. All public Backbone classes and modules will
- // be attached to this. Exported for both the browser and the server.
- var Backbone;
- if (typeof exports !== 'undefined') {
- Backbone = exports;
- } else {
- Backbone = root.Backbone = {};
- }
-
// Current version of the library. Keep in sync with `package.json`.
- Backbone.VERSION = '1.1.0';
+ Backbone.VERSION = '1.1.2';
- // Require Underscore, if we're on the server, and it's not already present.
- var _ = root._;
- if (!_ && (typeof require !== 'undefined')) _ = require('underscore');
-
// For Backbone's purposes, jQuery, Zepto, Ender, or My Library (kidding) owns
// the `$` variable.
- Backbone.$ = root.jQuery || root.Zepto || root.ender || root.$;
+ Backbone.$ = $;
// Runs Backbone.js in *noConflict* mode, returning the `Backbone` variable
// to its previous owner. Returns a reference to this Backbone object.
Backbone.noConflict = function() {
root.Backbone = previousBackbone;
@@ -107,11 +109,11 @@
// callbacks for all events.
off: function(name, callback, context) {
var retain, ev, events, names, i, l, j, k;
if (!this._events || !eventsApi(this, 'off', name, [callback, context])) return this;
if (!name && !callback && !context) {
- this._events = {};
+ this._events = void 0;
return this;
}
names = name ? [name] : _.keys(this._events);
for (i = 0, l = names.length; i < l; i++) {
name = names[i];
@@ -203,11 +205,11 @@
switch (args.length) {
case 0: while (++i < l) (ev = events[i]).callback.call(ev.ctx); return;
case 1: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1); return;
case 2: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2); return;
case 3: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2, a3); return;
- default: while (++i < l) (ev = events[i]).callback.apply(ev.ctx, args);
+ default: while (++i < l) (ev = events[i]).callback.apply(ev.ctx, args); return;
}
};
var listenMethods = {listenTo: 'on', listenToOnce: 'once'};
@@ -348,21 +350,22 @@
unset ? delete current[attr] : current[attr] = val;
}
// Trigger all relevant attribute changes.
if (!silent) {
- if (changes.length) this._pending = true;
+ if (changes.length) this._pending = options;
for (var i = 0, l = changes.length; i < l; i++) {
this.trigger('change:' + changes[i], this, current[changes[i]], options);
}
}
// You might be wondering why there's a `while` loop here. Changes can
// be recursively nested within `"change"` events.
if (changing) return this;
if (!silent) {
while (this._pending) {
+ options = this._pending;
this._pending = false;
this.trigger('change', this, options);
}
}
this._pending = false;
@@ -526,13 +529,16 @@
// Default URL for the model's representation on the server -- if you're
// using Backbone's restful methods, override this to change the endpoint
// that will be called.
url: function() {
- var base = _.result(this, 'urlRoot') || _.result(this.collection, 'url') || urlError();
+ var base =
+ _.result(this, 'urlRoot') ||
+ _.result(this.collection, 'url') ||
+ urlError();
if (this.isNew()) return base;
- return base + (base.charAt(base.length - 1) === '/' ? '' : '/') + encodeURIComponent(this.id);
+ return base.replace(/([^\/])$/, '$1/') + encodeURIComponent(this.id);
},
// **parse** converts a response into the hash of attributes to be `set` on
// the model. The default implementation is just to pass the response along.
parse: function(resp, options) {
@@ -544,11 +550,11 @@
return new this.constructor(this.attributes);
},
// A model is new if it has never been saved to the server, and lacks an id.
isNew: function() {
- return this.id == null;
+ return !this.has(this.idAttribute);
},
// Check if the model is currently in a valid state.
isValid: function(options) {
return this._validate({}, _.extend(options || {}, { validate: true }));
@@ -648,11 +654,11 @@
this.length--;
if (!options.silent) {
options.index = index;
model.trigger('remove', model, this, options);
}
- this._removeReference(model);
+ this._removeReference(model, options);
}
return singular ? models[0] : models;
},
// Update a collection by `set`-ing a new list of models, adding new ones,
@@ -674,15 +680,15 @@
var order = !sortable && add && remove ? [] : false;
// Turn bare objects into model references, and prevent invalid models
// from being added.
for (i = 0, l = models.length; i < l; i++) {
- attrs = models[i];
+ attrs = models[i] || {};
if (attrs instanceof Model) {
id = model = attrs;
} else {
- id = attrs[targetModel.prototype.idAttribute];
+ id = attrs[targetModel.prototype.idAttribute || 'id'];
}
// If a duplicate is found, prevent it from being added and
// optionally merge it into the existing model.
if (existing = this.get(id)) {
@@ -698,18 +704,17 @@
// If this is a new, valid model, push it to the `toAdd` list.
} else if (add) {
model = models[i] = this._prepareModel(attrs, options);
if (!model) continue;
toAdd.push(model);
-
- // Listen to added models' events, and index models for lookup by
- // `id` and by `cid`.
- model.on('all', this._onModelEvent, this);
- this._byId[model.cid] = model;
- if (model.id != null) this._byId[model.id] = model;
+ this._addReference(model, options);
}
- if (order) order.push(existing || model);
+
+ // Do not add multiple models with the same `id`.
+ model = existing || model;
+ if (order && (model.isNew() || !modelMap[model.id])) order.push(model);
+ modelMap[model.id] = true;
}
// Remove nonexistent models if appropriate.
if (remove) {
for (i = 0, l = this.length; i < l; ++i) {
@@ -743,11 +748,11 @@
for (i = 0, l = toAdd.length; i < l; i++) {
(model = toAdd[i]).trigger('add', model, this, options);
}
if (sort || (order && order.length)) this.trigger('sort', this, options);
}
-
+
// Return the added (or merged) model (or models).
return singular ? models[0] : models;
},
// When you have more items than you want to add or remove individually,
@@ -755,11 +760,11 @@
// any granular `add` or `remove` events. Fires `reset` when finished.
// Useful for bulk operations and optimizations.
reset: function(models, options) {
options || (options = {});
for (var i = 0, l = this.models.length; i < l; i++) {
- this._removeReference(this.models[i]);
+ this._removeReference(this.models[i], options);
}
options.previousModels = this.models;
this._reset();
models = this.add(models, _.extend({silent: true}, options));
if (!options.silent) this.trigger('reset', this, options);
@@ -796,11 +801,11 @@
},
// Get a model from the set by id.
get: function(obj) {
if (obj == null) return void 0;
- return this._byId[obj.id] || this._byId[obj.cid] || this._byId[obj];
+ return this._byId[obj] || this._byId[obj.id] || this._byId[obj.cid];
},
// Get the model at the given index.
at: function(index) {
return this.models[index];
@@ -872,11 +877,11 @@
options = options ? _.clone(options) : {};
if (!(model = this._prepareModel(model, options))) return false;
if (!options.wait) this.add(model, options);
var collection = this;
var success = options.success;
- options.success = function(model, resp, options) {
+ options.success = function(model, resp) {
if (options.wait) collection.add(model, options);
if (success) success(model, resp, options);
};
model.save(null, options);
return model;
@@ -902,24 +907,29 @@
},
// Prepare a hash of attributes (or other model) to be added to this
// collection.
_prepareModel: function(attrs, options) {
- if (attrs instanceof Model) {
- if (!attrs.collection) attrs.collection = this;
- return attrs;
- }
+ if (attrs instanceof Model) return attrs;
options = options ? _.clone(options) : {};
options.collection = this;
var model = new this.model(attrs, options);
if (!model.validationError) return model;
this.trigger('invalid', this, model.validationError, options);
return false;
},
+ // Internal method to create a model's ties to a collection.
+ _addReference: function(model, options) {
+ this._byId[model.cid] = model;
+ if (model.id != null) this._byId[model.id] = model;
+ if (!model.collection) model.collection = this;
+ model.on('all', this._onModelEvent, this);
+ },
+
// Internal method to sever a model's ties to a collection.
- _removeReference: function(model) {
+ _removeReference: function(model, options) {
if (this === model.collection) delete model.collection;
model.off('all', this._onModelEvent, this);
},
// Internal method called every time a model in the set fires an event.
@@ -944,11 +954,11 @@
var methods = ['forEach', 'each', 'map', 'collect', 'reduce', 'foldl',
'inject', 'reduceRight', 'foldr', 'find', 'detect', 'filter', 'select',
'reject', 'every', 'all', 'some', 'any', 'include', 'contains', 'invoke',
'max', 'min', 'toArray', 'size', 'first', 'head', 'take', 'initial', 'rest',
'tail', 'drop', 'last', 'without', 'difference', 'indexOf', 'shuffle',
- 'lastIndexOf', 'isEmpty', 'chain'];
+ 'lastIndexOf', 'isEmpty', 'chain', 'sample'];
// Mix in each Underscore method as a proxy to `Collection#models`.
_.each(methods, function(method) {
Collection.prototype[method] = function() {
var args = slice.call(arguments);
@@ -956,11 +966,11 @@
return _[method].apply(_, args);
};
});
// Underscore methods that take a property name as an argument.
- var attributeMethods = ['groupBy', 'countBy', 'sortBy'];
+ var attributeMethods = ['groupBy', 'countBy', 'sortBy', 'indexBy'];
// Use attributes instead of properties.
_.each(attributeMethods, function(method) {
Collection.prototype[method] = function(value, context) {
var iterator = _.isFunction(value) ? value : function(model) {
@@ -1178,11 +1188,13 @@
var xhr = options.xhr = Backbone.ajax(_.extend(params, options));
model.trigger('request', model, xhr, options);
return xhr;
};
- var noXhrPatch = typeof window !== 'undefined' && !!window.ActiveXObject && !(window.XMLHttpRequest && (new XMLHttpRequest).dispatchEvent);
+ var noXhrPatch =
+ typeof window !== 'undefined' && !!window.ActiveXObject &&
+ !(window.XMLHttpRequest && (new XMLHttpRequest).dispatchEvent);
// Map from CRUD to HTTP for our default `Backbone.sync` implementation.
var methodMap = {
'create': 'POST',
'update': 'PUT',
@@ -1237,18 +1249,24 @@
}
if (!callback) callback = this[name];
var router = this;
Backbone.history.route(route, function(fragment) {
var args = router._extractParameters(route, fragment);
- callback && callback.apply(router, args);
+ router.execute(callback, args);
router.trigger.apply(router, ['route:' + name].concat(args));
router.trigger('route', name, args);
Backbone.history.trigger('route', router, name, args);
});
return this;
},
+ // Execute a route handler with the provided parameters. This is an
+ // excellent place to do pre-route setup or post-route cleanup.
+ execute: function(callback, args) {
+ if (callback) callback.apply(this, args);
+ },
+
// Simple proxy to `Backbone.history` to save a fragment into the history.
navigate: function(fragment, options) {
Backbone.history.navigate(fragment, options);
return this;
},
@@ -1269,22 +1287,24 @@
// against the current location hash.
_routeToRegExp: function(route) {
route = route.replace(escapeRegExp, '\\$&')
.replace(optionalParam, '(?:$1)?')
.replace(namedParam, function(match, optional) {
- return optional ? match : '([^\/]+)';
+ return optional ? match : '([^/?]+)';
})
- .replace(splatParam, '(.*?)');
- return new RegExp('^' + route + '$');
+ .replace(splatParam, '([^?]*?)');
+ return new RegExp('^' + route + '(?:\\?([\\s\\S]*))?$');
},
// Given a route, and a URL fragment that it matches, return the array of
// extracted decoded parameters. Empty or unmatched parameters will be
// treated as `null` to normalize cross-browser behavior.
_extractParameters: function(route, fragment) {
var params = route.exec(fragment).slice(1);
- return _.map(params, function(param) {
+ return _.map(params, function(param, i) {
+ // Don't decode the search params.
+ if (i === params.length - 1) return param || null;
return param ? decodeURIComponent(param) : null;
});
}
});
@@ -1318,12 +1338,12 @@
var isExplorer = /msie [\w.]+/;
// Cached regex for removing a trailing slash.
var trailingSlash = /\/$/;
- // Cached regex for stripping urls of hash and query.
- var pathStripper = /[?#].*$/;
+ // Cached regex for stripping urls of hash.
+ var pathStripper = /#.*$/;
// Has the history handling already been started?
History.started = false;
// Set up all inheritable **Backbone.History** properties and methods.
@@ -1331,10 +1351,15 @@
// The default interval to poll for hash changes, if necessary, is
// twenty times a second.
interval: 50,
+ // Are we at the app root?
+ atRoot: function() {
+ return this.location.pathname.replace(/[^\/]$/, '$&/') === this.root;
+ },
+
// Gets the true hash value. Cannot use location.hash directly due to bug
// in Firefox where location.hash will always be decoded.
getHash: function(window) {
var match = (window || this).location.href.match(/#(.*)$/);
return match ? match[1] : '';
@@ -1343,11 +1368,11 @@
// Get the cross-browser normalized URL fragment, either from the URL,
// the hash, or the override.
getFragment: function(fragment, forcePushState) {
if (fragment == null) {
if (this._hasPushState || !this._wantsHashChange || forcePushState) {
- fragment = this.location.pathname;
+ fragment = decodeURI(this.location.pathname + this.location.search);
var root = this.root.replace(trailingSlash, '');
if (!fragment.indexOf(root)) fragment = fragment.slice(root.length);
} else {
fragment = this.getHash();
}
@@ -1374,11 +1399,12 @@
// Normalize root to always include a leading and trailing slash.
this.root = ('/' + this.root + '/').replace(rootStripper, '/');
if (oldIE && this._wantsHashChange) {
- this.iframe = Backbone.$('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo('body')[0].contentWindow;
+ var frame = Backbone.$('<iframe src="javascript:0" tabindex="-1">');
+ this.iframe = frame.hide().appendTo('body')[0].contentWindow;
this.navigate(fragment);
}
// Depending on whether we're using pushState or hashes, and whether
// 'onhashchange' is supported, determine how we check the URL state.
@@ -1392,29 +1418,28 @@
// Determine if we need to change the base url, for a pushState link
// opened by a non-pushState browser.
this.fragment = fragment;
var loc = this.location;
- var atRoot = loc.pathname.replace(/[^\/]$/, '$&/') === this.root;
// Transition from hashChange to pushState or vice versa if both are
// requested.
if (this._wantsHashChange && this._wantsPushState) {
// If we've started off with a route from a `pushState`-enabled
// browser, but we're currently in a browser that doesn't support it...
- if (!this._hasPushState && !atRoot) {
+ if (!this._hasPushState && !this.atRoot()) {
this.fragment = this.getFragment(null, true);
- this.location.replace(this.root + this.location.search + '#' + this.fragment);
+ this.location.replace(this.root + '#' + this.fragment);
// Return immediately as browser will do redirect to new url
return true;
// Or if we've started out with a hash-based route, but we're currently
// in a browser where it could be `pushState`-based instead...
- } else if (this._hasPushState && atRoot && loc.hash) {
+ } else if (this._hasPushState && this.atRoot() && loc.hash) {
this.fragment = this.getHash().replace(routeStripper, '');
- this.history.replaceState({}, document.title, this.root + this.fragment + loc.search);
+ this.history.replaceState({}, document.title, this.root + this.fragment);
}
}
if (!this.options.silent) return this.loadUrl();
@@ -1422,11 +1447,11 @@
// Disable Backbone.history, perhaps temporarily. Not useful in a real app,
// but possibly useful for unit testing Routers.
stop: function() {
Backbone.$(window).off('popstate', this.checkUrl).off('hashchange', this.checkUrl);
- clearInterval(this._checkUrlInterval);
+ if (this._checkUrlInterval) clearInterval(this._checkUrlInterval);
History.started = false;
},
// Add a route to be tested when the fragment changes. Routes added later
// may override previous routes.
@@ -1470,11 +1495,11 @@
if (!History.started) return false;
if (!options || options === true) options = {trigger: !!options};
var url = this.root + (fragment = this.getFragment(fragment || ''));
- // Strip the fragment of the query and hash for matching.
+ // Strip the hash for matching.
fragment = fragment.replace(pathStripper, '');
if (this.fragment === fragment) return;
this.fragment = fragment;
@@ -1576,6 +1601,8 @@
if (error) error(model, resp, options);
model.trigger('error', model, resp, options);
};
};
-}).call(this);
+ return Backbone;
+
+}));
\ No newline at end of file