Commit f57e0b77 authored by Bo-Yi Wu's avatar Bo-Yi Wu

upgrade backbone to 1.1.1 and underscore to 1.6.0

Signed-off-by: default avatarBo-Yi Wu <appleboy.tw@gmail.com>
parent 55c44717
...@@ -2,8 +2,8 @@ ...@@ -2,8 +2,8 @@
"name": "todomvc-backbone", "name": "todomvc-backbone",
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"backbone": "~1.1.0", "backbone": "~1.1.1",
"underscore": "~1.5.0", "underscore": "~1.6.0",
"jquery": "~2.0.0", "jquery": "~2.0.0",
"todomvc-common": "~0.1.4", "todomvc-common": "~0.1.4",
"backbone.localStorage": "~1.1.0" "backbone.localStorage": "~1.1.0"
......
// Backbone.js 1.1.0 // Backbone.js 1.1.1
// (c) 2010-2011 Jeremy Ashkenas, DocumentCloud Inc. // (c) 2010-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
// (c) 2011-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
// Backbone may be freely distributed under the MIT license. // Backbone may be freely distributed under the MIT license.
// For all details and documentation: // For all details and documentation:
// http://backbonejs.org // 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'), $;
try { $ = require('jquery'); } catch(e) {}
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 // 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 // Save the previous value of the `Backbone` variable, so that it can be
// restored later on, if `noConflict` is used. // restored later on, if `noConflict` is used.
var previousBackbone = root.Backbone; var previousBackbone = root.Backbone;
...@@ -25,25 +41,12 @@ ...@@ -25,25 +41,12 @@
var slice = array.slice; var slice = array.slice;
var splice = array.splice; 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`. // Current version of the library. Keep in sync with `package.json`.
Backbone.VERSION = '1.1.0'; Backbone.VERSION = '1.1.1';
// 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 // For Backbone's purposes, jQuery, Zepto, Ender, or My Library (kidding) owns
// the `$` variable. // the `$` variable.
Backbone.$ = root.jQuery || root.Zepto || root.ender || root.$; Backbone.$ = $;
// Runs Backbone.js in *noConflict* mode, returning the `Backbone` variable // Runs Backbone.js in *noConflict* mode, returning the `Backbone` variable
// to its previous owner. Returns a reference to this Backbone object. // to its previous owner. Returns a reference to this Backbone object.
...@@ -109,7 +112,7 @@ ...@@ -109,7 +112,7 @@
var retain, ev, events, names, i, l, j, k; var retain, ev, events, names, i, l, j, k;
if (!this._events || !eventsApi(this, 'off', name, [callback, context])) return this; if (!this._events || !eventsApi(this, 'off', name, [callback, context])) return this;
if (!name && !callback && !context) { if (!name && !callback && !context) {
this._events = {}; this._events = void 0;
return this; return this;
} }
names = name ? [name] : _.keys(this._events); names = name ? [name] : _.keys(this._events);
...@@ -205,7 +208,7 @@ ...@@ -205,7 +208,7 @@
case 1: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1); 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 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; 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;
} }
}; };
...@@ -350,7 +353,7 @@ ...@@ -350,7 +353,7 @@
// Trigger all relevant attribute changes. // Trigger all relevant attribute changes.
if (!silent) { if (!silent) {
if (changes.length) this._pending = true; if (changes.length) this._pending = options;
for (var i = 0, l = changes.length; i < l; i++) { for (var i = 0, l = changes.length; i < l; i++) {
this.trigger('change:' + changes[i], this, current[changes[i]], options); this.trigger('change:' + changes[i], this, current[changes[i]], options);
} }
...@@ -361,6 +364,7 @@ ...@@ -361,6 +364,7 @@
if (changing) return this; if (changing) return this;
if (!silent) { if (!silent) {
while (this._pending) { while (this._pending) {
options = this._pending;
this._pending = false; this._pending = false;
this.trigger('change', this, options); this.trigger('change', this, options);
} }
...@@ -528,9 +532,12 @@ ...@@ -528,9 +532,12 @@
// using Backbone's restful methods, override this to change the endpoint // using Backbone's restful methods, override this to change the endpoint
// that will be called. // that will be called.
url: function() { 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; 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 // **parse** converts a response into the hash of attributes to be `set` on
...@@ -546,7 +553,7 @@ ...@@ -546,7 +553,7 @@
// A model is new if it has never been saved to the server, and lacks an id. // A model is new if it has never been saved to the server, and lacks an id.
isNew: function() { isNew: function() {
return this.id == null; return !this.has(this.idAttribute);
}, },
// Check if the model is currently in a valid state. // Check if the model is currently in a valid state.
...@@ -650,7 +657,7 @@ ...@@ -650,7 +657,7 @@
options.index = index; options.index = index;
model.trigger('remove', model, this, options); model.trigger('remove', model, this, options);
} }
this._removeReference(model); this._removeReference(model, options);
} }
return singular ? models[0] : models; return singular ? models[0] : models;
}, },
...@@ -676,11 +683,11 @@ ...@@ -676,11 +683,11 @@
// Turn bare objects into model references, and prevent invalid models // Turn bare objects into model references, and prevent invalid models
// from being added. // from being added.
for (i = 0, l = models.length; i < l; i++) { for (i = 0, l = models.length; i < l; i++) {
attrs = models[i]; attrs = models[i] || {};
if (attrs instanceof Model) { if (attrs instanceof Model) {
id = model = attrs; id = model = attrs;
} else { } else {
id = attrs[targetModel.prototype.idAttribute]; id = attrs[targetModel.prototype.idAttribute || 'id'];
} }
// If a duplicate is found, prevent it from being added and // If a duplicate is found, prevent it from being added and
...@@ -700,14 +707,13 @@ ...@@ -700,14 +707,13 @@
model = models[i] = this._prepareModel(attrs, options); model = models[i] = this._prepareModel(attrs, options);
if (!model) continue; if (!model) continue;
toAdd.push(model); toAdd.push(model);
this._addReference(model, options);
// 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;
} }
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. // Remove nonexistent models if appropriate.
...@@ -757,7 +763,7 @@ ...@@ -757,7 +763,7 @@
reset: function(models, options) { reset: function(models, options) {
options || (options = {}); options || (options = {});
for (var i = 0, l = this.models.length; i < l; i++) { 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; options.previousModels = this.models;
this._reset(); this._reset();
...@@ -798,7 +804,7 @@ ...@@ -798,7 +804,7 @@
// Get a model from the set by id. // Get a model from the set by id.
get: function(obj) { get: function(obj) {
if (obj == null) return void 0; 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. // Get the model at the given index.
...@@ -874,7 +880,7 @@ ...@@ -874,7 +880,7 @@
if (!options.wait) this.add(model, options); if (!options.wait) this.add(model, options);
var collection = this; var collection = this;
var success = options.success; var success = options.success;
options.success = function(model, resp, options) { options.success = function(model, resp) {
if (options.wait) collection.add(model, options); if (options.wait) collection.add(model, options);
if (success) success(model, resp, options); if (success) success(model, resp, options);
}; };
...@@ -904,10 +910,7 @@ ...@@ -904,10 +910,7 @@
// Prepare a hash of attributes (or other model) to be added to this // Prepare a hash of attributes (or other model) to be added to this
// collection. // collection.
_prepareModel: function(attrs, options) { _prepareModel: function(attrs, options) {
if (attrs instanceof Model) { if (attrs instanceof Model) return attrs;
if (!attrs.collection) attrs.collection = this;
return attrs;
}
options = options ? _.clone(options) : {}; options = options ? _.clone(options) : {};
options.collection = this; options.collection = this;
var model = new this.model(attrs, options); var model = new this.model(attrs, options);
...@@ -916,8 +919,16 @@ ...@@ -916,8 +919,16 @@
return false; 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. // 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; if (this === model.collection) delete model.collection;
model.off('all', this._onModelEvent, this); model.off('all', this._onModelEvent, this);
}, },
...@@ -946,7 +957,7 @@ ...@@ -946,7 +957,7 @@
'reject', 'every', 'all', 'some', 'any', 'include', 'contains', 'invoke', 'reject', 'every', 'all', 'some', 'any', 'include', 'contains', 'invoke',
'max', 'min', 'toArray', 'size', 'first', 'head', 'take', 'initial', 'rest', 'max', 'min', 'toArray', 'size', 'first', 'head', 'take', 'initial', 'rest',
'tail', 'drop', 'last', 'without', 'difference', 'indexOf', 'shuffle', '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`. // Mix in each Underscore method as a proxy to `Collection#models`.
_.each(methods, function(method) { _.each(methods, function(method) {
...@@ -958,7 +969,7 @@ ...@@ -958,7 +969,7 @@
}); });
// Underscore methods that take a property name as an argument. // 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. // Use attributes instead of properties.
_.each(attributeMethods, function(method) { _.each(attributeMethods, function(method) {
...@@ -1180,7 +1191,9 @@ ...@@ -1180,7 +1191,9 @@
return xhr; 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. // Map from CRUD to HTTP for our default `Backbone.sync` implementation.
var methodMap = { var methodMap = {
...@@ -1239,7 +1252,7 @@ ...@@ -1239,7 +1252,7 @@
var router = this; var router = this;
Backbone.history.route(route, function(fragment) { Backbone.history.route(route, function(fragment) {
var args = router._extractParameters(route, 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.apply(router, ['route:' + name].concat(args));
router.trigger('route', name, args); router.trigger('route', name, args);
Backbone.history.trigger('route', router, name, args); Backbone.history.trigger('route', router, name, args);
...@@ -1247,6 +1260,12 @@ ...@@ -1247,6 +1260,12 @@
return this; 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. // Simple proxy to `Backbone.history` to save a fragment into the history.
navigate: function(fragment, options) { navigate: function(fragment, options) {
Backbone.history.navigate(fragment, options); Backbone.history.navigate(fragment, options);
...@@ -1271,10 +1290,10 @@ ...@@ -1271,10 +1290,10 @@
route = route.replace(escapeRegExp, '\\$&') route = route.replace(escapeRegExp, '\\$&')
.replace(optionalParam, '(?:$1)?') .replace(optionalParam, '(?:$1)?')
.replace(namedParam, function(match, optional) { .replace(namedParam, function(match, optional) {
return optional ? match : '([^\/]+)'; return optional ? match : '([^/?]+)';
}) })
.replace(splatParam, '(.*?)'); .replace(splatParam, '([^?]*?)');
return new RegExp('^' + route + '$'); return new RegExp('^' + route + '(?:\\?(.*))?$');
}, },
// Given a route, and a URL fragment that it matches, return the array of // Given a route, and a URL fragment that it matches, return the array of
...@@ -1282,7 +1301,9 @@ ...@@ -1282,7 +1301,9 @@
// treated as `null` to normalize cross-browser behavior. // treated as `null` to normalize cross-browser behavior.
_extractParameters: function(route, fragment) { _extractParameters: function(route, fragment) {
var params = route.exec(fragment).slice(1); 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; return param ? decodeURIComponent(param) : null;
}); });
} }
...@@ -1320,8 +1341,8 @@ ...@@ -1320,8 +1341,8 @@
// Cached regex for removing a trailing slash. // Cached regex for removing a trailing slash.
var trailingSlash = /\/$/; var trailingSlash = /\/$/;
// Cached regex for stripping urls of hash and query. // Cached regex for stripping urls of hash.
var pathStripper = /[?#].*$/; var pathStripper = /#.*$/;
// Has the history handling already been started? // Has the history handling already been started?
History.started = false; History.started = false;
...@@ -1333,6 +1354,11 @@ ...@@ -1333,6 +1354,11 @@
// twenty times a second. // twenty times a second.
interval: 50, 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 // Gets the true hash value. Cannot use location.hash directly due to bug
// in Firefox where location.hash will always be decoded. // in Firefox where location.hash will always be decoded.
getHash: function(window) { getHash: function(window) {
...@@ -1345,7 +1371,7 @@ ...@@ -1345,7 +1371,7 @@
getFragment: function(fragment, forcePushState) { getFragment: function(fragment, forcePushState) {
if (fragment == null) { if (fragment == null) {
if (this._hasPushState || !this._wantsHashChange || forcePushState) { if (this._hasPushState || !this._wantsHashChange || forcePushState) {
fragment = this.location.pathname; fragment = decodeURI(this.location.pathname + this.location.search);
var root = this.root.replace(trailingSlash, ''); var root = this.root.replace(trailingSlash, '');
if (!fragment.indexOf(root)) fragment = fragment.slice(root.length); if (!fragment.indexOf(root)) fragment = fragment.slice(root.length);
} else { } else {
...@@ -1376,7 +1402,8 @@ ...@@ -1376,7 +1402,8 @@
this.root = ('/' + this.root + '/').replace(rootStripper, '/'); this.root = ('/' + this.root + '/').replace(rootStripper, '/');
if (oldIE && this._wantsHashChange) { 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); this.navigate(fragment);
} }
...@@ -1394,7 +1421,6 @@ ...@@ -1394,7 +1421,6 @@
// opened by a non-pushState browser. // opened by a non-pushState browser.
this.fragment = fragment; this.fragment = fragment;
var loc = this.location; var loc = this.location;
var atRoot = loc.pathname.replace(/[^\/]$/, '$&/') === this.root;
// Transition from hashChange to pushState or vice versa if both are // Transition from hashChange to pushState or vice versa if both are
// requested. // requested.
...@@ -1402,17 +1428,17 @@ ...@@ -1402,17 +1428,17 @@
// If we've started off with a route from a `pushState`-enabled // 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... // 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.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 immediately as browser will do redirect to new url
return true; return true;
// Or if we've started out with a hash-based route, but we're currently // 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... // 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.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);
} }
} }
...@@ -1472,7 +1498,7 @@ ...@@ -1472,7 +1498,7 @@
var url = this.root + (fragment = this.getFragment(fragment || '')); 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, ''); fragment = fragment.replace(pathStripper, '');
if (this.fragment === fragment) return; if (this.fragment === fragment) return;
...@@ -1578,4 +1604,6 @@ ...@@ -1578,4 +1604,6 @@
}; };
}; };
}).call(this); return Backbone;
}));
// Underscore.js 1.5.2 // Underscore.js 1.6.0
// http://underscorejs.org // http://underscorejs.org
// (c) 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors // (c) 2009-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
// Underscore may be freely distributed under the MIT license. // Underscore may be freely distributed under the MIT license.
(function() { (function() {
...@@ -65,7 +65,7 @@ ...@@ -65,7 +65,7 @@
} }
// Current version. // Current version.
_.VERSION = '1.5.2'; _.VERSION = '1.6.0';
// Collection Functions // Collection Functions
// -------------------- // --------------------
...@@ -74,7 +74,7 @@ ...@@ -74,7 +74,7 @@
// Handles objects with the built-in `forEach`, arrays, and raw objects. // Handles objects with the built-in `forEach`, arrays, and raw objects.
// Delegates to **ECMAScript 5**'s native `forEach` if available. // Delegates to **ECMAScript 5**'s native `forEach` if available.
var each = _.each = _.forEach = function(obj, iterator, context) { var each = _.each = _.forEach = function(obj, iterator, context) {
if (obj == null) return; if (obj == null) return obj;
if (nativeForEach && obj.forEach === nativeForEach) { if (nativeForEach && obj.forEach === nativeForEach) {
obj.forEach(iterator, context); obj.forEach(iterator, context);
} else if (obj.length === +obj.length) { } else if (obj.length === +obj.length) {
...@@ -87,6 +87,7 @@ ...@@ -87,6 +87,7 @@
if (iterator.call(context, obj[keys[i]], keys[i], obj) === breaker) return; if (iterator.call(context, obj[keys[i]], keys[i], obj) === breaker) return;
} }
} }
return obj;
}; };
// Return the results of applying the iterator to each element. // Return the results of applying the iterator to each element.
...@@ -152,10 +153,10 @@ ...@@ -152,10 +153,10 @@
}; };
// Return the first value which passes a truth test. Aliased as `detect`. // Return the first value which passes a truth test. Aliased as `detect`.
_.find = _.detect = function(obj, iterator, context) { _.find = _.detect = function(obj, predicate, context) {
var result; var result;
any(obj, function(value, index, list) { any(obj, function(value, index, list) {
if (iterator.call(context, value, index, list)) { if (predicate.call(context, value, index, list)) {
result = value; result = value;
return true; return true;
} }
...@@ -166,33 +167,33 @@ ...@@ -166,33 +167,33 @@
// Return all the elements that pass a truth test. // Return all the elements that pass a truth test.
// Delegates to **ECMAScript 5**'s native `filter` if available. // Delegates to **ECMAScript 5**'s native `filter` if available.
// Aliased as `select`. // Aliased as `select`.
_.filter = _.select = function(obj, iterator, context) { _.filter = _.select = function(obj, predicate, context) {
var results = []; var results = [];
if (obj == null) return results; if (obj == null) return results;
if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context); if (nativeFilter && obj.filter === nativeFilter) return obj.filter(predicate, context);
each(obj, function(value, index, list) { each(obj, function(value, index, list) {
if (iterator.call(context, value, index, list)) results.push(value); if (predicate.call(context, value, index, list)) results.push(value);
}); });
return results; return results;
}; };
// Return all the elements for which a truth test fails. // Return all the elements for which a truth test fails.
_.reject = function(obj, iterator, context) { _.reject = function(obj, predicate, context) {
return _.filter(obj, function(value, index, list) { return _.filter(obj, function(value, index, list) {
return !iterator.call(context, value, index, list); return !predicate.call(context, value, index, list);
}, context); }, context);
}; };
// Determine whether all of the elements match a truth test. // Determine whether all of the elements match a truth test.
// Delegates to **ECMAScript 5**'s native `every` if available. // Delegates to **ECMAScript 5**'s native `every` if available.
// Aliased as `all`. // Aliased as `all`.
_.every = _.all = function(obj, iterator, context) { _.every = _.all = function(obj, predicate, context) {
iterator || (iterator = _.identity); predicate || (predicate = _.identity);
var result = true; var result = true;
if (obj == null) return result; if (obj == null) return result;
if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context); if (nativeEvery && obj.every === nativeEvery) return obj.every(predicate, context);
each(obj, function(value, index, list) { each(obj, function(value, index, list) {
if (!(result = result && iterator.call(context, value, index, list))) return breaker; if (!(result = result && predicate.call(context, value, index, list))) return breaker;
}); });
return !!result; return !!result;
}; };
...@@ -200,13 +201,13 @@ ...@@ -200,13 +201,13 @@
// Determine if at least one element in the object matches a truth test. // Determine if at least one element in the object matches a truth test.
// Delegates to **ECMAScript 5**'s native `some` if available. // Delegates to **ECMAScript 5**'s native `some` if available.
// Aliased as `any`. // Aliased as `any`.
var any = _.some = _.any = function(obj, iterator, context) { var any = _.some = _.any = function(obj, predicate, context) {
iterator || (iterator = _.identity); predicate || (predicate = _.identity);
var result = false; var result = false;
if (obj == null) return result; if (obj == null) return result;
if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context); if (nativeSome && obj.some === nativeSome) return obj.some(predicate, context);
each(obj, function(value, index, list) { each(obj, function(value, index, list) {
if (result || (result = iterator.call(context, value, index, list))) return breaker; if (result || (result = predicate.call(context, value, index, list))) return breaker;
}); });
return !!result; return !!result;
}; };
...@@ -232,25 +233,19 @@ ...@@ -232,25 +233,19 @@
// Convenience version of a common use case of `map`: fetching a property. // Convenience version of a common use case of `map`: fetching a property.
_.pluck = function(obj, key) { _.pluck = function(obj, key) {
return _.map(obj, function(value){ return value[key]; }); return _.map(obj, _.property(key));
}; };
// Convenience version of a common use case of `filter`: selecting only objects // Convenience version of a common use case of `filter`: selecting only objects
// containing specific `key:value` pairs. // containing specific `key:value` pairs.
_.where = function(obj, attrs, first) { _.where = function(obj, attrs) {
if (_.isEmpty(attrs)) return first ? void 0 : []; return _.filter(obj, _.matches(attrs));
return _[first ? 'find' : 'filter'](obj, function(value) {
for (var key in attrs) {
if (attrs[key] !== value[key]) return false;
}
return true;
});
}; };
// Convenience version of a common use case of `find`: getting the first object // Convenience version of a common use case of `find`: getting the first object
// containing specific `key:value` pairs. // containing specific `key:value` pairs.
_.findWhere = function(obj, attrs) { _.findWhere = function(obj, attrs) {
return _.where(obj, attrs, true); return _.find(obj, _.matches(attrs));
}; };
// Return the maximum element or (element-based computation). // Return the maximum element or (element-based computation).
...@@ -260,13 +255,15 @@ ...@@ -260,13 +255,15 @@
if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) { if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) {
return Math.max.apply(Math, obj); return Math.max.apply(Math, obj);
} }
if (!iterator && _.isEmpty(obj)) return -Infinity; var result = -Infinity, lastComputed = -Infinity;
var result = {computed : -Infinity, value: -Infinity};
each(obj, function(value, index, list) { each(obj, function(value, index, list) {
var computed = iterator ? iterator.call(context, value, index, list) : value; var computed = iterator ? iterator.call(context, value, index, list) : value;
computed > result.computed && (result = {value : value, computed : computed}); if (computed > lastComputed) {
result = value;
lastComputed = computed;
}
}); });
return result.value; return result;
}; };
// Return the minimum element (or element-based computation). // Return the minimum element (or element-based computation).
...@@ -274,13 +271,15 @@ ...@@ -274,13 +271,15 @@
if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) { if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) {
return Math.min.apply(Math, obj); return Math.min.apply(Math, obj);
} }
if (!iterator && _.isEmpty(obj)) return Infinity; var result = Infinity, lastComputed = Infinity;
var result = {computed : Infinity, value: Infinity};
each(obj, function(value, index, list) { each(obj, function(value, index, list) {
var computed = iterator ? iterator.call(context, value, index, list) : value; var computed = iterator ? iterator.call(context, value, index, list) : value;
computed < result.computed && (result = {value : value, computed : computed}); if (computed < lastComputed) {
result = value;
lastComputed = computed;
}
}); });
return result.value; return result;
}; };
// Shuffle an array, using the modern version of the // Shuffle an array, using the modern version of the
...@@ -297,11 +296,12 @@ ...@@ -297,11 +296,12 @@
return shuffled; return shuffled;
}; };
// Sample **n** random values from an array. // Sample **n** random values from a collection.
// If **n** is not specified, returns a single random element from the array. // If **n** is not specified, returns a single random element.
// The internal `guard` argument allows it to work with `map`. // The internal `guard` argument allows it to work with `map`.
_.sample = function(obj, n, guard) { _.sample = function(obj, n, guard) {
if (arguments.length < 2 || guard) { if (n == null || guard) {
if (obj.length !== +obj.length) obj = _.values(obj);
return obj[_.random(obj.length - 1)]; return obj[_.random(obj.length - 1)];
} }
return _.shuffle(obj).slice(0, Math.max(0, n)); return _.shuffle(obj).slice(0, Math.max(0, n));
...@@ -309,12 +309,14 @@ ...@@ -309,12 +309,14 @@
// An internal function to generate lookup iterators. // An internal function to generate lookup iterators.
var lookupIterator = function(value) { var lookupIterator = function(value) {
return _.isFunction(value) ? value : function(obj){ return obj[value]; }; if (value == null) return _.identity;
if (_.isFunction(value)) return value;
return _.property(value);
}; };
// Sort the object's values by a criterion produced by an iterator. // Sort the object's values by a criterion produced by an iterator.
_.sortBy = function(obj, value, context) { _.sortBy = function(obj, iterator, context) {
var iterator = lookupIterator(value); iterator = lookupIterator(iterator);
return _.pluck(_.map(obj, function(value, index, list) { return _.pluck(_.map(obj, function(value, index, list) {
return { return {
value: value, value: value,
...@@ -334,9 +336,9 @@ ...@@ -334,9 +336,9 @@
// An internal function used for aggregate "group by" operations. // An internal function used for aggregate "group by" operations.
var group = function(behavior) { var group = function(behavior) {
return function(obj, value, context) { return function(obj, iterator, context) {
var result = {}; var result = {};
var iterator = value == null ? _.identity : lookupIterator(value); iterator = lookupIterator(iterator);
each(obj, function(value, index) { each(obj, function(value, index) {
var key = iterator.call(context, value, index, obj); var key = iterator.call(context, value, index, obj);
behavior(result, key, value); behavior(result, key, value);
...@@ -348,7 +350,7 @@ ...@@ -348,7 +350,7 @@
// Groups the object's values by a criterion. Pass either a string attribute // Groups the object's values by a criterion. Pass either a string attribute
// to group by, or a function that returns the criterion. // to group by, or a function that returns the criterion.
_.groupBy = group(function(result, key, value) { _.groupBy = group(function(result, key, value) {
(_.has(result, key) ? result[key] : (result[key] = [])).push(value); _.has(result, key) ? result[key].push(value) : result[key] = [value];
}); });
// Indexes the object's values by a criterion, similar to `groupBy`, but for // Indexes the object's values by a criterion, similar to `groupBy`, but for
...@@ -367,7 +369,7 @@ ...@@ -367,7 +369,7 @@
// Use a comparator function to figure out the smallest index at which // Use a comparator function to figure out the smallest index at which
// an object should be inserted so as to maintain order. Uses binary search. // an object should be inserted so as to maintain order. Uses binary search.
_.sortedIndex = function(array, obj, iterator, context) { _.sortedIndex = function(array, obj, iterator, context) {
iterator = iterator == null ? _.identity : lookupIterator(iterator); iterator = lookupIterator(iterator);
var value = iterator.call(context, obj); var value = iterator.call(context, obj);
var low = 0, high = array.length; var low = 0, high = array.length;
while (low < high) { while (low < high) {
...@@ -399,7 +401,9 @@ ...@@ -399,7 +401,9 @@
// allows it to work with `_.map`. // allows it to work with `_.map`.
_.first = _.head = _.take = function(array, n, guard) { _.first = _.head = _.take = function(array, n, guard) {
if (array == null) return void 0; if (array == null) return void 0;
return (n == null) || guard ? array[0] : slice.call(array, 0, n); if ((n == null) || guard) return array[0];
if (n < 0) return [];
return slice.call(array, 0, n);
}; };
// Returns everything but the last entry of the array. Especially useful on // Returns everything but the last entry of the array. Especially useful on
...@@ -414,11 +418,8 @@ ...@@ -414,11 +418,8 @@
// values in the array. The **guard** check allows it to work with `_.map`. // values in the array. The **guard** check allows it to work with `_.map`.
_.last = function(array, n, guard) { _.last = function(array, n, guard) {
if (array == null) return void 0; if (array == null) return void 0;
if ((n == null) || guard) { if ((n == null) || guard) return array[array.length - 1];
return array[array.length - 1];
} else {
return slice.call(array, Math.max(array.length - n, 0)); return slice.call(array, Math.max(array.length - n, 0));
}
}; };
// Returns everything but the first entry of the array. Aliased as `tail` and `drop`. // Returns everything but the first entry of the array. Aliased as `tail` and `drop`.
...@@ -459,6 +460,16 @@ ...@@ -459,6 +460,16 @@
return _.difference(array, slice.call(arguments, 1)); return _.difference(array, slice.call(arguments, 1));
}; };
// Split an array into two arrays: one whose elements all satisfy the given
// predicate, and one whose elements all do not satisfy the predicate.
_.partition = function(array, predicate) {
var pass = [], fail = [];
each(array, function(elem) {
(predicate(elem) ? pass : fail).push(elem);
});
return [pass, fail];
};
// Produce a duplicate-free version of the array. If the array has already // Produce a duplicate-free version of the array. If the array has already
// been sorted, you have the option of using a faster algorithm. // been sorted, you have the option of using a faster algorithm.
// Aliased as `unique`. // Aliased as `unique`.
...@@ -492,7 +503,7 @@ ...@@ -492,7 +503,7 @@
var rest = slice.call(arguments, 1); var rest = slice.call(arguments, 1);
return _.filter(_.uniq(array), function(item) { return _.filter(_.uniq(array), function(item) {
return _.every(rest, function(other) { return _.every(rest, function(other) {
return _.indexOf(other, item) >= 0; return _.contains(other, item);
}); });
}); });
}; };
...@@ -507,7 +518,7 @@ ...@@ -507,7 +518,7 @@
// Zip together multiple lists into a single array -- elements that share // Zip together multiple lists into a single array -- elements that share
// an index go together. // an index go together.
_.zip = function() { _.zip = function() {
var length = _.max(_.pluck(arguments, "length").concat(0)); var length = _.max(_.pluck(arguments, 'length').concat(0));
var results = new Array(length); var results = new Array(length);
for (var i = 0; i < length; i++) { for (var i = 0; i < length; i++) {
results[i] = _.pluck(arguments, '' + i); results[i] = _.pluck(arguments, '' + i);
...@@ -613,19 +624,27 @@ ...@@ -613,19 +624,27 @@
}; };
// Partially apply a function by creating a version that has had some of its // Partially apply a function by creating a version that has had some of its
// arguments pre-filled, without changing its dynamic `this` context. // arguments pre-filled, without changing its dynamic `this` context. _ acts
// as a placeholder, allowing any combination of arguments to be pre-filled.
_.partial = function(func) { _.partial = function(func) {
var args = slice.call(arguments, 1); var boundArgs = slice.call(arguments, 1);
return function() { return function() {
return func.apply(this, args.concat(slice.call(arguments))); var position = 0;
var args = boundArgs.slice();
for (var i = 0, length = args.length; i < length; i++) {
if (args[i] === _) args[i] = arguments[position++];
}
while (position < arguments.length) args.push(arguments[position++]);
return func.apply(this, args);
}; };
}; };
// Bind all of an object's methods to that object. Useful for ensuring that // Bind a number of an object's methods to that object. Remaining arguments
// all callbacks defined on an object belong to it. // are the method names to be bound. Useful for ensuring that all callbacks
// defined on an object belong to it.
_.bindAll = function(obj) { _.bindAll = function(obj) {
var funcs = slice.call(arguments, 1); var funcs = slice.call(arguments, 1);
if (funcs.length === 0) throw new Error("bindAll must be passed function names"); if (funcs.length === 0) throw new Error('bindAll must be passed function names');
each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); }); each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); });
return obj; return obj;
}; };
...@@ -664,12 +683,13 @@ ...@@ -664,12 +683,13 @@
var previous = 0; var previous = 0;
options || (options = {}); options || (options = {});
var later = function() { var later = function() {
previous = options.leading === false ? 0 : new Date; previous = options.leading === false ? 0 : _.now();
timeout = null; timeout = null;
result = func.apply(context, args); result = func.apply(context, args);
context = args = null;
}; };
return function() { return function() {
var now = new Date; var now = _.now();
if (!previous && options.leading === false) previous = now; if (!previous && options.leading === false) previous = now;
var remaining = wait - (now - previous); var remaining = wait - (now - previous);
context = this; context = this;
...@@ -679,6 +699,7 @@ ...@@ -679,6 +699,7 @@
timeout = null; timeout = null;
previous = now; previous = now;
result = func.apply(context, args); result = func.apply(context, args);
context = args = null;
} else if (!timeout && options.trailing !== false) { } else if (!timeout && options.trailing !== false) {
timeout = setTimeout(later, remaining); timeout = setTimeout(later, remaining);
} }
...@@ -692,24 +713,33 @@ ...@@ -692,24 +713,33 @@
// leading edge, instead of the trailing. // leading edge, instead of the trailing.
_.debounce = function(func, wait, immediate) { _.debounce = function(func, wait, immediate) {
var timeout, args, context, timestamp, result; var timeout, args, context, timestamp, result;
return function() {
context = this;
args = arguments;
timestamp = new Date();
var later = function() { var later = function() {
var last = (new Date()) - timestamp; var last = _.now() - timestamp;
if (last < wait) { if (last < wait) {
timeout = setTimeout(later, wait - last); timeout = setTimeout(later, wait - last);
} else { } else {
timeout = null; timeout = null;
if (!immediate) result = func.apply(context, args); if (!immediate) {
result = func.apply(context, args);
context = args = null;
}
} }
}; };
return function() {
context = this;
args = arguments;
timestamp = _.now();
var callNow = immediate && !timeout; var callNow = immediate && !timeout;
if (!timeout) { if (!timeout) {
timeout = setTimeout(later, wait); timeout = setTimeout(later, wait);
} }
if (callNow) result = func.apply(context, args); if (callNow) {
result = func.apply(context, args);
context = args = null;
}
return result; return result;
}; };
}; };
...@@ -731,11 +761,7 @@ ...@@ -731,11 +761,7 @@
// allowing you to adjust arguments, run code before and after, and // allowing you to adjust arguments, run code before and after, and
// conditionally execute the original function. // conditionally execute the original function.
_.wrap = function(func, wrapper) { _.wrap = function(func, wrapper) {
return function() { return _.partial(wrapper, func);
var args = [func];
push.apply(args, arguments);
return wrapper.apply(this, args);
};
}; };
// Returns a function that is the composition of a list of functions, each // Returns a function that is the composition of a list of functions, each
...@@ -765,8 +791,9 @@ ...@@ -765,8 +791,9 @@
// Retrieve the names of an object's properties. // Retrieve the names of an object's properties.
// Delegates to **ECMAScript 5**'s native `Object.keys` // Delegates to **ECMAScript 5**'s native `Object.keys`
_.keys = nativeKeys || function(obj) { _.keys = function(obj) {
if (obj !== Object(obj)) throw new TypeError('Invalid object'); if (!_.isObject(obj)) return [];
if (nativeKeys) return nativeKeys(obj);
var keys = []; var keys = [];
for (var key in obj) if (_.has(obj, key)) keys.push(key); for (var key in obj) if (_.has(obj, key)) keys.push(key);
return keys; return keys;
...@@ -921,7 +948,8 @@ ...@@ -921,7 +948,8 @@
// from different frames are. // from different frames are.
var aCtor = a.constructor, bCtor = b.constructor; var aCtor = a.constructor, bCtor = b.constructor;
if (aCtor !== bCtor && !(_.isFunction(aCtor) && (aCtor instanceof aCtor) && if (aCtor !== bCtor && !(_.isFunction(aCtor) && (aCtor instanceof aCtor) &&
_.isFunction(bCtor) && (bCtor instanceof bCtor))) { _.isFunction(bCtor) && (bCtor instanceof bCtor))
&& ('constructor' in a && 'constructor' in b)) {
return false; return false;
} }
// Add the first object to the stack of traversed objects. // Add the first object to the stack of traversed objects.
...@@ -1061,6 +1089,30 @@ ...@@ -1061,6 +1089,30 @@
return value; return value;
}; };
_.constant = function(value) {
return function () {
return value;
};
};
_.property = function(key) {
return function(obj) {
return obj[key];
};
};
// Returns a predicate for checking whether an object has a given set of `key:value` pairs.
_.matches = function(attrs) {
return function(obj) {
if (obj === attrs) return true; //avoid comparing an object to itself.
for (var key in attrs) {
if (attrs[key] !== obj[key])
return false;
}
return true;
}
};
// Run a function **n** times. // Run a function **n** times.
_.times = function(n, iterator, context) { _.times = function(n, iterator, context) {
var accum = Array(Math.max(0, n)); var accum = Array(Math.max(0, n));
...@@ -1077,6 +1129,9 @@ ...@@ -1077,6 +1129,9 @@
return min + Math.floor(Math.random() * (max - min + 1)); return min + Math.floor(Math.random() * (max - min + 1));
}; };
// A (possibly faster) way to get the current timestamp as an integer.
_.now = Date.now || function() { return new Date().getTime(); };
// List of HTML entities for escaping. // List of HTML entities for escaping.
var entityMap = { var entityMap = {
escape: { escape: {
...@@ -1273,4 +1328,16 @@ ...@@ -1273,4 +1328,16 @@
}); });
// AMD registration happens at the end for compatibility with AMD loaders
// that may not enforce next-turn semantics on modules. Even though general
// practice for AMD registration is to be anonymous, underscore registers
// as a named module because, like jQuery, it is a base library that is
// popular enough to be bundled in a third party lib, but not be part of
// an AMD load request. Those cases could generate an error when an
// anonymous define() is called outside of a loader request.
if (typeof define === 'function' && define.amd) {
define('underscore', [], function() {
return _;
});
}
}).call(this); }).call(this);
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment