Commit 3c4b899e authored by André Ruffert's avatar André Ruffert Committed by Sindre Sorhus

Close #1151 PR: Update `backbone_require` to new app UI. Fixes #1110

parent d24fcd67
node_modules/*
node_modules/backbone/*
!node_modules/backbone/backbone.js
node_modules/backbone.localstorage/*
!node_modules/backbone.localstorage/backbone.localStorage.js
node_modules/jquery/*
!node_modules/jquery/dist
node_modules/jquery/dist/*
!node_modules/jquery/dist/jquery.js
node_modules/requirejs/*
!node_modules/requirejs/require.js
node_modules/requirejs-text/*
!node_modules/requirejs-text/text.js
node_modules/todomvc-app-css/*
!node_modules/todomvc-app-css/index.css
node_modules/todomvc-common/*
!node_modules/todomvc-common/base.css
!node_modules/todomvc-common/base.js
node_modules/underscore/*
!node_modules/underscore/underscore.js
{
"name": "todomvc-backbone-requirejs",
"version": "0.0.0",
"dependencies": {
"backbone": "~1.1.0",
"underscore": "~1.5.0",
"jquery": "~2.0.0",
"todomvc-common": "~0.3.0",
"backbone.localStorage": "~1.1.0",
"requirejs": "~2.1.5",
"requirejs-text": "~2.0.5"
}
}
...@@ -3,8 +3,8 @@ ...@@ -3,8 +3,8 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<title>Backbone.js + RequireJS • TodoMVC</title> <title>Backbone.js + RequireJS • TodoMVC</title>
<link rel="stylesheet" href="bower_components/todomvc-common/base.css"> <link rel="stylesheet" href="node_modules/todomvc-common/base.css">
<script src="bower_components/todomvc-common/base.js"></script> <link rel="stylesheet" href="node_modules/todomvc-app-css/index.css">
</head> </head>
<body> <body>
<section id="todoapp"> <section id="todoapp">
...@@ -24,6 +24,7 @@ ...@@ -24,6 +24,7 @@
<p>Written by <a href="http://addyosmani.github.com/todomvc/">Addy Osmani</a></p> <p>Written by <a href="http://addyosmani.github.com/todomvc/">Addy Osmani</a></p>
<p>Part of <a href="http://todomvc.com">TodoMVC</a></p> <p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
</footer> </footer>
<script data-main="js/main" src="bower_components/requirejs/require.js"></script> <script src="node_modules/todomvc-common/base.js"></script>
<script data-main="js/main" src="node_modules/requirejs/require.js"></script>
</body> </body>
</html> </html>
...@@ -22,11 +22,11 @@ require.config({ ...@@ -22,11 +22,11 @@ require.config({
} }
}, },
paths: { paths: {
jquery: '../bower_components/jquery/jquery', jquery: '../node_modules/jquery/dist/jquery',
underscore: '../bower_components/underscore/underscore', underscore: '../node_modules/underscore/underscore',
backbone: '../bower_components/backbone/backbone', backbone: '../node_modules/backbone/backbone',
backboneLocalstorage: '../bower_components/backbone.localStorage/backbone.localStorage', backboneLocalstorage: '../node_modules/backbone.localStorage/backbone.localStorage',
text: '../bower_components/requirejs-text/text' text: '../node_modules/requirejs-text/text'
} }
}); });
......
/** /**
* Backbone localStorage Adapter * Backbone localStorage Adapter
* Version 1.1.7 * Version 1.1.16
* *
* 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 {
...@@ -204,8 +238,10 @@ Backbone.LocalStorage.sync = window.Store.sync = Backbone.localSync = function(m ...@@ -204,8 +238,10 @@ Backbone.LocalStorage.sync = window.Store.sync = Backbone.localSync = function(m
Backbone.ajaxSync = Backbone.sync; Backbone.ajaxSync = Backbone.sync;
Backbone.getSyncMethod = function(model) { Backbone.getSyncMethod = function(model, options) {
if(model.localStorage || (model.collection && model.collection.localStorage)) { var forceAjaxSync = options && options.ajaxSync;
if(!forceAjaxSync && (result(model, 'localStorage') || result(model.collection, 'localStorage'))) {
return Backbone.localSync; return Backbone.localSync;
} }
...@@ -215,7 +251,7 @@ Backbone.getSyncMethod = function(model) { ...@@ -215,7 +251,7 @@ Backbone.getSyncMethod = function(model) {
// Override 'Backbone.sync' to default to localSync, // Override 'Backbone.sync' to default to localSync,
// the original 'Backbone.sync' is still available in 'Backbone.ajaxSync' // the original 'Backbone.sync' is still available in 'Backbone.ajaxSync'
Backbone.sync = function(method, model, options) { Backbone.sync = function(method, model, options) {
return Backbone.getSyncMethod(model).apply(this, [method, model, options]); return Backbone.getSyncMethod(model, options).apply(this, [method, model, options]);
}; };
return Backbone.LocalStorage; return Backbone.LocalStorage;
......
// Backbone.js 1.1.0 // Backbone.js 1.1.2
// (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');
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 +40,12 @@ ...@@ -25,25 +40,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.2';
// 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 +111,7 @@ ...@@ -109,7 +111,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 +207,7 @@ ...@@ -205,7 +207,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 +352,7 @@ ...@@ -350,7 +352,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 +363,7 @@ ...@@ -361,6 +363,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 +531,12 @@ ...@@ -528,9 +531,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 +552,7 @@ ...@@ -546,7 +552,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 +656,7 @@ ...@@ -650,7 +656,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 +682,11 @@ ...@@ -676,11 +682,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 +706,13 @@ ...@@ -700,14 +706,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 +762,7 @@ ...@@ -757,7 +762,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 +803,7 @@ ...@@ -798,7 +803,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 +879,7 @@ ...@@ -874,7 +879,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 +909,7 @@ ...@@ -904,10 +909,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 +918,16 @@ ...@@ -916,8 +918,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 +956,7 @@ ...@@ -946,7 +956,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 +968,7 @@ ...@@ -958,7 +968,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 +1190,9 @@ ...@@ -1180,7 +1190,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 +1251,7 @@ ...@@ -1239,7 +1251,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 +1259,12 @@ ...@@ -1247,6 +1259,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 +1289,10 @@ ...@@ -1271,10 +1289,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 + '(?:\\?([\\s\\S]*))?$');
}, },
// 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 +1300,9 @@ ...@@ -1282,7 +1300,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 +1340,8 @@ ...@@ -1320,8 +1340,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 +1353,11 @@ ...@@ -1333,6 +1353,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 +1370,7 @@ ...@@ -1345,7 +1370,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 +1401,8 @@ ...@@ -1376,7 +1401,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 +1420,6 @@ ...@@ -1394,7 +1420,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 +1427,17 @@ ...@@ -1402,17 +1427,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);
} }
} }
...@@ -1424,7 +1449,7 @@ ...@@ -1424,7 +1449,7 @@
// but possibly useful for unit testing Routers. // but possibly useful for unit testing Routers.
stop: function() { stop: function() {
Backbone.$(window).off('popstate', this.checkUrl).off('hashchange', this.checkUrl); Backbone.$(window).off('popstate', this.checkUrl).off('hashchange', this.checkUrl);
clearInterval(this._checkUrlInterval); if (this._checkUrlInterval) clearInterval(this._checkUrlInterval);
History.started = false; History.started = false;
}, },
...@@ -1472,7 +1497,7 @@ ...@@ -1472,7 +1497,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 +1603,6 @@ ...@@ -1578,4 +1603,6 @@
}; };
}; };
}).call(this); return Backbone;
}));
/** /**
* @license RequireJS text 2.0.10 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved. * @license RequireJS text 2.0.12 Copyright (c) 2010-2014, The Dojo Foundation All Rights Reserved.
* Available via the MIT or new BSD license. * Available via the MIT or new BSD license.
* see: http://github.com/requirejs/text for details * see: http://github.com/requirejs/text for details
*/ */
...@@ -23,7 +23,7 @@ define(['module'], function (module) { ...@@ -23,7 +23,7 @@ define(['module'], function (module) {
masterConfig = (module.config && module.config()) || {}; masterConfig = (module.config && module.config()) || {};
text = { text = {
version: '2.0.10', version: '2.0.12',
strip: function (content) { strip: function (content) {
//Strips <?xml ...?> declarations so that external SVG and XML //Strips <?xml ...?> declarations so that external SVG and XML
...@@ -162,12 +162,12 @@ define(['module'], function (module) { ...@@ -162,12 +162,12 @@ define(['module'], function (module) {
// Do not bother with the work if a build and text will // Do not bother with the work if a build and text will
// not be inlined. // not be inlined.
if (config.isBuild && !config.inlineText) { if (config && config.isBuild && !config.inlineText) {
onLoad(); onLoad();
return; return;
} }
masterConfig.isBuild = config.isBuild; masterConfig.isBuild = config && config.isBuild;
var parsed = text.parseName(name), var parsed = text.parseName(name),
nonStripName = parsed.moduleName + nonStripName = parsed.moduleName +
...@@ -257,7 +257,9 @@ define(['module'], function (module) { ...@@ -257,7 +257,9 @@ define(['module'], function (module) {
} }
callback(file); callback(file);
} catch (e) { } catch (e) {
errback(e); if (errback) {
errback(e);
}
} }
}; };
} else if (masterConfig.env === 'xhr' || (!masterConfig.env && } else if (masterConfig.env === 'xhr' || (!masterConfig.env &&
...@@ -285,12 +287,14 @@ define(['module'], function (module) { ...@@ -285,12 +287,14 @@ define(['module'], function (module) {
//Do not explicitly handle errors, those should be //Do not explicitly handle errors, those should be
//visible via console output in the browser. //visible via console output in the browser.
if (xhr.readyState === 4) { if (xhr.readyState === 4) {
status = xhr.status; status = xhr.status || 0;
if (status > 399 && status < 600) { if (status > 399 && status < 600) {
//An http 4xx or 5xx error. Signal an error. //An http 4xx or 5xx error. Signal an error.
err = new Error(url + ' HTTP status: ' + status); err = new Error(url + ' HTTP status: ' + status);
err.xhr = xhr; err.xhr = xhr;
errback(err); if (errback) {
errback(err);
}
} else { } else {
callback(xhr.responseText); callback(xhr.responseText);
} }
...@@ -347,7 +351,7 @@ define(['module'], function (module) { ...@@ -347,7 +351,7 @@ define(['module'], function (module) {
typeof Components !== 'undefined' && Components.classes && typeof Components !== 'undefined' && Components.classes &&
Components.interfaces)) { Components.interfaces)) {
//Avert your gaze! //Avert your gaze!
Cc = Components.classes, Cc = Components.classes;
Ci = Components.interfaces; Ci = Components.interfaces;
Components.utils['import']('resource://gre/modules/FileUtils.jsm'); Components.utils['import']('resource://gre/modules/FileUtils.jsm');
xpcIsWindows = ('@mozilla.org/windows-registry-key;1' in Cc); xpcIsWindows = ('@mozilla.org/windows-registry-key;1' in Cc);
......
/** vim: et:ts=4:sw=4:sts=4 /** vim: et:ts=4:sw=4:sts=4
* @license RequireJS 2.1.9 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved. * @license RequireJS 2.1.15 Copyright (c) 2010-2014, The Dojo Foundation All Rights Reserved.
* Available via the MIT or new BSD license. * Available via the MIT or new BSD license.
* see: http://github.com/jrburke/requirejs for details * see: http://github.com/jrburke/requirejs for details
*/ */
...@@ -12,7 +12,7 @@ var requirejs, require, define; ...@@ -12,7 +12,7 @@ var requirejs, require, define;
(function (global) { (function (global) {
var req, s, head, baseElement, dataMain, src, var req, s, head, baseElement, dataMain, src,
interactiveScript, currentlyAddingScript, mainScript, subPath, interactiveScript, currentlyAddingScript, mainScript, subPath,
version = '2.1.9', version = '2.1.15',
commentRegExp = /(\/\*([\s\S]*?)\*\/|([^:]|^)\/\/(.*)$)/mg, commentRegExp = /(\/\*([\s\S]*?)\*\/|([^:]|^)\/\/(.*)$)/mg,
cjsRequireRegExp = /[^.]\s*require\s*\(\s*["']([^'"\s]+)["']\s*\)/g, cjsRequireRegExp = /[^.]\s*require\s*\(\s*["']([^'"\s]+)["']\s*\)/g,
jsSuffixRegExp = /\.js$/, jsSuffixRegExp = /\.js$/,
...@@ -108,7 +108,10 @@ var requirejs, require, define; ...@@ -108,7 +108,10 @@ var requirejs, require, define;
if (source) { if (source) {
eachProp(source, function (value, prop) { eachProp(source, function (value, prop) {
if (force || !hasProp(target, prop)) { if (force || !hasProp(target, prop)) {
if (deepStringMixin && typeof value !== 'string') { if (deepStringMixin && typeof value === 'object' && value &&
!isArray(value) && !isFunction(value) &&
!(value instanceof RegExp)) {
if (!target[prop]) { if (!target[prop]) {
target[prop] = {}; target[prop] = {};
} }
...@@ -138,7 +141,7 @@ var requirejs, require, define; ...@@ -138,7 +141,7 @@ var requirejs, require, define;
throw err; throw err;
} }
//Allow getting a global that expressed in //Allow getting a global that is expressed in
//dot notation, like 'a.b.c'. //dot notation, like 'a.b.c'.
function getGlobal(value) { function getGlobal(value) {
if (!value) { if (!value) {
...@@ -177,7 +180,7 @@ var requirejs, require, define; ...@@ -177,7 +180,7 @@ var requirejs, require, define;
if (typeof requirejs !== 'undefined') { if (typeof requirejs !== 'undefined') {
if (isFunction(requirejs)) { if (isFunction(requirejs)) {
//Do not overwrite and existing requirejs instance. //Do not overwrite an existing requirejs instance.
return; return;
} }
cfg = requirejs; cfg = requirejs;
...@@ -201,6 +204,7 @@ var requirejs, require, define; ...@@ -201,6 +204,7 @@ var requirejs, require, define;
waitSeconds: 7, waitSeconds: 7,
baseUrl: './', baseUrl: './',
paths: {}, paths: {},
bundles: {},
pkgs: {}, pkgs: {},
shim: {}, shim: {},
config: {} config: {}
...@@ -214,6 +218,7 @@ var requirejs, require, define; ...@@ -214,6 +218,7 @@ var requirejs, require, define;
defQueue = [], defQueue = [],
defined = {}, defined = {},
urlFetched = {}, urlFetched = {},
bundlesMap = {},
requireCounter = 1, requireCounter = 1,
unnormalizedCounter = 1; unnormalizedCounter = 1;
...@@ -228,20 +233,19 @@ var requirejs, require, define; ...@@ -228,20 +233,19 @@ var requirejs, require, define;
*/ */
function trimDots(ary) { function trimDots(ary) {
var i, part; var i, part;
for (i = 0; ary[i]; i += 1) { for (i = 0; i < ary.length; i++) {
part = ary[i]; part = ary[i];
if (part === '.') { if (part === '.') {
ary.splice(i, 1); ary.splice(i, 1);
i -= 1; i -= 1;
} else if (part === '..') { } else if (part === '..') {
if (i === 1 && (ary[2] === '..' || ary[0] === '..')) { // If at the start, or previous value is still ..,
//End of the line. Keep at least one non-dot // keep them so that when converted to a path it may
//path segment at the front so it can be mapped // still work when converted to a path, even though
//correctly to disk. Otherwise, there is likely // as an ID it is less than ideal. In larger point
//no path mapping for a path starting with '..'. // releases, may be better to just kick out an error.
//This can still fail, but catches the most reasonable if (i === 0 || (i == 1 && ary[2] === '..') || ary[i - 1] === '..') {
//uses of .. continue;
break;
} else if (i > 0) { } else if (i > 0) {
ary.splice(i - 1, 2); ary.splice(i - 1, 2);
i -= 2; i -= 2;
...@@ -261,54 +265,45 @@ var requirejs, require, define; ...@@ -261,54 +265,45 @@ var requirejs, require, define;
* @returns {String} normalized name * @returns {String} normalized name
*/ */
function normalize(name, baseName, applyMap) { function normalize(name, baseName, applyMap) {
var pkgName, pkgConfig, mapValue, nameParts, i, j, nameSegment, var pkgMain, mapValue, nameParts, i, j, nameSegment, lastIndex,
foundMap, foundI, foundStarMap, starI, foundMap, foundI, foundStarMap, starI, normalizedBaseParts,
baseParts = baseName && baseName.split('/'), baseParts = (baseName && baseName.split('/')),
normalizedBaseParts = baseParts,
map = config.map, map = config.map,
starMap = map && map['*']; starMap = map && map['*'];
//Adjust any relative paths. //Adjust any relative paths.
if (name && name.charAt(0) === '.') { if (name) {
//If have a base name, try to normalize against it, name = name.split('/');
//otherwise, assume it is a top-level require that will lastIndex = name.length - 1;
//be relative to baseUrl in the end.
if (baseName) { // If wanting node ID compatibility, strip .js from end
if (getOwn(config.pkgs, baseName)) { // of IDs. Have to do this here, and not in nameToUrl
//If the baseName is a package name, then just treat it as one // because node allows either .js or non .js to map
//name to concat the name with. // to same file.
normalizedBaseParts = baseParts = [baseName]; if (config.nodeIdCompat && jsSuffixRegExp.test(name[lastIndex])) {
} else { name[lastIndex] = name[lastIndex].replace(jsSuffixRegExp, '');
//Convert baseName to array, and lop off the last part, }
//so that . matches that 'directory' and not name of the baseName's
//module. For instance, baseName of 'one/two/three', maps to
//'one/two/three.js', but we want the directory, 'one/two' for
//this normalization.
normalizedBaseParts = baseParts.slice(0, baseParts.length - 1);
}
name = normalizedBaseParts.concat(name.split('/'));
trimDots(name);
//Some use of packages may use a . path to reference the // Starts with a '.' so need the baseName
//'main' module name, so normalize for that. if (name[0].charAt(0) === '.' && baseParts) {
pkgConfig = getOwn(config.pkgs, (pkgName = name[0])); //Convert baseName to array, and lop off the last part,
name = name.join('/'); //so that . matches that 'directory' and not name of the baseName's
if (pkgConfig && name === pkgName + '/' + pkgConfig.main) { //module. For instance, baseName of 'one/two/three', maps to
name = pkgName; //'one/two/three.js', but we want the directory, 'one/two' for
} //this normalization.
} else if (name.indexOf('./') === 0) { normalizedBaseParts = baseParts.slice(0, baseParts.length - 1);
// No baseName, so this is ID is resolved relative name = normalizedBaseParts.concat(name);
// to baseUrl, pull off the leading dot.
name = name.substring(2);
} }
trimDots(name);
name = name.join('/');
} }
//Apply map config if available. //Apply map config if available.
if (applyMap && map && (baseParts || starMap)) { if (applyMap && map && (baseParts || starMap)) {
nameParts = name.split('/'); nameParts = name.split('/');
for (i = nameParts.length; i > 0; i -= 1) { outerLoop: for (i = nameParts.length; i > 0; i -= 1) {
nameSegment = nameParts.slice(0, i).join('/'); nameSegment = nameParts.slice(0, i).join('/');
if (baseParts) { if (baseParts) {
...@@ -325,16 +320,12 @@ var requirejs, require, define; ...@@ -325,16 +320,12 @@ var requirejs, require, define;
//Match, update name to the new value. //Match, update name to the new value.
foundMap = mapValue; foundMap = mapValue;
foundI = i; foundI = i;
break; break outerLoop;
} }
} }
} }
} }
if (foundMap) {
break;
}
//Check for a star map match, but just hold on to it, //Check for a star map match, but just hold on to it,
//if there is a shorter segment match later in a matching //if there is a shorter segment match later in a matching
//config, then favor over this star map. //config, then favor over this star map.
...@@ -355,7 +346,11 @@ var requirejs, require, define; ...@@ -355,7 +346,11 @@ var requirejs, require, define;
} }
} }
return name; // If the name points to a package's name, use
// the package main instead.
pkgMain = getOwn(config.pkgs, name);
return pkgMain ? pkgMain : name;
} }
function removeScript(name) { function removeScript(name) {
...@@ -377,7 +372,13 @@ var requirejs, require, define; ...@@ -377,7 +372,13 @@ var requirejs, require, define;
//retry //retry
pathConfig.shift(); pathConfig.shift();
context.require.undef(id); context.require.undef(id);
context.require([id]);
//Custom require that does not do map translation, since
//ID is "absolute", already mapped/resolved.
context.makeRequire(null, {
skipMap: true
})([id]);
return true; return true;
} }
} }
...@@ -443,7 +444,16 @@ var requirejs, require, define; ...@@ -443,7 +444,16 @@ var requirejs, require, define;
return normalize(name, parentName, applyMap); return normalize(name, parentName, applyMap);
}); });
} else { } else {
normalizedName = normalize(name, parentName, applyMap); // If nested plugin references, then do not try to
// normalize, as it will not normalize correctly. This
// places a restriction on resourceIds, and the longer
// term solution is not to normalize until plugins are
// loaded and all normalizations to allow for async
// loading of a loader plugin. But for now, fixes the
// common uses. Details in #1131
normalizedName = name.indexOf('!') === -1 ?
normalize(name, parentName, applyMap) :
name;
} }
} else { } else {
//A regular module. //A regular module.
...@@ -548,7 +558,7 @@ var requirejs, require, define; ...@@ -548,7 +558,7 @@ var requirejs, require, define;
//local var ref to defQueue, so cannot just reassign the one //local var ref to defQueue, so cannot just reassign the one
//on context. //on context.
apsp.apply(defQueue, apsp.apply(defQueue,
[defQueue.length - 1, 0].concat(globalDefQueue)); [defQueue.length, 0].concat(globalDefQueue));
globalDefQueue = []; globalDefQueue = [];
} }
} }
...@@ -565,7 +575,7 @@ var requirejs, require, define; ...@@ -565,7 +575,7 @@ var requirejs, require, define;
mod.usingExports = true; mod.usingExports = true;
if (mod.map.isDefine) { if (mod.map.isDefine) {
if (mod.exports) { if (mod.exports) {
return mod.exports; return (defined[mod.map.id] = mod.exports);
} else { } else {
return (mod.exports = defined[mod.map.id] = {}); return (mod.exports = defined[mod.map.id] = {});
} }
...@@ -579,15 +589,9 @@ var requirejs, require, define; ...@@ -579,15 +589,9 @@ var requirejs, require, define;
id: mod.map.id, id: mod.map.id,
uri: mod.map.url, uri: mod.map.url,
config: function () { config: function () {
var c, return getOwn(config.config, mod.map.id) || {};
pkg = getOwn(config.pkgs, mod.map.id);
// For packages, only support config targeted
// at the main module.
c = pkg ? getOwn(config.config, mod.map.id + '/' + pkg.main) :
getOwn(config.config, mod.map.id);
return c || {};
}, },
exports: defined[mod.map.id] exports: mod.exports || (mod.exports = {})
}); });
} }
} }
...@@ -628,7 +632,7 @@ var requirejs, require, define; ...@@ -628,7 +632,7 @@ var requirejs, require, define;
} }
function checkLoaded() { function checkLoaded() {
var map, modId, err, usingPathFallback, var err, usingPathFallback,
waitInterval = config.waitSeconds * 1000, waitInterval = config.waitSeconds * 1000,
//It is possible to disable the wait interval by using waitSeconds of 0. //It is possible to disable the wait interval by using waitSeconds of 0.
expired = waitInterval && (context.startTime + waitInterval) < new Date().getTime(), expired = waitInterval && (context.startTime + waitInterval) < new Date().getTime(),
...@@ -646,8 +650,8 @@ var requirejs, require, define; ...@@ -646,8 +650,8 @@ var requirejs, require, define;
//Figure out the state of all the modules. //Figure out the state of all the modules.
eachProp(enabledRegistry, function (mod) { eachProp(enabledRegistry, function (mod) {
map = mod.map; var map = mod.map,
modId = map.id; modId = map.id;
//Skip things that are not enabled or in error state. //Skip things that are not enabled or in error state.
if (!mod.enabled) { if (!mod.enabled) {
...@@ -870,17 +874,14 @@ var requirejs, require, define; ...@@ -870,17 +874,14 @@ var requirejs, require, define;
exports = context.execCb(id, factory, depExports, exports); exports = context.execCb(id, factory, depExports, exports);
} }
if (this.map.isDefine) { // Favor return value over exports. If node/cjs in play,
//If setting exports via 'module' is in play, // then will not have a return value anyway. Favor
//favor that over return value and exports. After that, // module.exports assignment over exports object.
//favor a non-undefined return value over exports use. if (this.map.isDefine && exports === undefined) {
cjsModule = this.module; cjsModule = this.module;
if (cjsModule && if (cjsModule) {
cjsModule.exports !== undefined &&
//Make sure it is not already the exports value
cjsModule.exports !== this.exports) {
exports = cjsModule.exports; exports = cjsModule.exports;
} else if (exports === undefined && this.usingExports) { } else if (this.usingExports) {
//exports already set the defined value. //exports already set the defined value.
exports = this.exports; exports = this.exports;
} }
...@@ -940,6 +941,7 @@ var requirejs, require, define; ...@@ -940,6 +941,7 @@ var requirejs, require, define;
on(pluginMap, 'defined', bind(this, function (plugin) { on(pluginMap, 'defined', bind(this, function (plugin) {
var load, normalizedMap, normalizedMod, var load, normalizedMap, normalizedMod,
bundleId = getOwn(bundlesMap, this.map.id),
name = this.map.name, name = this.map.name,
parentName = this.map.parentMap ? this.map.parentMap.name : null, parentName = this.map.parentMap ? this.map.parentMap.name : null,
localRequire = context.makeRequire(map.parentMap, { localRequire = context.makeRequire(map.parentMap, {
...@@ -985,6 +987,14 @@ var requirejs, require, define; ...@@ -985,6 +987,14 @@ var requirejs, require, define;
return; return;
} }
//If a paths config, then just load that file instead to
//resolve the plugin, as it is built into that paths layer.
if (bundleId) {
this.map.url = context.nameToUrl(bundleId);
this.load();
return;
}
load = bind(this, function (value) { load = bind(this, function (value) {
this.init([], function () { return value; }, null, { this.init([], function () { return value; }, null, {
enabled: true enabled: true
...@@ -1249,31 +1259,38 @@ var requirejs, require, define; ...@@ -1249,31 +1259,38 @@ var requirejs, require, define;
} }
} }
//Save off the paths and packages since they require special processing, //Save off the paths since they require special processing,
//they are additive. //they are additive.
var pkgs = config.pkgs, var shim = config.shim,
shim = config.shim,
objs = { objs = {
paths: true, paths: true,
bundles: true,
config: true, config: true,
map: true map: true
}; };
eachProp(cfg, function (value, prop) { eachProp(cfg, function (value, prop) {
if (objs[prop]) { if (objs[prop]) {
if (prop === 'map') { if (!config[prop]) {
if (!config.map) { config[prop] = {};
config.map = {};
}
mixin(config[prop], value, true, true);
} else {
mixin(config[prop], value, true);
} }
mixin(config[prop], value, true, true);
} else { } else {
config[prop] = value; config[prop] = value;
} }
}); });
//Reverse map the bundles
if (cfg.bundles) {
eachProp(cfg.bundles, function (value, prop) {
each(value, function (v) {
if (v !== prop) {
bundlesMap[v] = prop;
}
});
});
}
//Merge shim //Merge shim
if (cfg.shim) { if (cfg.shim) {
eachProp(cfg.shim, function (value, id) { eachProp(cfg.shim, function (value, id) {
...@@ -1294,29 +1311,25 @@ var requirejs, require, define; ...@@ -1294,29 +1311,25 @@ var requirejs, require, define;
//Adjust packages if necessary. //Adjust packages if necessary.
if (cfg.packages) { if (cfg.packages) {
each(cfg.packages, function (pkgObj) { each(cfg.packages, function (pkgObj) {
var location; var location, name;
pkgObj = typeof pkgObj === 'string' ? { name: pkgObj } : pkgObj; pkgObj = typeof pkgObj === 'string' ? { name: pkgObj } : pkgObj;
name = pkgObj.name;
location = pkgObj.location; location = pkgObj.location;
if (location) {
config.paths[name] = pkgObj.location;
}
//Create a brand new object on pkgs, since currentPackages can //Save pointer to main module ID for pkg name.
//be passed in again, and config.pkgs is the internal transformed //Remove leading dot in main, so main paths are normalized,
//state for all package configs. //and remove any trailing .js, since different package
pkgs[pkgObj.name] = { //envs have different conventions: some use a module name,
name: pkgObj.name, //some use a file name.
location: location || pkgObj.name, config.pkgs[name] = pkgObj.name + '/' + (pkgObj.main || 'main')
//Remove leading dot in main, so main paths are normalized, .replace(currDirRegExp, '')
//and remove any trailing .js, since different package .replace(jsSuffixRegExp, '');
//envs have different conventions: some use a module name,
//some use a file name.
main: (pkgObj.main || 'main')
.replace(currDirRegExp, '')
.replace(jsSuffixRegExp, '')
};
}); });
//Done with modifications, assing packages back to context config
config.pkgs = pkgs;
} }
//If there are any "waiting to execute" modules in the registry, //If there are any "waiting to execute" modules in the registry,
...@@ -1469,6 +1482,15 @@ var requirejs, require, define; ...@@ -1469,6 +1482,15 @@ var requirejs, require, define;
delete urlFetched[map.url]; delete urlFetched[map.url];
delete undefEvents[id]; delete undefEvents[id];
//Clean queued defines too. Go backwards
//in array so that the splices do not
//mess up the iteration.
eachReverse(defQueue, function(args, i) {
if(args[0] === id) {
defQueue.splice(i, 1);
}
});
if (mod) { if (mod) {
//Hold on to listeners in case the //Hold on to listeners in case the
//module will be attempted to be reloaded //module will be attempted to be reloaded
...@@ -1488,7 +1510,7 @@ var requirejs, require, define; ...@@ -1488,7 +1510,7 @@ var requirejs, require, define;
/** /**
* Called to enable a module if it is still in the registry * Called to enable a module if it is still in the registry
* awaiting enablement. A second arg, parent, the parent module, * awaiting enablement. A second arg, parent, the parent module,
* is passed in for context, when this method is overriden by * is passed in for context, when this method is overridden by
* the optimizer. Not shown here to keep code compact. * the optimizer. Not shown here to keep code compact.
*/ */
enable: function (depMap) { enable: function (depMap) {
...@@ -1562,8 +1584,19 @@ var requirejs, require, define; ...@@ -1562,8 +1584,19 @@ var requirejs, require, define;
* internal API, not a public one. Use toUrl for the public API. * internal API, not a public one. Use toUrl for the public API.
*/ */
nameToUrl: function (moduleName, ext, skipExt) { nameToUrl: function (moduleName, ext, skipExt) {
var paths, pkgs, pkg, pkgPath, syms, i, parentModule, url, var paths, syms, i, parentModule, url,
parentPath; parentPath, bundleId,
pkgMain = getOwn(config.pkgs, moduleName);
if (pkgMain) {
moduleName = pkgMain;
}
bundleId = getOwn(bundlesMap, moduleName);
if (bundleId) {
return context.nameToUrl(bundleId, ext, skipExt);
}
//If a colon is in the URL, it indicates a protocol is used and it is just //If a colon is in the URL, it indicates a protocol is used and it is just
//an URL to a file, or if it starts with a slash, contains a query arg (i.e. ?) //an URL to a file, or if it starts with a slash, contains a query arg (i.e. ?)
...@@ -1577,7 +1610,6 @@ var requirejs, require, define; ...@@ -1577,7 +1610,6 @@ var requirejs, require, define;
} else { } else {
//A module that needs to be converted to a path. //A module that needs to be converted to a path.
paths = config.paths; paths = config.paths;
pkgs = config.pkgs;
syms = moduleName.split('/'); syms = moduleName.split('/');
//For each module name segment, see if there is a path //For each module name segment, see if there is a path
...@@ -1585,7 +1617,7 @@ var requirejs, require, define; ...@@ -1585,7 +1617,7 @@ var requirejs, require, define;
//and work up from it. //and work up from it.
for (i = syms.length; i > 0; i -= 1) { for (i = syms.length; i > 0; i -= 1) {
parentModule = syms.slice(0, i).join('/'); parentModule = syms.slice(0, i).join('/');
pkg = getOwn(pkgs, parentModule);
parentPath = getOwn(paths, parentModule); parentPath = getOwn(paths, parentModule);
if (parentPath) { if (parentPath) {
//If an array, it means there are a few choices, //If an array, it means there are a few choices,
...@@ -1595,16 +1627,6 @@ var requirejs, require, define; ...@@ -1595,16 +1627,6 @@ var requirejs, require, define;
} }
syms.splice(0, i, parentPath); syms.splice(0, i, parentPath);
break; break;
} else if (pkg) {
//If module name is just the package name, then looking
//for the main module.
if (moduleName === pkg.name) {
pkgPath = pkg.location + '/' + pkg.main;
} else {
pkgPath = pkg.location;
}
syms.splice(0, i, pkgPath);
break;
} }
} }
......
...@@ -12,104 +12,81 @@ button { ...@@ -12,104 +12,81 @@ button {
font-size: 100%; font-size: 100%;
vertical-align: baseline; vertical-align: baseline;
font-family: inherit; font-family: inherit;
font-weight: inherit;
color: inherit; color: inherit;
-webkit-appearance: none; -webkit-appearance: none;
-ms-appearance: none; -ms-appearance: none;
-o-appearance: none;
appearance: none; appearance: none;
-webkit-font-smoothing: antialiased;
-moz-font-smoothing: antialiased;
-ms-font-smoothing: antialiased;
font-smoothing: antialiased;
} }
body { body {
font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif; font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
line-height: 1.4em; line-height: 1.4em;
background: #eaeaea url('bg.png'); background: #f5f5f5;
color: #4d4d4d; color: #4d4d4d;
width: 550px; min-width: 230px;
max-width: 550px;
margin: 0 auto; margin: 0 auto;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-font-smoothing: antialiased; -moz-font-smoothing: antialiased;
-ms-font-smoothing: antialiased; -ms-font-smoothing: antialiased;
-o-font-smoothing: antialiased;
font-smoothing: antialiased; font-smoothing: antialiased;
font-weight: 300;
} }
button, button,
input[type="checkbox"] { input[type="checkbox"] {
outline: none; outline: none;
}
.hidden {
display: none;
} }
#todoapp { #todoapp {
background: #fff; background: #fff;
background: rgba(255, 255, 255, 0.9);
margin: 130px 0 40px 0; margin: 130px 0 40px 0;
border: 1px solid #ccc;
position: relative; position: relative;
border-top-left-radius: 2px; box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2),
border-top-right-radius: 2px; 0 25px 50px 0 rgba(0, 0, 0, 0.1);
box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.2),
0 25px 50px 0 rgba(0, 0, 0, 0.15);
}
#todoapp:before {
content: '';
border-left: 1px solid #f5d6d6;
border-right: 1px solid #f5d6d6;
width: 2px;
position: absolute;
top: 0;
left: 40px;
height: 100%;
} }
#todoapp input::-webkit-input-placeholder { #todoapp input::-webkit-input-placeholder {
font-style: italic; font-style: italic;
font-weight: 300;
color: #e6e6e6;
} }
#todoapp input::-moz-placeholder { #todoapp input::-moz-placeholder {
font-style: italic; font-style: italic;
color: #a9a9a9; font-weight: 300;
color: #e6e6e6;
}
#todoapp input::input-placeholder {
font-style: italic;
font-weight: 300;
color: #e6e6e6;
} }
#todoapp h1 { #todoapp h1 {
position: absolute; position: absolute;
top: -120px; top: -155px;
width: 100%; width: 100%;
font-size: 70px; font-size: 100px;
font-weight: bold; font-weight: 100;
text-align: center; text-align: center;
color: #b3b3b3; color: rgba(175, 47, 47, 0.15);
color: rgba(255, 255, 255, 0.3);
text-shadow: -1px -1px rgba(0, 0, 0, 0.2);
-webkit-text-rendering: optimizeLegibility; -webkit-text-rendering: optimizeLegibility;
-moz-text-rendering: optimizeLegibility; -moz-text-rendering: optimizeLegibility;
-ms-text-rendering: optimizeLegibility; -ms-text-rendering: optimizeLegibility;
-o-text-rendering: optimizeLegibility;
text-rendering: optimizeLegibility; text-rendering: optimizeLegibility;
} }
#header {
padding-top: 15px;
border-radius: inherit;
}
#header:before {
content: '';
position: absolute;
top: 0;
right: 0;
left: 0;
height: 15px;
z-index: 2;
border-bottom: 1px solid #6c615c;
background: #8d7d77;
background: -webkit-gradient(linear, left top, left bottom, from(rgba(132, 110, 100, 0.8)),to(rgba(101, 84, 76, 0.8)));
background: -webkit-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
background: linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr='#9d8b83', EndColorStr='#847670');
border-top-left-radius: 1px;
border-top-right-radius: 1px;
}
#new-todo, #new-todo,
.edit { .edit {
position: relative; position: relative;
...@@ -117,6 +94,7 @@ input[type="checkbox"] { ...@@ -117,6 +94,7 @@ input[type="checkbox"] {
width: 100%; width: 100%;
font-size: 24px; font-size: 24px;
font-family: inherit; font-family: inherit;
font-weight: inherit;
line-height: 1.4em; line-height: 1.4em;
border: 0; border: 0;
outline: none; outline: none;
...@@ -124,29 +102,25 @@ input[type="checkbox"] { ...@@ -124,29 +102,25 @@ input[type="checkbox"] {
padding: 6px; padding: 6px;
border: 1px solid #999; border: 1px solid #999;
box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2); box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);
-moz-box-sizing: border-box;
-ms-box-sizing: border-box; -ms-box-sizing: border-box;
-o-box-sizing: border-box;
box-sizing: border-box; box-sizing: border-box;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-font-smoothing: antialiased; -moz-font-smoothing: antialiased;
-ms-font-smoothing: antialiased; -ms-font-smoothing: antialiased;
-o-font-smoothing: antialiased;
font-smoothing: antialiased; font-smoothing: antialiased;
} }
#new-todo { #new-todo {
padding: 16px 16px 16px 60px; padding: 16px 16px 16px 60px;
border: none; border: none;
background: rgba(0, 0, 0, 0.02); background: rgba(0, 0, 0, 0.003);
z-index: 2; box-shadow: inset 0 -2px 1px rgba(0,0,0,0.03);
box-shadow: none;
} }
#main { #main {
position: relative; position: relative;
z-index: 2; z-index: 2;
border-top: 1px dotted #adadad; border-top: 1px solid #e6e6e6;
} }
label[for='toggle-all'] { label[for='toggle-all'] {
...@@ -155,19 +129,19 @@ label[for='toggle-all'] { ...@@ -155,19 +129,19 @@ label[for='toggle-all'] {
#toggle-all { #toggle-all {
position: absolute; position: absolute;
top: -42px; top: -55px;
left: -4px; left: -12px;
width: 40px; width: 60px;
height: 34px;
text-align: center; text-align: center;
/* Mobile Safari */ border: none; /* Mobile Safari */
border: none;
} }
#toggle-all:before { #toggle-all:before {
content: '»'; content: '';
font-size: 28px; font-size: 22px;
color: #d9d9d9; color: #e6e6e6;
padding: 0 25px 7px; padding: 10px 27px 10px 27px;
} }
#toggle-all:checked:before { #toggle-all:checked:before {
...@@ -183,7 +157,7 @@ label[for='toggle-all'] { ...@@ -183,7 +157,7 @@ label[for='toggle-all'] {
#todo-list li { #todo-list li {
position: relative; position: relative;
font-size: 24px; font-size: 24px;
border-bottom: 1px dotted #ccc; border-bottom: 1px solid #ededed;
} }
#todo-list li:last-child { #todo-list li:last-child {
...@@ -215,28 +189,18 @@ label[for='toggle-all'] { ...@@ -215,28 +189,18 @@ label[for='toggle-all'] {
top: 0; top: 0;
bottom: 0; bottom: 0;
margin: auto 0; margin: auto 0;
/* Mobile Safari */ border: none; /* Mobile Safari */
border: none;
-webkit-appearance: none; -webkit-appearance: none;
-ms-appearance: none; -ms-appearance: none;
-o-appearance: none;
appearance: none; appearance: none;
} }
#todo-list li .toggle:after { #todo-list li .toggle:after {
content: '✔'; content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="-10 -18 100 135"><circle cx="50" cy="50" r="50" fill="none" stroke="#ededed" stroke-width="3"/></svg>');
/* 40 + a couple of pixels visual adjustment */
line-height: 43px;
font-size: 20px;
color: #d9d9d9;
text-shadow: 0 -1px 0 #bfbfbf;
} }
#todo-list li .toggle:checked:after { #todo-list li .toggle:checked:after {
color: #85ada7; content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="-10 -18 100 135"><circle cx="50" cy="50" r="50" fill="none" stroke="#bddad5" stroke-width="3"/><path fill="#5dc2af" d="M72 25L42 71 27 56l-4 4 20 20 34-52z"/></svg>');
text-shadow: 0 1px 0 #669991;
bottom: 1px;
position: relative;
} }
#todo-list li label { #todo-list li label {
...@@ -246,12 +210,11 @@ label[for='toggle-all'] { ...@@ -246,12 +210,11 @@ label[for='toggle-all'] {
margin-left: 45px; margin-left: 45px;
display: block; display: block;
line-height: 1.2; line-height: 1.2;
-webkit-transition: color 0.4s;
transition: color 0.4s; transition: color 0.4s;
} }
#todo-list li.completed label { #todo-list li.completed label {
color: #a9a9a9; color: #d9d9d9;
text-decoration: line-through; text-decoration: line-through;
} }
...@@ -264,21 +227,18 @@ label[for='toggle-all'] { ...@@ -264,21 +227,18 @@ label[for='toggle-all'] {
width: 40px; width: 40px;
height: 40px; height: 40px;
margin: auto 0; margin: auto 0;
font-size: 22px; font-size: 30px;
color: #a88a8a; color: #cc9a9a;
-webkit-transition: all 0.2s; margin-bottom: 11px;
transition: all 0.2s; transition: color 0.2s ease-out;
} }
#todo-list li .destroy:hover { #todo-list li .destroy:hover {
text-shadow: 0 0 1px #000, color: #af5b5e;
0 0 10px rgba(199, 107, 107, 0.8);
-webkit-transform: scale(1.3);
transform: scale(1.3);
} }
#todo-list li .destroy:after { #todo-list li .destroy:after {
content: ''; content: '×';
} }
#todo-list li:hover .destroy { #todo-list li:hover .destroy {
...@@ -295,29 +255,25 @@ label[for='toggle-all'] { ...@@ -295,29 +255,25 @@ label[for='toggle-all'] {
#footer { #footer {
color: #777; color: #777;
padding: 0 15px; padding: 10px 15px;
position: absolute;
right: 0;
bottom: -31px;
left: 0;
height: 20px; height: 20px;
z-index: 1;
text-align: center; text-align: center;
border-top: 1px solid #e6e6e6;
} }
#footer:before { #footer:before {
content: ''; content: '';
position: absolute; position: absolute;
right: 0; right: 0;
bottom: 31px; bottom: 0;
left: 0; left: 0;
height: 50px; height: 50px;
z-index: -1; overflow: hidden;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.3), box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2),
0 6px 0 -3px rgba(255, 255, 255, 0.8), 0 8px 0 -3px #f6f6f6,
0 7px 1px -3px rgba(0, 0, 0, 0.3), 0 9px 1px -3px rgba(0, 0, 0, 0.2),
0 43px 0 -6px rgba(255, 255, 255, 0.8), 0 16px 0 -6px #f6f6f6,
0 44px 2px -6px rgba(0, 0, 0, 0.2); 0 17px 2px -6px rgba(0, 0, 0, 0.2);
} }
#todo-count { #todo-count {
...@@ -325,6 +281,10 @@ label[for='toggle-all'] { ...@@ -325,6 +281,10 @@ label[for='toggle-all'] {
text-align: left; text-align: left;
} }
#todo-count strong {
font-weight: 300;
}
#filters { #filters {
margin: 0; margin: 0;
padding: 0; padding: 0;
...@@ -339,49 +299,72 @@ label[for='toggle-all'] { ...@@ -339,49 +299,72 @@ label[for='toggle-all'] {
} }
#filters li a { #filters li a {
color: #83756f; color: inherit;
margin: 2px; margin: 3px;
padding: 3px 7px;
text-decoration: none; text-decoration: none;
border: 1px solid transparent;
border-radius: 3px;
}
#filters li a.selected,
#filters li a:hover {
border-color: rgba(175, 47, 47, 0.1);
} }
#filters li a.selected { #filters li a.selected {
font-weight: bold; border-color: rgba(175, 47, 47, 0.2);
} }
#clear-completed { #clear-completed,
html #clear-completed:active {
float: right; float: right;
position: relative; position: relative;
line-height: 20px; line-height: 20px;
text-decoration: none; text-decoration: none;
background: rgba(0, 0, 0, 0.1); cursor: pointer;
font-size: 11px; visibility: hidden;
padding: 0 10px; position: relative;
border-radius: 3px; }
box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.2);
#clear-completed::after {
visibility: visible;
content: 'Clear completed';
position: absolute;
right: 0;
white-space: nowrap;
} }
#clear-completed:hover { #clear-completed:hover::after {
background: rgba(0, 0, 0, 0.15); text-decoration: underline;
box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.3);
} }
#info { #info {
margin: 65px auto 0; margin: 65px auto 0;
color: #a6a6a6; color: #bfbfbf;
font-size: 12px; font-size: 10px;
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.7); text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
text-align: center; text-align: center;
} }
#info p {
line-height: 1;
}
#info a { #info a {
color: inherit; color: inherit;
text-decoration: none;
font-weight: 400;
}
#info a:hover {
text-decoration: underline;
} }
/* /*
Hack to remove background from Mobile Safari. Hack to remove background from Mobile Safari.
Can't use it globally since it destroys checkboxes in Firefox and Opera Can't use it globally since it destroys checkboxes in Firefox
*/ */
@media screen and (-webkit-min-device-pixel-ratio:0) { @media screen and (-webkit-min-device-pixel-ratio:0) {
#toggle-all, #toggle-all,
#todo-list li .toggle { #todo-list li .toggle {
...@@ -393,10 +376,6 @@ label[for='toggle-all'] { ...@@ -393,10 +376,6 @@ label[for='toggle-all'] {
} }
#toggle-all { #toggle-all {
top: -56px;
left: -15px;
width: 65px;
height: 41px;
-webkit-transform: rotate(90deg); -webkit-transform: rotate(90deg);
transform: rotate(90deg); transform: rotate(90deg);
-webkit-appearance: none; -webkit-appearance: none;
...@@ -404,151 +383,12 @@ label[for='toggle-all'] { ...@@ -404,151 +383,12 @@ label[for='toggle-all'] {
} }
} }
.hidden { @media (max-width: 430px) {
display: none; #footer {
} height: 50px;
hr {
margin: 20px 0;
border: 0;
border-top: 1px dashed #C5C5C5;
border-bottom: 1px dashed #F7F7F7;
}
.learn a {
font-weight: normal;
text-decoration: none;
color: #b83f45;
}
.learn a:hover {
text-decoration: underline;
color: #787e7e;
}
.learn h3,
.learn h4,
.learn h5 {
margin: 10px 0;
font-weight: 500;
line-height: 1.2;
color: #000;
}
.learn h3 {
font-size: 24px;
}
.learn h4 {
font-size: 18px;
}
.learn h5 {
margin-bottom: 0;
font-size: 14px;
}
.learn ul {
padding: 0;
margin: 0 0 30px 25px;
}
.learn li {
line-height: 20px;
}
.learn p {
font-size: 15px;
font-weight: 300;
line-height: 1.3;
margin-top: 0;
margin-bottom: 0;
}
.quote {
border: none;
margin: 20px 0 60px 0;
}
.quote p {
font-style: italic;
}
.quote p:before {
content: '“';
font-size: 50px;
opacity: .15;
position: absolute;
top: -20px;
left: 3px;
}
.quote p:after {
content: '”';
font-size: 50px;
opacity: .15;
position: absolute;
bottom: -42px;
right: 3px;
}
.quote footer {
position: absolute;
bottom: -40px;
right: 0;
}
.quote footer img {
border-radius: 3px;
}
.quote footer a {
margin-left: 5px;
vertical-align: middle;
}
.speech-bubble {
position: relative;
padding: 10px;
background: rgba(0, 0, 0, .04);
border-radius: 5px;
}
.speech-bubble:after {
content: '';
position: absolute;
top: 100%;
right: 30px;
border: 13px solid transparent;
border-top-color: rgba(0, 0, 0, .04);
}
.learn-bar > .learn {
position: absolute;
width: 272px;
top: 8px;
left: -300px;
padding: 10px;
border-radius: 5px;
background-color: rgba(255, 255, 255, .6);
-webkit-transition-property: left;
transition-property: left;
-webkit-transition-duration: 500ms;
transition-duration: 500ms;
}
@media (min-width: 899px) {
.learn-bar {
width: auto;
margin: 0 0 0 300px;
}
.learn-bar > .learn {
left: 8px;
} }
.learn-bar #todoapp { #filters {
width: 550px; bottom: 10px;
margin: 130px auto 40px auto;
} }
} }
hr {
margin: 20px 0;
border: 0;
border-top: 1px dashed #c5c5c5;
border-bottom: 1px dashed #f7f7f7;
}
.learn a {
font-weight: normal;
text-decoration: none;
color: #b83f45;
}
.learn a:hover {
text-decoration: underline;
color: #787e7e;
}
.learn h3,
.learn h4,
.learn h5 {
margin: 10px 0;
font-weight: 500;
line-height: 1.2;
color: #000;
}
.learn h3 {
font-size: 24px;
}
.learn h4 {
font-size: 18px;
}
.learn h5 {
margin-bottom: 0;
font-size: 14px;
}
.learn ul {
padding: 0;
margin: 0 0 30px 25px;
}
.learn li {
line-height: 20px;
}
.learn p {
font-size: 15px;
font-weight: 300;
line-height: 1.3;
margin-top: 0;
margin-bottom: 0;
}
#issue-count {
display: none;
}
.quote {
border: none;
margin: 20px 0 60px 0;
}
.quote p {
font-style: italic;
}
.quote p:before {
content: '“';
font-size: 50px;
opacity: .15;
position: absolute;
top: -20px;
left: 3px;
}
.quote p:after {
content: '”';
font-size: 50px;
opacity: .15;
position: absolute;
bottom: -42px;
right: 3px;
}
.quote footer {
position: absolute;
bottom: -40px;
right: 0;
}
.quote footer img {
border-radius: 3px;
}
.quote footer a {
margin-left: 5px;
vertical-align: middle;
}
.speech-bubble {
position: relative;
padding: 10px;
background: rgba(0, 0, 0, .04);
border-radius: 5px;
}
.speech-bubble:after {
content: '';
position: absolute;
top: 100%;
right: 30px;
border: 13px solid transparent;
border-top-color: rgba(0, 0, 0, .04);
}
.learn-bar > .learn {
position: absolute;
width: 272px;
top: 8px;
left: -300px;
padding: 10px;
border-radius: 5px;
background-color: rgba(255, 255, 255, .6);
transition-property: left;
transition-duration: 500ms;
}
@media (min-width: 899px) {
.learn-bar {
width: auto;
padding-left: 300px;
}
.learn-bar > .learn {
left: 8px;
}
}
/* global _ */
(function () { (function () {
'use strict'; 'use strict';
/* jshint ignore:start */
// Underscore's Template Module // Underscore's Template Module
// Courtesy of underscorejs.org // Courtesy of underscorejs.org
var _ = (function (_) { var _ = (function (_) {
...@@ -114,6 +116,7 @@ ...@@ -114,6 +116,7 @@
if (location.hostname === 'todomvc.com') { if (location.hostname === 'todomvc.com') {
window._gaq = [['_setAccount','UA-31081062-1'],['_trackPageview']];(function(d,t){var g=d.createElement(t),s=d.getElementsByTagName(t)[0];g.src='//www.google-analytics.com/ga.js';s.parentNode.insertBefore(g,s)}(document,'script')); window._gaq = [['_setAccount','UA-31081062-1'],['_trackPageview']];(function(d,t){var g=d.createElement(t),s=d.getElementsByTagName(t)[0];g.src='//www.google-analytics.com/ga.js';s.parentNode.insertBefore(g,s)}(document,'script'));
} }
/* jshint ignore:end */
function redirect() { function redirect() {
if (location.hostname === 'tastejs.github.io') { if (location.hostname === 'tastejs.github.io') {
...@@ -175,13 +178,17 @@ ...@@ -175,13 +178,17 @@
if (learnJSON.backend) { if (learnJSON.backend) {
this.frameworkJSON = learnJSON.backend; this.frameworkJSON = learnJSON.backend;
this.frameworkJSON.issueLabel = framework;
this.append({ this.append({
backend: true backend: true
}); });
} else if (learnJSON[framework]) { } else if (learnJSON[framework]) {
this.frameworkJSON = learnJSON[framework]; this.frameworkJSON = learnJSON[framework];
this.frameworkJSON.issueLabel = framework;
this.append(); this.append();
} }
this.fetchIssueCount();
} }
Learn.prototype.append = function (opts) { Learn.prototype.append = function (opts) {
...@@ -212,6 +219,26 @@ ...@@ -212,6 +219,26 @@
document.body.insertAdjacentHTML('afterBegin', aside.outerHTML); document.body.insertAdjacentHTML('afterBegin', aside.outerHTML);
}; };
Learn.prototype.fetchIssueCount = function () {
var issueLink = document.getElementById('issue-count-link');
if (issueLink) {
var url = issueLink.href.replace('https://github.com', 'https://api.github.com/repos');
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.onload = function (e) {
var parsedResponse = JSON.parse(e.target.responseText);
if (parsedResponse instanceof Array) {
var count = parsedResponse.length
if (count !== 0) {
issueLink.innerHTML = 'This app has ' + count + ' open issues';
document.getElementById('issue-count').style.display = 'inline';
}
}
};
xhr.send();
}
};
redirect(); redirect();
getFile('learn.json', Learn); getFile('learn.json', Learn);
})(); })();
// Underscore.js 1.5.2 // Underscore.js 1.7.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() {
...@@ -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,97 +52,125 @@ ...@@ -65,97 +52,125 @@
} }
// Current version. // Current version.
_.VERSION = '1.5.2'; _.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; 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 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, iterator, context) { _.find = _.detect = function(obj, predicate, context) {
var result; var result;
any(obj, function(value, index, list) { predicate = _.iteratee(predicate, context);
if (iterator.call(context, value, index, list)) { _.some(obj, function(value, index, list) {
if (predicate(value, index, list)) {
result = value; result = value;
return true; return true;
} }
...@@ -164,61 +179,58 @@ ...@@ -164,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, 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); predicate = _.iteratee(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(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, _.negate(_.iteratee(predicate)), context);
return !iterator.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, iterator, context) { _.every = _.all = function(obj, predicate, context) {
iterator || (iterator = _.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(iterator, context); length = (keys || obj).length,
each(obj, function(value, index, list) { index, currentKey;
if (!(result = result && iterator.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, iterator, context) { _.some = _.any = function(obj, predicate, context) {
iterator || (iterator = _.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(iterator, context); length = (keys || obj).length,
each(obj, function(value, index, list) { index, currentKey;
if (result || (result = iterator.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.
...@@ -232,94 +244,104 @@ ...@@ -232,94 +244,104 @@
// 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).
// 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++) {
value = obj[i];
if (value > result) {
result = value;
}
}
} 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;
}
});
} }
if (!iterator && _.isEmpty(obj)) return -Infinity; return result;
var result = {computed : -Infinity, value: -Infinity};
each(obj, function(value, index, list) {
var computed = iterator ? iterator.call(context, value, index, list) : value;
computed > result.computed && (result = {value : value, computed : computed});
});
return result.value;
}; };
// 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) {
obj = obj.length === +obj.length ? obj : _.values(obj);
for (var i = 0, length = obj.length; i < length; i++) {
value = obj[i];
if (value < result) {
result = value;
}
}
} 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;
}
});
} }
if (!iterator && _.isEmpty(obj)) return Infinity; return result;
var result = {computed : Infinity, value: Infinity};
each(obj, function(value, index, list) {
var computed = iterator ? iterator.call(context, value, index, list) : value;
computed < result.computed && (result = {value : value, computed : computed});
});
return result.value;
}; };
// 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;
}; };
// 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));
}; };
// 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) {
return _.isFunction(value) ? value : function(obj){ return obj[value]; }; iteratee = _.iteratee(iteratee, context);
};
// Sort the object's values by a criterion produced by an iterator.
_.sortBy = function(obj, value, context) {
var iterator = lookupIterator(value);
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;
...@@ -334,12 +356,12 @@ ...@@ -334,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, value, context) { return function(obj, iteratee, context) {
var result = {}; var result = {};
var iterator = value == null ? _.identity : lookupIterator(value); 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;
}; };
...@@ -347,32 +369,32 @@ ...@@ -347,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] : (result[key] = [])).push(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 = iterator == null ? _.identity : 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;
}; };
...@@ -388,7 +410,18 @@ ...@@ -388,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
...@@ -399,7 +432,9 @@ ...@@ -399,7 +432,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
...@@ -407,18 +442,15 @@ ...@@ -407,18 +442,15 @@
// 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) { if (n == null || guard) return array[array.length - 1];
return array[array.length - 1]; return slice.call(array, Math.max(array.length - n, 0));
} else {
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`.
...@@ -426,7 +458,7 @@ ...@@ -426,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.
...@@ -435,23 +467,26 @@ ...@@ -435,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).
...@@ -462,55 +497,74 @@ ...@@ -462,55 +497,74 @@
// 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 _.indexOf(other, item) >= 0; 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;
}; };
...@@ -531,10 +585,8 @@ ...@@ -531,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) {
...@@ -542,26 +594,23 @@ ...@@ -542,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;
}; };
...@@ -573,15 +622,13 @@ ...@@ -573,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;
...@@ -591,7 +638,7 @@ ...@@ -591,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
...@@ -599,52 +646,68 @@ ...@@ -599,52 +646,68 @@
_.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
// 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 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
...@@ -662,23 +725,25 @@ ...@@ -662,23 +725,25 @@
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 : new Date; previous = options.leading === false ? 0 : _.now();
timeout = null; timeout = null;
result = func.apply(context, args); result = func.apply(context, args);
if (!timeout) 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;
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);
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);
} }
...@@ -692,38 +757,33 @@ ...@@ -692,38 +757,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;
var later = function() {
var last = _.now() - timestamp;
if (last < wait && last > 0) {
timeout = setTimeout(later, wait - last);
} else {
timeout = null;
if (!immediate) {
result = func.apply(context, args);
if (!timeout) context = args = null;
}
}
};
return function() { return function() {
context = this; context = this;
args = arguments; args = arguments;
timestamp = new Date(); timestamp = _.now();
var later = function() {
var last = (new Date()) - timestamp;
if (last < wait) {
timeout = setTimeout(later, wait - last);
} else {
timeout = null;
if (!immediate) result = func.apply(context, args);
}
};
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);
context = args = null;
} }
if (callNow) result = func.apply(context, args);
return result;
};
};
// Returns a function that will be executed at most one time, no matter how return result;
// 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;
}; };
}; };
...@@ -731,23 +791,26 @@ ...@@ -731,23 +791,26 @@
// 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 _.partial(wrapper, func);
};
// Returns a negated version of the passed-in predicate.
_.negate = function(predicate) {
return function() { return function() {
var args = [func]; return !predicate.apply(this, arguments);
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
// 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];
}; };
}; };
...@@ -760,13 +823,31 @@ ...@@ -760,13 +823,31 @@
}; };
}; };
// 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
// ---------------- // ----------------
// 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;
...@@ -776,7 +857,7 @@ ...@@ -776,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]];
} }
...@@ -787,7 +868,7 @@ ...@@ -787,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]]];
} }
...@@ -816,45 +897,62 @@ ...@@ -816,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;
}; };
...@@ -876,7 +974,7 @@ ...@@ -876,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.
...@@ -884,29 +982,27 @@ ...@@ -884,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
...@@ -915,24 +1011,29 @@ ...@@ -915,24 +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 &&
// 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--) {
...@@ -941,20 +1042,16 @@ ...@@ -941,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.
...@@ -972,7 +1069,7 @@ ...@@ -972,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;
}; };
...@@ -985,18 +1082,19 @@ ...@@ -985,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 + ']';
}; };
}); });
...@@ -1004,14 +1102,14 @@ ...@@ -1004,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;
}; };
} }
...@@ -1022,12 +1120,12 @@ ...@@ -1022,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?
...@@ -1043,7 +1141,7 @@ ...@@ -1043,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
...@@ -1056,15 +1154,44 @@ ...@@ -1056,15 +1154,44 @@
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) {
return function() {
return value;
};
};
_.noop = function(){};
_.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) {
var pairs = _.pairs(attrs), length = pairs.length;
return function(obj) {
if (obj == null) return !length;
obj = new Object(obj);
for (var i = 0; i < length; i++) {
var pair = pairs[i], key = pair[0];
if (pair[1] !== obj[key] || !(key in obj)) return false;
}
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;
}; };
...@@ -1077,52 +1204,45 @@ ...@@ -1077,52 +1204,45 @@
return min + Math.floor(Math.random() * (max - min + 1)); return min + Math.floor(Math.random() * (max - min + 1));
}; };
// List of HTML entities for escaping. // A (possibly faster) way to get the current timestamp as an integer.
var entityMap = { _.now = Date.now || function() {
escape: { return new Date().getTime();
'&': '&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).
...@@ -1153,22 +1273,26 @@ ...@@ -1153,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
...@@ -1178,19 +1302,18 @@ ...@@ -1178,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";
...@@ -1200,29 +1323,31 @@ ...@@ -1200,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
...@@ -1236,41 +1361,55 @@ ...@@ -1236,41 +1361,55 @@
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; // 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
// Extracts the result from a wrapped and chained object. // as a named module because, like jQuery, it is a base library that is
value: function() { // popular enough to be bundled in a third party lib, but not be part of
return this._wrapped; // 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));
{
"private": true,
"dependencies": {
"backbone": "^1.1.2",
"backbone.localstorage": "^1.1.16",
"jquery": "^2.1.3",
"requirejs": "^2.1.15",
"requirejs-text": "^2.0.12",
"todomvc-app-css": "^1.0.0",
"todomvc-common": "^1.0.1",
"underscore": "^1.7.0"
}
}
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