From 9100db55b062585c076247473ab44979fcaf55bd Mon Sep 17 00:00:00 2001 From: John-David Dalton Date: Fri, 31 Aug 2012 12:47:55 -0700 Subject: [PATCH] Update vendors. Former-commit-id: 88e9746e94e8ec899227b1a925bea4ab4d373fb0 --- vendor/backbone/backbone.js | 91 +++++------- vendor/backbone/test/collection.js | 89 +++++------- vendor/backbone/test/model.js | 82 +++-------- vendor/backbone/test/router.js | 9 +- vendor/backbone/test/sync.js | 102 ++++++------- vendor/backbone/test/vendor/qunit.css | 55 ++++--- vendor/backbone/test/vendor/qunit.js | 198 ++++++++++++++++++++------ vendor/underscore/test/arrays.js | 13 +- vendor/underscore/test/collections.js | 9 ++ vendor/underscore/test/objects.js | 10 ++ vendor/underscore/test/utility.js | 17 +++ vendor/underscore/underscore-min.js | 51 +++---- vendor/underscore/underscore.js | 103 ++++++++++---- 13 files changed, 474 insertions(+), 355 deletions(-) diff --git a/vendor/backbone/backbone.js b/vendor/backbone/backbone.js index 21bf42bb3..cfa836155 100644 --- a/vendor/backbone/backbone.js +++ b/vendor/backbone/backbone.js @@ -183,7 +183,7 @@ attributes || (attributes = {}); if (options && options.collection) this.collection = options.collection; if (options && options.parse) attributes = this.parse(attributes); - if (defaults = getValue(this, 'defaults')) { + if (defaults = _.result(this, 'defaults')) { attributes = _.extend({}, defaults, attributes); } this.attributes = {}; @@ -336,9 +336,7 @@ options.success = function(resp, status, xhr) { if (!model.set(model.parse(resp, xhr), options)) return false; if (success) success(model, resp, options); - model.trigger('sync', model, resp, options); }; - options.error = Backbone.wrapError(options.error, model, options); return this.sync('read', this, options); }, @@ -383,11 +381,9 @@ if (options.wait) serverAttrs = _.extend(attrs || {}, serverAttrs); if (!model.set(serverAttrs, options)) return false; if (success) success(model, resp, options); - model.trigger('sync', model, resp, options); }; // Finish configuring and sending the Ajax request. - options.error = Backbone.wrapError(options.error, model, options); var xhr = this.sync(this.isNew() ? 'create' : 'update', this, options); // When using `wait`, reset attributes to original values unless @@ -415,7 +411,6 @@ options.success = function(resp) { if (options.wait || model.isNew()) destroy(); if (success) success(model, resp, options); - if (!model.isNew()) model.trigger('sync', model, resp, options); }; if (this.isNew()) { @@ -423,7 +418,6 @@ return false; } - options.error = Backbone.wrapError(options.error, model, options); var xhr = this.sync('delete', this, options); if (!options.wait) destroy(); return xhr; @@ -433,7 +427,7 @@ // using Backbone's restful methods, override this to change the endpoint // that will be called. url: function() { - var base = getValue(this, 'urlRoot') || getValue(this.collection, 'url') || urlError(); + var base = _.result(this, 'urlRoot') || _.result(this.collection, 'url') || urlError(); if (this.isNew()) return base; return base + (base.charAt(base.length - 1) === '/' ? '' : '/') + encodeURIComponent(this.id); }, @@ -527,8 +521,8 @@ // Check if the model is currently in a valid state. It's only possible to // get into an *invalid* state if you're using silent changes. - isValid: function() { - return !this.validate || !this.validate(this.attributes); + isValid: function(options) { + return !this.validate || !this.validate(this.attributes, options); }, // Run validation against the next complete set of model attributes, @@ -539,11 +533,8 @@ attrs = _.extend({}, this.attributes, attrs); var error = this.validate(attrs, options); if (!error) return true; - if (options && options.error) { - options.error(this, error, options); - } else { - this.trigger('error', this, error, options); - } + if (options && options.error) options.error(this, error, options); + this.trigger('error', this, error, options); return false; } @@ -781,9 +772,7 @@ options.success = function(resp, status, xhr) { collection[options.add ? 'add' : 'reset'](collection.parse(resp, xhr), options); if (success) success(collection, resp, options); - collection.trigger('sync', collection, resp, options); }; - options.error = Backbone.wrapError(options.error, collection, options); return this.sync('read', this, options); }, @@ -913,7 +902,6 @@ // }); // route: function(route, name, callback) { - Backbone.history || (Backbone.history = new History); if (!_.isRegExp(route)) route = this._routeToRegExp(route); if (!callback) callback = this[name]; Backbone.history.route(route, _.bind(function(fragment) { @@ -1163,6 +1151,9 @@ }); + // Create the default Backbone.history. + Backbone.history = new History; + // Backbone.View // ------------- @@ -1260,7 +1251,7 @@ // This only works for delegate-able events: not `focus`, `blur`, and // not `change`, `submit`, and `reset` in Internet Explorer. delegateEvents: function(events) { - if (!(events || (events = getValue(this, 'events')))) return; + if (!(events || (events = _.result(this, 'events')))) return; this.undelegateEvents(); for (var key in events) { var method = events[key]; @@ -1303,10 +1294,10 @@ // an element from the `id`, `className` and `tagName` properties. _ensureElement: function() { if (!this.el) { - var attrs = _.extend({}, getValue(this, 'attributes')); - if (this.id) attrs.id = getValue(this, 'id'); - if (this.className) attrs['class'] = getValue(this, 'className'); - this.setElement(this.make(getValue(this, 'tagName'), attrs), false); + var attrs = _.extend({}, _.result(this, 'attributes')); + if (this.id) attrs.id = _.result(this, 'id'); + if (this.className) attrs['class'] = _.result(this, 'className'); + this.setElement(this.make(_.result(this, 'tagName'), attrs), false); } else { this.setElement(this.el, false); } @@ -1351,7 +1342,7 @@ // Ensure that we have a URL. if (!options.url) { - params.url = getValue(model, 'url') || urlError(); + params.url = _.result(model, 'url') || urlError(); } // Ensure that we have the appropriate request data. @@ -1383,6 +1374,18 @@ params.processData = false; } + var success = options.success; + options.success = function(resp, status, xhr) { + if (success) success(resp, status, xhr); + model.trigger('sync', model, resp, options); + }; + + var error = options.error; + options.error = function(xhr, status, thrown) { + if (error) error(model, xhr, options); + model.trigger('error', model, xhr, options); + }; + // Make the request, allowing the user to override any Ajax options. return Backbone.ajax(_.extend(params, options)); }; @@ -1392,24 +1395,9 @@ return Backbone.$.ajax.apply(Backbone.$, arguments); }; - // Wrap an optional error callback with a fallback error event. - Backbone.wrapError = function(onError, originalModel, options) { - return function(model, resp) { - resp = model === originalModel ? resp : model; - if (onError) { - onError(originalModel, resp, options); - } else { - originalModel.trigger('error', originalModel, resp, options); - } - }; - }; - // Helpers // ------- - // Shared empty constructor function to aid in prototype-chain creation. - var ctor = function(){}; - // Helper function to correctly set up the prototype chain, for subclasses. // Similar to `goog.inherits`, but uses a hash of prototype properties and // class properties to be extended. @@ -1420,31 +1408,27 @@ // The constructor function for the new subclass is either defined by you // (the "constructor" property in your `extend` definition), or defaulted // by us to simply call the parent's constructor. - if (protoProps && protoProps.hasOwnProperty('constructor')) { + if (protoProps && _.has(protoProps, 'constructor')) { child = protoProps.constructor; } else { child = function(){ parent.apply(this, arguments); }; } - // Inherit class (static) properties from parent. - _.extend(child, parent); - // Set the prototype chain to inherit from `parent`, without calling // `parent`'s constructor function. - ctor.prototype = parent.prototype; - child.prototype = new ctor(); + function Surrogate(){ this.constructor = child; }; + Surrogate.prototype = parent.prototype; + child.prototype = new Surrogate; // Add prototype properties (instance properties) to the subclass, // if supplied. if (protoProps) _.extend(child.prototype, protoProps); // Add static properties to the constructor function, if supplied. - if (staticProps) _.extend(child, staticProps); + _.extend(child, parent, staticProps); - // Correctly set child's `prototype.constructor`. - child.prototype.constructor = child; - - // Set a convenience property in case the parent's prototype is needed later. + // Set a convenience property in case the parent's prototype is needed + // later. child.__super__ = parent.prototype; return child; @@ -1453,13 +1437,6 @@ // Set up inheritance for the model, collection, and view. Model.extend = Collection.extend = Router.extend = View.extend = extend; - // Helper function to get a value from a Backbone object as a property - // or as a function. - var getValue = function(object, prop) { - if (!(object && object[prop])) return null; - return _.isFunction(object[prop]) ? object[prop]() : object[prop]; - }; - // Throw an error when a URL is needed, and none is supplied. var urlError = function() { throw new Error('A "url" property or function must be specified'); diff --git a/vendor/backbone/test/collection.js b/vendor/backbone/test/collection.js index 4752d1dcb..d3d5aaf87 100644 --- a/vendor/backbone/test/collection.js +++ b/vendor/backbone/test/collection.js @@ -1,13 +1,12 @@ $(document).ready(function() { - var lastRequest = null; - var sync = Backbone.sync; - var a, b, c, d, e, col, otherCol; - module("Backbone.Collection", { + module("Backbone.Collection", _.extend(new Environment, { setup: function() { + Environment.prototype.setup.apply(this, arguments); + a = new Backbone.Model({id: 3, label: 'a'}); b = new Backbone.Model({id: 2, label: 'b'}); c = new Backbone.Model({id: 1, label: 'c'}); @@ -15,21 +14,9 @@ $(document).ready(function() { e = null; col = new Backbone.Collection([a,b,c,d]); otherCol = new Backbone.Collection(); - - Backbone.sync = function(method, model, options) { - lastRequest = { - method: method, - model: model, - options: options - }; - }; - }, - - teardown: function() { - Backbone.sync = sync; } - }); + })); test("Collection: new and sort", 7, function() { equal(col.first(), a, "a should be first"); @@ -48,26 +35,18 @@ $(document).ready(function() { }); test("Collection: new and parse", 3, function() { - var MyCol = Backbone.Collection.extend({ - // only save the models that have an even value. + var Collection = Backbone.Collection.extend({ parse : function(data) { - var onlyEven = []; - _.each(data, function(datum) { - if (datum.a % 2 === 0) { - onlyEven.push(datum); - } + return _.filter(data, function(datum) { + return datum.a % 2 === 0; }); - - return onlyEven; } }); - anotherCol = new MyCol([ - { a : 1 },{ a : 2 },{ a : 3 },{ a : 4 } - ], { parse : true }); - - equal(anotherCol.length, 2); - equal(anotherCol.first().get('a'), 2) - equal(anotherCol.last().get('a'), 4); + var models = [{a: 1}, {a: 2}, {a: 3}, {a: 4}]; + var collection = new Collection(models, {parse: true}); + strictEqual(collection.length, 2); + strictEqual(collection.first().get('a'), 2); + strictEqual(collection.last().get('a'), 4); }); test("Collection: get, getByCid", 3, function() { @@ -379,21 +358,25 @@ $(document).ready(function() { }); test("Collection: fetch", 4, function() { - col.fetch(); - equal(lastRequest.method, 'read'); - equal(lastRequest.model, col); - equal(lastRequest.options.parse, true); + var collection = new Backbone.Collection; + collection.url = '/test'; + collection.fetch(); + equal(this.syncArgs.method, 'read'); + equal(this.syncArgs.model, collection); + equal(this.syncArgs.options.parse, true); - col.fetch({parse: false}); - equal(lastRequest.options.parse, false); + collection.fetch({parse: false}); + equal(this.syncArgs.options.parse, false); }); test("Collection: create", 4, function() { - var model = col.create({label: 'f'}, {wait: true}); - equal(lastRequest.method, 'create'); - equal(lastRequest.model, model); + var collection = new Backbone.Collection; + collection.url = '/test'; + var model = collection.create({label: 'f'}, {wait: true}); + equal(this.syncArgs.method, 'create'); + equal(this.syncArgs.model, model); equal(model.get('label'), 'f'); - equal(model.collection, col); + equal(model.collection, collection); }); test("Collection: create enforces validation", 1, function() { @@ -524,16 +507,17 @@ $(document).ready(function() { }); test("#714: access `model.collection` in a brand new model.", 2, function() { - var col = new Backbone.Collection; + var collection = new Backbone.Collection; + collection.url = '/test'; var Model = Backbone.Model.extend({ set: function(attrs) { equal(attrs.prop, 'value'); - equal(this.collection, col); + equal(this.collection, collection); return this; } }); - col.model = Model; - col.create({prop: 'value'}); + collection.model = Model; + collection.create({prop: 'value'}); }); test("#574, remove its own reference to the .models array.", 2, function() { @@ -659,15 +643,10 @@ $(document).ready(function() { }); test("#1412 - Trigger 'sync' event.", 2, function() { - var collection = new Backbone.Collection([], { - model: Backbone.Model.extend({ - sync: function(method, model, options) { - options.success(); - } - }) - }); - collection.sync = function(method, model, options) { options.success(); }; + var collection = new Backbone.Collection; + collection.url = '/test'; collection.on('sync', function() { ok(true); }); + Backbone.ajax = function(settings){ settings.success(); }; collection.fetch(); collection.create({id: 1}); }); diff --git a/vendor/backbone/test/model.js b/vendor/backbone/test/model.js index 8689d9754..2da0ae0f9 100644 --- a/vendor/backbone/test/model.js +++ b/vendor/backbone/test/model.js @@ -1,22 +1,15 @@ $(document).ready(function() { - // Variable to catch the last request. - var lastRequest = null; - // Variable to catch ajax params. - var ajaxParams = null; - var sync = Backbone.sync; - var ajax = Backbone.ajax; - var urlRoot = null; - var proxy = Backbone.Model.extend(); var klass = Backbone.Collection.extend({ url : function() { return '/collection'; } }); var doc, collection; - module("Backbone.Model", { + module("Backbone.Model", _.extend(new Environment, { setup: function() { + Environment.prototype.setup.apply(this, arguments); doc = new proxy({ id : '1-the-tempest', title : "The Tempest", @@ -25,27 +18,9 @@ $(document).ready(function() { }); collection = new klass(); collection.add(doc); - - Backbone.sync = function(method, model, options) { - lastRequest = { - method: method, - model: model, - options: options - }; - sync.apply(this, arguments); - }; - Backbone.ajax = function(params) { ajaxParams = params; }; - urlRoot = Backbone.Model.prototype.urlRoot; - Backbone.Model.prototype.urlRoot = '/'; - }, - - teardown: function() { - Backbone.sync = sync; - Backbone.ajax = ajax; - Backbone.Model.prototype.urlRoot = urlRoot; } - }); + })); test("Model: initialize", 3, function() { var Model = Backbone.Model.extend({ @@ -334,10 +309,12 @@ $(document).ready(function() { }); test("Model: save within change event", 1, function () { + var env = this; var model = new Backbone.Model({firstName : "Taylor", lastName: "Swift"}); + model.url = '/test'; model.on('change', function () { model.save(); - ok(_.isEqual(lastRequest.model, model)); + ok(_.isEqual(env.syncArgs.model, model)); }); model.set({lastName: 'Hicks'}); }); @@ -371,8 +348,8 @@ $(document).ready(function() { test("Model: save", 2, function() { doc.save({title : "Henry V"}); - equal(lastRequest.method, 'update'); - ok(_.isEqual(lastRequest.model, doc)); + equal(this.syncArgs.method, 'update'); + ok(_.isEqual(this.syncArgs.model, doc)); }); test("Model: save in positional style", 1, function() { @@ -388,14 +365,14 @@ $(document).ready(function() { test("Model: fetch", 2, function() { doc.fetch(); - equal(lastRequest.method, 'read'); - ok(_.isEqual(lastRequest.model, doc)); + equal(this.syncArgs.method, 'read'); + ok(_.isEqual(this.syncArgs.model, doc)); }); test("Model: destroy", 3, function() { doc.destroy(); - equal(lastRequest.method, 'delete'); - ok(_.isEqual(lastRequest.model, doc)); + equal(this.syncArgs.method, 'delete'); + ok(_.isEqual(this.syncArgs.model, doc)); var newModel = new Backbone.Model; equal(newModel.destroy(), false); @@ -472,7 +449,7 @@ $(document).ready(function() { equal(result, false); equal(model.get('a'), 100); equal(lastError, "Can't change admin status."); - equal(boundError, undefined); + equal(boundError, true); }); test("Model: defaults always extend attrs (#459)", 2, function() { @@ -595,8 +572,9 @@ $(document).ready(function() { test("save with `wait` succeeds without `validate`", 1, function() { var model = new Backbone.Model(); + model.url = '/test'; model.save({x: 1}, {wait: true}); - ok(lastRequest.model === model); + ok(this.syncArgs.model === model); }); test("`hasChanged` for falsey keys", 2, function() { @@ -616,18 +594,20 @@ $(document).ready(function() { test("`save` with `wait` sends correct attributes", 5, function() { var changed = 0; var model = new Backbone.Model({x: 1, y: 2}); + model.url = '/test'; model.on('change:x', function() { changed++; }); model.save({x: 3}, {wait: true}); - deepEqual(JSON.parse(ajaxParams.data), {x: 3, y: 2}); + deepEqual(JSON.parse(this.ajaxSettings.data), {x: 3, y: 2}); equal(model.get('x'), 1); equal(changed, 0); - lastRequest.options.success({}); + this.syncArgs.options.success({}); equal(model.get('x'), 3); equal(changed, 1); }); test("a failed `save` with `wait` doesn't leave attributes behind", 1, function() { var model = new Backbone.Model; + model.url = '/test'; model.save({x: 1}, {wait: true}); equal(model.get('x'), void 0); }); @@ -644,6 +624,7 @@ $(document).ready(function() { test("save with wait validates attributes", 1, function() { var model = new Backbone.Model(); + model.url = '/test'; model.validate = function() { ok(true); }; model.save({x: 1}, {wait: true}); }); @@ -776,24 +757,6 @@ $(document).ready(function() { model.set({a: true}); }); - test("Backbone.wrapError triggers `'error'`", 12, function() { - var resp = {}; - var options = {}; - var model = new Backbone.Model(); - model.on('error', error); - var callback = Backbone.wrapError(null, model, options); - callback(model, resp); - callback(resp); - callback = Backbone.wrapError(error, model, options); - callback(model, resp); - callback(resp); - function error(_model, _resp, _options) { - ok(model === _model); - ok(resp === _resp); - ok(options === _options); - } - }); - test("#1179 - isValid returns true in the absence of validate.", 1, function() { var model = new Backbone.Model(); model.validate = null; @@ -831,8 +794,9 @@ $(document).ready(function() { test("#1412 - Trigger 'sync' event.", 3, function() { var model = new Backbone.Model({id: 1}); - model.sync = function(method, model, options) { options.success(); }; - model.on('sync', function() { ok(true); }); + model.url = '/test'; + model.on('sync', function(){ ok(true); }); + Backbone.ajax = function(settings){ settings.success(); }; model.fetch(); model.save(); model.destroy(); diff --git a/vendor/backbone/test/router.js b/vendor/backbone/test/router.js index 701a1f07a..56d8a10d4 100644 --- a/vendor/backbone/test/router.js +++ b/vendor/backbone/test/router.js @@ -244,14 +244,11 @@ $(document).ready(function() { }); test("#1003 - History is started before navigate is called", 1, function() { - var history = new Backbone.History(); - history.navigate = function(){ - ok(Backbone.History.started); - }; Backbone.history.stop(); - history.start(); + Backbone.history.navigate = function(){ ok(Backbone.History.started); }; + Backbone.history.start(); // If this is not an old IE navigate will not be called. - if (!history.iframe) ok(true); + if (!Backbone.history.iframe) ok(true); }); test("Router: route callback gets passed decoded values", 3, function() { diff --git a/vendor/backbone/test/sync.js b/vendor/backbone/test/sync.js index f4afb5c14..d25c76ff2 100644 --- a/vendor/backbone/test/sync.js +++ b/vendor/backbone/test/sync.js @@ -1,8 +1,5 @@ $(document).ready(function() { - var ajax = Backbone.ajax; - var lastRequest = null; - var Library = Backbone.Collection.extend({ url : function() { return '/library'; } }); @@ -14,42 +11,36 @@ $(document).ready(function() { length : 123 }; - module("Backbone.sync", { + module("Backbone.sync", _.extend(new Environment, { setup : function() { - library = new Library(); - Backbone.ajax = function(obj) { - lastRequest = obj; - }; + Environment.prototype.setup.apply(this, arguments); + library = new Library; library.create(attrs, {wait: false}); - }, - - teardown: function() { - Backbone.ajax = ajax; } - }); + })); test("sync: read", 4, function() { library.fetch(); - equal(lastRequest.url, '/library'); - equal(lastRequest.type, 'GET'); - equal(lastRequest.dataType, 'json'); - ok(_.isEmpty(lastRequest.data)); + equal(this.ajaxSettings.url, '/library'); + equal(this.ajaxSettings.type, 'GET'); + equal(this.ajaxSettings.dataType, 'json'); + ok(_.isEmpty(this.ajaxSettings.data)); }); test("sync: passing data", 3, function() { library.fetch({data: {a: 'a', one: 1}}); - equal(lastRequest.url, '/library'); - equal(lastRequest.data.a, 'a'); - equal(lastRequest.data.one, 1); + equal(this.ajaxSettings.url, '/library'); + equal(this.ajaxSettings.data.a, 'a'); + equal(this.ajaxSettings.data.one, 1); }); test("sync: create", 6, function() { - equal(lastRequest.url, '/library'); - equal(lastRequest.type, 'POST'); - equal(lastRequest.dataType, 'json'); - var data = JSON.parse(lastRequest.data); + equal(this.ajaxSettings.url, '/library'); + equal(this.ajaxSettings.type, 'POST'); + equal(this.ajaxSettings.dataType, 'json'); + var data = JSON.parse(this.ajaxSettings.data); equal(data.title, 'The Tempest'); equal(data.author, 'Bill Shakespeare'); equal(data.length, 123); @@ -57,10 +48,10 @@ $(document).ready(function() { test("sync: update", 7, function() { library.first().save({id: '1-the-tempest', author: 'William Shakespeare'}); - equal(lastRequest.url, '/library/1-the-tempest'); - equal(lastRequest.type, 'PUT'); - equal(lastRequest.dataType, 'json'); - var data = JSON.parse(lastRequest.data); + equal(this.ajaxSettings.url, '/library/1-the-tempest'); + equal(this.ajaxSettings.type, 'PUT'); + equal(this.ajaxSettings.dataType, 'json'); + var data = JSON.parse(this.ajaxSettings.data); equal(data.id, '1-the-tempest'); equal(data.title, 'The Tempest'); equal(data.author, 'William Shakespeare'); @@ -70,11 +61,11 @@ $(document).ready(function() { test("sync: update with emulateHTTP and emulateJSON", 7, function() { Backbone.emulateHTTP = Backbone.emulateJSON = true; library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'}); - equal(lastRequest.url, '/library/2-the-tempest'); - equal(lastRequest.type, 'POST'); - equal(lastRequest.dataType, 'json'); - equal(lastRequest.data._method, 'PUT'); - var data = JSON.parse(lastRequest.data.model); + equal(this.ajaxSettings.url, '/library/2-the-tempest'); + equal(this.ajaxSettings.type, 'POST'); + equal(this.ajaxSettings.dataType, 'json'); + equal(this.ajaxSettings.data._method, 'PUT'); + var data = JSON.parse(this.ajaxSettings.data.model); equal(data.id, '2-the-tempest'); equal(data.author, 'Tim Shakespeare'); equal(data.length, 123); @@ -84,10 +75,10 @@ $(document).ready(function() { test("sync: update with just emulateHTTP", 6, function() { Backbone.emulateHTTP = true; library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'}); - equal(lastRequest.url, '/library/2-the-tempest'); - equal(lastRequest.type, 'POST'); - equal(lastRequest.contentType, 'application/json'); - var data = JSON.parse(lastRequest.data); + equal(this.ajaxSettings.url, '/library/2-the-tempest'); + equal(this.ajaxSettings.type, 'POST'); + equal(this.ajaxSettings.contentType, 'application/json'); + var data = JSON.parse(this.ajaxSettings.data); equal(data.id, '2-the-tempest'); equal(data.author, 'Tim Shakespeare'); equal(data.length, 123); @@ -97,10 +88,10 @@ $(document).ready(function() { test("sync: update with just emulateJSON", 6, function() { Backbone.emulateJSON = true; library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'}); - equal(lastRequest.url, '/library/2-the-tempest'); - equal(lastRequest.type, 'PUT'); - equal(lastRequest.contentType, 'application/x-www-form-urlencoded'); - var data = JSON.parse(lastRequest.data.model); + equal(this.ajaxSettings.url, '/library/2-the-tempest'); + equal(this.ajaxSettings.type, 'PUT'); + equal(this.ajaxSettings.contentType, 'application/x-www-form-urlencoded'); + var data = JSON.parse(this.ajaxSettings.data.model); equal(data.id, '2-the-tempest'); equal(data.author, 'Tim Shakespeare'); equal(data.length, 123); @@ -110,26 +101,26 @@ $(document).ready(function() { test("sync: read model", 3, function() { library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'}); library.first().fetch(); - equal(lastRequest.url, '/library/2-the-tempest'); - equal(lastRequest.type, 'GET'); - ok(_.isEmpty(lastRequest.data)); + equal(this.ajaxSettings.url, '/library/2-the-tempest'); + equal(this.ajaxSettings.type, 'GET'); + ok(_.isEmpty(this.ajaxSettings.data)); }); test("sync: destroy", 3, function() { library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'}); library.first().destroy({wait: true}); - equal(lastRequest.url, '/library/2-the-tempest'); - equal(lastRequest.type, 'DELETE'); - equal(lastRequest.data, null); + equal(this.ajaxSettings.url, '/library/2-the-tempest'); + equal(this.ajaxSettings.type, 'DELETE'); + equal(this.ajaxSettings.data, null); }); test("sync: destroy with emulateHTTP", 3, function() { library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'}); Backbone.emulateHTTP = Backbone.emulateJSON = true; library.first().destroy(); - equal(lastRequest.url, '/library/2-the-tempest'); - equal(lastRequest.type, 'POST'); - equal(JSON.stringify(lastRequest.data), '{"_method":"DELETE"}'); + equal(this.ajaxSettings.url, '/library/2-the-tempest'); + equal(this.ajaxSettings.type, 'POST'); + equal(JSON.stringify(this.ajaxSettings.data), '{"_method":"DELETE"}'); Backbone.emulateHTTP = Backbone.emulateJSON = false; }); @@ -139,7 +130,7 @@ $(document).ready(function() { model.fetch(); }); model.fetch({url: '/one/two'}); - equal(lastRequest.url, '/one/two'); + equal(this.ajaxSettings.url, '/one/two'); }); test("#1052 - `options` is optional.", 0, function() { @@ -157,4 +148,13 @@ $(document).ready(function() { Backbone.sync('create', model); }); + test("Call provided error callback on error.", 1, function() { + var model = new Backbone.Model; + model.url = '/test'; + Backbone.sync('read', model, { + error: function() { ok(true); } + }); + this.ajaxSettings.error(); + }); + }); diff --git a/vendor/backbone/test/vendor/qunit.css b/vendor/backbone/test/vendor/qunit.css index 5684a4485..55970e006 100755 --- a/vendor/backbone/test/vendor/qunit.css +++ b/vendor/backbone/test/vendor/qunit.css @@ -1,11 +1,11 @@ /** - * QUnit v1.8.0 - A JavaScript Unit Testing Framework + * QUnit v1.10.0 - A JavaScript Unit Testing Framework * - * http://docs.jquery.com/QUnit + * http://qunitjs.com * - * Copyright (c) 2012 John Resig, Jörn Zaefferer - * Dual licensed under the MIT (MIT-LICENSE.txt) - * or GPL (GPL-LICENSE.txt) licenses. + * Copyright 2012 jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license */ /** Font Family and Sizes */ @@ -20,7 +20,7 @@ /** Resets */ -#qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult { +#qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter { margin: 0; padding: 0; } @@ -38,10 +38,10 @@ line-height: 1em; font-weight: normal; - border-radius: 15px 15px 0 0; - -moz-border-radius: 15px 15px 0 0; - -webkit-border-top-right-radius: 15px; - -webkit-border-top-left-radius: 15px; + border-radius: 5px 5px 0 0; + -moz-border-radius: 5px 5px 0 0; + -webkit-border-top-right-radius: 5px; + -webkit-border-top-left-radius: 5px; } #qunit-header a { @@ -54,9 +54,9 @@ color: #fff; } -#qunit-header label { +#qunit-testrunner-toolbar label { display: inline-block; - padding-left: 0.5em; + padding: 0 .5em 0 .1em; } #qunit-banner { @@ -67,6 +67,7 @@ padding: 0.5em 0 0.5em 2em; color: #5E740B; background-color: #eee; + overflow: hidden; } #qunit-userAgent { @@ -76,6 +77,9 @@ text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; } +#qunit-modulefilter-container { + float: right; +} /** Tests: Pass/Fail */ @@ -113,13 +117,9 @@ background-color: #fff; - border-radius: 15px; - -moz-border-radius: 15px; - -webkit-border-radius: 15px; - - box-shadow: inset 0px 2px 13px #999; - -moz-box-shadow: inset 0px 2px 13px #999; - -webkit-box-shadow: inset 0px 2px 13px #999; + border-radius: 5px; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; } #qunit-tests table { @@ -162,8 +162,7 @@ #qunit-tests b.failed { color: #710909; } #qunit-tests li li { - margin: 0.5em; - padding: 0.4em 0.5em 0.4em 0.5em; + padding: 5px; background-color: #fff; border-bottom: none; list-style-position: inside; @@ -172,9 +171,9 @@ /*** Passing Styles */ #qunit-tests li li.pass { - color: #5E740B; + color: #3c510c; background-color: #fff; - border-left: 26px solid #C6E746; + border-left: 10px solid #C6E746; } #qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; } @@ -190,15 +189,15 @@ #qunit-tests li li.fail { color: #710909; background-color: #fff; - border-left: 26px solid #EE5757; + border-left: 10px solid #EE5757; white-space: pre; } #qunit-tests > li:last-child { - border-radius: 0 0 15px 15px; - -moz-border-radius: 0 0 15px 15px; - -webkit-border-bottom-right-radius: 15px; - -webkit-border-bottom-left-radius: 15px; + border-radius: 0 0 5px 5px; + -moz-border-radius: 0 0 5px 5px; + -webkit-border-bottom-right-radius: 5px; + -webkit-border-bottom-left-radius: 5px; } #qunit-tests .fail { color: #000000; background-color: #EE5757; } diff --git a/vendor/backbone/test/vendor/qunit.js b/vendor/backbone/test/vendor/qunit.js index c1570c252..d4f17b5ae 100755 --- a/vendor/backbone/test/vendor/qunit.js +++ b/vendor/backbone/test/vendor/qunit.js @@ -1,11 +1,11 @@ /** - * QUnit v1.8.0 - A JavaScript Unit Testing Framework + * QUnit v1.10.0 - A JavaScript Unit Testing Framework * - * http://docs.jquery.com/QUnit + * http://qunitjs.com * - * Copyright (c) 2012 John Resig, Jörn Zaefferer - * Dual licensed under the MIT (MIT-LICENSE.txt) - * or GPL (GPL-LICENSE.txt) licenses. + * Copyright 2012 jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license */ (function( window ) { @@ -17,6 +17,8 @@ var QUnit, fileName = (sourceFromStacktrace( 0 ) || "" ).replace(/(:\d+)+\)?/, "").replace(/.+\//, ""), toString = Object.prototype.toString, hasOwn = Object.prototype.hasOwnProperty, + // Keep a local reference to Date (GH-283) + Date = window.Date, defined = { setTimeout: typeof window.setTimeout !== "undefined", sessionStorage: (function() { @@ -304,7 +306,8 @@ QUnit = { // call on start of module test to prepend name to all tests module: function( name, testEnvironment ) { config.currentModule = name; - config.currentModuleTestEnviroment = testEnvironment; + config.currentModuleTestEnvironment = testEnvironment; + config.modules[name] = true; }, asyncTest: function( testName, expected, callback ) { @@ -336,7 +339,7 @@ QUnit = { async: async, callback: callback, module: config.currentModule, - moduleTestEnvironment: config.currentModuleTestEnviroment, + moduleTestEnvironment: config.currentModuleTestEnvironment, stack: sourceFromStacktrace( 2 ) }); @@ -349,7 +352,11 @@ QUnit = { // Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through. expect: function( asserts ) { - config.current.expected = asserts; + if (arguments.length === 1) { + config.current.expected = asserts; + } else { + return config.current.expected; + } }, start: function( count ) { @@ -403,6 +410,8 @@ QUnit = { QUnit.assert = { /** * Asserts rough true-ish result. + * @name ok + * @function * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" ); */ ok: function( result, msg ) { @@ -413,6 +422,8 @@ QUnit.assert = { var source, details = { + module: config.current.module, + name: config.current.testName, result: result, message: msg }; @@ -437,36 +448,59 @@ QUnit.assert = { /** * Assert that the first two arguments are equal, with an optional message. * Prints out both actual and expected values. + * @name equal + * @function * @example equal( format( "Received {0} bytes.", 2), "Received 2 bytes.", "format() replaces {0} with next argument" ); */ equal: function( actual, expected, message ) { QUnit.push( expected == actual, actual, expected, message ); }, + /** + * @name notEqual + * @function + */ notEqual: function( actual, expected, message ) { QUnit.push( expected != actual, actual, expected, message ); }, + /** + * @name deepEqual + * @function + */ deepEqual: function( actual, expected, message ) { QUnit.push( QUnit.equiv(actual, expected), actual, expected, message ); }, + /** + * @name notDeepEqual + * @function + */ notDeepEqual: function( actual, expected, message ) { QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message ); }, + /** + * @name strictEqual + * @function + */ strictEqual: function( actual, expected, message ) { QUnit.push( expected === actual, actual, expected, message ); }, + /** + * @name notStrictEqual + * @function + */ notStrictEqual: function( actual, expected, message ) { QUnit.push( expected !== actual, actual, expected, message ); }, - raises: function( block, expected, message ) { + throws: function( block, expected, message ) { var actual, ok = false; + // 'expected' is optional if ( typeof expected === "string" ) { message = expected; expected = null; @@ -494,18 +528,29 @@ QUnit.assert = { } else if ( expected.call( {}, actual ) === true ) { ok = true; } - } - QUnit.push( ok, actual, null, message ); + QUnit.push( ok, actual, null, message ); + } else { + QUnit.pushFailure( message, null, 'No exception was thrown.' ); + } } }; -// @deprecated: Kept assertion helpers in root for backwards compatibility +/** + * @deprecate since 1.8.0 + * Kept assertion helpers in root for backwards compatibility + */ extend( QUnit, QUnit.assert ); /** - * @deprecated: Kept for backwards compatibility - * next step: remove entirely + * @deprecated since 1.9.0 + * Kept global "raises()" for backwards compatibility + */ +QUnit.raises = QUnit.assert.throws; + +/** + * @deprecated since 1.0.0, replaced with error pushes since 1.3.0 + * Kept to avoid TypeErrors for undefined methods. */ QUnit.equals = function() { QUnit.push( false, false, false, "QUnit.equals has been deprecated since 2009 (e88049a0), use QUnit.equal instead" ); @@ -549,7 +594,23 @@ config = { // when enabled, all tests must call expect() requireExpects: false, - urlConfig: [ "noglobals", "notrycatch" ], + // add checkboxes that are persisted in the query-string + // when enabled, the id is set to `true` as a `QUnit.config` property + urlConfig: [ + { + id: "noglobals", + label: "Check for Globals", + tooltip: "Enabling this will test if any test introduces new properties on the `window` object. Stored as query-strings." + }, + { + id: "notrycatch", + label: "No try-catch", + tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging exceptions in IE reasonable. Stored as query-strings." + } + ], + + // Set of all modules. + modules: {}, // logging callback queues begin: [], @@ -661,17 +722,10 @@ extend( QUnit, { }, // Resets the test setup. Useful for tests that modify the DOM. - // If jQuery is available, uses jQuery's html(), otherwise just innerHTML. reset: function() { - var fixture; - - if ( window.jQuery ) { - jQuery( "#qunit-fixture" ).html( config.fixture ); - } else { - fixture = id( "qunit-fixture" ); - if ( fixture ) { - fixture.innerHTML = config.fixture; - } + var fixture = id( "qunit-fixture" ); + if ( fixture ) { + fixture.innerHTML = config.fixture; } }, @@ -732,6 +786,8 @@ extend( QUnit, { var output, source, details = { + module: config.current.module, + name: config.current.testName, result: result, message: message, actual: actual, @@ -770,26 +826,36 @@ extend( QUnit, { }); }, - pushFailure: function( message, source ) { + pushFailure: function( message, source, actual ) { if ( !config.current ) { throw new Error( "pushFailure() assertion outside test context, was " + sourceFromStacktrace(2) ); } var output, details = { + module: config.current.module, + name: config.current.testName, result: false, message: message }; - message = escapeInnerText(message ) || "error"; + message = escapeInnerText( message ) || "error"; message = "" + message + ""; output = message; + output += ""; + + if ( actual ) { + output += ""; + } + if ( source ) { details.source = source; - output += "
Result:
" + escapeInnerText( actual ) + "
Source:
" + escapeInnerText( source ) + "
"; + output += "Source:
" + escapeInnerText( source ) + "
"; } + output += ""; + runLoggingCallbacks( "log", QUnit, details ); config.current.assertions.push({ @@ -859,7 +925,9 @@ QUnit.load = function() { runLoggingCallbacks( "begin", QUnit, {} ); // Initialize the config, saving the execution queue - var banner, filter, i, label, len, main, ol, toolbar, userAgent, val, + var banner, filter, i, label, len, main, ol, toolbar, userAgent, val, urlConfigCheckboxes, moduleFilter, + numModules = 0, + moduleFilterHtml = "", urlConfigHtml = "", oldconfig = extend( {}, config ); @@ -872,10 +940,26 @@ QUnit.load = function() { for ( i = 0; i < len; i++ ) { val = config.urlConfig[i]; - config[val] = QUnit.urlParams[val]; - urlConfigHtml += ""; + if ( typeof val === "string" ) { + val = { + id: val, + label: val, + tooltip: "[no tooltip available]" + }; + } + config[ val.id ] = QUnit.urlParams[ val.id ]; + urlConfigHtml += ""; } + moduleFilterHtml += ""; + // `userAgent` initialized at top of scope userAgent = id( "qunit-userAgent" ); if ( userAgent ) { @@ -885,12 +969,7 @@ QUnit.load = function() { // `banner` initialized at top of scope banner = id( "qunit-header" ); if ( banner ) { - banner.innerHTML = "" + banner.innerHTML + " " + urlConfigHtml; - addEvent( banner, "change", function( event ) { - var params = {}; - params[ event.target.name ] = event.target.checked ? true : undefined; - window.location = QUnit.url( params ); - }); + banner.innerHTML = "" + banner.innerHTML + " "; } // `toolbar` initialized at top of scope @@ -931,8 +1010,31 @@ QUnit.load = function() { // `label` initialized at top of scope label = document.createElement( "label" ); label.setAttribute( "for", "qunit-filter-pass" ); + label.setAttribute( "title", "Only show tests and assertons that fail. Stored in sessionStorage." ); label.innerHTML = "Hide passed tests"; toolbar.appendChild( label ); + + urlConfigCheckboxes = document.createElement( 'span' ); + urlConfigCheckboxes.innerHTML = urlConfigHtml; + addEvent( urlConfigCheckboxes, "change", function( event ) { + var params = {}; + params[ event.target.name ] = event.target.checked ? true : undefined; + window.location = QUnit.url( params ); + }); + toolbar.appendChild( urlConfigCheckboxes ); + + if (numModules > 1) { + moduleFilter = document.createElement( 'span' ); + moduleFilter.setAttribute( 'id', 'qunit-modulefilter-container' ); + moduleFilter.innerHTML = moduleFilterHtml; + addEvent( moduleFilter, "change", function() { + var selectBox = moduleFilter.getElementsByTagName("select")[0], + selectedModule = decodeURIComponent(selectBox.options[selectBox.selectedIndex].value); + + window.location = QUnit.url( { module: ( selectedModule === "" ) ? undefined : selectedModule } ); + }); + toolbar.appendChild(moduleFilter); + } } // `main` initialized at top of scope @@ -970,9 +1072,9 @@ window.onerror = function ( error, filePath, linerNr ) { } QUnit.pushFailure( error, filePath + ":" + linerNr ); } else { - QUnit.test( "global failure", function() { + QUnit.test( "global failure", extend( function() { QUnit.pushFailure( error, filePath + ":" + linerNr ); - }); + }, { validTest: validTest } ) ); } return false; } @@ -1039,6 +1141,11 @@ function done() { } } + // scroll back to top to show results + if ( window.scrollTo ) { + window.scrollTo(0, 0); + } + runLoggingCallbacks( "done", QUnit, { failed: config.stats.bad, passed: passed, @@ -1051,14 +1158,20 @@ function done() { function validTest( test ) { var include, filter = config.filter && config.filter.toLowerCase(), - module = config.module, + module = config.module && config.module.toLowerCase(), fullName = (test.module + ": " + test.testName).toLowerCase(); + // Internally-generated tests are always valid + if ( test.callback && test.callback.validTest === validTest ) { + delete test.callback.validTest; + return true; + } + if ( config.testNumber ) { return test.testNumber === config.testNumber; } - if ( module && test.module !== module ) { + if ( module && ( !test.module || test.module.toLowerCase() !== module ) ) { return false; } @@ -1335,7 +1448,8 @@ QUnit.equiv = (function() { a.global === b.global && // (gmi) ... a.ignoreCase === b.ignoreCase && - a.multiline === b.multiline; + a.multiline === b.multiline && + a.sticky === b.sticky; }, // - skip when the property is a method of an instance (OOP) diff --git a/vendor/underscore/test/arrays.js b/vendor/underscore/test/arrays.js index 87a21cf3f..78cf23399 100644 --- a/vendor/underscore/test/arrays.js +++ b/vendor/underscore/test/arrays.js @@ -25,6 +25,8 @@ $(document).ready(function() { equal(result.join(', '), '2, 3, 4', 'aliased as tail and works on arguments object'); result = _.map([[1,2,3],[1,2,3]], _.rest); equal(_.flatten(result).join(','), '2,3,2,3', 'works well with _.map'); + result = (function(){ return _(arguments).drop(); })(1, 2, 3, 4); + equal(result.join(', '), '2, 3, 4', 'aliased as drop and works on arguments object'); }); test("arrays: initial", function() { @@ -123,10 +125,17 @@ $(document).ready(function() { equal(String(stooges), 'moe,30,true,larry,40,,curly,50,', 'zipped together arrays of different lengths'); }); - test('arrays: zipObject', function() { - var result = _.zipObject(['moe', 'larry', 'curly'], [30, 40, 50]); + test('arrays: object', function() { + var result = _.object(['moe', 'larry', 'curly'], [30, 40, 50]); var shouldBe = {moe: 30, larry: 40, curly: 50}; ok(_.isEqual(result, shouldBe), 'two arrays zipped together into an object'); + + result = _.object([['one', 1], ['two', 2], ['three', 3]]); + shouldBe = {one: 1, two: 2, three: 3}; + ok(_.isEqual(result, shouldBe), 'an array of pairs zipped together into an object'); + + var stooges = {moe: 30, larry: 40, curly: 50}; + ok(_.isEqual(_.object(_.pairs(stooges)), stooges), 'an object converted to pairs and back to an object'); }); test("arrays: indexOf", function() { diff --git a/vendor/underscore/test/collections.js b/vendor/underscore/test/collections.js index 711053a90..b9ed33f6a 100644 --- a/vendor/underscore/test/collections.js +++ b/vendor/underscore/test/collections.js @@ -141,6 +141,7 @@ $(document).ready(function() { ok(_.all([1], _.identity) === true, 'cast to boolean - true'); ok(_.all([0], _.identity) === false, 'cast to boolean - false'); ok(_.every([true, true, true], _.identity), 'aliased as "every"'); + ok(!_.all([undefined, undefined, undefined], _.identity), 'works with arrays of undefined'); }); test('collections: any', function() { @@ -303,6 +304,14 @@ $(document).ready(function() { test('collections: size', function() { equal(_.size({one : 1, two : 2, three : 3}), 3, 'can compute the size of an object'); equal(_.size([1, 2, 3]), 3, 'can compute the size of an array'); + + var func = function() { + return _.size(arguments); + }; + + equal(func(1, 2, 3, 4), 4, 'can test the size of the arguments object'); + + equal(_.size('hello'), 5, 'can compute the size of a string'); }); }); diff --git a/vendor/underscore/test/objects.js b/vendor/underscore/test/objects.js index b5096c84c..6c207f4f3 100644 --- a/vendor/underscore/test/objects.js +++ b/vendor/underscore/test/objects.js @@ -18,6 +18,16 @@ $(document).ready(function() { equal(_.values({one : 1, two : 2}).join(', '), '1, 2', 'can extract the values from an object'); }); + test("objects: pairs", function() { + deepEqual(_.pairs({one: 1, two: 2}), [['one', 1], ['two', 2]], 'can convert an object into pairs'); + }); + + test("objects: invert", function() { + var obj = {first: 'Moe', second: 'Larry', third: 'Curly'}; + equal(_.keys(_.invert(obj)).join(' '), 'Moe Larry Curly', 'can invert an object'); + ok(_.isEqual(_.invert(_.invert(obj)), obj), 'two inverts gets you back where you started'); + }); + test("objects: functions", function() { var obj = {a : 'dash', b : _.map, c : (/yo/), d : _.reduce}; ok(_.isEqual(['b', 'd'], _.functions(obj)), 'can grab the function names of any passed-in object'); diff --git a/vendor/underscore/test/utility.js b/vendor/underscore/test/utility.js index 36f52664f..c8ebc889b 100644 --- a/vendor/underscore/test/utility.js +++ b/vendor/underscore/test/utility.js @@ -48,6 +48,15 @@ $(document).ready(function() { test("utility: _.escape", function() { equal(_.escape("Curly & Moe"), "Curly & Moe"); equal(_.escape("Curly & Moe"), "Curly &amp; Moe"); + equal(_.escape(null), ''); + }); + + test("utility: _.unescape", function() { + var string = "Curly & Moe"; + equal(_.unescape("Curly & Moe"), string); + equal(_.unescape("Curly &amp; Moe"), "Curly & Moe"); + equal(_.unescape(null), ''); + equal(_.unescape(_.escape(string)), string); }); test("utility: template", function() { @@ -156,6 +165,14 @@ $(document).ready(function() { equal(templateWithNull({planet : "world"}), "a null undefined world", "can handle missing escape and evaluate settings"); }); + test('utility: _.template provides the generated function source, when a SyntaxError occurs', function() { + try { + _.template('<%= if %>'); + } catch (e) { + ok(e.source.indexOf('( if )') > 0); + } + }); + test('_.template handles \\u2028 & \\u2029', function() { var tmpl = _.template('

\u2028<%= "\\u2028\\u2029" %>\u2029

'); strictEqual(tmpl(), '

\u2028\u2028\u2029\u2029

'); diff --git a/vendor/underscore/underscore-min.js b/vendor/underscore/underscore-min.js index 9158d59ce..15530c637 100644 --- a/vendor/underscore/underscore-min.js +++ b/vendor/underscore/underscore-min.js @@ -5,28 +5,29 @@ // Oliver Steele's Functional, and John Resig's Micro-Templating. // For all details and documentation: // http://documentcloud.github.com/underscore -(function(){var s=this,L=s._,o={},k=Array.prototype,p=Object.prototype,M=k.push,h=k.slice,N=k.unshift,m=p.toString,O=p.hasOwnProperty,z=k.forEach,A=k.map,B=k.reduce,C=k.reduceRight,D=k.filter,E=k.every,F=k.some,q=k.indexOf,G=k.lastIndexOf,p=Array.isArray,P=Object.keys,t=Function.prototype.bind,b=function(a){return new l(a)};"undefined"!==typeof exports?("undefined"!==typeof module&&module.exports&&(exports=module.exports=b),exports._=b):s._=b;b.VERSION="1.3.3";var i=b.each=b.forEach=function(a,c, -d){if(a!=null)if(z&&a.forEach===z)a.forEach(c,d);else if(a.length===+a.length)for(var e=0,f=a.length;e2;a==null&&(a=[]);if(B&&a.reduce===B){e&&(c=b.bind(c,e));return f?a.reduce(c, -d):a.reduce(c)}i(a,function(a,b,h){if(f)d=c.call(e,d,a,b,h);else{d=a;f=true}});if(!f)throw new TypeError("Reduce of empty array with no initial value");return d};b.reduceRight=b.foldr=function(a,c,d,e){var f=arguments.length>2;a==null&&(a=[]);if(C&&a.reduceRight===C){e&&(c=b.bind(c,e));return f?a.reduceRight(c,d):a.reduceRight(c)}var g=b.toArray(a).reverse();e&&!f&&(c=b.bind(c,e));return f?b.reduce(g,c,d,e):b.reduce(g,c)};b.find=b.detect=function(a,c,b){var e;H(a,function(a,g,j){if(c.call(b,a,g,j)){e= -a;return true}});return e};b.filter=b.select=function(a,c,b){var e=[];if(a==null)return e;if(D&&a.filter===D)return a.filter(c,b);i(a,function(a,g,j){c.call(b,a,g,j)&&(e[e.length]=a)});return e};b.reject=function(a,c,b){var e=[];if(a==null)return e;i(a,function(a,g,j){c.call(b,a,g,j)||(e[e.length]=a)});return e};b.every=b.all=function(a,c,b){var e=true;if(a==null)return e;if(E&&a.every===E)return a.every(c,b);i(a,function(a,g,j){if(!(e=e&&c.call(b,a,g,j)))return o});return!!e};var H=b.some=b.any= -function(a,c,d){c||(c=b.identity);var e=false;if(a==null)return e;if(F&&a.some===F)return a.some(c,d);i(a,function(a,b,j){if(e||(e=c.call(d,a,b,j)))return o});return!!e};b.include=b.contains=function(a,c){var b=false;if(a==null)return b;if(q&&a.indexOf===q)return a.indexOf(c)!=-1;return b=H(a,function(a){return a===c})};b.invoke=function(a,c){var d=h.call(arguments,2);return b.map(a,function(a){return(b.isFunction(c)?c:a[c]).apply(a,d)})};b.pluck=function(a,c){return b.map(a,function(a){return a[c]})}; -b.max=function(a,c,d){if(!c&&b.isArray(a)&&a[0]===+a[0]&&a.length<65535)return Math.max.apply(Math,a);if(!c&&b.isEmpty(a))return-Infinity;var e={computed:-Infinity};i(a,function(a,b,j){b=c?c.call(d,a,b,j):a;b>=e.computed&&(e={value:a,computed:b})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a)&&a[0]===+a[0]&&a.length<65535)return Math.min.apply(Math,a);if(!c&&b.isEmpty(a))return Infinity;var e={computed:Infinity};i(a,function(a,b,j){b=c?c.call(d,a,b,j):a;bd?1:0}),"value")};var I=function(a,c){return b.isFunction(c)?c:function(a){return a[c]}},J=function(a,c,b){var e={},f=I(a,c);i(a,function(a,c){var h=f(a,c);b(e, -h,a)});return e};b.groupBy=function(a,c){return J(a,c,function(a,c,b){(a[c]||(a[c]=[])).push(b)})};b.countBy=function(a,c){return J(a,c,function(a,c){a[c]||(a[c]=0);a[c]++})};b.sortedIndex=function(a,c,d){d||(d=b.identity);for(var c=d(c),e=0,f=a.length;e>1;d(a[g])=0})})};b.difference=function(a){var c=r(h.call(arguments,1),true,[]); -return b.filter(a,function(a){return!b.include(c,a)})};b.zip=function(){for(var a=h.call(arguments),c=b.max(b.pluck(a,"length")),d=Array(c),e=0;e=0;d--)b=[a[d].apply(this,b)];return b[0]}};b.after=function(a,b){return a<=0?b():function(){if(--a<1)return b.apply(this,arguments)}};b.keys=P||function(a){if(a!==Object(a))throw new TypeError("Invalid object");var c=[],d;for(d in a)b.has(a,d)&&(c[c.length]=d);return c};b.values= -function(a){return b.map(a,b.identity)};b.functions=b.methods=function(a){var c=[],d;for(d in a)b.isFunction(a[d])&&c.push(d);return c.sort()};b.extend=function(a){i(h.call(arguments,1),function(b){for(var d in b)a[d]=b[d]});return a};b.pick=function(a){var c={},d=b.flatten(h.call(arguments,1));i(d,function(b){b in a&&(c[b]=a[b])});return c};b.omit=function(a){var c={},d=b.flatten(h.call(arguments,1)),e;for(e in a)b.include(d,e)||(c[e]=a[e]);return c};b.defaults=function(a){i(h.call(arguments,1), -function(b){for(var d in b)a[d]==null&&(a[d]=b[d])});return a};b.clone=function(a){return!b.isObject(a)?a:b.isArray(a)?a.slice():b.extend({},a)};b.tap=function(a,b){b(a);return a};var u=function(a,c,d){if(a===c)return a!==0||1/a==1/c;if(a==null||c==null)return a===c;if(a._chain)a=a._wrapped;if(c._chain)c=c._wrapped;if(a.isEqual&&b.isFunction(a.isEqual))return a.isEqual(c);if(c.isEqual&&b.isFunction(c.isEqual))return c.isEqual(a);var e=m.call(a);if(e!=m.call(c))return false;switch(e){case "[object String]":return a== -""+c;case "[object Number]":return a!=+a?c!=+c:a==0?1/a==1/c:a==+c;case "[object Date]":case "[object Boolean]":return+a==+c;case "[object RegExp]":return a.source==c.source&&a.global==c.global&&a.multiline==c.multiline&&a.ignoreCase==c.ignoreCase}if(typeof a!="object"||typeof c!="object")return false;for(var f=d.length;f--;)if(d[f]==a)return true;d.push(a);var f=0,g=true;if(e=="[object Array]"){f=a.length;if(g=f==c.length)for(;f--;)if(!(g=f in a==f in c&&u(a[f],c[f],d)))break}else{if("constructor"in -a!="constructor"in c||a.constructor!=c.constructor)return false;for(var h in a)if(b.has(a,h)){f++;if(!(g=b.has(c,h)&&u(a[h],c[h],d)))break}if(g){for(h in c)if(b.has(c,h)&&!f--)break;g=!f}}d.pop();return g};b.isEqual=function(a,b){return u(a,b,[])};b.isEmpty=function(a){if(a==null)return true;if(b.isArray(a)||b.isString(a))return a.length===0;for(var c in a)if(b.has(a,c))return false;return true};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=p||function(a){return m.call(a)=="[object Array]"}; -b.isObject=function(a){return a===Object(a)};i("Arguments,Function,String,Number,Date,RegExp".split(","),function(a){b["is"+a]=function(b){return m.call(b)=="[object "+a+"]"}});b.isArguments(arguments)||(b.isArguments=function(a){return!(!a||!b.has(a,"callee"))});b.isFinite=function(a){return b.isNumber(a)&&isFinite(a)};b.isNaN=function(a){return a!==a};b.isBoolean=function(a){return a===true||a===false||m.call(a)=="[object Boolean]"};b.isNull=function(a){return a===null};b.isUndefined=function(a){return a=== -void 0};b.has=function(a,b){return O.call(a,b)};b.noConflict=function(){s._=L;return this};b.identity=function(a){return a};b.times=function(a,b,d){for(var e=0;e":">",'"':""","'":"'","/":"/"},R=/[&<>"'\/]/g;b.escape=function(a){return(""+a).replace(R,function(a){return Q[a]})};b.result=function(a,c){if(a==null)return null;var d=a[c];return b.isFunction(d)?d.call(a):d};b.mixin=function(a){i(b.functions(a),function(c){S(c,b[c]=a[c])})}; -var T=0;b.uniqueId=function(a){var b=T++;return a?a+b:b};b.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var v=/.^/,n={"\\":"\\","'":"'",r:"\r",n:"\n",t:"\t",u2028:"\u2028",u2029:"\u2029"},w;for(w in n)n[n[w]]=w;var U=/\\|'|\r|\n|\t|\u2028|\u2029/g,V=/\\(\\|'|r|n|t|u2028|u2029)/g,x=function(a){return a.replace(V,function(a,b){return n[b]})};b.template=function(a,c,d){d=b.defaults(d||{},b.templateSettings);a="__p+='"+a.replace(U,function(a){return"\\"+ -n[a]}).replace(d.escape||v,function(a,b){return"'+\n((__t=("+x(b)+"))==null?'':_.escape(__t))+\n'"}).replace(d.interpolate||v,function(a,b){return"'+\n((__t=("+x(b)+"))==null?'':__t)+\n'"}).replace(d.evaluate||v,function(a,b){return"';\n"+x(b)+"\n__p+='"})+"';\n";d.variable||(a="with(obj||{}){\n"+a+"}\n");var a="var __t,__p='',__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,'')};\n"+a+"return __p;\n",e=new Function(d.variable||"obj","_",a);if(c)return e(c,b);c=function(a){return e.call(this, -a,b)};c.source="function("+(d.variable||"obj")+"){\n"+a+"}";return c};b.chain=function(a){return b(a).chain()};var l=function(a){this._wrapped=a};b.prototype=l.prototype;var y=function(a,c){return c?b(a).chain():a},S=function(a,c){l.prototype[a]=function(){var a=h.call(arguments);N.call(a,this._wrapped);return y(c.apply(b,a),this._chain)}};b.mixin(b);i("pop,push,reverse,shift,sort,splice,unshift".split(","),function(a){var b=k[a];l.prototype[a]=function(){var d=this._wrapped;b.apply(d,arguments); -(a=="shift"||a=="splice")&&d.length===0&&delete d[0];return y(d,this._chain)}});i(["concat","join","slice"],function(a){var b=k[a];l.prototype[a]=function(){return y(b.apply(this._wrapped,arguments),this._chain)}});l.prototype.chain=function(){this._chain=true;return this};l.prototype.value=function(){return this._wrapped}}).call(this); +(function(){var t=this,M=t._,p={},k=Array.prototype,q=Object.prototype,N=k.push,h=k.slice,O=k.unshift,m=q.toString,P=q.hasOwnProperty,A=k.forEach,B=k.map,C=k.reduce,D=k.reduceRight,E=k.filter,F=k.every,G=k.some,r=k.indexOf,H=k.lastIndexOf,q=Array.isArray,Q=Object.keys,u=Function.prototype.bind,b=function(a){return new l(a)};"undefined"!==typeof exports?("undefined"!==typeof module&&module.exports&&(exports=module.exports=b),exports._=b):t._=b;b.VERSION="1.3.3";var j=b.each=b.forEach=function(a,c, +d){if(a!=null)if(A&&a.forEach===A)a.forEach(c,d);else if(a.length===+a.length)for(var e=0,f=a.length;e2;a==null&&(a=[]);if(C&&a.reduce===C){e&&(c=b.bind(c,e));return f?a.reduce(c, +d):a.reduce(c)}j(a,function(a,b,h){if(f)d=c.call(e,d,a,b,h);else{d=a;f=true}});if(!f)throw new TypeError("Reduce of empty array with no initial value");return d};b.reduceRight=b.foldr=function(a,c,d,e){var f=arguments.length>2;a==null&&(a=[]);if(D&&a.reduceRight===D){e&&(c=b.bind(c,e));return f?a.reduceRight(c,d):a.reduceRight(c)}var g=b.toArray(a).reverse();e&&!f&&(c=b.bind(c,e));return f?b.reduce(g,c,d,e):b.reduce(g,c)};b.find=b.detect=function(a,c,b){var e;I(a,function(a,g,i){if(c.call(b,a,g,i)){e= +a;return true}});return e};b.filter=b.select=function(a,c,b){var e=[];if(a==null)return e;if(E&&a.filter===E)return a.filter(c,b);j(a,function(a,g,i){c.call(b,a,g,i)&&(e[e.length]=a)});return e};b.reject=function(a,c,b){var e=[];if(a==null)return e;j(a,function(a,g,i){c.call(b,a,g,i)||(e[e.length]=a)});return e};b.every=b.all=function(a,c,d){c||(c=b.identity);var e=true;if(a==null)return e;if(F&&a.every===F)return a.every(c,d);j(a,function(a,b,i){if(!(e=e&&c.call(d,a,b,i)))return p});return!!e};var I= +b.some=b.any=function(a,c,d){c||(c=b.identity);var e=false;if(a==null)return e;if(G&&a.some===G)return a.some(c,d);j(a,function(a,b,i){if(e||(e=c.call(d,a,b,i)))return p});return!!e};b.include=b.contains=function(a,c){var b=false;if(a==null)return b;if(r&&a.indexOf===r)return a.indexOf(c)!=-1;return b=I(a,function(a){return a===c})};b.invoke=function(a,c){var d=h.call(arguments,2);return b.map(a,function(a){return(b.isFunction(c)?c:a[c]).apply(a,d)})};b.pluck=function(a,c){return b.map(a,function(a){return a[c]})}; +b.max=function(a,c,d){if(!c&&b.isArray(a)&&a[0]===+a[0]&&a.length<65535)return Math.max.apply(Math,a);if(!c&&b.isEmpty(a))return-Infinity;var e={computed:-Infinity};j(a,function(a,b,i){b=c?c.call(d,a,b,i):a;b>=e.computed&&(e={value:a,computed:b})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a)&&a[0]===+a[0]&&a.length<65535)return Math.min.apply(Math,a);if(!c&&b.isEmpty(a))return Infinity;var e={computed:Infinity};j(a,function(a,b,i){b=c?c.call(d,a,b,i):a;bd?1:0}),"value")};var J=function(a,c){return b.isFunction(c)?c:function(a){return a[c]}},K=function(a,c,b){var e={},f=J(a,c);j(a,function(a,c){var h=f(a,c);b(e, +h,a)});return e};b.groupBy=function(a,c){return K(a,c,function(a,c,b){(a[c]||(a[c]=[])).push(b)})};b.countBy=function(a,c){return K(a,c,function(a,c){a[c]||(a[c]=0);a[c]++})};b.sortedIndex=function(a,c,d){d||(d=b.identity);for(var c=d(c),e=0,f=a.length;e>1;d(a[g])=0})})};b.difference=function(a){var c=s(h.call(arguments, +1),true,[]);return b.filter(a,function(a){return!b.include(c,a)})};b.zip=function(){for(var a=h.call(arguments),c=b.max(b.pluck(a,"length")),d=Array(c),e=0;e=0;d--)b=[a[d].apply(this,b)];return b[0]}};b.after=function(a,b){return a<=0?b():function(){if(--a<1)return b.apply(this,arguments)}};b.keys=Q||function(a){if(a!==Object(a))throw new TypeError("Invalid object");var c= +[],d;for(d in a)b.has(a,d)&&(c[c.length]=d);return c};b.values=function(a){return b.map(a,b.identity)};b.pairs=function(a){return b.map(a,function(a,b){return[b,a]})};b.invert=function(a){return b.reduce(a,function(a,b,e){a[b]=e;return a},{})};b.functions=b.methods=function(a){var c=[],d;for(d in a)b.isFunction(a[d])&&c.push(d);return c.sort()};b.extend=function(a){j(h.call(arguments,1),function(b){for(var d in b)a[d]=b[d]});return a};b.pick=function(a){var c={},d=b.flatten(h.call(arguments,1));j(d, +function(b){b in a&&(c[b]=a[b])});return c};b.omit=function(a){var c={},d=b.flatten(h.call(arguments,1)),e;for(e in a)b.include(d,e)||(c[e]=a[e]);return c};b.defaults=function(a){j(h.call(arguments,1),function(b){for(var d in b)a[d]==null&&(a[d]=b[d])});return a};b.clone=function(a){return!b.isObject(a)?a:b.isArray(a)?a.slice():b.extend({},a)};b.tap=function(a,b){b(a);return a};var v=function(a,c,d){if(a===c)return a!==0||1/a==1/c;if(a==null||c==null)return a===c;if(a._chain)a=a._wrapped;if(c._chain)c= +c._wrapped;if(a.isEqual&&b.isFunction(a.isEqual))return a.isEqual(c);if(c.isEqual&&b.isFunction(c.isEqual))return c.isEqual(a);var e=m.call(a);if(e!=m.call(c))return false;switch(e){case "[object String]":return a==""+c;case "[object Number]":return a!=+a?c!=+c:a==0?1/a==1/c:a==+c;case "[object Date]":case "[object Boolean]":return+a==+c;case "[object RegExp]":return a.source==c.source&&a.global==c.global&&a.multiline==c.multiline&&a.ignoreCase==c.ignoreCase}if(typeof a!="object"||typeof c!="object")return false; +for(var f=d.length;f--;)if(d[f]==a)return true;d.push(a);var f=0,g=true;if(e=="[object Array]"){f=a.length;if(g=f==c.length)for(;f--;)if(!(g=f in a==f in c&&v(a[f],c[f],d)))break}else{if("constructor"in a!="constructor"in c||a.constructor!=c.constructor)return false;for(var i in a)if(b.has(a,i)){f++;if(!(g=b.has(c,i)&&v(a[i],c[i],d)))break}if(g){for(i in c)if(b.has(c,i)&&!f--)break;g=!f}}d.pop();return g};b.isEqual=function(a,b){return v(a,b,[])};b.isEmpty=function(a){if(a==null)return true;if(b.isArray(a)|| +b.isString(a))return a.length===0;for(var c in a)if(b.has(a,c))return false;return true};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=q||function(a){return m.call(a)=="[object Array]"};b.isObject=function(a){return a===Object(a)};j("Arguments,Function,String,Number,Date,RegExp".split(","),function(a){b["is"+a]=function(b){return m.call(b)=="[object "+a+"]"}});b.isArguments(arguments)||(b.isArguments=function(a){return!(!a||!b.has(a,"callee"))});b.isFinite=function(a){return b.isNumber(a)&& +isFinite(a)};b.isNaN=function(a){return a!==a};b.isBoolean=function(a){return a===true||a===false||m.call(a)=="[object Boolean]"};b.isNull=function(a){return a===null};b.isUndefined=function(a){return a===void 0};b.has=function(a,b){return P.call(a,b)};b.noConflict=function(){t._=M;return this};b.identity=function(a){return a};b.times=function(a,b,d){for(var e=0;e":">",'"':""", +"'":"'","/":"/"}};n.unescape=b.invert(n.escape);var R={escape:RegExp("["+b.keys(n.escape).join("")+"]","g"),unescape:RegExp("("+b.keys(n.unescape).join("|")+")","g")};b.each(["escape","unescape"],function(a){b[a]=function(b){return b==null?"":(""+b).replace(R[a],function(b){return n[a][b]})}});b.result=function(a,c){if(a==null)return null;var d=a[c];return b.isFunction(d)?d.call(a):d};b.mixin=function(a){j(b.functions(a),function(c){S(c,b[c]=a[c])})};var T=0;b.uniqueId=function(a){var b= +T++;return a?a+b:b};b.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var w=/.^/,o={"\\":"\\","'":"'",r:"\r",n:"\n",t:"\t",u2028:"\u2028",u2029:"\u2029"},x;for(x in o)o[o[x]]=x;var U=/\\|'|\r|\n|\t|\u2028|\u2029/g,V=/\\(\\|'|r|n|t|u2028|u2029)/g,y=function(a){return a.replace(V,function(a,b){return o[b]})};b.template=function(a,c,d){d=b.defaults(d||{},b.templateSettings);a="__p+='"+a.replace(U,function(a){return"\\"+o[a]}).replace(d.escape||w, +function(a,b){return"'+\n((__t=("+y(b)+"))==null?'':_.escape(__t))+\n'"}).replace(d.interpolate||w,function(a,b){return"'+\n((__t=("+y(b)+"))==null?'':__t)+\n'"}).replace(d.evaluate||w,function(a,b){return"';\n"+y(b)+"\n__p+='"})+"';\n";d.variable||(a="with(obj||{}){\n"+a+"}\n");a="var __t,__p='',__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,'');};\n"+a+"return __p;\n";try{var e=new Function(d.variable||"obj","_",a)}catch(f){f.source=a;throw f;}if(c)return e(c,b);c=function(a){return e.call(this, +a,b)};c.source="function("+(d.variable||"obj")+"){\n"+a+"}";return c};b.chain=function(a){return b(a).chain()};var l=function(a){this._wrapped=a};b.prototype=l.prototype;var z=function(a,c){return c?b(a).chain():a},S=function(a,c){l.prototype[a]=function(){var a=h.call(arguments);O.call(a,this._wrapped);return z(c.apply(b,a),this._chain)}};b.mixin(b);j("pop,push,reverse,shift,sort,splice,unshift".split(","),function(a){var b=k[a];l.prototype[a]=function(){var d=this._wrapped;b.apply(d,arguments); +(a=="shift"||a=="splice")&&d.length===0&&delete d[0];return z(d,this._chain)}});j(["concat","join","slice"],function(a){var b=k[a];l.prototype[a]=function(){return z(b.apply(this._wrapped,arguments),this._chain)}});l.prototype.chain=function(){this._chain=true;return this};l.prototype.value=function(){return this._wrapped}}).call(this); diff --git a/vendor/underscore/underscore.js b/vendor/underscore/underscore.js index 2e95ca6f5..4b6e76167 100644 --- a/vendor/underscore/underscore.js +++ b/vendor/underscore/underscore.js @@ -174,6 +174,7 @@ // Delegates to **ECMAScript 5**'s native `every` if available. // Aliased as `all`. _.every = _.all = function(obj, iterator, context) { + iterator || (iterator = _.identity); var result = true; if (obj == null) return result; if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context); @@ -338,7 +339,7 @@ // Return the number of elements in an object. _.size = function(obj) { - return _.isArray(obj) ? obj.length : _.keys(obj).length; + return (obj.length === +obj.length) ? obj.length : _.keys(obj).length; }; // Array Functions @@ -369,12 +370,12 @@ } }; - // Returns everything but the first entry of the array. Aliased as `tail`. - // Especially useful on the arguments object. Passing an **index** will return - // the rest of the values in the array from that index onward. The **guard** + // Returns everything but the first entry of the array. Aliased as `tail` and `drop`. + // Especially useful on the arguments object. Passing an **n** will return + // the rest N values in the array. The **guard** // check allows it to work with `_.map`. - _.rest = _.tail = function(array, index, guard) { - return slice.call(array, (index == null) || guard ? 1 : index); + _.rest = _.tail = _.drop = function(array, n, guard) { + return slice.call(array, (n == null) || guard ? 1 : n); }; // Trim out all falsy values from an array. @@ -456,12 +457,17 @@ return results; }; - // Zip together two arrays -- an array of keys and an array of values -- into - // a single object. - _.zipObject = function(keys, values) { + // Converts lists into objects. Pass either a single array of `[key, value]` + // pairs, or two parallel arrays of the same length -- one of keys, and one of + // the corresponding values. + _.object = function(list, values) { var result = {}; - for (var i = 0, l = keys.length; i < l; i++) { - result[keys[i]] = values[i]; + for (var i = 0, l = list.length; i < l; i++) { + if (values) { + result[list[i]] = values[i]; + } else { + result[list[i][0]] = list[i][1]; + } } return result; }; @@ -622,7 +628,9 @@ return function() { if (ran) return memo; ran = true; - return memo = func.apply(this, arguments); + memo = func.apply(this, arguments); + func = null; + return memo; }; }; @@ -676,6 +684,21 @@ return _.map(obj, _.identity); }; + // Convert an object into a list of `[key, value]` pairs. + _.pairs = function(obj) { + return _.map(obj, function(value, key) { + return [key, value]; + }); + }; + + // Invert the keys and values of an object. The values must be serializable. + _.invert = function(obj) { + return _.reduce(obj, function(memo, value, key) { + memo[value] = key; + return memo; + }, {}); + }; + // Return a sorted list of the function names available on the object. // Aliased as `methods` _.functions = _.methods = function(obj) { @@ -925,25 +948,39 @@ for (var i = 0; i < n; i++) iterator.call(context, i); }; + // Return a random integer between min and max (inclusive). + _.random = function(min, max) { + return min + (0 | Math.random() * (max - min + 1)); + }; + // List of HTML entities for escaping. - var htmlEscapes = { - '&': '&', - '<': '<', - '>': '>', - '"': '"', - "'": ''', - '/': '/' + var entityMap = { + escape: { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + '/': '/' + } + }; + entityMap.unescape = _.invert(entityMap.escape); + + // Regexes containing the keys and values listed immediately above. + var entityRegexes = { + escape: new RegExp('[' + _.keys(entityMap.escape).join('') + ']', 'g'), + unescape: new RegExp('(' + _.keys(entityMap.unescape).join('|') + ')', 'g') }; - // Regex containing the keys listed immediately above. - var htmlEscaper = /[&<>"'\/]/g; - - // Escape a string for HTML interpolation. - _.escape = function(string) { - return ('' + string).replace(htmlEscaper, function(match) { - return htmlEscapes[match]; - }); - }; + // Functions for escaping and unescaping strings to/from HTML interpolation. + _.each(['escape', 'unescape'], function(method) { + _[method] = function(string) { + if (string == null) return ''; + return ('' + string).replace(entityRegexes[method], function(match) { + return entityMap[method][match]; + }); + }; + }); // If the value of the named property is a function then invoke it; // otherwise, return it. @@ -1033,10 +1070,16 @@ if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; 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"; - var render = new Function(settings.variable || 'obj', '_', source); + try { + var render = new Function(settings.variable || 'obj', '_', source); + } catch (e) { + e.source = source; + throw e; + } + if (data) return render(data, _); var template = function(data) { return render.call(this, data, _);