// Backbone.js 1.2.3
-// (c) 2010-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+// (c) 2010-2016 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
// Backbone may be freely distributed under the MIT license.
// For all details and documentation:
// http://backbonejs.org
// Establish the root object, `window` (`self`) in the browser, or `global` on the server.
// We use `self` instead of `window` for `WebWorker` support.
- var root = (typeof self == 'object' && self.self == self && self) ||
- (typeof global == 'object' && global.global == global && global);
+ var root = (typeof self == 'object' && self.self === self && self) ||
+ (typeof global == 'object' && global.global === global && global);
// Set up Backbone appropriately for the environment. Start with AMD.
if (typeof define === 'function' && define.amd) {
events = eventsApi(iteratee, events, names[i], name[names[i]], opts);
}
} else if (name && eventSplitter.test(name)) {
- // Handle space separated event names by delegating them individually.
+ // Handle space-separated event names by delegating them individually.
for (names = name.split(eventSplitter); i < names.length; i++) {
events = iteratee(events, names[i], callback, opts);
}
};
// Handles triggering the appropriate event callbacks.
- var triggerApi = function(objEvents, name, cb, args) {
+ var triggerApi = function(objEvents, name, callback, args) {
if (objEvents) {
var events = objEvents[name];
var allEvents = objEvents.all;
at = Math.min(Math.max(at, 0), array.length);
var tail = Array(array.length - at);
var length = insert.length;
- for (var i = 0; i < tail.length; i++) tail[i] = array[i + at];
+ var i;
+ for (i = 0; i < tail.length; i++) tail[i] = array[i + at];
for (i = 0; i < length; i++) array[i + at] = insert[i];
for (i = 0; i < tail.length; i++) array[i + length + at] = tail[i];
};
var singular = !_.isArray(models);
models = singular ? [models] : models.slice();
var removed = this._removeModels(models, options);
- if (!options.silent && removed.length) this.trigger('update', this, options);
+ if (!options.silent && removed.length) {
+ options.changes = {added: [], merged: [], removed: removed};
+ this.trigger('update', this, options);
+ }
return singular ? removed[0] : removed;
},
if (models == null) return;
options = _.defaults({}, options, setOptions);
- if (options.parse && !this._isModel(models)) models = this.parse(models, options);
+ if (options.parse && !this._isModel(models)) {
+ models = this.parse(models, options) || [];
+ }
var singular = !_.isArray(models);
- models = singular ? (models ? [models] : []) : models.slice();
+ models = singular ? [models] : models.slice();
var at = options.at;
if (at != null) at = +at;
var set = [];
var toAdd = [];
+ var toMerge = [];
var toRemove = [];
var modelMap = {};
// Turn bare objects into model references, and prevent invalid models
// from being added.
- var model;
- for (var i = 0; i < models.length; i++) {
+ var model, i;
+ for (i = 0; i < models.length; i++) {
model = models[i];
// If a duplicate is found, prevent it from being added and
var attrs = this._isModel(model) ? model.attributes : model;
if (options.parse) attrs = existing.parse(attrs, options);
existing.set(attrs, options);
+ toMerge.push(existing);
if (sortable && !sort) sort = existing.hasChanged(sortAttr);
}
if (!modelMap[existing.cid]) {
var orderChanged = false;
var replace = !sortable && add && remove;
if (set.length && replace) {
- orderChanged = this.length != set.length || _.some(this.models, function(model, index) {
- return model !== set[index];
+ orderChanged = this.length !== set.length || _.some(this.models, function(m, index) {
+ return m !== set[index];
});
this.models.length = 0;
splice(this.models, set, 0);
// Silently sort the collection if appropriate.
if (sort) this.sort({silent: true});
- // Unless silenced, it's time to fire all appropriate add/sort events.
+ // Unless silenced, it's time to fire all appropriate add/sort/update events.
if (!options.silent) {
for (i = 0; i < toAdd.length; i++) {
if (at != null) options.index = at + i;
model.trigger('add', model, this, options);
}
if (sort || orderChanged) this.trigger('sort', this, options);
- if (toAdd.length || toRemove.length) this.trigger('update', this, options);
+ if (toAdd.length || toRemove.length || toMerge.length) {
+ options.changes = {
+ added: toAdd,
+ removed: toRemove,
+ merged: toMerge
+ };
+ this.trigger('update', this, options);
+ }
}
// Return the added (or merged) model (or models).
return this._byId[obj] || this._byId[id] || this._byId[obj.cid];
},
+ // Returns `true` if the model is in the collection.
+ has: function(obj) {
+ return this.get(obj) != null;
+ },
+
// Get the model at the given index.
at: function(index) {
if (index < 0) index += this.length;
if (!wait) this.add(model, options);
var collection = this;
var success = options.success;
- options.success = function(model, resp, callbackOpts) {
- if (wait) collection.add(model, callbackOpts);
- if (success) success.call(callbackOpts.context, model, resp, callbackOpts);
+ options.success = function(m, resp, callbackOpts) {
+ if (wait) collection.add(m, callbackOpts);
+ if (success) success.call(callbackOpts.context, m, resp, callbackOpts);
};
model.save(null, options);
return model;
// Does the pathname match the root?
matchRoot: function() {
var path = this.decodeFragment(this.location.pathname);
- var root = path.slice(0, this.root.length - 1) + '/';
- return root === this.root;
+ var rootPath = path.slice(0, this.root.length - 1) + '/';
+ return rootPath === this.root;
},
// Unicode characters in `location.pathname` are percent encoded so they're
// 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 && !this.atRoot()) {
- var root = this.root.slice(0, -1) || '/';
- this.location.replace(root + '#' + this.getPath());
+ var rootPath = this.root.slice(0, -1) || '/';
+ this.location.replace(rootPath + '#' + this.getPath());
// Return immediately as browser will do redirect to new url
return true;
fragment = this.getFragment(fragment || '');
// Don't include a trailing slash on the root.
- var root = this.root;
+ var rootPath = this.root;
if (fragment === '' || fragment.charAt(0) === '?') {
- root = root.slice(0, -1) || '/';
+ rootPath = rootPath.slice(0, -1) || '/';
}
- var url = root + fragment;
+ var url = rootPath + fragment;
// Strip the hash and decode for matching.
fragment = this.decodeFragment(fragment.replace(pathStripper, ''));