Commit e80de6d1 authored by Pascal Hartig's avatar Pascal Hartig

Backbone: Update jQuery, localStorage plugin

parent f6786e58
...@@ -3,8 +3,8 @@ ...@@ -3,8 +3,8 @@
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"backbone": "~1.1.1", "backbone": "~1.1.1",
"underscore": "~1.6.0", "underscore": "~1.7.0",
"jquery": "~2.0.0", "jquery": "~2.1.0",
"todomvc-common": "~0.3.0", "todomvc-common": "~0.3.0",
"backbone.localStorage": "~1.1.0" "backbone.localStorage": "~1.1.0"
} }
......
/** /**
* Backbone localStorage Adapter * Backbone localStorage Adapter
* Version 1.1.7 * Version 1.1.14
* *
* https://github.com/jeromegn/Backbone.localStorage * https://github.com/jeromegn/Backbone.localStorage
*/ */
(function (root, factory) { (function (root, factory) {
if (typeof exports === 'object' && typeof require === 'function') { if (typeof exports === 'object' && typeof require === 'function') {
module.exports = factory(require("underscore"), require("backbone")); module.exports = factory(require("backbone"));
} else if (typeof define === "function" && define.amd) { } else if (typeof define === "function" && define.amd) {
// AMD. Register as an anonymous module. // AMD. Register as an anonymous module.
define(["underscore","backbone"], function(_, Backbone) { define(["backbone"], function(Backbone) {
// Use global variables if the locals are undefined. // Use global variables if the locals are undefined.
return factory(_ || root._, Backbone || root.Backbone); return factory(Backbone || root.Backbone);
}); });
} else { } else {
// RequireJS isn't being used. Assume underscore and backbone are loaded in <script> tags factory(Backbone);
factory(_, Backbone); }
} }(this, function(Backbone) {
}(this, function(_, Backbone) {
// A simple module to replace `Backbone.sync` with *localStorage*-based // A simple module to replace `Backbone.sync` with *localStorage*-based
// persistence. Models are given GUIDS, and saved into a JSON object. Simple // persistence. Models are given GUIDS, and saved into a JSON object. Simple
// as that. // as that.
// Hold reference to Underscore.js and Backbone.js in the closure in order
// to make things work even if they are removed from the global namespace
// Generate four random hex digits. // Generate four random hex digits.
function S4() { function S4() {
return (((1+Math.random())*0x10000)|0).toString(16).substring(1); return (((1+Math.random())*0x10000)|0).toString(16).substring(1);
...@@ -35,19 +31,49 @@ function guid() { ...@@ -35,19 +31,49 @@ function guid() {
return (S4()+S4()+"-"+S4()+"-"+S4()+"-"+S4()+"-"+S4()+S4()+S4()); return (S4()+S4()+"-"+S4()+"-"+S4()+"-"+S4()+"-"+S4()+S4()+S4());
}; };
function isObject(item) {
return item === Object(item);
}
function contains(array, item) {
var i = array.length;
while (i--) if (array[i] === item) return true;
return false;
}
function extend(obj, props) {
for (var key in props) obj[key] = props[key]
return obj;
}
function result(object, property) {
if (object == null) return void 0;
var value = object[property];
return (typeof value === 'function') ? object[property]() : value;
}
// Our Store is represented by a single JS object in *localStorage*. Create it // Our Store is represented by a single JS object in *localStorage*. Create it
// with a meaningful name, like the name you'd give a table. // with a meaningful name, like the name you'd give a table.
// window.Store is deprectated, use Backbone.LocalStorage instead // window.Store is deprectated, use Backbone.LocalStorage instead
Backbone.LocalStorage = window.Store = function(name) { Backbone.LocalStorage = window.Store = function(name, serializer) {
if( !this.localStorage ) { if( !this.localStorage ) {
throw "Backbone.localStorage: Environment does not support localStorage." throw "Backbone.localStorage: Environment does not support localStorage."
} }
this.name = name; this.name = name;
this.serializer = serializer || {
serialize: function(item) {
return isObject(item) ? JSON.stringify(item) : item;
},
// fix for "illegal access" error on Android when JSON.parse is passed null
deserialize: function (data) {
return data && JSON.parse(data);
}
};
var store = this.localStorage().getItem(this.name); var store = this.localStorage().getItem(this.name);
this.records = (store && store.split(",")) || []; this.records = (store && store.split(",")) || [];
}; };
_.extend(Backbone.LocalStorage.prototype, { extend(Backbone.LocalStorage.prototype, {
// Save the current state of the **Store** to *localStorage*. // Save the current state of the **Store** to *localStorage*.
save: function() { save: function() {
...@@ -57,11 +83,11 @@ _.extend(Backbone.LocalStorage.prototype, { ...@@ -57,11 +83,11 @@ _.extend(Backbone.LocalStorage.prototype, {
// Add a model, giving it a (hopefully)-unique GUID, if it doesn't already // Add a model, giving it a (hopefully)-unique GUID, if it doesn't already
// have an id of it's own. // have an id of it's own.
create: function(model) { create: function(model) {
if (!model.id) { if (!model.id && model.id !== 0) {
model.id = guid(); model.id = guid();
model.set(model.idAttribute, model.id); model.set(model.idAttribute, model.id);
} }
this.localStorage().setItem(this.name+"-"+model.id, JSON.stringify(model)); this.localStorage().setItem(this._itemName(model.id), this.serializer.serialize(model));
this.records.push(model.id.toString()); this.records.push(model.id.toString());
this.save(); this.save();
return this.find(model); return this.find(model);
...@@ -69,36 +95,40 @@ _.extend(Backbone.LocalStorage.prototype, { ...@@ -69,36 +95,40 @@ _.extend(Backbone.LocalStorage.prototype, {
// Update a model by replacing its copy in `this.data`. // Update a model by replacing its copy in `this.data`.
update: function(model) { update: function(model) {
this.localStorage().setItem(this.name+"-"+model.id, JSON.stringify(model)); this.localStorage().setItem(this._itemName(model.id), this.serializer.serialize(model));
if (!_.include(this.records, model.id.toString())) var modelId = model.id.toString();
this.records.push(model.id.toString()); this.save(); if (!contains(this.records, modelId)) {
this.records.push(modelId);
this.save();
}
return this.find(model); return this.find(model);
}, },
// Retrieve a model from `this.data` by id. // Retrieve a model from `this.data` by id.
find: function(model) { find: function(model) {
return this.jsonData(this.localStorage().getItem(this.name+"-"+model.id)); return this.serializer.deserialize(this.localStorage().getItem(this._itemName(model.id)));
}, },
// Return the array of all models currently in storage. // Return the array of all models currently in storage.
findAll: function() { findAll: function() {
// Lodash removed _#chain in v1.0.0-rc.1 var result = [];
return (_.chain || _)(this.records) for (var i = 0, id, data; i < this.records.length; i++) {
.map(function(id){ id = this.records[i];
return this.jsonData(this.localStorage().getItem(this.name+"-"+id)); data = this.serializer.deserialize(this.localStorage().getItem(this._itemName(id)));
}, this) if (data != null) result.push(data);
.compact() }
.value(); return result;
}, },
// Delete a model from `this.data`, returning it. // Delete a model from `this.data`, returning it.
destroy: function(model) { destroy: function(model) {
if (model.isNew()) this.localStorage().removeItem(this._itemName(model.id));
return false var modelId = model.id.toString();
this.localStorage().removeItem(this.name+"-"+model.id); for (var i = 0, id; i < this.records.length; i++) {
this.records = _.reject(this.records, function(id){ if (this.records[i] === modelId) {
return id === model.id.toString(); this.records.splice(i, 1);
}); }
}
this.save(); this.save();
return model; return model;
}, },
...@@ -107,11 +137,6 @@ _.extend(Backbone.LocalStorage.prototype, { ...@@ -107,11 +137,6 @@ _.extend(Backbone.LocalStorage.prototype, {
return localStorage; return localStorage;
}, },
// fix for "illegal access" error on Android when JSON.parse is passed null
jsonData: function (data) {
return data && JSON.parse(data);
},
// Clear localStorage for specific collection. // Clear localStorage for specific collection.
_clear: function() { _clear: function() {
var local = this.localStorage(), var local = this.localStorage(),
...@@ -120,11 +145,12 @@ _.extend(Backbone.LocalStorage.prototype, { ...@@ -120,11 +145,12 @@ _.extend(Backbone.LocalStorage.prototype, {
// Remove id-tracking item (e.g., "foo"). // Remove id-tracking item (e.g., "foo").
local.removeItem(this.name); local.removeItem(this.name);
// Lodash removed _#chain in v1.0.0-rc.1
// Match all data items (e.g., "foo-ID") and remove. // Match all data items (e.g., "foo-ID") and remove.
(_.chain || _)(local).keys() for (var k in local) {
.filter(function (k) { return itemRe.test(k); }) if (itemRe.test(k)) {
.each(function (k) { local.removeItem(k); }); local.removeItem(k);
}
}
this.records.length = 0; this.records.length = 0;
}, },
...@@ -132,6 +158,10 @@ _.extend(Backbone.LocalStorage.prototype, { ...@@ -132,6 +158,10 @@ _.extend(Backbone.LocalStorage.prototype, {
// Size of localStorage. // Size of localStorage.
_storageSize: function() { _storageSize: function() {
return this.localStorage().length; return this.localStorage().length;
},
_itemName: function(id) {
return this.name+"-"+id;
} }
}); });
...@@ -140,9 +170,13 @@ _.extend(Backbone.LocalStorage.prototype, { ...@@ -140,9 +170,13 @@ _.extend(Backbone.LocalStorage.prototype, {
// *localStorage* property, which should be an instance of `Store`. // *localStorage* property, which should be an instance of `Store`.
// window.Store.sync and Backbone.localSync is deprecated, use Backbone.LocalStorage.sync instead // window.Store.sync and Backbone.localSync is deprecated, use Backbone.LocalStorage.sync instead
Backbone.LocalStorage.sync = window.Store.sync = Backbone.localSync = function(method, model, options) { Backbone.LocalStorage.sync = window.Store.sync = Backbone.localSync = function(method, model, options) {
var store = model.localStorage || model.collection.localStorage; var store = result(model, 'localStorage') || result(model.collection, 'localStorage');
var resp, errorMessage, syncDfd = Backbone.$.Deferred && Backbone.$.Deferred(); //If $ is having Deferred - use it. var resp, errorMessage;
//If $ is having Deferred - use it.
var syncDfd = Backbone.$ ?
(Backbone.$.Deferred && Backbone.$.Deferred()) :
(Backbone.Deferred && Backbone.Deferred());
try { try {
......
This source diff could not be displayed because it is too large. You can view the blob instead.
// Underscore.js 1.6.0 // Underscore.js 1.7.0
// http://underscorejs.org // http://underscorejs.org
// (c) 2009-2014 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.
...@@ -14,9 +14,6 @@ ...@@ -14,9 +14,6 @@
// Save the previous value of the `_` variable. // Save the previous value of the `_` variable.
var previousUnderscore = root._; var previousUnderscore = root._;
// Establish the object that gets returned to break out of a loop iteration.
var breaker = {};
// Save bytes in the minified (but not gzipped) version: // Save bytes in the minified (but not gzipped) version:
var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype; var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype;
...@@ -31,15 +28,6 @@ ...@@ -31,15 +28,6 @@
// All **ECMAScript 5** native function implementations that we hope to use // All **ECMAScript 5** native function implementations that we hope to use
// are declared here. // are declared here.
var var
nativeForEach = ArrayProto.forEach,
nativeMap = ArrayProto.map,
nativeReduce = ArrayProto.reduce,
nativeReduceRight = ArrayProto.reduceRight,
nativeFilter = ArrayProto.filter,
nativeEvery = ArrayProto.every,
nativeSome = ArrayProto.some,
nativeIndexOf = ArrayProto.indexOf,
nativeLastIndexOf = ArrayProto.lastIndexOf,
nativeIsArray = Array.isArray, nativeIsArray = Array.isArray,
nativeKeys = Object.keys, nativeKeys = Object.keys,
nativeBind = FuncProto.bind; nativeBind = FuncProto.bind;
...@@ -53,8 +41,7 @@ ...@@ -53,8 +41,7 @@
// Export the Underscore object for **Node.js**, with // Export the Underscore object for **Node.js**, with
// backwards-compatibility for the old `require()` API. If we're in // backwards-compatibility for the old `require()` API. If we're in
// the browser, add `_` as a global object via a string identifier, // the browser, add `_` as a global object.
// for Closure Compiler "advanced" mode.
if (typeof exports !== 'undefined') { if (typeof exports !== 'undefined') {
if (typeof module !== 'undefined' && module.exports) { if (typeof module !== 'undefined' && module.exports) {
exports = module.exports = _; exports = module.exports = _;
...@@ -65,98 +52,125 @@ ...@@ -65,98 +52,125 @@
} }
// Current version. // Current version.
_.VERSION = '1.6.0'; _.VERSION = '1.7.0';
// Internal function that returns an efficient (for current engines) version
// of the passed-in callback, to be repeatedly applied in other Underscore
// functions.
var createCallback = function(func, context, argCount) {
if (context === void 0) return func;
switch (argCount == null ? 3 : argCount) {
case 1: return function(value) {
return func.call(context, value);
};
case 2: return function(value, other) {
return func.call(context, value, other);
};
case 3: return function(value, index, collection) {
return func.call(context, value, index, collection);
};
case 4: return function(accumulator, value, index, collection) {
return func.call(context, accumulator, value, index, collection);
};
}
return function() {
return func.apply(context, arguments);
};
};
// A mostly-internal function to generate callbacks that can be applied
// to each element in a collection, returning the desired result — either
// identity, an arbitrary callback, a property matcher, or a property accessor.
_.iteratee = function(value, context, argCount) {
if (value == null) return _.identity;
if (_.isFunction(value)) return createCallback(value, context, argCount);
if (_.isObject(value)) return _.matches(value);
return _.property(value);
};
// Collection Functions // Collection Functions
// -------------------- // --------------------
// The cornerstone, an `each` implementation, aka `forEach`. // The cornerstone, an `each` implementation, aka `forEach`.
// Handles objects with the built-in `forEach`, arrays, and raw objects. // Handles raw objects in addition to array-likes. Treats all
// Delegates to **ECMAScript 5**'s native `forEach` if available. // sparse array-likes as if they were dense.
var each = _.each = _.forEach = function(obj, iterator, context) { _.each = _.forEach = function(obj, iteratee, context) {
if (obj == null) return obj; if (obj == null) return obj;
if (nativeForEach && obj.forEach === nativeForEach) { iteratee = createCallback(iteratee, context);
obj.forEach(iterator, context); var i, length = obj.length;
} else if (obj.length === +obj.length) { if (length === +length) {
for (var i = 0, length = obj.length; i < length; i++) { for (i = 0; i < length; i++) {
if (iterator.call(context, obj[i], i, obj) === breaker) return; iteratee(obj[i], i, obj);
} }
} else { } else {
var keys = _.keys(obj); var keys = _.keys(obj);
for (var i = 0, length = keys.length; i < length; i++) { for (i = 0, length = keys.length; i < length; i++) {
if (iterator.call(context, obj[keys[i]], keys[i], obj) === breaker) return; iteratee(obj[keys[i]], keys[i], obj);
} }
} }
return obj; return obj;
}; };
// Return the results of applying the iterator to each element. // Return the results of applying the iteratee to each element.
// Delegates to **ECMAScript 5**'s native `map` if available. _.map = _.collect = function(obj, iteratee, context) {
_.map = _.collect = function(obj, iterator, context) { if (obj == null) return [];
var results = []; iteratee = _.iteratee(iteratee, context);
if (obj == null) return results; var keys = obj.length !== +obj.length && _.keys(obj),
if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context); length = (keys || obj).length,
each(obj, function(value, index, list) { results = Array(length),
results.push(iterator.call(context, value, index, list)); currentKey;
}); for (var index = 0; index < length; index++) {
currentKey = keys ? keys[index] : index;
results[index] = iteratee(obj[currentKey], currentKey, obj);
}
return results; return results;
}; };
var reduceError = 'Reduce of empty array with no initial value'; var reduceError = 'Reduce of empty array with no initial value';
// **Reduce** builds up a single result from a list of values, aka `inject`, // **Reduce** builds up a single result from a list of values, aka `inject`,
// or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available. // or `foldl`.
_.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) { _.reduce = _.foldl = _.inject = function(obj, iteratee, memo, context) {
var initial = arguments.length > 2;
if (obj == null) obj = []; if (obj == null) obj = [];
if (nativeReduce && obj.reduce === nativeReduce) { iteratee = createCallback(iteratee, context, 4);
if (context) iterator = _.bind(iterator, context); var keys = obj.length !== +obj.length && _.keys(obj),
return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator); length = (keys || obj).length,
index = 0, currentKey;
if (arguments.length < 3) {
if (!length) throw new TypeError(reduceError);
memo = obj[keys ? keys[index++] : index++];
}
for (; index < length; index++) {
currentKey = keys ? keys[index] : index;
memo = iteratee(memo, obj[currentKey], currentKey, obj);
} }
each(obj, function(value, index, list) {
if (!initial) {
memo = value;
initial = true;
} else {
memo = iterator.call(context, memo, value, index, list);
}
});
if (!initial) throw new TypeError(reduceError);
return memo; return memo;
}; };
// The right-associative version of reduce, also known as `foldr`. // The right-associative version of reduce, also known as `foldr`.
// Delegates to **ECMAScript 5**'s native `reduceRight` if available. _.reduceRight = _.foldr = function(obj, iteratee, memo, context) {
_.reduceRight = _.foldr = function(obj, iterator, memo, context) {
var initial = arguments.length > 2;
if (obj == null) obj = []; if (obj == null) obj = [];
if (nativeReduceRight && obj.reduceRight === nativeReduceRight) { iteratee = createCallback(iteratee, context, 4);
if (context) iterator = _.bind(iterator, context); var keys = obj.length !== + obj.length && _.keys(obj),
return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator); index = (keys || obj).length,
currentKey;
if (arguments.length < 3) {
if (!index) throw new TypeError(reduceError);
memo = obj[keys ? keys[--index] : --index];
} }
var length = obj.length; while (index--) {
if (length !== +length) { currentKey = keys ? keys[index] : index;
var keys = _.keys(obj); memo = iteratee(memo, obj[currentKey], currentKey, obj);
length = keys.length;
} }
each(obj, function(value, index, list) {
index = keys ? keys[--length] : --length;
if (!initial) {
memo = obj[index];
initial = true;
} else {
memo = iterator.call(context, memo, obj[index], index, list);
}
});
if (!initial) throw new TypeError(reduceError);
return memo; return memo;
}; };
// 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, predicate, context) { _.find = _.detect = function(obj, predicate, context) {
var result; var result;
any(obj, function(value, index, list) { predicate = _.iteratee(predicate, context);
if (predicate.call(context, value, index, list)) { _.some(obj, function(value, index, list) {
if (predicate(value, index, list)) {
result = value; result = value;
return true; return true;
} }
...@@ -165,61 +179,58 @@ ...@@ -165,61 +179,58 @@
}; };
// 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.
// Aliased as `select`. // Aliased as `select`.
_.filter = _.select = function(obj, predicate, 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(predicate, context); predicate = _.iteratee(predicate, context);
each(obj, function(value, index, list) { _.each(obj, function(value, index, list) {
if (predicate.call(context, value, index, list)) results.push(value); if (predicate(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, predicate, context) { _.reject = function(obj, predicate, context) {
return _.filter(obj, function(value, index, list) { return _.filter(obj, _.negate(_.iteratee(predicate)), context);
return !predicate.call(context, value, index, list);
}, 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.
// Aliased as `all`. // Aliased as `all`.
_.every = _.all = function(obj, predicate, context) { _.every = _.all = function(obj, predicate, context) {
predicate || (predicate = _.identity); if (obj == null) return true;
var result = true; predicate = _.iteratee(predicate, context);
if (obj == null) return result; var keys = obj.length !== +obj.length && _.keys(obj),
if (nativeEvery && obj.every === nativeEvery) return obj.every(predicate, context); length = (keys || obj).length,
each(obj, function(value, index, list) { index, currentKey;
if (!(result = result && predicate.call(context, value, index, list))) return breaker; for (index = 0; index < length; index++) {
}); currentKey = keys ? keys[index] : index;
return !!result; if (!predicate(obj[currentKey], currentKey, obj)) return false;
}
return true;
}; };
// 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.
// Aliased as `any`. // Aliased as `any`.
var any = _.some = _.any = function(obj, predicate, context) { _.some = _.any = function(obj, predicate, context) {
predicate || (predicate = _.identity); if (obj == null) return false;
var result = false; predicate = _.iteratee(predicate, context);
if (obj == null) return result; var keys = obj.length !== +obj.length && _.keys(obj),
if (nativeSome && obj.some === nativeSome) return obj.some(predicate, context); length = (keys || obj).length,
each(obj, function(value, index, list) { index, currentKey;
if (result || (result = predicate.call(context, value, index, list))) return breaker; for (index = 0; index < length; index++) {
}); currentKey = keys ? keys[index] : index;
return !!result; if (predicate(obj[currentKey], currentKey, obj)) return true;
}
return false;
}; };
// Determine if the array or object contains a given value (using `===`). // Determine if the array or object contains a given value (using `===`).
// Aliased as `include`. // Aliased as `include`.
_.contains = _.include = function(obj, target) { _.contains = _.include = function(obj, target) {
if (obj == null) return false; if (obj == null) return false;
if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1; if (obj.length !== +obj.length) obj = _.values(obj);
return any(obj, function(value) { return _.indexOf(obj, target) >= 0;
return value === target;
});
}; };
// Invoke a method (with arguments) on every item in a collection. // Invoke a method (with arguments) on every item in a collection.
...@@ -248,51 +259,67 @@ ...@@ -248,51 +259,67 @@
return _.find(obj, _.matches(attrs)); return _.find(obj, _.matches(attrs));
}; };
// Return the maximum element or (element-based computation). // Return the maximum element (or element-based computation).
// Can't optimize arrays of integers longer than 65,535 elements. _.max = function(obj, iteratee, context) {
// See [WebKit Bug 80797](https://bugs.webkit.org/show_bug.cgi?id=80797) var result = -Infinity, lastComputed = -Infinity,
_.max = function(obj, iterator, context) { value, computed;
if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) { if (iteratee == null && obj != null) {
return Math.max.apply(Math, obj); obj = obj.length === +obj.length ? obj : _.values(obj);
} for (var i = 0, length = obj.length; i < length; i++) {
var result = -Infinity, lastComputed = -Infinity; value = obj[i];
each(obj, function(value, index, list) { if (value > result) {
var computed = iterator ? iterator.call(context, value, index, list) : value; result = value;
if (computed > lastComputed) { }
result = value;
lastComputed = computed;
} }
}); } else {
iteratee = _.iteratee(iteratee, context);
_.each(obj, function(value, index, list) {
computed = iteratee(value, index, list);
if (computed > lastComputed || computed === -Infinity && result === -Infinity) {
result = value;
lastComputed = computed;
}
});
}
return result; return result;
}; };
// Return the minimum element (or element-based computation). // Return the minimum element (or element-based computation).
_.min = function(obj, iterator, context) { _.min = function(obj, iteratee, context) {
if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) { var result = Infinity, lastComputed = Infinity,
return Math.min.apply(Math, obj); value, computed;
} if (iteratee == null && obj != null) {
var result = Infinity, lastComputed = Infinity; obj = obj.length === +obj.length ? obj : _.values(obj);
each(obj, function(value, index, list) { for (var i = 0, length = obj.length; i < length; i++) {
var computed = iterator ? iterator.call(context, value, index, list) : value; value = obj[i];
if (computed < lastComputed) { if (value < result) {
result = value; result = value;
lastComputed = computed; }
} }
}); } else {
iteratee = _.iteratee(iteratee, context);
_.each(obj, function(value, index, list) {
computed = iteratee(value, index, list);
if (computed < lastComputed || computed === Infinity && result === Infinity) {
result = value;
lastComputed = computed;
}
});
}
return result; return result;
}; };
// Shuffle an array, using the modern version of the // Shuffle a collection, using the modern version of the
// [Fisher-Yates shuffle](http://en.wikipedia.org/wiki/Fisher–Yates_shuffle). // [Fisher-Yates shuffle](http://en.wikipedia.org/wiki/Fisher–Yates_shuffle).
_.shuffle = function(obj) { _.shuffle = function(obj) {
var rand; var set = obj && obj.length === +obj.length ? obj : _.values(obj);
var index = 0; var length = set.length;
var shuffled = []; var shuffled = Array(length);
each(obj, function(value) { for (var index = 0, rand; index < length; index++) {
rand = _.random(index++); rand = _.random(0, index);
shuffled[index - 1] = shuffled[rand]; if (rand !== index) shuffled[index] = shuffled[rand];
shuffled[rand] = value; shuffled[rand] = set[index];
}); }
return shuffled; return shuffled;
}; };
...@@ -307,21 +334,14 @@ ...@@ -307,21 +334,14 @@
return _.shuffle(obj).slice(0, Math.max(0, n)); return _.shuffle(obj).slice(0, Math.max(0, n));
}; };
// An internal function to generate lookup iterators. // Sort the object's values by a criterion produced by an iteratee.
var lookupIterator = function(value) { _.sortBy = function(obj, iteratee, context) {
if (value == null) return _.identity; iteratee = _.iteratee(iteratee, context);
if (_.isFunction(value)) return value;
return _.property(value);
};
// Sort the object's values by a criterion produced by an iterator.
_.sortBy = function(obj, iterator, context) {
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,
index: index, index: index,
criteria: iterator.call(context, value, index, list) criteria: iteratee(value, index, list)
}; };
}).sort(function(left, right) { }).sort(function(left, right) {
var a = left.criteria; var a = left.criteria;
...@@ -336,12 +356,12 @@ ...@@ -336,12 +356,12 @@
// 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, iterator, context) { return function(obj, iteratee, context) {
var result = {}; var result = {};
iterator = lookupIterator(iterator); iteratee = _.iteratee(iteratee, context);
each(obj, function(value, index) { _.each(obj, function(value, index) {
var key = iterator.call(context, value, index, obj); var key = iteratee(value, index, obj);
behavior(result, key, value); behavior(result, value, key);
}); });
return result; return result;
}; };
...@@ -349,32 +369,32 @@ ...@@ -349,32 +369,32 @@
// 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, value, key) {
_.has(result, key) ? result[key].push(value) : result[key] = [value]; if (_.has(result, key)) result[key].push(value); else 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
// when you know that your index values will be unique. // when you know that your index values will be unique.
_.indexBy = group(function(result, key, value) { _.indexBy = group(function(result, value, key) {
result[key] = value; result[key] = value;
}); });
// Counts instances of an object that group by a certain criterion. Pass // Counts instances of an object that group by a certain criterion. Pass
// either a string attribute to count by, or a function that returns the // either a string attribute to count by, or a function that returns the
// criterion. // criterion.
_.countBy = group(function(result, key) { _.countBy = group(function(result, value, key) {
_.has(result, key) ? result[key]++ : result[key] = 1; if (_.has(result, key)) result[key]++; else result[key] = 1;
}); });
// 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, iteratee, context) {
iterator = lookupIterator(iterator); iteratee = _.iteratee(iteratee, context, 1);
var value = iterator.call(context, obj); var value = iteratee(obj);
var low = 0, high = array.length; var low = 0, high = array.length;
while (low < high) { while (low < high) {
var mid = (low + high) >>> 1; var mid = low + high >>> 1;
iterator.call(context, array[mid]) < value ? low = mid + 1 : high = mid; if (iteratee(array[mid]) < value) low = mid + 1; else high = mid;
} }
return low; return low;
}; };
...@@ -390,7 +410,18 @@ ...@@ -390,7 +410,18 @@
// Return the number of elements in an object. // Return the number of elements in an object.
_.size = function(obj) { _.size = function(obj) {
if (obj == null) return 0; if (obj == null) return 0;
return (obj.length === +obj.length) ? obj.length : _.keys(obj).length; return obj.length === +obj.length ? obj.length : _.keys(obj).length;
};
// Split a collection into two arrays: one whose elements all satisfy the given
// predicate, and one whose elements all do not satisfy the predicate.
_.partition = function(obj, predicate, context) {
predicate = _.iteratee(predicate, context);
var pass = [], fail = [];
_.each(obj, function(value, key, obj) {
(predicate(value, key, obj) ? pass : fail).push(value);
});
return [pass, fail];
}; };
// Array Functions // Array Functions
...@@ -401,7 +432,7 @@ ...@@ -401,7 +432,7 @@
// 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;
if ((n == null) || guard) return array[0]; if (n == null || guard) return array[0];
if (n < 0) return []; if (n < 0) return [];
return slice.call(array, 0, n); return slice.call(array, 0, n);
}; };
...@@ -411,14 +442,14 @@ ...@@ -411,14 +442,14 @@
// the array, excluding the last N. The **guard** check allows it to work with // the array, excluding the last N. The **guard** check allows it to work with
// `_.map`. // `_.map`.
_.initial = function(array, n, guard) { _.initial = function(array, n, guard) {
return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n)); return slice.call(array, 0, Math.max(0, array.length - (n == null || guard ? 1 : n)));
}; };
// Get the last element of an array. Passing **n** will return the last N // Get the last element of an array. Passing **n** will return the last N
// 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) return array[array.length - 1]; if (n == null || guard) return array[array.length - 1];
return slice.call(array, Math.max(array.length - n, 0)); return slice.call(array, Math.max(array.length - n, 0));
}; };
...@@ -427,7 +458,7 @@ ...@@ -427,7 +458,7 @@
// the rest N values in the array. The **guard** // the rest N values in the array. The **guard**
// check allows it to work with `_.map`. // check allows it to work with `_.map`.
_.rest = _.tail = _.drop = function(array, n, guard) { _.rest = _.tail = _.drop = function(array, n, guard) {
return slice.call(array, (n == null) || guard ? 1 : n); return slice.call(array, n == null || guard ? 1 : n);
}; };
// Trim out all falsy values from an array. // Trim out all falsy values from an array.
...@@ -436,23 +467,26 @@ ...@@ -436,23 +467,26 @@
}; };
// Internal implementation of a recursive `flatten` function. // Internal implementation of a recursive `flatten` function.
var flatten = function(input, shallow, output) { var flatten = function(input, shallow, strict, output) {
if (shallow && _.every(input, _.isArray)) { if (shallow && _.every(input, _.isArray)) {
return concat.apply(output, input); return concat.apply(output, input);
} }
each(input, function(value) { for (var i = 0, length = input.length; i < length; i++) {
if (_.isArray(value) || _.isArguments(value)) { var value = input[i];
shallow ? push.apply(output, value) : flatten(value, shallow, output); if (!_.isArray(value) && !_.isArguments(value)) {
if (!strict) output.push(value);
} else if (shallow) {
push.apply(output, value);
} else { } else {
output.push(value); flatten(value, shallow, strict, output);
} }
}); }
return output; return output;
}; };
// Flatten out an array, either recursively (by default), or just one level. // Flatten out an array, either recursively (by default), or just one level.
_.flatten = function(array, shallow) { _.flatten = function(array, shallow) {
return flatten(array, shallow, []); return flatten(array, shallow, false, []);
}; };
// Return a version of the array that does not contain the specified value(s). // Return a version of the array that does not contain the specified value(s).
...@@ -460,68 +494,77 @@ ...@@ -460,68 +494,77 @@
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`.
_.uniq = _.unique = function(array, isSorted, iterator, context) { _.uniq = _.unique = function(array, isSorted, iteratee, context) {
if (_.isFunction(isSorted)) { if (array == null) return [];
context = iterator; if (!_.isBoolean(isSorted)) {
iterator = isSorted; context = iteratee;
iteratee = isSorted;
isSorted = false; isSorted = false;
} }
var initial = iterator ? _.map(array, iterator, context) : array; if (iteratee != null) iteratee = _.iteratee(iteratee, context);
var results = []; var result = [];
var seen = []; var seen = [];
each(initial, function(value, index) { for (var i = 0, length = array.length; i < length; i++) {
if (isSorted ? (!index || seen[seen.length - 1] !== value) : !_.contains(seen, value)) { var value = array[i];
seen.push(value); if (isSorted) {
results.push(array[index]); if (!i || seen !== value) result.push(value);
seen = value;
} else if (iteratee) {
var computed = iteratee(value, i, array);
if (_.indexOf(seen, computed) < 0) {
seen.push(computed);
result.push(value);
}
} else if (_.indexOf(result, value) < 0) {
result.push(value);
} }
}); }
return results; return result;
}; };
// Produce an array that contains the union: each distinct element from all of // Produce an array that contains the union: each distinct element from all of
// the passed-in arrays. // the passed-in arrays.
_.union = function() { _.union = function() {
return _.uniq(_.flatten(arguments, true)); return _.uniq(flatten(arguments, true, true, []));
}; };
// Produce an array that contains every item shared between all the // Produce an array that contains every item shared between all the
// passed-in arrays. // passed-in arrays.
_.intersection = function(array) { _.intersection = function(array) {
var rest = slice.call(arguments, 1); if (array == null) return [];
return _.filter(_.uniq(array), function(item) { var result = [];
return _.every(rest, function(other) { var argsLength = arguments.length;
return _.contains(other, item); for (var i = 0, length = array.length; i < length; i++) {
}); var item = array[i];
}); if (_.contains(result, item)) continue;
for (var j = 1; j < argsLength; j++) {
if (!_.contains(arguments[j], item)) break;
}
if (j === argsLength) result.push(item);
}
return result;
}; };
// Take the difference between one array and a number of other arrays. // Take the difference between one array and a number of other arrays.
// Only the elements present in just the first array will remain. // Only the elements present in just the first array will remain.
_.difference = function(array) { _.difference = function(array) {
var rest = concat.apply(ArrayProto, slice.call(arguments, 1)); var rest = flatten(slice.call(arguments, 1), true, true, []);
return _.filter(array, function(value){ return !_.contains(rest, value); }); return _.filter(array, function(value){
return !_.contains(rest, value);
});
}; };
// 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(array) {
var length = _.max(_.pluck(arguments, 'length').concat(0)); if (array == null) return [];
var results = new Array(length); var length = _.max(arguments, 'length').length;
var results = 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);
} }
return results; return results;
}; };
...@@ -542,10 +585,8 @@ ...@@ -542,10 +585,8 @@
return result; return result;
}; };
// If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**), // Return the position of the first occurrence of an item in an array,
// we need this function. Return the position of the first occurrence of an // or -1 if the item is not included in the array.
// item in an array, or -1 if the item is not included in the array.
// Delegates to **ECMAScript 5**'s native `indexOf` if available.
// If the array is large and already in sort order, pass `true` // If the array is large and already in sort order, pass `true`
// for **isSorted** to use binary search. // for **isSorted** to use binary search.
_.indexOf = function(array, item, isSorted) { _.indexOf = function(array, item, isSorted) {
...@@ -553,26 +594,23 @@ ...@@ -553,26 +594,23 @@
var i = 0, length = array.length; var i = 0, length = array.length;
if (isSorted) { if (isSorted) {
if (typeof isSorted == 'number') { if (typeof isSorted == 'number') {
i = (isSorted < 0 ? Math.max(0, length + isSorted) : isSorted); i = isSorted < 0 ? Math.max(0, length + isSorted) : isSorted;
} else { } else {
i = _.sortedIndex(array, item); i = _.sortedIndex(array, item);
return array[i] === item ? i : -1; return array[i] === item ? i : -1;
} }
} }
if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item, isSorted);
for (; i < length; i++) if (array[i] === item) return i; for (; i < length; i++) if (array[i] === item) return i;
return -1; return -1;
}; };
// Delegates to **ECMAScript 5**'s native `lastIndexOf` if available.
_.lastIndexOf = function(array, item, from) { _.lastIndexOf = function(array, item, from) {
if (array == null) return -1; if (array == null) return -1;
var hasIndex = from != null; var idx = array.length;
if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) { if (typeof from == 'number') {
return hasIndex ? array.lastIndexOf(item, from) : array.lastIndexOf(item); idx = from < 0 ? idx + from + 1 : Math.min(idx, from + 1);
} }
var i = (hasIndex ? from : array.length); while (--idx >= 0) if (array[idx] === item) return idx;
while (i--) if (array[i] === item) return i;
return -1; return -1;
}; };
...@@ -584,15 +622,13 @@ ...@@ -584,15 +622,13 @@
stop = start || 0; stop = start || 0;
start = 0; start = 0;
} }
step = arguments[2] || 1; step = step || 1;
var length = Math.max(Math.ceil((stop - start) / step), 0); var length = Math.max(Math.ceil((stop - start) / step), 0);
var idx = 0; var range = Array(length);
var range = new Array(length);
while(idx < length) { for (var idx = 0; idx < length; idx++, start += step) {
range[idx++] = start; range[idx] = start;
start += step;
} }
return range; return range;
...@@ -602,7 +638,7 @@ ...@@ -602,7 +638,7 @@
// ------------------ // ------------------
// Reusable constructor function for prototype setting. // Reusable constructor function for prototype setting.
var ctor = function(){}; var Ctor = function(){};
// Create a function bound to a given object (assigning `this`, and arguments, // Create a function bound to a given object (assigning `this`, and arguments,
// optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if // optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if
...@@ -610,17 +646,18 @@ ...@@ -610,17 +646,18 @@
_.bind = function(func, context) { _.bind = function(func, context) {
var args, bound; var args, bound;
if (nativeBind && func.bind === nativeBind) return nativeBind.apply(func, slice.call(arguments, 1)); if (nativeBind && func.bind === nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
if (!_.isFunction(func)) throw new TypeError; if (!_.isFunction(func)) throw new TypeError('Bind must be called on a function');
args = slice.call(arguments, 2); args = slice.call(arguments, 2);
return bound = function() { bound = function() {
if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments))); if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments)));
ctor.prototype = func.prototype; Ctor.prototype = func.prototype;
var self = new ctor; var self = new Ctor;
ctor.prototype = null; Ctor.prototype = null;
var result = func.apply(self, args.concat(slice.call(arguments))); var result = func.apply(self, args.concat(slice.call(arguments)));
if (Object(result) === result) return result; if (_.isObject(result)) return result;
return self; return self;
}; };
return bound;
}; };
// 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
...@@ -643,27 +680,34 @@ ...@@ -643,27 +680,34 @@
// are the method names to be bound. Useful for ensuring that all callbacks // are the method names to be bound. Useful for ensuring that all callbacks
// defined on an object belong to it. // defined on an object belong to it.
_.bindAll = function(obj) { _.bindAll = function(obj) {
var funcs = slice.call(arguments, 1); var i, length = arguments.length, key;
if (funcs.length === 0) throw new Error('bindAll must be passed function names'); if (length <= 1) throw new Error('bindAll must be passed function names');
each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); }); for (i = 1; i < length; i++) {
key = arguments[i];
obj[key] = _.bind(obj[key], obj);
}
return obj; return obj;
}; };
// Memoize an expensive function by storing its results. // Memoize an expensive function by storing its results.
_.memoize = function(func, hasher) { _.memoize = function(func, hasher) {
var memo = {}; var memoize = function(key) {
hasher || (hasher = _.identity); var cache = memoize.cache;
return function() { var address = hasher ? hasher.apply(this, arguments) : key;
var key = hasher.apply(this, arguments); if (!_.has(cache, address)) cache[address] = func.apply(this, arguments);
return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments)); return cache[address];
}; };
memoize.cache = {};
return memoize;
}; };
// Delays a function for the given number of milliseconds, and then calls // Delays a function for the given number of milliseconds, and then calls
// it with the arguments supplied. // it with the arguments supplied.
_.delay = function(func, wait) { _.delay = function(func, wait) {
var args = slice.call(arguments, 2); var args = slice.call(arguments, 2);
return setTimeout(function(){ return func.apply(null, args); }, wait); return setTimeout(function(){
return func.apply(null, args);
}, wait);
}; };
// Defers a function, scheduling it to run after the current call stack has // Defers a function, scheduling it to run after the current call stack has
...@@ -681,12 +725,12 @@ ...@@ -681,12 +725,12 @@
var context, args, result; var context, args, result;
var timeout = null; var timeout = null;
var previous = 0; var previous = 0;
options || (options = {}); if (!options) options = {};
var later = function() { var later = function() {
previous = options.leading === false ? 0 : _.now(); previous = options.leading === false ? 0 : _.now();
timeout = null; timeout = null;
result = func.apply(context, args); result = func.apply(context, args);
context = args = null; if (!timeout) context = args = null;
}; };
return function() { return function() {
var now = _.now(); var now = _.now();
...@@ -694,12 +738,12 @@ ...@@ -694,12 +738,12 @@
var remaining = wait - (now - previous); var remaining = wait - (now - previous);
context = this; context = this;
args = arguments; args = arguments;
if (remaining <= 0) { if (remaining <= 0 || remaining > wait) {
clearTimeout(timeout); clearTimeout(timeout);
timeout = null; timeout = null;
previous = now; previous = now;
result = func.apply(context, args); result = func.apply(context, args);
context = args = null; if (!timeout) context = args = null;
} else if (!timeout && options.trailing !== false) { } else if (!timeout && options.trailing !== false) {
timeout = setTimeout(later, remaining); timeout = setTimeout(later, remaining);
} }
...@@ -716,13 +760,14 @@ ...@@ -716,13 +760,14 @@
var later = function() { var later = function() {
var last = _.now() - timestamp; var last = _.now() - timestamp;
if (last < wait) {
if (last < wait && last > 0) {
timeout = setTimeout(later, wait - last); timeout = setTimeout(later, wait - last);
} else { } else {
timeout = null; timeout = null;
if (!immediate) { if (!immediate) {
result = func.apply(context, args); result = func.apply(context, args);
context = args = null; if (!timeout) context = args = null;
} }
} }
}; };
...@@ -732,9 +777,7 @@ ...@@ -732,9 +777,7 @@
args = arguments; args = arguments;
timestamp = _.now(); timestamp = _.now();
var callNow = immediate && !timeout; var callNow = immediate && !timeout;
if (!timeout) { if (!timeout) timeout = setTimeout(later, wait);
timeout = setTimeout(later, wait);
}
if (callNow) { if (callNow) {
result = func.apply(context, args); result = func.apply(context, args);
context = args = null; context = args = null;
...@@ -744,19 +787,6 @@ ...@@ -744,19 +787,6 @@
}; };
}; };
// Returns a function that will be executed at most one time, no matter how
// often you call it. Useful for lazy initialization.
_.once = function(func) {
var ran = false, memo;
return function() {
if (ran) return memo;
ran = true;
memo = func.apply(this, arguments);
func = null;
return memo;
};
};
// Returns the first function passed as an argument to the second, // Returns the first function passed as an argument to the second,
// 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.
...@@ -764,16 +794,23 @@ ...@@ -764,16 +794,23 @@
return _.partial(wrapper, func); return _.partial(wrapper, func);
}; };
// Returns a negated version of the passed-in predicate.
_.negate = function(predicate) {
return function() {
return !predicate.apply(this, arguments);
};
};
// 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
// consuming the return value of the function that follows. // consuming the return value of the function that follows.
_.compose = function() { _.compose = function() {
var funcs = arguments; var args = arguments;
var start = args.length - 1;
return function() { return function() {
var args = arguments; var i = start;
for (var i = funcs.length - 1; i >= 0; i--) { var result = args[start].apply(this, arguments);
args = [funcs[i].apply(this, args)]; while (i--) result = args[i].call(this, result);
} return result;
return args[0];
}; };
}; };
...@@ -786,6 +823,23 @@ ...@@ -786,6 +823,23 @@
}; };
}; };
// Returns a function that will only be executed before being called N times.
_.before = function(times, func) {
var memo;
return function() {
if (--times > 0) {
memo = func.apply(this, arguments);
} else {
func = null;
}
return memo;
};
};
// Returns a function that will be executed at most one time, no matter how
// often you call it. Useful for lazy initialization.
_.once = _.partial(_.before, 2);
// Object Functions // Object Functions
// ---------------- // ----------------
...@@ -803,7 +857,7 @@ ...@@ -803,7 +857,7 @@
_.values = function(obj) { _.values = function(obj) {
var keys = _.keys(obj); var keys = _.keys(obj);
var length = keys.length; var length = keys.length;
var values = new Array(length); var values = Array(length);
for (var i = 0; i < length; i++) { for (var i = 0; i < length; i++) {
values[i] = obj[keys[i]]; values[i] = obj[keys[i]];
} }
...@@ -814,7 +868,7 @@ ...@@ -814,7 +868,7 @@
_.pairs = function(obj) { _.pairs = function(obj) {
var keys = _.keys(obj); var keys = _.keys(obj);
var length = keys.length; var length = keys.length;
var pairs = new Array(length); var pairs = Array(length);
for (var i = 0; i < length; i++) { for (var i = 0; i < length; i++) {
pairs[i] = [keys[i], obj[keys[i]]]; pairs[i] = [keys[i], obj[keys[i]]];
} }
...@@ -843,45 +897,62 @@ ...@@ -843,45 +897,62 @@
// Extend a given object with all the properties in passed-in object(s). // Extend a given object with all the properties in passed-in object(s).
_.extend = function(obj) { _.extend = function(obj) {
each(slice.call(arguments, 1), function(source) { if (!_.isObject(obj)) return obj;
if (source) { var source, prop;
for (var prop in source) { for (var i = 1, length = arguments.length; i < length; i++) {
obj[prop] = source[prop]; source = arguments[i];
for (prop in source) {
if (hasOwnProperty.call(source, prop)) {
obj[prop] = source[prop];
} }
} }
}); }
return obj; return obj;
}; };
// Return a copy of the object only containing the whitelisted properties. // Return a copy of the object only containing the whitelisted properties.
_.pick = function(obj) { _.pick = function(obj, iteratee, context) {
var copy = {}; var result = {}, key;
var keys = concat.apply(ArrayProto, slice.call(arguments, 1)); if (obj == null) return result;
each(keys, function(key) { if (_.isFunction(iteratee)) {
if (key in obj) copy[key] = obj[key]; iteratee = createCallback(iteratee, context);
}); for (key in obj) {
return copy; var value = obj[key];
if (iteratee(value, key, obj)) result[key] = value;
}
} else {
var keys = concat.apply([], slice.call(arguments, 1));
obj = new Object(obj);
for (var i = 0, length = keys.length; i < length; i++) {
key = keys[i];
if (key in obj) result[key] = obj[key];
}
}
return result;
}; };
// Return a copy of the object without the blacklisted properties. // Return a copy of the object without the blacklisted properties.
_.omit = function(obj) { _.omit = function(obj, iteratee, context) {
var copy = {}; if (_.isFunction(iteratee)) {
var keys = concat.apply(ArrayProto, slice.call(arguments, 1)); iteratee = _.negate(iteratee);
for (var key in obj) { } else {
if (!_.contains(keys, key)) copy[key] = obj[key]; var keys = _.map(concat.apply([], slice.call(arguments, 1)), String);
iteratee = function(value, key) {
return !_.contains(keys, key);
};
} }
return copy; return _.pick(obj, iteratee, context);
}; };
// Fill in a given object with default properties. // Fill in a given object with default properties.
_.defaults = function(obj) { _.defaults = function(obj) {
each(slice.call(arguments, 1), function(source) { if (!_.isObject(obj)) return obj;
if (source) { for (var i = 1, length = arguments.length; i < length; i++) {
for (var prop in source) { var source = arguments[i];
if (obj[prop] === void 0) obj[prop] = source[prop]; for (var prop in source) {
} if (obj[prop] === void 0) obj[prop] = source[prop];
} }
}); }
return obj; return obj;
}; };
...@@ -903,7 +974,7 @@ ...@@ -903,7 +974,7 @@
var eq = function(a, b, aStack, bStack) { var eq = function(a, b, aStack, bStack) {
// Identical objects are equal. `0 === -0`, but they aren't identical. // Identical objects are equal. `0 === -0`, but they aren't identical.
// See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal). // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal).
if (a === b) return a !== 0 || 1 / a == 1 / b; if (a === b) return a !== 0 || 1 / a === 1 / b;
// A strict comparison is necessary because `null == undefined`. // A strict comparison is necessary because `null == undefined`.
if (a == null || b == null) return a === b; if (a == null || b == null) return a === b;
// Unwrap any wrapped objects. // Unwrap any wrapped objects.
...@@ -911,29 +982,27 @@ ...@@ -911,29 +982,27 @@
if (b instanceof _) b = b._wrapped; if (b instanceof _) b = b._wrapped;
// Compare `[[Class]]` names. // Compare `[[Class]]` names.
var className = toString.call(a); var className = toString.call(a);
if (className != toString.call(b)) return false; if (className !== toString.call(b)) return false;
switch (className) { switch (className) {
// Strings, numbers, dates, and booleans are compared by value. // Strings, numbers, regular expressions, dates, and booleans are compared by value.
case '[object RegExp]':
// RegExps are coerced to strings for comparison (Note: '' + /a/i === '/a/i')
case '[object String]': case '[object String]':
// Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
// equivalent to `new String("5")`. // equivalent to `new String("5")`.
return a == String(b); return '' + a === '' + b;
case '[object Number]': case '[object Number]':
// `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for // `NaN`s are equivalent, but non-reflexive.
// other numeric values. // Object(NaN) is equivalent to NaN
return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b); if (+a !== +a) return +b !== +b;
// An `egal` comparison is performed for other numeric values.
return +a === 0 ? 1 / +a === 1 / b : +a === +b;
case '[object Date]': case '[object Date]':
case '[object Boolean]': case '[object Boolean]':
// Coerce dates and booleans to numeric primitive values. Dates are compared by their // Coerce dates and booleans to numeric primitive values. Dates are compared by their
// millisecond representations. Note that invalid dates with millisecond representations // millisecond representations. Note that invalid dates with millisecond representations
// of `NaN` are not equivalent. // of `NaN` are not equivalent.
return +a == +b; return +a === +b;
// RegExps are compared by their source patterns and flags.
case '[object RegExp]':
return a.source == b.source &&
a.global == b.global &&
a.multiline == b.multiline &&
a.ignoreCase == b.ignoreCase;
} }
if (typeof a != 'object' || typeof b != 'object') return false; if (typeof a != 'object' || typeof b != 'object') return false;
// Assume equality for cyclic structures. The algorithm for detecting cyclic // Assume equality for cyclic structures. The algorithm for detecting cyclic
...@@ -942,25 +1011,29 @@ ...@@ -942,25 +1011,29 @@
while (length--) { while (length--) {
// Linear search. Performance is inversely proportional to the number of // Linear search. Performance is inversely proportional to the number of
// unique nested structures. // unique nested structures.
if (aStack[length] == a) return bStack[length] == b; if (aStack[length] === a) return bStack[length] === b;
} }
// Objects with different constructors are not equivalent, but `Object`s // Objects with different constructors are not equivalent, but `Object`s
// 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 (
_.isFunction(bCtor) && (bCtor instanceof bCtor)) aCtor !== bCtor &&
&& ('constructor' in a && 'constructor' in b)) { // Handle Object.create(x) cases
'constructor' in a && 'constructor' in b &&
!(_.isFunction(aCtor) && aCtor instanceof aCtor &&
_.isFunction(bCtor) && bCtor instanceof bCtor)
) {
return false; return false;
} }
// Add the first object to the stack of traversed objects. // Add the first object to the stack of traversed objects.
aStack.push(a); aStack.push(a);
bStack.push(b); bStack.push(b);
var size = 0, result = true; var size, result;
// Recursively compare objects and arrays. // Recursively compare objects and arrays.
if (className == '[object Array]') { if (className === '[object Array]') {
// Compare array lengths to determine if a deep comparison is necessary. // Compare array lengths to determine if a deep comparison is necessary.
size = a.length; size = a.length;
result = size == b.length; result = size === b.length;
if (result) { if (result) {
// Deep compare the contents, ignoring non-numeric properties. // Deep compare the contents, ignoring non-numeric properties.
while (size--) { while (size--) {
...@@ -969,20 +1042,16 @@ ...@@ -969,20 +1042,16 @@
} }
} else { } else {
// Deep compare objects. // Deep compare objects.
for (var key in a) { var keys = _.keys(a), key;
if (_.has(a, key)) { size = keys.length;
// Count the expected number of properties. // Ensure that both objects contain the same number of properties before comparing deep equality.
size++; result = _.keys(b).length === size;
// Deep compare each member.
if (!(result = _.has(b, key) && eq(a[key], b[key], aStack, bStack))) break;
}
}
// Ensure that both objects contain the same number of properties.
if (result) { if (result) {
for (key in b) { while (size--) {
if (_.has(b, key) && !(size--)) break; // Deep compare each member
key = keys[size];
if (!(result = _.has(b, key) && eq(a[key], b[key], aStack, bStack))) break;
} }
result = !size;
} }
} }
// Remove the first object from the stack of traversed objects. // Remove the first object from the stack of traversed objects.
...@@ -1000,7 +1069,7 @@ ...@@ -1000,7 +1069,7 @@
// An "empty" object has no enumerable own-properties. // An "empty" object has no enumerable own-properties.
_.isEmpty = function(obj) { _.isEmpty = function(obj) {
if (obj == null) return true; if (obj == null) return true;
if (_.isArray(obj) || _.isString(obj)) return obj.length === 0; if (_.isArray(obj) || _.isString(obj) || _.isArguments(obj)) return obj.length === 0;
for (var key in obj) if (_.has(obj, key)) return false; for (var key in obj) if (_.has(obj, key)) return false;
return true; return true;
}; };
...@@ -1013,18 +1082,19 @@ ...@@ -1013,18 +1082,19 @@
// Is a given value an array? // Is a given value an array?
// Delegates to ECMA5's native Array.isArray // Delegates to ECMA5's native Array.isArray
_.isArray = nativeIsArray || function(obj) { _.isArray = nativeIsArray || function(obj) {
return toString.call(obj) == '[object Array]'; return toString.call(obj) === '[object Array]';
}; };
// Is a given variable an object? // Is a given variable an object?
_.isObject = function(obj) { _.isObject = function(obj) {
return obj === Object(obj); var type = typeof obj;
return type === 'function' || type === 'object' && !!obj;
}; };
// Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp. // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp.
each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'], function(name) { _.each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'], function(name) {
_['is' + name] = function(obj) { _['is' + name] = function(obj) {
return toString.call(obj) == '[object ' + name + ']'; return toString.call(obj) === '[object ' + name + ']';
}; };
}); });
...@@ -1032,14 +1102,14 @@ ...@@ -1032,14 +1102,14 @@
// there isn't any inspectable "Arguments" type. // there isn't any inspectable "Arguments" type.
if (!_.isArguments(arguments)) { if (!_.isArguments(arguments)) {
_.isArguments = function(obj) { _.isArguments = function(obj) {
return !!(obj && _.has(obj, 'callee')); return _.has(obj, 'callee');
}; };
} }
// Optimize `isFunction` if appropriate. // Optimize `isFunction` if appropriate. Work around an IE 11 bug.
if (typeof (/./) !== 'function') { if (typeof /./ !== 'function') {
_.isFunction = function(obj) { _.isFunction = function(obj) {
return typeof obj === 'function'; return typeof obj == 'function' || false;
}; };
} }
...@@ -1050,12 +1120,12 @@ ...@@ -1050,12 +1120,12 @@
// Is the given value `NaN`? (NaN is the only number which does not equal itself). // Is the given value `NaN`? (NaN is the only number which does not equal itself).
_.isNaN = function(obj) { _.isNaN = function(obj) {
return _.isNumber(obj) && obj != +obj; return _.isNumber(obj) && obj !== +obj;
}; };
// Is a given value a boolean? // Is a given value a boolean?
_.isBoolean = function(obj) { _.isBoolean = function(obj) {
return obj === true || obj === false || toString.call(obj) == '[object Boolean]'; return obj === true || obj === false || toString.call(obj) === '[object Boolean]';
}; };
// Is a given value equal to null? // Is a given value equal to null?
...@@ -1071,7 +1141,7 @@ ...@@ -1071,7 +1141,7 @@
// Shortcut function for checking if an object has a given property directly // Shortcut function for checking if an object has a given property directly
// on itself (in other words, not on a prototype). // on itself (in other words, not on a prototype).
_.has = function(obj, key) { _.has = function(obj, key) {
return hasOwnProperty.call(obj, key); return obj != null && hasOwnProperty.call(obj, key);
}; };
// Utility Functions // Utility Functions
...@@ -1084,17 +1154,19 @@ ...@@ -1084,17 +1154,19 @@
return this; return this;
}; };
// Keep the identity function around for default iterators. // Keep the identity function around for default iteratees.
_.identity = function(value) { _.identity = function(value) {
return value; return value;
}; };
_.constant = function(value) { _.constant = function(value) {
return function () { return function() {
return value; return value;
}; };
}; };
_.noop = function(){};
_.property = function(key) { _.property = function(key) {
return function(obj) { return function(obj) {
return obj[key]; return obj[key];
...@@ -1103,20 +1175,23 @@ ...@@ -1103,20 +1175,23 @@
// Returns a predicate for checking whether an object has a given set of `key:value` pairs. // Returns a predicate for checking whether an object has a given set of `key:value` pairs.
_.matches = function(attrs) { _.matches = function(attrs) {
var pairs = _.pairs(attrs), length = pairs.length;
return function(obj) { return function(obj) {
if (obj === attrs) return true; //avoid comparing an object to itself. if (obj == null) return !length;
for (var key in attrs) { obj = new Object(obj);
if (attrs[key] !== obj[key]) for (var i = 0; i < length; i++) {
return false; var pair = pairs[i], key = pair[0];
if (pair[1] !== obj[key] || !(key in obj)) return false;
} }
return true; return true;
} };
}; };
// Run a function **n** times. // Run a function **n** times.
_.times = function(n, iterator, context) { _.times = function(n, iteratee, context) {
var accum = Array(Math.max(0, n)); var accum = Array(Math.max(0, n));
for (var i = 0; i < n; i++) accum[i] = iterator.call(context, i); iteratee = createCallback(iteratee, context, 1);
for (var i = 0; i < n; i++) accum[i] = iteratee(i);
return accum; return accum;
}; };
...@@ -1130,54 +1205,44 @@ ...@@ -1130,54 +1205,44 @@
}; };
// A (possibly faster) way to get the current timestamp as an integer. // A (possibly faster) way to get the current timestamp as an integer.
_.now = Date.now || function() { return new Date().getTime(); }; _.now = Date.now || function() {
return new Date().getTime();
// List of HTML entities for escaping.
var entityMap = {
escape: {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#x27;'
}
}; };
entityMap.unescape = _.invert(entityMap.escape);
// Regexes containing the keys and values listed immediately above. // List of HTML entities for escaping.
var entityRegexes = { var escapeMap = {
escape: new RegExp('[' + _.keys(entityMap.escape).join('') + ']', 'g'), '&': '&amp;',
unescape: new RegExp('(' + _.keys(entityMap.unescape).join('|') + ')', 'g') '<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#x27;',
'`': '&#x60;'
}; };
var unescapeMap = _.invert(escapeMap);
// Functions for escaping and unescaping strings to/from HTML interpolation. // Functions for escaping and unescaping strings to/from HTML interpolation.
_.each(['escape', 'unescape'], function(method) { var createEscaper = function(map) {
_[method] = function(string) { var escaper = function(match) {
if (string == null) return ''; return map[match];
return ('' + string).replace(entityRegexes[method], function(match) {
return entityMap[method][match];
});
}; };
}); // Regexes for identifying a key that needs to be escaped
var source = '(?:' + _.keys(map).join('|') + ')';
var testRegexp = RegExp(source);
var replaceRegexp = RegExp(source, 'g');
return function(string) {
string = string == null ? '' : '' + string;
return testRegexp.test(string) ? string.replace(replaceRegexp, escaper) : string;
};
};
_.escape = createEscaper(escapeMap);
_.unescape = createEscaper(unescapeMap);
// If the value of the named `property` is a function then invoke it with the // If the value of the named `property` is a function then invoke it with the
// `object` as context; otherwise, return it. // `object` as context; otherwise, return it.
_.result = function(object, property) { _.result = function(object, property) {
if (object == null) return void 0; if (object == null) return void 0;
var value = object[property]; var value = object[property];
return _.isFunction(value) ? value.call(object) : value; return _.isFunction(value) ? object[property]() : value;
};
// Add your own custom functions to the Underscore object.
_.mixin = function(obj) {
each(_.functions(obj), function(name) {
var func = _[name] = obj[name];
_.prototype[name] = function() {
var args = [this._wrapped];
push.apply(args, arguments);
return result.call(this, func.apply(_, args));
};
});
}; };
// Generate a unique integer id (unique within the entire client session). // Generate a unique integer id (unique within the entire client session).
...@@ -1208,22 +1273,26 @@ ...@@ -1208,22 +1273,26 @@
'\\': '\\', '\\': '\\',
'\r': 'r', '\r': 'r',
'\n': 'n', '\n': 'n',
'\t': 't',
'\u2028': 'u2028', '\u2028': 'u2028',
'\u2029': 'u2029' '\u2029': 'u2029'
}; };
var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g; var escaper = /\\|'|\r|\n|\u2028|\u2029/g;
var escapeChar = function(match) {
return '\\' + escapes[match];
};
// JavaScript micro-templating, similar to John Resig's implementation. // JavaScript micro-templating, similar to John Resig's implementation.
// Underscore templating handles arbitrary delimiters, preserves whitespace, // Underscore templating handles arbitrary delimiters, preserves whitespace,
// and correctly escapes quotes within interpolated code. // and correctly escapes quotes within interpolated code.
_.template = function(text, data, settings) { // NB: `oldSettings` only exists for backwards compatibility.
var render; _.template = function(text, settings, oldSettings) {
if (!settings && oldSettings) settings = oldSettings;
settings = _.defaults({}, settings, _.templateSettings); settings = _.defaults({}, settings, _.templateSettings);
// Combine delimiters into one regular expression via alternation. // Combine delimiters into one regular expression via alternation.
var matcher = new RegExp([ var matcher = RegExp([
(settings.escape || noMatch).source, (settings.escape || noMatch).source,
(settings.interpolate || noMatch).source, (settings.interpolate || noMatch).source,
(settings.evaluate || noMatch).source (settings.evaluate || noMatch).source
...@@ -1233,19 +1302,18 @@ ...@@ -1233,19 +1302,18 @@
var index = 0; var index = 0;
var source = "__p+='"; var source = "__p+='";
text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
source += text.slice(index, offset) source += text.slice(index, offset).replace(escaper, escapeChar);
.replace(escaper, function(match) { return '\\' + escapes[match]; }); index = offset + match.length;
if (escape) { if (escape) {
source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'"; source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";
} } else if (interpolate) {
if (interpolate) {
source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'"; source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'";
} } else if (evaluate) {
if (evaluate) {
source += "';\n" + evaluate + "\n__p+='"; source += "';\n" + evaluate + "\n__p+='";
} }
index = offset + match.length;
// Adobe VMs need the match returned to produce the correct offest.
return match; return match;
}); });
source += "';\n"; source += "';\n";
...@@ -1255,29 +1323,31 @@ ...@@ -1255,29 +1323,31 @@
source = "var __t,__p='',__j=Array.prototype.join," + source = "var __t,__p='',__j=Array.prototype.join," +
"print=function(){__p+=__j.call(arguments,'');};\n" + "print=function(){__p+=__j.call(arguments,'');};\n" +
source + "return __p;\n"; source + 'return __p;\n';
try { try {
render = new Function(settings.variable || 'obj', '_', source); var render = new Function(settings.variable || 'obj', '_', source);
} catch (e) { } catch (e) {
e.source = source; e.source = source;
throw e; throw e;
} }
if (data) return render(data, _);
var template = function(data) { var template = function(data) {
return render.call(this, data, _); return render.call(this, data, _);
}; };
// Provide the compiled function source as a convenience for precompilation. // Provide the compiled source as a convenience for precompilation.
template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}'; var argument = settings.variable || 'obj';
template.source = 'function(' + argument + '){\n' + source + '}';
return template; return template;
}; };
// Add a "chain" function, which will delegate to the wrapper. // Add a "chain" function. Start chaining a wrapped Underscore object.
_.chain = function(obj) { _.chain = function(obj) {
return _(obj).chain(); var instance = _(obj);
instance._chain = true;
return instance;
}; };
// OOP // OOP
...@@ -1291,42 +1361,44 @@ ...@@ -1291,42 +1361,44 @@
return this._chain ? _(obj).chain() : obj; return this._chain ? _(obj).chain() : obj;
}; };
// Add your own custom functions to the Underscore object.
_.mixin = function(obj) {
_.each(_.functions(obj), function(name) {
var func = _[name] = obj[name];
_.prototype[name] = function() {
var args = [this._wrapped];
push.apply(args, arguments);
return result.call(this, func.apply(_, args));
};
});
};
// Add all of the Underscore functions to the wrapper object. // Add all of the Underscore functions to the wrapper object.
_.mixin(_); _.mixin(_);
// Add all mutator Array functions to the wrapper. // Add all mutator Array functions to the wrapper.
each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { _.each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
var method = ArrayProto[name]; var method = ArrayProto[name];
_.prototype[name] = function() { _.prototype[name] = function() {
var obj = this._wrapped; var obj = this._wrapped;
method.apply(obj, arguments); method.apply(obj, arguments);
if ((name == 'shift' || name == 'splice') && obj.length === 0) delete obj[0]; if ((name === 'shift' || name === 'splice') && obj.length === 0) delete obj[0];
return result.call(this, obj); return result.call(this, obj);
}; };
}); });
// Add all accessor Array functions to the wrapper. // Add all accessor Array functions to the wrapper.
each(['concat', 'join', 'slice'], function(name) { _.each(['concat', 'join', 'slice'], function(name) {
var method = ArrayProto[name]; var method = ArrayProto[name];
_.prototype[name] = function() { _.prototype[name] = function() {
return result.call(this, method.apply(this._wrapped, arguments)); return result.call(this, method.apply(this._wrapped, arguments));
}; };
}); });
_.extend(_.prototype, { // Extracts the result from a wrapped and chained object.
_.prototype.value = function() {
// Start chaining a wrapped Underscore object. return this._wrapped;
chain: function() { };
this._chain = true;
return this;
},
// Extracts the result from a wrapped and chained object.
value: function() {
return this._wrapped;
}
});
// AMD registration happens at the end for compatibility with AMD loaders // AMD registration happens at the end for compatibility with AMD loaders
// that may not enforce next-turn semantics on modules. Even though general // that may not enforce next-turn semantics on modules. Even though general
...@@ -1340,4 +1412,4 @@ ...@@ -1340,4 +1412,4 @@
return _; return _;
}); });
} }
}).call(this); }.call(this));
...@@ -49,7 +49,7 @@ ...@@ -49,7 +49,7 @@
<% } %> <% } %>
</script> </script>
<script src="bower_components/todomvc-common/base.js"></script> <script src="bower_components/todomvc-common/base.js"></script>
<script src="bower_components/jquery/jquery.js"></script> <script src="bower_components/jquery/dist/jquery.js"></script>
<script src="bower_components/underscore/underscore.js"></script> <script src="bower_components/underscore/underscore.js"></script>
<script src="bower_components/backbone/backbone.js"></script> <script src="bower_components/backbone/backbone.js"></script>
<script src="bower_components/backbone.localStorage/backbone.localStorage.js"></script> <script src="bower_components/backbone.localStorage/backbone.localStorage.js"></script>
......
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