Skip to content
Browse files

Merge pull request #195 from rda1902/master

Update backbone to 1.2.2 version
  • Loading branch information...
2 parents fc036f3 + 64409e8 commit 231090bd690cd365a9486c17b00eec1e73ec37ec @manusajith manusajith committed Sep 3, 2015
Showing with 248 additions and 223 deletions.
  1. +5 −5 README.md
  2. +1 −1 backbone-rails.gemspec
  3. +242 −217 vendor/assets/javascripts/backbone.js
View
10 README.md
@@ -1,22 +1,22 @@
# Backbone-Rails [![Build Status](https://secure.travis-ci.org/codebrew/backbone-rails.png)](http://travis-ci.org/codebrew/backbone-rails)[![Gem Version](https://badge.fury.io/rb/rails-backbone.png)](http://badge.fury.io/rb/rails-backbone)
-Easily setup and use backbone.js (1.2.0) with Rails 3.1 and greater
+Easily setup and use backbone.js (1.2.2) with Rails 3.1 and greater
##Version##
###Github master branch###
-Gem version : 1.2.0
+Gem version : 1.2.2
-Backbone version : 1.2.0
+Backbone version : 1.2.2
Underscore version : 1.8.3
###Rubygems###
-Gem version : 1.2.0
+Gem version : 1.2.2
-Backbone version : 1.2.0
+Backbone version : 1.2.2
Underscore version : 1.8.3
View
2 backbone-rails.gemspec
@@ -2,7 +2,7 @@
# project in your rails apps through git.
Gem::Specification.new do |s|
s.name = "rails-backbone"
- s.version = "1.2.0"
+ s.version = "1.2.2"
s.authors = ["Ryan Fitzgerald", "Code Brew Studios", "Manu S Ajith"]
s.email = ["ryan@codebrewstudios.com", "neo@codingarena.in"]
s.homepage = "http://github.com/codebrew/backbone-rails"
View
459 vendor/assets/javascripts/backbone.js
@@ -1,4 +1,4 @@
-// Backbone.js 1.2.0
+// Backbone.js 1.2.2
// (c) 2010-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
// Backbone may be freely distributed under the MIT license.
@@ -40,12 +40,11 @@
// 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 slice = array.slice;
+ // Create a local reference to a common array method we'll want to use later.
+ var slice = Array.prototype.slice;
// Current version of the library. Keep in sync with `package.json`.
- Backbone.VERSION = '1.2.0';
+ Backbone.VERSION = '1.2.2';
// For Backbone's purposes, jQuery, Zepto, Ender, or My Library (kidding) owns
// the `$` variable.
@@ -69,12 +68,60 @@
// form param named `model`.
Backbone.emulateJSON = false;
+ // Proxy Backbone class methods to Underscore functions, wrapping the model's
+ // `attributes` object or collection's `models` array behind the scenes.
+ //
+ // collection.filter(function(model) { return model.get('age') > 10 });
+ // collection.each(this.addView);
+ //
+ // `Function#apply` can be slow so we use the method's arg count, if we know it.
+ var addMethod = function(length, method, attribute) {
+ switch (length) {
+ case 1: return function() {
+ return _[method](this[attribute]);
+ };
+ case 2: return function(value) {
+ return _[method](this[attribute], value);
+ };
+ case 3: return function(iteratee, context) {
+ return _[method](this[attribute], cb(iteratee, this), context);
+ };
+ case 4: return function(iteratee, defaultVal, context) {
+ return _[method](this[attribute], cb(iteratee, this), defaultVal, context);
+ };
+ default: return function() {
+ var args = slice.call(arguments);
+ args.unshift(this[attribute]);
+ return _[method].apply(_, args);
+ };
+ }
+ };
+ var addUnderscoreMethods = function(Class, methods, attribute) {
+ _.each(methods, function(length, method) {
+ if (_[method]) Class.prototype[method] = addMethod(length, method, attribute);
+ });
+ };
+
+ // Support `collection.sortBy('attr')` and `collection.findWhere({id: 1})`.
+ var cb = function(iteratee, instance) {
+ if (_.isFunction(iteratee)) return iteratee;
+ if (_.isObject(iteratee) && !instance._isModel(iteratee)) return modelMatcher(iteratee);
+ if (_.isString(iteratee)) return function(model) { return model.get(iteratee); };
+ return iteratee;
+ };
+ var modelMatcher = function(attrs) {
+ var matcher = _.matches(attrs);
+ return function(model) {
+ return matcher(model.attributes);
+ };
+ };
+
// Backbone.Events
// ---------------
// A module that can be mixed in to *any object* in order to provide it with
- // custom events. You may bind with `on` or remove with `off` callback
- // functions to an event; `trigger`-ing an event fires all callbacks in
+ // a custom event channel. You may bind a callback to an event with `on` or
+ // remove with `off`; `trigger`-ing an event fires all callbacks in
// succession.
//
// var object = {};
@@ -89,25 +136,25 @@
// Iterates over the standard `event, callback` (as well as the fancy multiple
// space-separated events `"change blur", callback` and jQuery-style event
- // maps `{event: callback}`), reducing them by manipulating `memo`.
- // Passes a normalized single event name and callback, as well as any
- // optional `opts`.
- var eventsApi = function(iteratee, memo, name, callback, opts) {
+ // maps `{event: callback}`).
+ var eventsApi = function(iteratee, events, name, callback, opts) {
var i = 0, names;
if (name && typeof name === 'object') {
// Handle event maps.
+ if (callback !== void 0 && 'context' in opts && opts.context === void 0) opts.context = callback;
for (names = _.keys(name); i < names.length ; i++) {
- memo = iteratee(memo, names[i], name[names[i]], opts);
+ events = eventsApi(iteratee, events, names[i], name[names[i]], opts);
}
} else if (name && eventSplitter.test(name)) {
- // Handle space separated event names.
+ // Handle space separated event names by delegating them individually.
for (names = name.split(eventSplitter); i < names.length; i++) {
- memo = iteratee(memo, names[i], callback, opts);
+ events = iteratee(events, names[i], callback, opts);
}
} else {
- memo = iteratee(memo, name, callback, opts);
+ // Finally, standard events.
+ events = iteratee(events, name, callback, opts);
}
- return memo;
+ return events;
};
// Bind an event to a `callback` function. Passing `"all"` will bind
@@ -116,8 +163,7 @@
return internalOn(this, name, callback, context);
};
- // An internal use `on` function, used to guard the `listening` argument from
- // the public API.
+ // Guard the `listening` argument from the public API.
var internalOn = function(obj, name, callback, context, listening) {
obj._events = eventsApi(onApi, obj._events || {}, name, callback, {
context: context,
@@ -134,7 +180,8 @@
};
// Inversion-of-control versions of `on`. Tell *this* object to listen to
- // an event in another object... keeping track of what it's listening to.
+ // an event in another object... keeping track of what it's listening to
+ // for easier unbinding later.
Events.listenTo = function(obj, name, callback) {
if (!obj) return this;
var id = obj._listenId || (obj._listenId = _.uniqueId('l'));
@@ -202,10 +249,9 @@
// The reducing API that removes a callback from the `events` object.
var offApi = function(events, name, callback, options) {
- // No events to consider.
if (!events) return;
- var i = 0, length, listening;
+ var i = 0, listening;
var context = options.context, listeners = options.listeners;
// Delete all events listeners and "drop" events.
@@ -257,9 +303,9 @@
};
// Bind an event to only be triggered a single time. After the first time
- // the callback is invoked, it will be removed. When multiple events are
- // passed in using the space-separated syntax, the event will fire once for every
- // event you passed in, not once for a combination of all events
+ // the callback is invoked, its listener will be removed. If multiple events
+ // are passed in using the space-separated syntax, the handler will fire
+ // once for each event, not once for a combination of all events.
Events.once = function(name, callback, context) {
// Map the event into a `{event: once}` object.
var events = eventsApi(onceMap, {}, name, callback, _.bind(this.off, this));
@@ -274,7 +320,7 @@
};
// Reduces the event callbacks into a map of `{event: onceWrapper}`.
- // `offer` unbinds the `onceWrapper` after it as been called.
+ // `offer` unbinds the `onceWrapper` after it has been called.
var onceMap = function(map, name, callback, offer) {
if (callback) {
var once = map[name] = _.once(function() {
@@ -327,35 +373,6 @@
}
};
- // Proxy Underscore methods to a Backbone class' prototype using a
- // particular attribute as the data argument
- var addMethod = function(length, method, attribute) {
- switch (length) {
- case 1: return function() {
- return _[method](this[attribute]);
- };
- case 2: return function(value) {
- return _[method](this[attribute], value);
- };
- case 3: return function(iteratee, context) {
- return _[method](this[attribute], iteratee, context);
- };
- case 4: return function(iteratee, defaultVal, context) {
- return _[method](this[attribute], iteratee, defaultVal, context);
- };
- default: return function() {
- var args = slice.call(arguments);
- args.unshift(this[attribute]);
- return _[method].apply(_, args);
- };
- }
- };
- var addUnderscoreMethods = function(Class, methods, attribute) {
- _.each(methods, function(length, method) {
- if (_[method]) Class.prototype[method] = addMethod(length, method, attribute);
- });
- };
-
// Aliases for backwards compatibility.
Events.bind = Events.on;
Events.unbind = Events.off;
@@ -444,10 +461,10 @@
// the core primitive operation of a model, updating the data and notifying
// anyone who needs to know about the change in state. The heart of the beast.
set: function(key, val, options) {
- var attr, attrs, unset, changes, silent, changing, prev, current;
if (key == null) return this;
// Handle both `"key", value` and `{key: value}` -style arguments.
+ var attrs;
if (typeof key === 'object') {
attrs = key;
options = val;
@@ -461,33 +478,36 @@
if (!this._validate(attrs, options)) return false;
// Extract attributes and options.
- unset = options.unset;
- silent = options.silent;
- changes = [];
- changing = this._changing;
- this._changing = true;
+ var unset = options.unset;
+ var silent = options.silent;
+ var changes = [];
+ var changing = this._changing;
+ this._changing = true;
if (!changing) {
this._previousAttributes = _.clone(this.attributes);
this.changed = {};
}
- current = this.attributes, prev = this._previousAttributes;
- // Check for changes of `id`.
- if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
+ var current = this.attributes;
+ var changed = this.changed;
+ var prev = this._previousAttributes;
// For each `set` attribute, update or delete the current value.
- for (attr in attrs) {
+ for (var attr in attrs) {
val = attrs[attr];
if (!_.isEqual(current[attr], val)) changes.push(attr);
if (!_.isEqual(prev[attr], val)) {
- this.changed[attr] = val;
+ changed[attr] = val;
} else {
- delete this.changed[attr];
+ delete changed[attr];
}
unset ? delete current[attr] : current[attr] = val;
}
+ // Update the `id`.
+ this.id = this.get(this.idAttribute);
+
// Trigger all relevant attribute changes.
if (!silent) {
if (changes.length) this._pending = options;
@@ -539,13 +559,14 @@
// determining if there *would be* a change.
changedAttributes: function(diff) {
if (!diff) return this.hasChanged() ? _.clone(this.changed) : false;
- var val, changed = false;
var old = this._changing ? this._previousAttributes : this.attributes;
+ var changed = {};
for (var attr in diff) {
- if (_.isEqual(old[attr], (val = diff[attr]))) continue;
- (changed || (changed = {}))[attr] = val;
+ var val = diff[attr];
+ if (_.isEqual(old[attr], val)) continue;
+ changed[attr] = val;
}
- return changed;
+ return _.size(changed) ? changed : false;
},
// Get the previous value of an attribute, recorded at the time the last
@@ -564,12 +585,12 @@
// Fetch the model from the server, merging the response with the model's
// local attributes. Any changed attributes will trigger a "change" event.
fetch: function(options) {
- options = options ? _.clone(options) : {};
- if (options.parse === void 0) options.parse = true;
+ options = _.extend({parse: true}, options);
var model = this;
var success = options.success;
options.success = function(resp) {
- if (!model.set(model.parse(resp, options), options)) return false;
+ var serverAttrs = options.parse ? model.parse(resp, options) : resp;
+ if (!model.set(serverAttrs, options)) return false;
if (success) success.call(options.context, model, resp, options);
model.trigger('sync', model, resp, options);
};
@@ -581,18 +602,17 @@
// If the server returns an attributes hash that differs, the model's
// state will be `set` again.
save: function(key, val, options) {
- var attrs, method, xhr, attributes = this.attributes, wait;
-
// Handle both `"key", value` and `{key: value}` -style arguments.
+ var attrs;
if (key == null || typeof key === 'object') {
attrs = key;
options = val;
} else {
(attrs = {})[key] = val;
}
- options = _.extend({validate: true}, options);
- wait = options.wait;
+ options = _.extend({validate: true, parse: true}, options);
+ var wait = options.wait;
// If we're not waiting and attributes exist, save acts as
// `set(attr).save(null, opts)` with validation. Otherwise, check if
@@ -603,35 +623,31 @@
if (!this._validate(attrs, options)) return false;
}
- // Set temporary attributes if `{wait: true}`.
- if (attrs && wait) {
- this.attributes = _.extend({}, attributes, attrs);
- }
-
// After a successful server-side save, the client is (optionally)
// updated with the server-side state.
- if (options.parse === void 0) options.parse = true;
var model = this;
var success = options.success;
+ var attributes = this.attributes;
options.success = function(resp) {
// Ensure attributes are restored during synchronous saves.
model.attributes = attributes;
var serverAttrs = options.parse ? model.parse(resp, options) : resp;
- if (wait) serverAttrs = _.extend(attrs || {}, serverAttrs);
- if (_.isObject(serverAttrs) && !model.set(serverAttrs, options)) {
- return false;
- }
+ if (wait) serverAttrs = _.extend({}, attrs, serverAttrs);
+ if (serverAttrs && !model.set(serverAttrs, options)) return false;
if (success) success.call(options.context, model, resp, options);
model.trigger('sync', model, resp, options);
};
wrapError(this, options);
- method = this.isNew() ? 'create' : (options.patch ? 'patch' : 'update');
+ // Set temporary attributes if `{wait: true}` to properly find new ids.
+ if (attrs && wait) this.attributes = _.extend({}, attributes, attrs);
+
+ var method = this.isNew() ? 'create' : (options.patch ? 'patch' : 'update');
if (method === 'patch' && !options.attrs) options.attrs = attrs;
- xhr = this.sync(method, this, options);
+ var xhr = this.sync(method, this, options);
// Restore attributes.
- if (attrs && wait) this.attributes = attributes;
+ this.attributes = attributes;
return xhr;
},
@@ -676,8 +692,8 @@
_.result(this.collection, 'url') ||
urlError();
if (this.isNew()) return base;
- var id = this.id || this.attributes[this.idAttribute];
- return base.replace(/([^\/])$/, '$1/') + encodeURIComponent(id);
+ var id = this.get(this.idAttribute);
+ return base.replace(/[^\/]$/, '$&/') + encodeURIComponent(id);
},
// **parse** converts a response into the hash of attributes to be `set` on
@@ -698,7 +714,7 @@
// Check if the model is currently in a valid state.
isValid: function(options) {
- return this._validate({}, _.extend(options || {}, { validate: true }));
+ return this._validate({}, _.defaults({validate: true}, options));
},
// Run validation against the next complete set of model attributes,
@@ -714,7 +730,8 @@
});
- // Underscore methods that we want to implement on the Model.
+ // Underscore methods that we want to implement on the Model, mapped to the
+ // number of arguments they take.
var modelMethods = { keys: 1, values: 1, pairs: 1, invert: 1, pick: 0,
omit: 0, chain: 1, isEmpty: 1 };
@@ -747,6 +764,15 @@
var setOptions = {add: true, remove: true, merge: true};
var addOptions = {add: true, remove: false};
+ // Splices `insert` into `array` at index `at`.
+ var splice = function(array, insert, at) {
+ var tail = Array(array.length - at);
+ var length = insert.length;
+ for (var 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];
+ };
+
// Define the Collection's inheritable methods.
_.extend(Collection.prototype, Events, {
@@ -761,122 +787,129 @@
// The JSON representation of a Collection is an array of the
// models' attributes.
toJSON: function(options) {
- return this.map(function(model){ return model.toJSON(options); });
+ return this.map(function(model) { return model.toJSON(options); });
},
// Proxy `Backbone.sync` by default.
sync: function() {
return Backbone.sync.apply(this, arguments);
},
- // Add a model, or list of models to the set.
+ // Add a model, or list of models to the set. `models` may be Backbone
+ // Models or raw JavaScript objects to be converted to Models, or any
+ // combination of the two.
add: function(models, options) {
return this.set(models, _.extend({merge: false}, options, addOptions));
},
// Remove a model, or a list of models from the set.
remove: function(models, options) {
- var singular = !_.isArray(models), removed;
+ options = _.extend({}, options);
+ var singular = !_.isArray(models);
models = singular ? [models] : _.clone(models);
- options || (options = {});
- removed = this._removeModels(models, options);
+ var removed = this._removeModels(models, options);
if (!options.silent && removed) this.trigger('update', this, options);
- return singular ? models[0] : models;
+ return singular ? removed[0] : removed;
},
// Update a collection by `set`-ing a new list of models, adding new ones,
// removing models that are no longer present, and merging models that
// already exist in the collection, as necessary. Similar to **Model#set**,
// the core operation for updating the data contained by the collection.
set: function(models, options) {
+ if (models == null) return;
+
options = _.defaults({}, options, setOptions);
- if (options.parse) 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();
- var id, model, attrs, existing, sort;
+ models = singular ? [models] : models.slice();
+
var at = options.at;
if (at != null) at = +at;
if (at < 0) at += this.length + 1;
+
+ var set = [];
+ var toAdd = [];
+ var toRemove = [];
+ var modelMap = {};
+
+ var add = options.add;
+ var merge = options.merge;
+ var remove = options.remove;
+
+ var sort = false;
var sortable = this.comparator && (at == null) && options.sort !== false;
var sortAttr = _.isString(this.comparator) ? this.comparator : null;
- var toAdd = [], toRemove = [], modelMap = {};
- var add = options.add, merge = options.merge, remove = options.remove;
- var order = !sortable && add && remove ? [] : false;
- var orderChanged = false;
// Turn bare objects into model references, and prevent invalid models
// from being added.
+ var model;
for (var i = 0; i < models.length; i++) {
- attrs = models[i];
+ model = models[i];
// If a duplicate is found, prevent it from being added and
// optionally merge it into the existing model.
- if (existing = this.get(attrs)) {
- if (remove) modelMap[existing.cid] = true;
- if (merge && attrs !== existing) {
- attrs = this._isModel(attrs) ? attrs.attributes : attrs;
+ var existing = this.get(model);
+ if (existing) {
+ if (merge && model !== existing) {
+ var attrs = this._isModel(model) ? model.attributes : model;
if (options.parse) attrs = existing.parse(attrs, options);
existing.set(attrs, options);
- if (sortable && !sort && existing.hasChanged(sortAttr)) sort = true;
+ if (sortable && !sort) sort = existing.hasChanged(sortAttr);
+ }
+ if (!modelMap[existing.cid]) {
+ modelMap[existing.cid] = true;
+ set.push(existing);
}
models[i] = existing;
// 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);
- this._addReference(model, options);
- }
-
- // Do not add multiple models with the same `id`.
- model = existing || model;
- if (!model) continue;
- id = this.modelId(model.attributes);
- if (order && (model.isNew() || !modelMap[id])) {
- order.push(model);
-
- // Check to see if this is actually a new model at this index.
- orderChanged = orderChanged || !this.models[i] || model.cid !== this.models[i].cid;
+ model = models[i] = this._prepareModel(model, options);
+ if (model) {
+ toAdd.push(model);
+ this._addReference(model, options);
+ modelMap[model.cid] = true;
+ set.push(model);
+ }
}
-
- modelMap[id] = true;
}
- // Remove nonexistent models if appropriate.
+ // Remove stale models.
if (remove) {
- for (var i = 0; i < this.length; i++) {
- if (!modelMap[(model = this.models[i]).cid]) toRemove.push(model);
+ for (i = 0; i < this.length; i++) {
+ model = this.models[i];
+ if (!modelMap[model.cid]) toRemove.push(model);
}
if (toRemove.length) this._removeModels(toRemove, options);
}
// See if sorting is needed, update `length` and splice in new models.
- if (toAdd.length || orderChanged) {
+ 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];
+ });
+ this.models.length = 0;
+ splice(this.models, set, 0);
+ this.length = this.models.length;
+ } else if (toAdd.length) {
if (sortable) sort = true;
- this.length += toAdd.length;
- if (at != null) {
- for (var i = 0; i < toAdd.length; i++) {
- this.models.splice(at + i, 0, toAdd[i]);
- }
- } else {
- if (order) this.models.length = 0;
- var orderedModels = order || toAdd;
- for (var i = 0; i < orderedModels.length; i++) {
- this.models.push(orderedModels[i]);
- }
- }
+ splice(this.models, toAdd, at == null ? this.length : at);
+ this.length = this.models.length;
}
// Silently sort the collection if appropriate.
if (sort) this.sort({silent: true});
// Unless silenced, it's time to fire all appropriate add/sort events.
if (!options.silent) {
- var addOpts = at != null ? _.clone(options) : options;
- for (var i = 0; i < toAdd.length; i++) {
- if (at != null) addOpts.index = at + i;
- (model = toAdd[i]).trigger('add', model, this, addOpts);
+ for (i = 0; i < toAdd.length; i++) {
+ if (at != null) options.index = at + i;
+ model = toAdd[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);
@@ -910,8 +943,7 @@
// Remove a model from the end of the collection.
pop: function(options) {
var model = this.at(this.length - 1);
- this.remove(model, options);
- return model;
+ return this.remove(model, options);
},
// Add a model to the beginning of the collection.
@@ -922,8 +954,7 @@
// Remove a model from the beginning of the collection.
shift: function(options) {
var model = this.at(0);
- this.remove(model, options);
- return model;
+ return this.remove(model, options);
},
// Slice out a sub-array of models from the collection.
@@ -947,10 +978,7 @@
// Return models with matching attributes. Useful for simple cases of
// `filter`.
where: function(attrs, first) {
- var matches = _.matches(attrs);
- return this[first ? 'find' : 'filter'](function(model) {
- return matches(model.attributes);
- });
+ return this[first ? 'find' : 'filter'](attrs);
},
// Return the first model with matching attributes. Useful for simple cases
@@ -963,16 +991,19 @@
// normal circumstances, as the set will maintain sort order as each item
// is added.
sort: function(options) {
- if (!this.comparator) throw new Error('Cannot sort a set without a comparator');
+ var comparator = this.comparator;
+ if (!comparator) throw new Error('Cannot sort a set without a comparator');
options || (options = {});
+ var length = comparator.length;
+ if (_.isFunction(comparator)) comparator = _.bind(comparator, this);
+
// Run sort based on type of `comparator`.
- if (_.isString(this.comparator) || this.comparator.length === 1) {
- this.models = this.sortBy(this.comparator, this);
+ if (length === 1 || _.isString(comparator)) {
+ this.models = this.sortBy(comparator);
} else {
- this.models.sort(_.bind(this.comparator, this));
+ this.models.sort(comparator);
}
-
if (!options.silent) this.trigger('sort', this, options);
return this;
},
@@ -986,8 +1017,7 @@
// collection when they arrive. If `reset: true` is passed, the response
// data will be passed through the `reset` method instead of `set`.
fetch: function(options) {
- options = options ? _.clone(options) : {};
- if (options.parse === void 0) options.parse = true;
+ options = _.extend({parse: true}, options);
var success = options.success;
var collection = this;
options.success = function(resp) {
@@ -1006,7 +1036,8 @@
create: function(model, options) {
options = options ? _.clone(options) : {};
var wait = options.wait;
- if (!(model = this._prepareModel(model, options))) return false;
+ model = this._prepareModel(model, options);
+ if (!model) return false;
if (!wait) this.add(model, options);
var collection = this;
var success = options.success;
@@ -1060,31 +1091,26 @@
return false;
},
- // Internal method called by both remove and set. Does not trigger any
- // additional events. Returns true if anything was actually removed.
+ // Internal method called by both remove and set.
_removeModels: function(models, options) {
- var i, l, index, model, removed = false;
- for (var i = 0, j = 0; i < models.length; i++) {
- var model = models[i] = this.get(models[i]);
+ var removed = [];
+ for (var i = 0; i < models.length; i++) {
+ var model = this.get(models[i]);
if (!model) continue;
- var id = this.modelId(model.attributes);
- if (id != null) delete this._byId[id];
- delete this._byId[model.cid];
+
var index = this.indexOf(model);
this.models.splice(index, 1);
this.length--;
+
if (!options.silent) {
options.index = index;
model.trigger('remove', model, this, options);
}
- models[j++] = model;
+
+ removed.push(model);
this._removeReference(model, options);
- removed = true;
}
- // We only need to slice if models array should be smaller, which is
- // caused by some models not actually getting removed.
- if (models.length !== j) models = models.slice(0, j);
- return removed;
+ return removed.length ? removed : false;
},
// Method for checking whether an object should be considered a model for
@@ -1103,6 +1129,9 @@
// Internal method to sever a model's ties to a collection.
_removeReference: function(model, options) {
+ delete this._byId[model.cid];
+ var id = this.modelId(model.attributes);
+ if (id != null) delete this._byId[id];
if (this === model.collection) delete model.collection;
model.off('all', this._onModelEvent, this);
},
@@ -1132,29 +1161,16 @@
// right here:
var collectionMethods = { forEach: 3, each: 3, map: 3, collect: 3, reduce: 4,
foldl: 4, inject: 4, reduceRight: 4, foldr: 4, find: 3, detect: 3, filter: 3,
- select: 3, reject: 3, every: 3, all: 3, some: 3, any: 3, include: 2,
- contains: 2, invoke: 2, max: 3, min: 3, toArray: 1, size: 1, first: 3,
+ select: 3, reject: 3, every: 3, all: 3, some: 3, any: 3, include: 3, includes: 3,
+ contains: 3, invoke: 0, max: 3, min: 3, toArray: 1, size: 1, first: 3,
head: 3, take: 3, initial: 3, rest: 3, tail: 3, drop: 3, last: 3,
without: 0, difference: 0, indexOf: 3, shuffle: 1, lastIndexOf: 3,
- isEmpty: 1, chain: 1, sample: 3, partition: 3 };
+ isEmpty: 1, chain: 1, sample: 3, partition: 3, groupBy: 3, countBy: 3,
+ sortBy: 3, indexBy: 3};
// Mix in each Underscore method as a proxy to `Collection#models`.
addUnderscoreMethods(Collection, collectionMethods, 'models');
- // Underscore methods that take a property name as an argument.
- var attributeMethods = ['groupBy', 'countBy', 'sortBy', 'indexBy'];
-
- // Use attributes instead of properties.
- _.each(attributeMethods, function(method) {
- if (!_[method]) return;
- Collection.prototype[method] = function(value, context) {
- var iterator = _.isFunction(value) ? value : function(model) {
- return model.get(value);
- };
- return _[method](this.models, iterator, context);
- };
- });
-
// Backbone.View
// -------------
@@ -1170,7 +1186,6 @@
// if an existing element is not provided...
var View = Backbone.View = function(options) {
this.cid = _.uniqueId('view');
- options || (options = {});
_.extend(this, _.pick(options, viewOptions));
this._ensureElement();
this.initialize.apply(this, arguments);
@@ -1179,7 +1194,7 @@
// Cached regex to split keys for `delegate`.
var delegateEventSplitter = /^(\S+)\s*(.*)$/;
- // List of view options to be merged as properties.
+ // List of view options to be set as properties.
var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName', 'events'];
// Set up all inheritable **Backbone.View** properties and methods.
@@ -1253,11 +1268,12 @@
// Uses event delegation for efficiency.
// Omitting the selector binds the event to `this.el`.
delegateEvents: function(events) {
- if (!(events || (events = _.result(this, 'events')))) return this;
+ events || (events = _.result(this, 'events'));
+ if (!events) return this;
this.undelegateEvents();
for (var key in events) {
var method = events[key];
- if (!_.isFunction(method)) method = this[events[key]];
+ if (!_.isFunction(method)) method = this[method];
if (!method) continue;
var match = key.match(delegateEventSplitter);
this.delegate(match[1], match[2], _.bind(method, this));
@@ -1270,6 +1286,7 @@
// `blur`, and not `change`, `submit`, and `reset` in Internet Explorer.
delegate: function(eventName, selector, listener) {
this.$el.on(eventName + '.delegateEvents' + this.cid, selector, listener);
+ return this;
},
// Clears all callbacks previously bound to the view by `delegateEvents`.
@@ -1284,6 +1301,7 @@
// `selector` and `listener` are both optional.
undelegate: function(eventName, selector, listener) {
this.$el.off(eventName + '.delegateEvents' + this.cid, selector, listener);
+ return this;
},
// Produces a DOM element to be assigned to your view. Exposed for
@@ -1520,7 +1538,7 @@
// falls back to polling.
var History = Backbone.History = function() {
this.handlers = [];
- _.bindAll(this, 'checkUrl');
+ this.checkUrl = _.bind(this.checkUrl, this);
// Ensure that `History` can be used outside of the browser.
if (typeof window !== 'undefined') {
@@ -1613,7 +1631,7 @@
this.options = _.extend({root: '/'}, this.options, options);
this.root = this.options.root;
this._wantsHashChange = this.options.hashChange !== false;
- this._hasHashChange = 'onhashchange' in window;
+ this._hasHashChange = 'onhashchange' in window && (document.documentMode === void 0 || document.documentMode > 7);
this._useHashChange = this._wantsHashChange && this._hasHashChange;
this._wantsPushState = !!this.options.pushState;
this._hasPushState = !!(this.history && this.history.pushState);
@@ -1647,15 +1665,16 @@
// support the `hashchange` event, HTML5 history, or the user wants
// `hashChange` but not `pushState`.
if (!this._hasHashChange && this._wantsHashChange && !this._usePushState) {
- var iframe = document.createElement('iframe');
- iframe.src = 'javascript:0';
- iframe.style.display = 'none';
- iframe.tabIndex = -1;
+ this.iframe = document.createElement('iframe');
+ this.iframe.src = 'javascript:0';
+ this.iframe.style.display = 'none';
+ this.iframe.tabIndex = -1;
var body = document.body;
// Using `appendChild` will throw on IE < 9 if the document is not ready.
- this.iframe = body.insertBefore(iframe, body.firstChild).contentWindow;
- this.iframe.document.open().close();
- this.iframe.location.hash = '#' + this.fragment;
+ var iWindow = body.insertBefore(this.iframe, body.firstChild).contentWindow;
+ iWindow.document.open();
+ iWindow.document.close();
+ iWindow.location.hash = '#' + this.fragment;
}
// Add a cross-platform `addEventListener` shim for older browsers.
@@ -1693,7 +1712,7 @@
// Clean up the iframe if necessary.
if (this.iframe) {
- document.body.removeChild(this.iframe.frameElement);
+ document.body.removeChild(this.iframe);
this.iframe = null;
}
@@ -1716,7 +1735,7 @@
// If the user pressed the back button, the iframe's hash will have
// changed and we should use that for comparison.
if (current === this.fragment && this.iframe) {
- current = this.getHash(this.iframe);
+ current = this.getHash(this.iframe.contentWindow);
}
if (current === this.fragment) return false;
@@ -1731,7 +1750,7 @@
// If the root doesn't match, no routes can match either.
if (!this.matchRoot()) return false;
fragment = this.fragment = this.getFragment(fragment);
- return _.any(this.handlers, function(handler) {
+ return _.some(this.handlers, function(handler) {
if (handler.route.test(fragment)) {
handler.callback(fragment);
return true;
@@ -1774,12 +1793,18 @@
// fragment to store history.
} else if (this._wantsHashChange) {
this._updateHash(this.location, fragment, options.replace);
- if (this.iframe && (fragment !== this.getHash(this.iframe))) {
+ if (this.iframe && (fragment !== this.getHash(this.iframe.contentWindow))) {
+ var iWindow = this.iframe.contentWindow;
+
// Opening and closing the iframe tricks IE7 and earlier to push a
// history entry on hash-tag change. When replace is true, we don't
// want this.
- if (!options.replace) this.iframe.document.open().close();
- this._updateHash(this.iframe.location, fragment, options.replace);
+ if (!options.replace) {
+ iWindow.document.open();
+ iWindow.document.close();
+ }
+
+ this._updateHash(iWindow.location, fragment, options.replace);
}
// If you've told us that you explicitly don't want fallback hashchange-
@@ -1865,4 +1890,4 @@
return Backbone;
-}));
+}));

0 comments on commit 231090b

Please sign in to comment.
Something went wrong with that request. Please try again.