-// Backbone.js 1.2.3
+// Backbone.js 1.3.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) {
var slice = Array.prototype.slice;
// Current version of the library. Keep in sync with `package.json`.
- Backbone.VERSION = '1.2.3';
+ Backbone.VERSION = '1.3.3';
// For Backbone's purposes, jQuery, Zepto, Ender, or My Library (kidding) owns
// the `$` variable.
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);
}
listening.obj.off(name, callback, this);
}
- if (_.isEmpty(listeningTo)) this._listeningTo = void 0;
return this;
};
delete events[name];
}
}
- if (_.size(events)) return events;
+ return events;
};
// Bind an event to only be triggered a single time. After the first time
Events.once = function(name, callback, context) {
// Map the event into a `{event: once}` object.
var events = eventsApi(onceMap, {}, name, callback, _.bind(this.off, this));
- return this.on(events, void 0, context);
+ if (typeof name === 'string' && context == null) callback = void 0;
+ return this.on(events, callback, context);
};
// Inversion-of-control versions of `once`.
};
// 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;
this.attributes = {};
if (options.collection) this.collection = options.collection;
if (options.parse) attrs = this.parse(attrs, options) || {};
- attrs = _.defaults({}, attrs, _.result(this, 'defaults'));
+ var defaults = _.result(this, 'defaults');
+ attrs = _.defaults(_.extend({}, defaults, attrs), defaults);
this.set(attrs, options);
this.changed = {};
this.initialize.apply(this, arguments);
// Check if the model is currently in a valid state.
isValid: function(options) {
- return this._validate({}, _.defaults({validate: true}, options));
+ return this._validate({}, _.extend({}, options, {validate: true}));
},
// Run validation against the next complete set of model attributes,
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;
},
set: function(models, options) {
if (models == null) return;
- options = _.defaults({}, options, setOptions);
- if (options.parse && !this._isModel(models)) models = this.parse(models, options);
+ options = _.extend({}, setOptions, 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;
+ if (at > this.length) at = this.length;
if (at < 0) at += this.length + 1;
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 slice.apply(this.models, arguments);
},
- // Get a model from the set by id.
+ // Get a model from the set by id, cid, model object with id or cid
+ // properties, or an attributes object that is transformed through modelId.
get: function(obj) {
if (obj == null) return void 0;
- var id = this.modelId(this._isModel(obj) ? obj.attributes : obj);
- return this._byId[obj] || this._byId[id] || this._byId[obj.cid];
+ return this._byId[obj] ||
+ this._byId[this.modelId(obj.attributes || obj)] ||
+ obj.cid && 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.
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, ''));
};
return Backbone;
-
});