backbone-associations
Advanced tools
Comparing version 0.4.1 to 0.4.2
@@ -1,7 +0,7 @@ | ||
(function(){var f,j,q,m,p,o,s;"undefined"!==typeof require?(f=require("underscore"),j=require("backbone"),exports=module.exports=j):(f=this._,j=this.Backbone);q=j.Model;m=j.Collection;p=q.prototype;s=/[\.\[\]]+/g;j.Many="Many";j.One="One";o=j.AssociatedModel=q.extend({relations:void 0,_proxyCalls:void 0,get:function(b){var a=p.get.call(this,b);return a?a:this.getAttr.apply(this,arguments)},set:function(b,a,d){var c,e,k,h,g=this;if(f.isObject(b)||b==null){c=b;d=a}else{c={};c[b]=a}if(!c)return this; | ||
for(e in c){k||(k={});if(e.match(s)){b=r(e);a=f.initial(b);b=b[b.length-1];a=this.get(a);if(a instanceof o){a=k[a.cid]||(k[a.cid]={model:a,data:{}});a.data[b]=c[e]}}else{a=k[this.cid]||(k[this.cid]={model:this,data:{}});a.data[e]=c[e]}}if(k)for(h in k){a=k[h];this.setAttr.call(a.model,a.data,d)||(g=false)}else return this.setAttr.call(this,c,d);return g},setAttr:function(b,a){var d;a||(a={});if(a.unset)for(d in b)b[d]=void 0;this.relations&&f.each(this.relations,function(c){var e=c.key,d=c.relatedModel, | ||
h=c.collectionType,g,i,l,n;if(b[e]){g=f.result(b,e);d&&f.isString(d)&&(d=eval(d));h&&f.isString(h)&&(h=eval(h));i=c.options?f.extend({},c.options,a):a;if(c.type===j.Many){if(h&&!h.prototype instanceof m)throw Error("collectionType must inherit from Backbone.Collection");if(g instanceof m){l=g;b[e]=l}else if(this.attributes[e]){this.attributes[e].reset(g,i);delete b[e]}else{l=h?new h:this._createCollection(d);l.add(g,i);b[e]=l}}else if(c.type===j.One&&d){l=g instanceof o?g:new d(g);b[e]=l}if((n=l)&& | ||
!n._proxyCallback){n._proxyCallback=function(){return this._bubbleEvent.call(this,e,n,arguments)};n.on("all",n._proxyCallback,this)}}},this);return p.set.call(this,b,a)},_bubbleEvent:function(b,a,d){var c=d[0].split(":"),e=c[0],k=d[1],h=-1,g=a._proxyCalls,i;f.size(c)>1&&(i=c[1]);if(a instanceof m&&"change"===e&&k){var l=r(i),j=f.initial(l);(c=a.find(function(a){var b=a.get(l);return k===(b instanceof o||b instanceof m)?b:a.get(j)||a}))&&(h=a.indexOf(c))}i=b+(h!==-1?"["+h+"]":"")+(i?"."+i:"");d[0]= | ||
e+":"+i;if(g){if(h=f.find(g,function(a,b){return i.indexOf(b,i.length-b.length)!==-1}))return this}else g=a._proxyCalls={};g[i]=true;if("change"===e){this._previousAttributes[b]=a._previousAttributes;this.changed[b]=a}this.trigger.apply(this,d);i&&g&&delete g[i];return this},_createCollection:function(b){var a=b;f.isString(a)&&(a=eval(a));if(a&&a.prototype instanceof o){b=new m;b.model=a}else throw Error("type must inherit from Backbone.AssociatedModel");return b},toJSON:function(b){var a,d;if(!this.visited){this.visited= | ||
true;a=p.toJSON.apply(this,arguments);this.relations&&f.each(this.relations,function(c){var e=this.attributes[c.key];if(e){d=e.toJSON(b);a[c.key]=f.isArray(d)?f.compact(d):d}},this);delete this.visited}return a},clone:function(){return new this.constructor(this.toJSON())},getAttr:function(b){var a=this,b=r(b),d,c;if(!(f.size(b)<1)){for(c=0;c<b.length;c++){d=b[c];if(!a)break;a=a instanceof m&&!isNaN(d)?a.at(d):a.attributes[d]}return a}}});var t=/[^\.\[\]]+/g,r=function(b){return b===""?[""]:f.isString(b)? | ||
b.match(t):b||[]}}).call(this); | ||
(function(){var f,h,q,l,p,o,s;"undefined"!==typeof require?(f=require("underscore"),h=require("backbone"),exports=module.exports=h):(f=this._,h=this.Backbone);q=h.Model;l=h.Collection;p=q.prototype;s=/[\.\[\]]+/g;h.Many="Many";h.One="One";o=h.AssociatedModel=q.extend({relations:void 0,_proxyCalls:void 0,get:function(b){var a=p.get.call(this,b);return a?a:this.getAttr.apply(this,arguments)},set:function(b,a,d){var c,k,e,i,g=this;if(f.isObject(b)||b==null){c=b;d=a}else{c={};c[b]=a}if(!c)return this; | ||
for(k in c){e||(e={});if(k.match(s)){b=r(k);a=f.initial(b);b=b[b.length-1];a=this.get(a);if(a instanceof o){a=e[a.cid]||(e[a.cid]={model:a,data:{}});a.data[b]=c[k]}}else{a=e[this.cid]||(e[this.cid]={model:this,data:{}});a.data[k]=c[k]}}if(e)for(i in e){a=e[i];this.setAttr.call(a.model,a.data,d)||(g=false)}else return this.setAttr.call(this,c,d);return g},setAttr:function(b,a){var d;a||(a={});if(a.unset)for(d in b)b[d]=void 0;this.relations&&f.each(this.relations,function(c){var d=c.key,e=c.relatedModel, | ||
i=c.collectionType,g,j,n,m;if(b[d]){g=f.result(b,d);e&&f.isString(e)&&(e=eval(e));i&&f.isString(i)&&(i=eval(i));j=c.options?f.extend({},c.options,a):a;if(c.type===h.Many){if(i&&!i.prototype instanceof l)throw Error("collectionType must inherit from Backbone.Collection");if(g instanceof l)n=g;else{n=i?new i:this._createCollection(e);n.add(g,j)}b[d]=n}else if(c.type===h.One&&e){n=g instanceof o?g:new e(g);b[d]=n}if((m=n)&&!m._proxyCallback){m._proxyCallback=function(){return this._bubbleEvent.call(this, | ||
d,m,arguments)};m.on("all",m._proxyCallback,this)}}},this);return p.set.call(this,b,a)},_bubbleEvent:function(b,a,d){var c=d[0].split(":"),k=c[0],e=d[1],i=-1,g=a._proxyCalls,j;f.size(c)>1&&(j=c[1]);if(a instanceof l&&"change"===k&&e){var h=r(j),m=f.initial(h);(c=a.find(function(a){if(e===a)return true;if(a){var b=a.get(m);if((b instanceof o||b instanceof l)&&e===b)return true;b=a.get(h);return(b instanceof o||b instanceof l)&&e===b}return false}))&&(i=a.indexOf(c))}j=b+(i!==-1?"["+i+"]":"")+(j?"."+ | ||
j:"");d[0]=k+":"+j;if(g){if(i=f.find(g,function(a,b){return j.indexOf(b,j.length-b.length)!==-1}))return this}else g=a._proxyCalls={};g[j]=true;if("change"===k){this._previousAttributes[b]=a._previousAttributes;this.changed[b]=a}this.trigger.apply(this,d);j&&g&&delete g[j];return this},_createCollection:function(b){var a=b;f.isString(a)&&(a=eval(a));if(a&&a.prototype instanceof o){b=new l;b.model=a}else throw Error("type must inherit from Backbone.AssociatedModel");return b},toJSON:function(b){var a, | ||
d;if(!this.visited){this.visited=true;a=p.toJSON.apply(this,arguments);this.relations&&f.each(this.relations,function(c){var h=this.attributes[c.key];if(h){d=h.toJSON(b);a[c.key]=f.isArray(d)?f.compact(d):d}},this);delete this.visited}return a},clone:function(){return new this.constructor(this.toJSON())},getAttr:function(b){var a=this,b=r(b),d,c;if(!(f.size(b)<1)){for(c=0;c<b.length;c++){d=b[c];if(!a)break;a=a instanceof l&&!isNaN(d)?a.at(d):a.attributes[d]}return a}}});var t=/[^\.\[\]]+/g,r=function(b){return b=== | ||
""?[""]:f.isString(b)?b.match(t):b||[]}}).call(this); |
// | ||
// Backbone-associations.js 0.4.1 | ||
// Backbone-associations.js 0.4.2 | ||
// | ||
@@ -138,10 +138,5 @@ // (c) 2013 Dhruva Ray, Jaynti Kanani, Persistent Systems Ltd. | ||
} else { | ||
if (!this.attributes[relationKey]) { | ||
data = collectionType ? new collectionType() : this._createCollection(relatedModel); | ||
data.add(val, relationOptions); | ||
attributes[relationKey] = data; | ||
} else { | ||
this.attributes[relationKey].reset(val, relationOptions); | ||
delete attributes[relationKey]; | ||
} | ||
data = collectionType ? new collectionType() : this._createCollection(relatedModel); | ||
data.add(val, relationOptions); | ||
attributes[relationKey] = data; | ||
} | ||
@@ -189,6 +184,10 @@ | ||
colModel = relationValue.find(function (model) { | ||
var changedModel = model.get(pathTokens); | ||
return eventObject === (changedModel instanceof AssociatedModel | ||
|| changedModel instanceof BackboneCollection) | ||
? changedModel : (model.get(initialTokens) || model); | ||
if (eventObject === model) return true; | ||
if (model) { | ||
var changedModel = model.get(initialTokens); | ||
if ((changedModel instanceof AssociatedModel || changedModel instanceof BackboneCollection) && eventObject === changedModel) return true; | ||
changedModel = model.get(pathTokens); | ||
return ((changedModel instanceof AssociatedModel || changedModel instanceof BackboneCollection) && eventObject === changedModel); | ||
} | ||
return false; | ||
}); | ||
@@ -195,0 +194,0 @@ colModel && (indexEventObject = relationValue.indexOf(colModel)); |
{ | ||
"name":"backbone-associations", | ||
"version":"0.4.1", | ||
"version":"0.4.2", | ||
"main":"./backbone-associations.js", | ||
@@ -5,0 +5,0 @@ "dependencies":{ |
@@ -18,3 +18,3 @@ { | ||
"main":"backbone-associations.js", | ||
"version":"0.4.1" | ||
"version":"0.4.2" | ||
} |
@@ -26,4 +26,4 @@ # Backbone-associations | ||
* [Production version - 0.4.1](http://dhruvaray.github.com/backbone-associations/backbone-associations-min.js) (1.44K packed and gzipped) | ||
* [Development version - 0.4.1](http://dhruvaray.github.com/backbone-associations/backbone-associations.js) | ||
* [Production version - 0.4.2](http://dhruvaray.github.com/backbone-associations/backbone-associations-min.js) (1.44K packed and gzipped) | ||
* [Development version - 0.4.2](http://dhruvaray.github.com/backbone-associations/backbone-associations.js) | ||
* [Edge version : ] (https://raw.github.com/dhruvaray/backbone-associations/master/backbone-associations.js)[![Build Status](https://travis-ci.org/dhruvaray/backbone-associations.png?branch=master)](https://travis-ci.org/dhruvaray/backbone-associations) | ||
@@ -509,2 +509,4 @@ | ||
##### Query the appropriate object to determine change | ||
When assigning a previously created object graph to a property in an associated model, care must be taken to query the appropriate object for the changed properties. | ||
@@ -523,3 +525,3 @@ | ||
then inside a previously defined `change` event handler | ||
Then inside a previously defined `change` event handler | ||
@@ -535,2 +537,35 @@ ````javascript | ||
##### Use unqualified `change` event name with care | ||
This extension makes use of _fully-qualified-event-path names_ to identify the location of the change in the object graph. (And the event arguments would have the changed object or object property). | ||
The unqualified `change` event would work if an entire object graph is being replaced with another. For example | ||
```javascript | ||
emp.on('change', function () { | ||
console.log("emp has changed");//This WILL fire | ||
}); | ||
emp.on('change:works_for', function () { | ||
console.log('emp attribute works_for has changed');//This WILL fire | ||
}); | ||
emp.set('works_for', {name:'Marketing', number:'24'}); | ||
``` | ||
However, if attributes of a nested object are changed, the unqualified `change` event will not fire for objects (and their parents) who have that nested object as their child. | ||
```javascript | ||
emp.on('change', function () { | ||
console.log("emp has changed"); //This will NOT fire | ||
}); | ||
emp.on('change:works_for', function () { | ||
console.log('emp attribute works_for has changed');//This WILL fire | ||
}); | ||
emp.get('works_for').set('name','Marketing'); | ||
``` | ||
Refer to issue [#28](https://github.com/dhruvaray/backbone-associations/issues/28) for a more detailed reasoning. | ||
## <a name="performance"/>Performance Comparison | ||
@@ -548,2 +583,5 @@ | ||
#### Version 0.4.2 - [Diff](https://github.com/dhruvaray/backbone-associations/compare/v0.4.1...v0.4.2) | ||
* Support for backbone 1.0.0. | ||
#### Version 0.4.1 - [Diff](https://github.com/dhruvaray/backbone-associations/compare/v0.4.0...v0.4.1) | ||
@@ -550,0 +588,0 @@ * Support for backbone 0.9.10. |
@@ -370,2 +370,13 @@ $(document).ready(function () { | ||
test("change : second model of nested collection", 1, function () { | ||
dept1.on('change:locations[0]', function() { | ||
ok(false, 'First model in nested collection should not change'); | ||
}); | ||
dept1.on('change:locations[1]', function() { | ||
ok(true, 'Second model in nested collection should change'); | ||
}); | ||
loc2.set('zip', '97008'); | ||
}); | ||
test("change : all attributes get updated in an atomic operation", 8, function () { | ||
@@ -773,3 +784,3 @@ emp.on('change', function () { | ||
emp.sync = function (method, model, options) { | ||
options.success(this, null, options); | ||
options.success.call(this, null, options); | ||
}; | ||
@@ -789,3 +800,3 @@ emp.save(null, { | ||
emp.sync = function (method, model, options) { | ||
options.success(this, {sex:'O'}, options); | ||
options.success.call(this, {sex:'O'}, options); | ||
}; | ||
@@ -858,3 +869,3 @@ //Backbone 0.9.9 | ||
sync:function (method, model, options) { | ||
return options.success(this, { | ||
return options.success.call(this, { | ||
name:'c-name', | ||
@@ -904,3 +915,3 @@ employees:[ | ||
sync:function (method, model, options) { | ||
options.success(this, null, options); | ||
options.success.call(this, null, options); | ||
} | ||
@@ -907,0 +918,0 @@ }); |
@@ -1,3 +0,3 @@ | ||
//adopted from Backbone 0.9.10 test suite | ||
$(document).ready(function () { | ||
//adopted from Backbone 1.0.0 test suite | ||
$(document).ready(function() { | ||
// test Backbone.Associated instead of Backbone.Model - reset at end of function | ||
@@ -69,4 +69,5 @@ var OriginalModel = Backbone.Model; | ||
test("get", 5, function() { | ||
test("get", 6, function() { | ||
equal(col.get(0), d); | ||
equal(col.get(d.clone()), d); | ||
equal(col.get(2), b); | ||
@@ -78,18 +79,16 @@ equal(col.get({id: 1}), c); | ||
test("get with non-default ids", 4, function() { | ||
test("get with non-default ids", 5, function() { | ||
var col = new Backbone.Collection(); | ||
var MongoModel = Backbone.Model.extend({ | ||
idAttribute: '_id' | ||
}); | ||
var MongoModel = Backbone.Model.extend({idAttribute: '_id'}); | ||
var model = new MongoModel({_id: 100}); | ||
col.push(model); | ||
col.add(model); | ||
equal(col.get(100), model); | ||
model.set({_id: 101}); | ||
equal(col.get(101), model); | ||
equal(col.get(model.cid), model); | ||
equal(col.get(model), model); | ||
equal(col.get(101), void 0); | ||
var Col2 = Backbone.Collection.extend({ model: MongoModel }); | ||
var col2 = new Col2(); | ||
col2.push(model); | ||
equal(col2.get({_id: 101}), model); | ||
equal(col2.get(model.clone()), model); | ||
var col2 = new Backbone.Collection(); | ||
col2.model = MongoModel; | ||
col2.add(model.attributes); | ||
equal(col2.get(model.clone()), col2.first()); | ||
}); | ||
@@ -235,2 +234,15 @@ | ||
test("add with parse and merge", function() { | ||
var Model = Backbone.Model.extend({ | ||
parse: function (data) { | ||
return data.model; | ||
} | ||
}); | ||
var collection = new Backbone.Collection(); | ||
collection.model = Model; | ||
collection.add({id: 1}); | ||
collection.add({model: {id: 1, name: 'Alf'}}, {parse: true, merge: true}); | ||
equal(collection.first().get('name'), 'Alf'); | ||
}); | ||
test("add model to collection with sort()-style comparator", 3, function() { | ||
@@ -372,5 +384,3 @@ var col = new Backbone.Collection; | ||
var e = new Backbone.Model({id: 5, title: 'Othello'}); | ||
e.sync = function(method, model, options) { | ||
options.success(model, [], options); | ||
}; | ||
e.sync = function(method, model, options) { options.success(); }; | ||
var colE = new Backbone.Collection([e]); | ||
@@ -407,2 +417,11 @@ var colF = new Backbone.Collection([e]); | ||
test("fetch with an error response triggers an error event", 1, function () { | ||
var collection = new Backbone.Collection(); | ||
collection.on('error', function () { | ||
ok(true); | ||
}); | ||
collection.sync = function (method, model, options) { options.error(); }; | ||
collection.fetch(); | ||
}); | ||
test("ensure fetch only parses once", 1, function() { | ||
@@ -417,3 +436,3 @@ var collection = new Backbone.Collection; | ||
collection.fetch(); | ||
this.syncArgs.options.success([]); | ||
this.syncArgs.options.success(); | ||
equal(counter, 1); | ||
@@ -432,3 +451,3 @@ }); | ||
test("create with validate:true enforces validation", 1, function() { | ||
test("create with validate:true enforces validation", 2, function() { | ||
var ValidatingModel = Backbone.Model.extend({ | ||
@@ -443,2 +462,5 @@ validate: function(attrs) { | ||
var col = new ValidatingCollection(); | ||
col.on('invalid', function (collection, attrs, options) { | ||
equal(options.validationError, 'fail'); | ||
}); | ||
equal(col.create({"foo":"bar"}, {validate:true}), false); | ||
@@ -476,6 +498,7 @@ }); | ||
test("where", 6, function() { | ||
test("where and findWhere", 8, function() { | ||
var model = new Backbone.Model({a: 1}); | ||
var coll = new Backbone.Collection([ | ||
model, | ||
{a: 1}, | ||
{a: 1}, | ||
{a: 1, b: 2}, | ||
@@ -491,2 +514,4 @@ {a: 2, b: 2}, | ||
equal(coll.where({a: 1, b: 2}).length, 1); | ||
equal(coll.findWhere({a: 1}), model); | ||
equal(coll.findWhere({a: 4}), void 0); | ||
}); | ||
@@ -501,6 +526,6 @@ | ||
equal(col.rest().length, 3); | ||
ok(!_.include(col.rest()), a); | ||
ok(!_.include(col.rest()), d); | ||
ok(!_.include(col.rest(), a)); | ||
ok(_.include(col.rest(), d)); | ||
ok(!col.isEmpty()); | ||
ok(!_.include(col.without(d)), d); | ||
ok(!_.include(col.without(d), d)); | ||
equal(col.max(function(model){ return model.id; }).id, 3); | ||
@@ -527,5 +552,5 @@ equal(col.min(function(model){ return model.id; }).id, 0); | ||
test("reset", 10, function() { | ||
test("reset", 12, function() { | ||
var resetCount = 0; | ||
var models = col.models.slice(); | ||
var models = col.models; | ||
col.on('reset', function() { resetCount += 1; }); | ||
@@ -545,4 +570,20 @@ col.reset([]); | ||
ok(_.isEqual(col.last().attributes, d.attributes)); | ||
col.reset(); | ||
equal(col.length, 0); | ||
equal(resetCount, 4); | ||
}); | ||
test ("reset with different values", function(){ | ||
var col = new Backbone.Collection({id: 1}); | ||
col.reset({id: 1, a: 1}); | ||
equal(col.get(1).get('a'), 1); | ||
}); | ||
test("same references in reset", function() { | ||
var model = new Backbone.Model({id: 1}); | ||
var collection = new Backbone.Collection({id: 1}); | ||
collection.reset(model); | ||
equal(collection.get(1), model); | ||
}); | ||
test("reset passes caller options", 3, function() { | ||
@@ -722,5 +763,3 @@ var Model = Backbone.Model.extend({ | ||
var model = new Backbone.Model; | ||
model.sync = function(method, model, options){ | ||
options.success(model, [], options); | ||
}; | ||
model.sync = function(method, model, options){ options.success(); }; | ||
collection.on('add', function(){ ok(true); }); | ||
@@ -855,3 +894,3 @@ collection.create(model, {wait: true}); | ||
test("update", function() { | ||
test("set", function() { | ||
var m1 = new Backbone.Model(); | ||
@@ -874,15 +913,15 @@ var m2 = new Backbone.Model({id: 2}); | ||
// remove: false doesn't remove any models | ||
c.update([], {remove: false}); | ||
c.set([], {remove: false}); | ||
strictEqual(c.length, 2); | ||
// add: false doesn't add any models | ||
c.update([m1, m2, m3], {add: false}); | ||
c.set([m1, m2, m3], {add: false}); | ||
strictEqual(c.length, 2); | ||
// merge: false doesn't change any models | ||
c.update([m1, {id: 2, a: 1}], {merge: false}); | ||
c.set([m1, {id: 2, a: 1}], {merge: false}); | ||
strictEqual(m2.get('a'), void 0); | ||
// add: false, remove: false only merges existing models | ||
c.update([m1, {id: 2, a: 0}, m3, {id: 4}], {add: false, remove: false}); | ||
c.set([m1, {id: 2, a: 0}, m3, {id: 4}], {add: false, remove: false}); | ||
strictEqual(c.length, 2); | ||
@@ -892,3 +931,3 @@ strictEqual(m2.get('a'), 0); | ||
// default options add/remove/merge as appropriate | ||
c.update([{id: 2, a: 1}, m3]); | ||
c.set([{id: 2, a: 1}, m3]); | ||
strictEqual(c.length, 2); | ||
@@ -901,19 +940,19 @@ strictEqual(m2.get('a'), 1); | ||
}); | ||
c.update([]); | ||
c.set([]); | ||
strictEqual(c.length, 0); | ||
}); | ||
test("update with only cids", 3, function() { | ||
test("set with only cids", 3, function() { | ||
var m1 = new Backbone.Model; | ||
var m2 = new Backbone.Model; | ||
var c = new Backbone.Collection; | ||
c.update([m1, m2]); | ||
c.set([m1, m2]); | ||
equal(c.length, 2); | ||
c.update([m1]); | ||
c.set([m1]); | ||
equal(c.length, 1); | ||
c.update([m1, m1, m1, m2, m2], {remove: false}); | ||
c.set([m1, m1, m1, m2, m2], {remove: false}); | ||
equal(c.length, 2); | ||
}); | ||
test("update with only idAttribute", 3, function() { | ||
test("set with only idAttribute", 3, function() { | ||
var m1 = { _id: 1 }; | ||
@@ -927,11 +966,11 @@ var m2 = { _id: 2 }; | ||
var c = new col; | ||
c.update([m1, m2]); | ||
c.set([m1, m2]); | ||
equal(c.length, 2); | ||
c.update([m1]); | ||
c.set([m1]); | ||
equal(c.length, 1); | ||
c.update([m1, m1, m1, m2, m2], {remove: false}); | ||
c.set([m1, m1, m1, m2, m2], {remove: false}); | ||
equal(c.length, 2); | ||
}); | ||
test("update + merge with default values defined", function() { | ||
test("set + merge with default values defined", function() { | ||
var Model = Backbone.Model.extend({ | ||
@@ -946,10 +985,39 @@ defaults: { | ||
col.update({id: 1, key: 'other'}); | ||
col.set({id: 1, key: 'other'}); | ||
equal(col.first().get('key'), 'other'); | ||
col.update({id: 1, other: 'value'}); | ||
equal(col.first().get('key'), 'other'); | ||
col.set({id: 1, other: 'value'}); | ||
equal(col.first().get('key'), 'value'); | ||
equal(col.length, 1); | ||
}); | ||
test("`set` and model level `parse`", function() { | ||
var Model = Backbone.Model.extend({ | ||
parse: function (res) { return res.model; } | ||
}); | ||
var Collection = Backbone.Collection.extend({ | ||
model: Model, | ||
parse: function (res) { return res.models; } | ||
}); | ||
var model = new Model({id: 1}); | ||
var collection = new Collection(model); | ||
collection.set({models: [ | ||
{model: {id: 1}}, | ||
{model: {id: 2}} | ||
]}, {parse: true}); | ||
equal(collection.first(), model); | ||
}); | ||
test("`set` data is only parsed once", function() { | ||
var collection = new Backbone.Collection(); | ||
collection.model = Backbone.Model.extend({ | ||
parse: function (data) { | ||
equal(data.parsed, void 0); | ||
data.parsed = true; | ||
return data; | ||
} | ||
}); | ||
collection.set({}, {parse: true}); | ||
}); | ||
test("#1894 - Push should not trigger a sort", 0, function() { | ||
@@ -965,3 +1033,3 @@ var Collection = Backbone.Collection.extend({ | ||
test("`update` with non-normal id", function() { | ||
test("`set` with non-normal id", function() { | ||
var Collection = Backbone.Collection.extend({ | ||
@@ -971,3 +1039,3 @@ model: Backbone.Model.extend({idAttribute: '_id'}) | ||
var collection = new Collection({_id: 1}); | ||
collection.update([{_id: 1, a: 1}], {add: false}); | ||
collection.set([{_id: 1, a: 1}], {add: false}); | ||
equal(collection.first().get('a'), 1); | ||
@@ -984,3 +1052,3 @@ }); | ||
test("#1915 - `parse` data in the right order in `update`", function() { | ||
test("#1915 - `parse` data in the right order in `set`", function() { | ||
var collection = new (Backbone.Collection.extend({ | ||
@@ -993,3 +1061,3 @@ parse: function (data) { | ||
var res = {status: 'ok', data:[{id: 1}]}; | ||
collection.update(res, {parse: true}); | ||
collection.set(res, {parse: true}); | ||
}); | ||
@@ -1032,3 +1100,3 @@ | ||
comparator: function(a, b) { | ||
a.get('a') > b.get('a') ? 1 : (a.get('a') < b.get('a') ? -1 : 0); | ||
return a.get('a') > b.get('a') ? 1 : (a.get('a') < b.get('a') ? -1 : 0); | ||
} | ||
@@ -1045,2 +1113,18 @@ }))([{id: 1}, {id: 2}, {id: 3}]); | ||
}); | ||
test("Attach options to collection.", 3, function() { | ||
var url = '/somewhere'; | ||
var model = new Backbone.Model; | ||
var comparator = function(){}; | ||
var collection = new Backbone.Collection([], { | ||
url: url, | ||
model: model, | ||
comparator: comparator | ||
}); | ||
strictEqual(collection.url, url); | ||
ok(collection.model === model); | ||
ok(collection.comparator === comparator); | ||
}); | ||
}); |
@@ -1,2 +0,2 @@ | ||
//adopted from Backbone 0.9.10 test suite | ||
//adopted from Backbone 1.0.0 test suite | ||
(function() { | ||
@@ -3,0 +3,0 @@ |
@@ -1,3 +0,3 @@ | ||
//adopted from Backbone 0.9.10 test suite | ||
$(document).ready(function () { | ||
//adopted from Backbone 1.0.0 test suite | ||
$(document).ready(function() { | ||
// test Backbone.Associated instead of Backbone.Model - reset at end of function | ||
@@ -53,5 +53,5 @@ var OriginalModel = Backbone.Model; | ||
var Model = Backbone.Model.extend({ | ||
parse: function(obj) { | ||
obj.value += 1; | ||
return obj; | ||
parse: function(attrs) { | ||
attrs.value += 1; | ||
return attrs; | ||
} | ||
@@ -77,4 +77,4 @@ }); | ||
var Model = Backbone.Model.extend({ | ||
parse: function(obj) { | ||
obj.value += 1; | ||
parse: function(attrs) { | ||
attrs.value += 1; | ||
return null; | ||
@@ -120,2 +120,19 @@ } | ||
test('url and urlRoot are directly attached if passed in the options', 2, function () { | ||
var model = new Backbone.Model({a: 1}, {url: '/test'}); | ||
var model2 = new Backbone.Model({a: 2}, {urlRoot: '/test2'}); | ||
equal(model.url, '/test'); | ||
equal(model2.urlRoot, '/test2'); | ||
}); | ||
test("underscore methods", 5, function() { | ||
var model = new Backbone.Model({ 'foo': 'a', 'bar': 'b', 'baz': 'c' }); | ||
var model2 = model.clone(); | ||
deepEqual(model.keys(), ['foo', 'bar', 'baz']); | ||
deepEqual(model.values(), ['a', 'b', 'c']); | ||
deepEqual(model.invert(), { 'a': 'foo', 'b': 'bar', 'c': 'baz' }); | ||
deepEqual(model.pick('foo', 'baz'), {'foo': 'a', 'baz': 'c'}); | ||
deepEqual(model.omit('foo', 'bar'), {'baz': 'c'}); | ||
}); | ||
test("clone", 10, function() { | ||
@@ -334,3 +351,3 @@ var a = new Backbone.Model({ 'foo': 1, 'bar': 2, 'baz': 3}); | ||
}); | ||
var model = new Defaulted({two: null}); | ||
var model = new Defaulted({two: undefined}); | ||
equal(model.get('one'), 1); | ||
@@ -346,3 +363,3 @@ equal(model.get('two'), 2); | ||
}); | ||
model = new Defaulted({two: null}); | ||
model = new Defaulted({two: undefined}); | ||
equal(model.get('one'), 3); | ||
@@ -413,3 +430,3 @@ equal(model.get('two'), 4); | ||
model.sync = function(method, model, options) { | ||
options.success.call(this, this, {admin: true}, options); | ||
options.success.call(this, {admin: true}); | ||
}; | ||
@@ -431,2 +448,15 @@ model.on('invalid', function(model, error) { | ||
test("save, fetch, destroy triggers error event when an error occurs", 3, function () { | ||
var model = new Backbone.Model(); | ||
model.on('error', function () { | ||
ok(true); | ||
}); | ||
model.sync = function (method, model, options) { | ||
options.error(); | ||
}; | ||
model.save({data: 2, id: 1}); | ||
model.fetch(); | ||
model.destroy(); | ||
}); | ||
test("save with PATCH", function() { | ||
@@ -449,3 +479,3 @@ doc.clear().set({id: 1, a: 1, b: 2, c: 3, d: 4}); | ||
model.sync = function(method, model, options) { | ||
options.success(model, {}, options); | ||
options.success(); | ||
}; | ||
@@ -459,4 +489,4 @@ model.save('title', 'Twelfth Night'); | ||
model.sync = function(method, model, options) { | ||
options.success(model, '', options); | ||
options.success(model, null, options); | ||
options.success('', options); | ||
options.success(null, options); | ||
}; | ||
@@ -736,3 +766,3 @@ model.save({testing:'empty'}, { | ||
model.sync = function(method, model, options) { | ||
options.success(model, {}, options); | ||
options.success(); | ||
}; | ||
@@ -910,3 +940,3 @@ model.on("change:x", function() { ok(true); }); | ||
model.sync = function(method, model, options) { | ||
options.success(model, {}, options); | ||
options.success(); | ||
}; | ||
@@ -920,5 +950,4 @@ model.save({id: 1}, opts); | ||
var model = new Backbone.Model({id: 1}); | ||
model.url = '/test'; | ||
model.sync = function (method, model, options) { options.success(); }; | ||
model.on('sync', function(){ ok(true); }); | ||
Backbone.ajax = function(settings){ settings.success(); }; | ||
model.fetch(); | ||
@@ -969,3 +998,3 @@ model.save(); | ||
setTimeout(function(){ | ||
options.success(model, {}, options); | ||
options.success(); | ||
start(); | ||
@@ -972,0 +1001,0 @@ }, 0); |
@@ -1,3 +0,3 @@ | ||
//adopted from Backbone 0.9.10 test suite | ||
$(document).ready(function () { | ||
//adopted from Backbone 1.0.0 test suite | ||
$(document).ready(function() { | ||
// test Backbone.Associated instead of Backbone.Model - reset at end of function | ||
@@ -4,0 +4,0 @@ var OriginalModel = Backbone.Model; |
@@ -1,3 +0,3 @@ | ||
//adopted from Backbone 0.9.10 test suite | ||
$(document).ready(function () { | ||
//adopted from Backbone 1.0.0 test suite | ||
$(document).ready(function() { | ||
// test Backbone.Associated instead of Backbone.Model - reset at end of function | ||
@@ -92,2 +92,9 @@ var OriginalModel = Backbone.Model; | ||
test("delegateEvents ignore undefined methods", 0, function() { | ||
var view = new Backbone.View({el: '<p></p>'}); | ||
view.delegateEvents({'click': 'undefinedMethod'}); | ||
view.$el.trigger('click'); | ||
}); | ||
test("undelegateEvents", 6, function() { | ||
@@ -94,0 +101,0 @@ var counter1 = 0, counter2 = 0; |
/** | ||
* QUnit v1.10.0 - A JavaScript Unit Testing Framework | ||
* QUnit v1.11.0 - A JavaScript Unit Testing Framework | ||
* | ||
@@ -14,2 +14,3 @@ * http://qunitjs.com | ||
var QUnit, | ||
assert, | ||
config, | ||
@@ -24,14 +25,63 @@ onErrorFnPrev, | ||
defined = { | ||
setTimeout: typeof window.setTimeout !== "undefined", | ||
sessionStorage: (function() { | ||
var x = "qunit-test-string"; | ||
try { | ||
sessionStorage.setItem( x, x ); | ||
sessionStorage.removeItem( x ); | ||
return true; | ||
} catch( e ) { | ||
return false; | ||
setTimeout: typeof window.setTimeout !== "undefined", | ||
sessionStorage: (function() { | ||
var x = "qunit-test-string"; | ||
try { | ||
sessionStorage.setItem( x, x ); | ||
sessionStorage.removeItem( x ); | ||
return true; | ||
} catch( e ) { | ||
return false; | ||
} | ||
}()) | ||
}, | ||
/** | ||
* Provides a normalized error string, correcting an issue | ||
* with IE 7 (and prior) where Error.prototype.toString is | ||
* not properly implemented | ||
* | ||
* Based on http://es5.github.com/#x15.11.4.4 | ||
* | ||
* @param {String|Error} error | ||
* @return {String} error message | ||
*/ | ||
errorString = function( error ) { | ||
var name, message, | ||
errorString = error.toString(); | ||
if ( errorString.substring( 0, 7 ) === "[object" ) { | ||
name = error.name ? error.name.toString() : "Error"; | ||
message = error.message ? error.message.toString() : ""; | ||
if ( name && message ) { | ||
return name + ": " + message; | ||
} else if ( name ) { | ||
return name; | ||
} else if ( message ) { | ||
return message; | ||
} else { | ||
return "Error"; | ||
} | ||
} else { | ||
return errorString; | ||
} | ||
}()) | ||
}; | ||
}, | ||
/** | ||
* Makes a clone of an object using only Array or Object as base, | ||
* and copies over the own enumerable properties. | ||
* | ||
* @param {Object} obj | ||
* @return {Object} New object with only the own properties (recursively). | ||
*/ | ||
objectValues = function( obj ) { | ||
// Grunt 0.3.x uses an older version of jshint that still has jshint/jshint#392. | ||
/*jshint newcap: false */ | ||
var key, val, | ||
vals = QUnit.is( "array", obj ) ? [] : {}; | ||
for ( key in obj ) { | ||
if ( hasOwn.call( obj, key ) ) { | ||
val = obj[key]; | ||
vals[key] = val === Object(val) ? objectValues(val) : val; | ||
} | ||
} | ||
return vals; | ||
}; | ||
@@ -49,7 +99,7 @@ function Test( settings ) { | ||
var a, b, li, | ||
tests = id( "qunit-tests" ); | ||
tests = id( "qunit-tests" ); | ||
if ( tests ) { | ||
b = document.createElement( "strong" ); | ||
b.innerHTML = this.name; | ||
b.innerHTML = this.nameHtml; | ||
@@ -98,2 +148,3 @@ // `a` initialized at top of scope | ||
this.started = +new Date(); | ||
runLoggingCallbacks( "testStart", QUnit, { | ||
@@ -118,3 +169,3 @@ name: this.testName, | ||
} catch( e ) { | ||
QUnit.pushFailure( "Setup failed on " + this.testName + ": " + e.message, extractStacktrace( e, 1 ) ); | ||
QUnit.pushFailure( "Setup failed on " + this.testName + ": " + ( e.message || e ), extractStacktrace( e, 1 ) ); | ||
} | ||
@@ -128,3 +179,3 @@ }, | ||
if ( running ) { | ||
running.innerHTML = "Running: <br/>" + this.name; | ||
running.innerHTML = "Running: <br/>" + this.nameHtml; | ||
} | ||
@@ -136,4 +187,7 @@ | ||
this.callbackStarted = +new Date(); | ||
if ( config.notrycatch ) { | ||
this.callback.call( this.testEnvironment, QUnit.assert ); | ||
this.callbackRuntime = +new Date() - this.callbackStarted; | ||
return; | ||
@@ -144,4 +198,7 @@ } | ||
this.callback.call( this.testEnvironment, QUnit.assert ); | ||
this.callbackRuntime = +new Date() - this.callbackStarted; | ||
} catch( e ) { | ||
QUnit.pushFailure( "Died on test #" + (this.assertions.length + 1) + " " + this.stack + ": " + e.message, extractStacktrace( e, 0 ) ); | ||
this.callbackRuntime = +new Date() - this.callbackStarted; | ||
QUnit.pushFailure( "Died on test #" + (this.assertions.length + 1) + " " + this.stack + ": " + ( e.message || e ), extractStacktrace( e, 0 ) ); | ||
// else next test will carry the responsibility | ||
@@ -159,2 +216,5 @@ saveGlobal(); | ||
if ( config.notrycatch ) { | ||
if ( typeof this.callbackRuntime === "undefined" ) { | ||
this.callbackRuntime = +new Date() - this.callbackStarted; | ||
} | ||
this.testEnvironment.teardown.call( this.testEnvironment ); | ||
@@ -166,3 +226,3 @@ return; | ||
} catch( e ) { | ||
QUnit.pushFailure( "Teardown failed on " + this.testName + ": " + e.message, extractStacktrace( e, 1 ) ); | ||
QUnit.pushFailure( "Teardown failed on " + this.testName + ": " + ( e.message || e ), extractStacktrace( e, 1 ) ); | ||
} | ||
@@ -174,11 +234,11 @@ } | ||
config.current = this; | ||
if ( config.requireExpects && this.expected == null ) { | ||
if ( config.requireExpects && this.expected === null ) { | ||
QUnit.pushFailure( "Expected number of assertions to be defined, but expect() was not called.", this.stack ); | ||
} else if ( this.expected != null && this.expected != this.assertions.length ) { | ||
} else if ( this.expected !== null && this.expected !== this.assertions.length ) { | ||
QUnit.pushFailure( "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run", this.stack ); | ||
} else if ( this.expected == null && !this.assertions.length ) { | ||
} else if ( this.expected === null && !this.assertions.length ) { | ||
QUnit.pushFailure( "Expected at least one assertion, but none were run - call expect(0) to accept zero assertions.", this.stack ); | ||
} | ||
var assertion, a, b, i, li, ol, | ||
var i, assertion, a, b, time, li, ol, | ||
test = this, | ||
@@ -189,2 +249,3 @@ good = 0, | ||
this.runtime = +new Date() - this.started; | ||
config.stats.all += this.assertions.length; | ||
@@ -195,2 +256,3 @@ config.moduleStats.all += this.assertions.length; | ||
ol = document.createElement( "ol" ); | ||
ol.className = "qunit-assert-list"; | ||
@@ -224,3 +286,3 @@ for ( i = 0; i < this.assertions.length; i++ ) { | ||
if ( bad === 0 ) { | ||
ol.style.display = "none"; | ||
addClass( ol, "qunit-collapsed" ); | ||
} | ||
@@ -230,8 +292,8 @@ | ||
b = document.createElement( "strong" ); | ||
b.innerHTML = this.name + " <b class='counts'>(<b class='failed'>" + bad + "</b>, <b class='passed'>" + good + "</b>, " + this.assertions.length + ")</b>"; | ||
b.innerHTML = this.nameHtml + " <b class='counts'>(<b class='failed'>" + bad + "</b>, <b class='passed'>" + good + "</b>, " + this.assertions.length + ")</b>"; | ||
addEvent(b, "click", function() { | ||
var next = b.nextSibling.nextSibling, | ||
display = next.style.display; | ||
next.style.display = display === "none" ? "block" : "none"; | ||
var next = b.parentNode.lastChild, | ||
collapsed = hasClass( next, "qunit-collapsed" ); | ||
( collapsed ? removeClass : addClass )( next, "qunit-collapsed" ); | ||
}); | ||
@@ -241,3 +303,3 @@ | ||
var target = e && e.target ? e.target : window.event.srcElement; | ||
if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) { | ||
if ( target.nodeName.toLowerCase() === "span" || target.nodeName.toLowerCase() === "b" ) { | ||
target = target.parentNode; | ||
@@ -250,2 +312,7 @@ } | ||
// `time` initialized at top of scope | ||
time = document.createElement( "span" ); | ||
time.className = "runtime"; | ||
time.innerHTML = this.runtime + " ms"; | ||
// `li` initialized at top of scope | ||
@@ -257,3 +324,4 @@ li = id( this.id ); | ||
li.appendChild( b ); | ||
li.appendChild ( a ); | ||
li.appendChild( a ); | ||
li.appendChild( time ); | ||
li.appendChild( ol ); | ||
@@ -276,3 +344,4 @@ | ||
passed: this.assertions.length - bad, | ||
total: this.assertions.length | ||
total: this.assertions.length, | ||
duration: this.runtime | ||
}); | ||
@@ -343,3 +412,3 @@ | ||
var test, | ||
name = "<span class='test-name'>" + escapeInnerText( testName ) + "</span>"; | ||
nameHtml = "<span class='test-name'>" + escapeText( testName ) + "</span>"; | ||
@@ -352,7 +421,7 @@ if ( arguments.length === 2 ) { | ||
if ( config.currentModule ) { | ||
name = "<span class='module-name'>" + config.currentModule + "</span>: " + name; | ||
nameHtml = "<span class='module-name'>" + escapeText( config.currentModule ) + "</span>: " + nameHtml; | ||
} | ||
test = new Test({ | ||
name: name, | ||
nameHtml: nameHtml, | ||
testName: testName, | ||
@@ -384,2 +453,14 @@ expected: expected, | ||
start: function( count ) { | ||
// QUnit hasn't been initialized yet. | ||
// Note: RequireJS (et al) may delay onLoad | ||
if ( config.semaphore === undefined ) { | ||
QUnit.begin(function() { | ||
// This is triggered at the top of QUnit.load, push start() to the event loop, to allow QUnit.load to finish first | ||
setTimeout(function() { | ||
QUnit.start( count ); | ||
}); | ||
}); | ||
return; | ||
} | ||
config.semaphore -= count || 1; | ||
@@ -393,2 +474,4 @@ // don't start until equal number of stop-calls | ||
config.semaphore = 0; | ||
QUnit.pushFailure( "Called start() while already started (QUnit.config.semaphore was 0 already)", null, sourceFromStacktrace(2) ); | ||
return; | ||
} | ||
@@ -429,7 +512,10 @@ // A slight delay, to avoid any current callbacks | ||
// `assert` initialized at top of scope | ||
// Asssert helpers | ||
// All of these must call either QUnit.push() or manually do: | ||
// All of these must either call QUnit.push() or manually do: | ||
// - runLoggingCallbacks( "log", .. ); | ||
// - config.current.assertions.push({ .. }); | ||
QUnit.assert = { | ||
// We attach it to the QUnit object *after* we expose the public API, | ||
// otherwise `assert` will become a global variable in browsers (#341). | ||
assert = { | ||
/** | ||
@@ -455,3 +541,3 @@ * Asserts rough true-ish result. | ||
msg = escapeInnerText( msg || (result ? "okay" : "failed" ) ); | ||
msg = escapeText( msg || (result ? "okay" : "failed" ) ); | ||
msg = "<span class='test-message'>" + msg + "</span>"; | ||
@@ -463,3 +549,3 @@ | ||
details.source = source; | ||
msg += "<table><tr class='test-source'><th>Source: </th><td><pre>" + escapeInnerText( source ) + "</pre></td></tr></table>"; | ||
msg += "<table><tr class='test-source'><th>Source: </th><td><pre>" + escapeText( source ) + "</pre></td></tr></table>"; | ||
} | ||
@@ -482,2 +568,3 @@ } | ||
equal: function( actual, expected, message ) { | ||
/*jshint eqeqeq:false */ | ||
QUnit.push( expected == actual, actual, expected, message ); | ||
@@ -491,2 +578,3 @@ }, | ||
notEqual: function( actual, expected, message ) { | ||
/*jshint eqeqeq:false */ | ||
QUnit.push( expected != actual, actual, expected, message ); | ||
@@ -496,2 +584,22 @@ }, | ||
/** | ||
* @name propEqual | ||
* @function | ||
*/ | ||
propEqual: function( actual, expected, message ) { | ||
actual = objectValues(actual); | ||
expected = objectValues(expected); | ||
QUnit.push( QUnit.equiv(actual, expected), actual, expected, message ); | ||
}, | ||
/** | ||
* @name notPropEqual | ||
* @function | ||
*/ | ||
notPropEqual: function( actual, expected, message ) { | ||
actual = objectValues(actual); | ||
expected = objectValues(expected); | ||
QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message ); | ||
}, | ||
/** | ||
* @name deepEqual | ||
@@ -528,4 +636,5 @@ * @function | ||
throws: function( block, expected, message ) { | ||
"throws": function( block, expected, message ) { | ||
var actual, | ||
expectedOutput = expected, | ||
ok = false; | ||
@@ -551,5 +660,6 @@ | ||
ok = true; | ||
expectedOutput = null; | ||
// expected is a regexp | ||
} else if ( QUnit.objectType( expected ) === "regexp" ) { | ||
ok = expected.test( actual ); | ||
ok = expected.test( errorString( actual ) ); | ||
// expected is a constructor | ||
@@ -560,6 +670,7 @@ } else if ( actual instanceof expected ) { | ||
} else if ( expected.call( {}, actual ) === true ) { | ||
expectedOutput = null; | ||
ok = true; | ||
} | ||
QUnit.push( ok, actual, null, message ); | ||
QUnit.push( ok, actual, expectedOutput, message ); | ||
} else { | ||
@@ -573,11 +684,12 @@ QUnit.pushFailure( message, null, 'No exception was thrown.' ); | ||
* @deprecate since 1.8.0 | ||
* Kept assertion helpers in root for backwards compatibility | ||
* Kept assertion helpers in root for backwards compatibility. | ||
*/ | ||
extend( QUnit, QUnit.assert ); | ||
extend( QUnit, assert ); | ||
/** | ||
* @deprecated since 1.9.0 | ||
* Kept global "raises()" for backwards compatibility | ||
* Kept root "raises()" for backwards compatibility. | ||
* (Note that we don't introduce assert.raises). | ||
*/ | ||
QUnit.raises = QUnit.assert.throws; | ||
QUnit.raises = assert[ "throws" ]; | ||
@@ -658,2 +770,11 @@ /** | ||
// Export global variables, unless an 'exports' object exists, | ||
// in that case we assume we're in CommonJS (dealt with on the bottom of the script) | ||
if ( typeof exports === "undefined" ) { | ||
extend( window, QUnit ); | ||
// Expose QUnit object | ||
window.QUnit = QUnit; | ||
} | ||
// Initialize more QUnit.config and QUnit.urlParams | ||
@@ -692,14 +813,7 @@ (function() { | ||
// Export global variables, unless an 'exports' object exists, | ||
// in that case we assume we're in CommonJS (dealt with on the bottom of the script) | ||
if ( typeof exports === "undefined" ) { | ||
extend( window, QUnit ); | ||
// Expose QUnit object | ||
window.QUnit = QUnit; | ||
} | ||
// Extend QUnit object, | ||
// these after set here because they should not be exposed as global functions | ||
extend( QUnit, { | ||
assert: assert, | ||
config: config, | ||
@@ -719,3 +833,3 @@ | ||
queue: [], | ||
semaphore: 0 | ||
semaphore: 1 | ||
}); | ||
@@ -728,3 +842,3 @@ | ||
qunit.innerHTML = | ||
"<h1 id='qunit-header'>" + escapeInnerText( document.title ) + "</h1>" + | ||
"<h1 id='qunit-header'>" + escapeText( document.title ) + "</h1>" + | ||
"<h2 id='qunit-banner'></h2>" + | ||
@@ -785,3 +899,3 @@ "<div id='qunit-testrunner-toolbar'></div>" + | ||
is: function( type, obj ) { | ||
return QUnit.objectType( obj ) == type; | ||
return QUnit.objectType( obj ) === type; | ||
}, | ||
@@ -798,3 +912,4 @@ | ||
var type = toString.call( obj ).match(/^\[object\s(.*)\]$/)[1] || ""; | ||
var match = toString.call( obj ).match(/^\[object\s(.*)\]$/), | ||
type = match && match[1] || ""; | ||
@@ -836,3 +951,3 @@ switch ( type ) { | ||
message = escapeInnerText( message ) || ( result ? "okay" : "failed" ); | ||
message = escapeText( message ) || ( result ? "okay" : "failed" ); | ||
message = "<span class='test-message'>" + message + "</span>"; | ||
@@ -842,7 +957,7 @@ output = message; | ||
if ( !result ) { | ||
expected = escapeInnerText( QUnit.jsDump.parse(expected) ); | ||
actual = escapeInnerText( QUnit.jsDump.parse(actual) ); | ||
expected = escapeText( QUnit.jsDump.parse(expected) ); | ||
actual = escapeText( QUnit.jsDump.parse(actual) ); | ||
output += "<table><tr class='test-expected'><th>Expected: </th><td><pre>" + expected + "</pre></td></tr>"; | ||
if ( actual != expected ) { | ||
if ( actual !== expected ) { | ||
output += "<tr class='test-actual'><th>Result: </th><td><pre>" + actual + "</pre></td></tr>"; | ||
@@ -856,3 +971,3 @@ output += "<tr class='test-diff'><th>Diff: </th><td><pre>" + QUnit.diff( expected, actual ) + "</pre></td></tr>"; | ||
details.source = source; | ||
output += "<tr class='test-source'><th>Source: </th><td><pre>" + escapeInnerText( source ) + "</pre></td></tr>"; | ||
output += "<tr class='test-source'><th>Source: </th><td><pre>" + escapeText( source ) + "</pre></td></tr>"; | ||
} | ||
@@ -884,3 +999,3 @@ | ||
message = escapeInnerText( message ) || "error"; | ||
message = escapeText( message ) || "error"; | ||
message = "<span class='test-message'>" + message + "</span>"; | ||
@@ -892,3 +1007,3 @@ output = message; | ||
if ( actual ) { | ||
output += "<tr class='test-actual'><th>Result: </th><td><pre>" + escapeInnerText( actual ) + "</pre></td></tr>"; | ||
output += "<tr class='test-actual'><th>Result: </th><td><pre>" + escapeText( actual ) + "</pre></td></tr>"; | ||
} | ||
@@ -898,3 +1013,3 @@ | ||
details.source = source; | ||
output += "<tr class='test-source'><th>Source: </th><td><pre>" + escapeInnerText( source ) + "</pre></td></tr>"; | ||
output += "<tr class='test-source'><th>Source: </th><td><pre>" + escapeText( source ) + "</pre></td></tr>"; | ||
} | ||
@@ -924,3 +1039,4 @@ | ||
} | ||
return window.location.pathname + querystring.slice( 0, -1 ); | ||
return window.location.protocol + "//" + window.location.host + | ||
window.location.pathname + querystring.slice( 0, -1 ); | ||
}, | ||
@@ -956,3 +1072,3 @@ | ||
// testDone: { name, failed, passed, total } | ||
// testDone: { name, failed, passed, total, duration } | ||
testDone: registerLoggingCallback( "testDone" ), | ||
@@ -975,5 +1091,6 @@ | ||
// Initialize the config, saving the execution queue | ||
var banner, filter, i, label, len, main, ol, toolbar, userAgent, val, urlConfigCheckboxes, moduleFilter, | ||
numModules = 0, | ||
moduleFilterHtml = "", | ||
var banner, filter, i, label, len, main, ol, toolbar, userAgent, val, | ||
urlConfigCheckboxesContainer, urlConfigCheckboxes, moduleFilter, | ||
numModules = 0, | ||
moduleFilterHtml = "", | ||
urlConfigHtml = "", | ||
@@ -999,10 +1116,20 @@ oldconfig = extend( {}, config ); | ||
config[ val.id ] = QUnit.urlParams[ val.id ]; | ||
urlConfigHtml += "<input id='qunit-urlconfig-" + val.id + "' name='" + val.id + "' type='checkbox'" + ( config[ val.id ] ? " checked='checked'" : "" ) + " title='" + val.tooltip + "'><label for='qunit-urlconfig-" + val.id + "' title='" + val.tooltip + "'>" + val.label + "</label>"; | ||
urlConfigHtml += "<input id='qunit-urlconfig-" + escapeText( val.id ) + | ||
"' name='" + escapeText( val.id ) + | ||
"' type='checkbox'" + ( config[ val.id ] ? " checked='checked'" : "" ) + | ||
" title='" + escapeText( val.tooltip ) + | ||
"'><label for='qunit-urlconfig-" + escapeText( val.id ) + | ||
"' title='" + escapeText( val.tooltip ) + "'>" + val.label + "</label>"; | ||
} | ||
moduleFilterHtml += "<label for='qunit-modulefilter'>Module: </label><select id='qunit-modulefilter' name='modulefilter'><option value='' " + ( config.module === undefined ? "selected" : "" ) + ">< All Modules ></option>"; | ||
moduleFilterHtml += "<label for='qunit-modulefilter'>Module: </label><select id='qunit-modulefilter' name='modulefilter'><option value='' " + | ||
( config.module === undefined ? "selected='selected'" : "" ) + | ||
">< All Modules ></option>"; | ||
for ( i in config.modules ) { | ||
if ( config.modules.hasOwnProperty( i ) ) { | ||
numModules += 1; | ||
moduleFilterHtml += "<option value='" + encodeURIComponent(i) + "' " + ( config.module === i ? "selected" : "" ) + ">" + i + "</option>"; | ||
moduleFilterHtml += "<option value='" + escapeText( encodeURIComponent(i) ) + "' " + | ||
( config.module === i ? "selected='selected'" : "" ) + | ||
">" + escapeText(i) + "</option>"; | ||
} | ||
@@ -1066,10 +1193,16 @@ } | ||
urlConfigCheckboxes = document.createElement( 'span' ); | ||
urlConfigCheckboxes.innerHTML = urlConfigHtml; | ||
addEvent( urlConfigCheckboxes, "change", function( event ) { | ||
var params = {}; | ||
params[ event.target.name ] = event.target.checked ? true : undefined; | ||
urlConfigCheckboxesContainer = document.createElement("span"); | ||
urlConfigCheckboxesContainer.innerHTML = urlConfigHtml; | ||
urlConfigCheckboxes = urlConfigCheckboxesContainer.getElementsByTagName("input"); | ||
// For oldIE support: | ||
// * Add handlers to the individual elements instead of the container | ||
// * Use "click" instead of "change" | ||
// * Fallback from event.target to event.srcElement | ||
addEvents( urlConfigCheckboxes, "click", function( event ) { | ||
var params = {}, | ||
target = event.target || event.srcElement; | ||
params[ target.name ] = target.checked ? true : undefined; | ||
window.location = QUnit.url( params ); | ||
}); | ||
toolbar.appendChild( urlConfigCheckboxes ); | ||
toolbar.appendChild( urlConfigCheckboxesContainer ); | ||
@@ -1080,5 +1213,5 @@ if (numModules > 1) { | ||
moduleFilter.innerHTML = moduleFilterHtml; | ||
addEvent( moduleFilter, "change", function() { | ||
addEvent( moduleFilter.lastChild, "change", function() { | ||
var selectBox = moduleFilter.getElementsByTagName("select")[0], | ||
selectedModule = decodeURIComponent(selectBox.options[selectBox.selectedIndex].value); | ||
selectedModule = decodeURIComponent(selectBox.options[selectBox.selectedIndex].value); | ||
@@ -1160,3 +1293,3 @@ window.location = QUnit.url( { module: ( selectedModule === "" ) ? undefined : selectedModule } ); | ||
passed, | ||
"</span> tests of <span class='total'>", | ||
"</span> assertions of <span class='total'>", | ||
config.stats.all, | ||
@@ -1254,3 +1387,3 @@ "</span> passed, <span class='failed'>", | ||
var stack, include, i, regex; | ||
var stack, include, i; | ||
@@ -1269,3 +1402,3 @@ if ( e.stacktrace ) { | ||
for ( i = offset; i < stack.length; i++ ) { | ||
if ( stack[ i ].indexOf( fileName ) != -1 ) { | ||
if ( stack[ i ].indexOf( fileName ) !== -1 ) { | ||
break; | ||
@@ -1299,3 +1432,6 @@ } | ||
function escapeInnerText( s ) { | ||
/** | ||
* Escape text for attribute or text content. | ||
*/ | ||
function escapeText( s ) { | ||
if ( !s ) { | ||
@@ -1305,8 +1441,15 @@ return ""; | ||
s = s + ""; | ||
return s.replace( /[\&<>]/g, function( s ) { | ||
// Both single quotes and double quotes (for attributes) | ||
return s.replace( /['"<>&]/g, function( s ) { | ||
switch( s ) { | ||
case "&": return "&"; | ||
case "<": return "<"; | ||
case ">": return ">"; | ||
default: return s; | ||
case '\'': | ||
return '''; | ||
case '"': | ||
return '"'; | ||
case '<': | ||
return '<'; | ||
case '>': | ||
return '>'; | ||
case '&': | ||
return '&'; | ||
} | ||
@@ -1359,3 +1502,3 @@ }); | ||
function checkPollution( name ) { | ||
function checkPollution() { | ||
var newGlobals, | ||
@@ -1409,12 +1552,49 @@ deletedGlobals, | ||
/** | ||
* @param {HTMLElement} elem | ||
* @param {string} type | ||
* @param {Function} fn | ||
*/ | ||
function addEvent( elem, type, fn ) { | ||
// Standards-based browsers | ||
if ( elem.addEventListener ) { | ||
elem.addEventListener( type, fn, false ); | ||
} else if ( elem.attachEvent ) { | ||
// IE | ||
} else { | ||
elem.attachEvent( "on" + type, fn ); | ||
} else { | ||
fn(); | ||
} | ||
} | ||
/** | ||
* @param {Array|NodeList} elems | ||
* @param {string} type | ||
* @param {Function} fn | ||
*/ | ||
function addEvents( elems, type, fn ) { | ||
var i = elems.length; | ||
while ( i-- ) { | ||
addEvent( elems[i], type, fn ); | ||
} | ||
} | ||
function hasClass( elem, name ) { | ||
return (" " + elem.className + " ").indexOf(" " + name + " ") > -1; | ||
} | ||
function addClass( elem, name ) { | ||
if ( !hasClass( elem, name ) ) { | ||
elem.className += (elem.className ? " " : "") + name; | ||
} | ||
} | ||
function removeClass( elem, name ) { | ||
var set = " " + elem.className + " "; | ||
// Class name may appear multiple times | ||
while ( set.indexOf(" " + name + " ") > -1 ) { | ||
set = set.replace(" " + name + " " , " "); | ||
} | ||
// If possible, trim it for prettiness, but not neccecarily | ||
elem.className = window.jQuery ? jQuery.trim( set ) : ( set.trim ? set.trim() : set ); | ||
} | ||
function id( name ) { | ||
@@ -1433,3 +1613,2 @@ return !!( typeof document !== "undefined" && document && document.getElementById ) && | ||
function runLoggingCallbacks( key, scope, args ) { | ||
//debugger; | ||
var i, callbacks; | ||
@@ -1476,2 +1655,3 @@ if ( QUnit.hasOwnProperty( key ) ) { | ||
function useStrictEquality( b, a ) { | ||
/*jshint eqeqeq:false */ | ||
if ( b instanceof a.constructor || a instanceof b.constructor ) { | ||
@@ -1673,3 +1853,4 @@ // to catch short annotaion VS 'new' annotation of a | ||
jsDump = { | ||
parse: function( obj, type, stack ) { //type is used mostly internally, you can fix a (custom)type in advance | ||
// type is used mostly internally, you can fix a (custom)type in advance | ||
parse: function( obj, type, stack ) { | ||
stack = stack || [ ]; | ||
@@ -1682,7 +1863,6 @@ var inStack, res, | ||
if ( inStack != -1 ) { | ||
if ( inStack !== -1 ) { | ||
return "recursion(" + (inStack - stack.length) + ")"; | ||
} | ||
//else | ||
if ( type == "function" ) { | ||
if ( type === "function" ) { | ||
stack.push( obj ); | ||
@@ -1693,4 +1873,3 @@ res = parser.call( this, obj, stack ); | ||
} | ||
// else | ||
return ( type == "string" ) ? parser : this.parsers.error; | ||
return ( type === "string" ) ? parser : this.parsers.error; | ||
}, | ||
@@ -1722,2 +1901,4 @@ typeOf: function( obj ) { | ||
type = "array"; | ||
} else if ( obj.constructor === Error.prototype.constructor ) { | ||
type = "error"; | ||
} else { | ||
@@ -1731,3 +1912,4 @@ type = typeof obj; | ||
}, | ||
indent: function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing | ||
// extra can be a number, shortcut for increasing-calling-decreasing | ||
indent: function( extra ) { | ||
if ( !this.multiline ) { | ||
@@ -1761,3 +1943,5 @@ return ""; | ||
document: "[Document]", | ||
error: "[ERROR]", //when no parser is found, shouldn"t happen | ||
error: function(error) { | ||
return "Error(\"" + error.message + "\")"; | ||
}, | ||
unknown: "[Unknown]", | ||
@@ -1768,3 +1952,4 @@ "null": "null", | ||
var ret = "function", | ||
name = "name" in fn ? fn.name : (reName.exec(fn) || [])[1];//functions never have name in IE | ||
// functions never have name in IE | ||
name = "name" in fn ? fn.name : (reName.exec(fn) || [])[1]; | ||
@@ -1785,9 +1970,5 @@ if ( name ) { | ||
QUnit.jsDump.up(); | ||
if ( Object.keys ) { | ||
keys = Object.keys( map ); | ||
} else { | ||
keys = []; | ||
for ( key in map ) { | ||
keys.push( key ); | ||
} | ||
keys = []; | ||
for ( key in map ) { | ||
keys.push( key ); | ||
} | ||
@@ -1804,17 +1985,30 @@ keys.sort(); | ||
node: function( node ) { | ||
var a, val, | ||
var len, i, val, | ||
open = QUnit.jsDump.HTML ? "<" : "<", | ||
close = QUnit.jsDump.HTML ? ">" : ">", | ||
tag = node.nodeName.toLowerCase(), | ||
ret = open + tag; | ||
ret = open + tag, | ||
attrs = node.attributes; | ||
for ( a in QUnit.jsDump.DOMAttrs ) { | ||
val = node[ QUnit.jsDump.DOMAttrs[a] ]; | ||
if ( val ) { | ||
ret += " " + a + "=" + QUnit.jsDump.parse( val, "attribute" ); | ||
if ( attrs ) { | ||
for ( i = 0, len = attrs.length; i < len; i++ ) { | ||
val = attrs[i].nodeValue; | ||
// IE6 includes all attributes in .attributes, even ones not explicitly set. | ||
// Those have values like undefined, null, 0, false, "" or "inherit". | ||
if ( val && val !== "inherit" ) { | ||
ret += " " + attrs[i].nodeName + "=" + QUnit.jsDump.parse( val, "attribute" ); | ||
} | ||
} | ||
} | ||
return ret + close + open + "/" + tag + close; | ||
ret += close; | ||
// Show content of TextNode or CDATASection | ||
if ( node.nodeType === 3 || node.nodeType === 4 ) { | ||
ret += node.nodeValue; | ||
} | ||
return ret + open + "/" + tag + close; | ||
}, | ||
functionArgs: function( fn ) {//function calls it internally, it's the arguments part of the function | ||
// function calls it internally, it's the arguments part of the function | ||
functionArgs: function( fn ) { | ||
var args, | ||
@@ -1829,24 +2023,25 @@ l = fn.length; | ||
while ( l-- ) { | ||
args[l] = String.fromCharCode(97+l);//97 is 'a' | ||
// 97 is 'a' | ||
args[l] = String.fromCharCode(97+l); | ||
} | ||
return " " + args.join( ", " ) + " "; | ||
}, | ||
key: quote, //object calls it internally, the key part of an item in a map | ||
functionCode: "[code]", //function calls it internally, it's the content of the function | ||
attribute: quote, //node calls it internally, it's an html attribute value | ||
// object calls it internally, the key part of an item in a map | ||
key: quote, | ||
// function calls it internally, it's the content of the function | ||
functionCode: "[code]", | ||
// node calls it internally, it's an html attribute value | ||
attribute: quote, | ||
string: quote, | ||
date: quote, | ||
regexp: literal, //regex | ||
regexp: literal, | ||
number: literal, | ||
"boolean": literal | ||
}, | ||
DOMAttrs: { | ||
//attributes to dump from nodes, name=>realName | ||
id: "id", | ||
name: "name", | ||
"class": "className" | ||
}, | ||
HTML: false,//if true, entities are escaped ( <, >, \t, space and \n ) | ||
indentChar: " ",//indentation unit | ||
multiline: true //if true, items in a collection, are separated by a \n, else just a space. | ||
// if true, entities are escaped ( <, >, \t, space and \n ) | ||
HTML: false, | ||
// indentation unit | ||
indentChar: " ", | ||
// if true, items in a collection, are separated by a \n, else just a space. | ||
multiline: true | ||
}; | ||
@@ -1857,23 +2052,2 @@ | ||
// from Sizzle.js | ||
function getText( elems ) { | ||
var i, elem, | ||
ret = ""; | ||
for ( i = 0; elems[i]; i++ ) { | ||
elem = elems[i]; | ||
// Get the text from text nodes and CDATA nodes | ||
if ( elem.nodeType === 3 || elem.nodeType === 4 ) { | ||
ret += elem.nodeValue; | ||
// Traverse everything else, except comment nodes | ||
} else if ( elem.nodeType !== 8 ) { | ||
ret += getText( elem.childNodes ); | ||
} | ||
} | ||
return ret; | ||
} | ||
// from jquery.js | ||
@@ -1909,2 +2083,3 @@ function inArray( elem, array ) { | ||
QUnit.diff = (function() { | ||
/*jshint eqeqeq:false, eqnull:true */ | ||
function diff( o, n ) { | ||
@@ -1916,3 +2091,3 @@ var i, | ||
for ( i = 0; i < n.length; i++ ) { | ||
if ( ns[ n[i] ] == null ) { | ||
if ( !hasOwn.call( ns, n[i] ) ) { | ||
ns[ n[i] ] = { | ||
@@ -1927,3 +2102,3 @@ rows: [], | ||
for ( i = 0; i < o.length; i++ ) { | ||
if ( os[ o[i] ] == null ) { | ||
if ( !hasOwn.call( os, o[i] ) ) { | ||
os[ o[i] ] = { | ||
@@ -1941,3 +2116,3 @@ rows: [], | ||
} | ||
if ( ns[i].rows.length == 1 && typeof os[i] != "undefined" && os[i].rows.length == 1 ) { | ||
if ( ns[i].rows.length === 1 && hasOwn.call( os, i ) && os[i].rows.length === 1 ) { | ||
n[ ns[i].rows[0] ] = { | ||
@@ -2048,3 +2223,3 @@ text: n[ ns[i].rows[0] ], | ||
if ( typeof exports !== "undefined" ) { | ||
extend(exports, QUnit); | ||
extend( exports, QUnit ); | ||
} | ||
@@ -2051,0 +2226,0 @@ |
@@ -1,1221 +0,1227 @@ | ||
// Underscore.js 1.4.3 | ||
// http://underscorejs.org | ||
// (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc. | ||
// Underscore may be freely distributed under the MIT license. | ||
// Underscore.js 1.4.4 | ||
// =================== | ||
// > http://underscorejs.org | ||
// > (c) 2009-2013 Jeremy Ashkenas, DocumentCloud Inc. | ||
// > Underscore may be freely distributed under the MIT license. | ||
// Baseline setup | ||
// -------------- | ||
(function() { | ||
// Baseline setup | ||
// -------------- | ||
// Establish the root object, `window` in the browser, or `global` on the server. | ||
var root = this; | ||
// Establish the root object, `window` in the browser, or `global` on the server. | ||
var root = this; | ||
// Save the previous value of the `_` variable. | ||
var previousUnderscore = root._; | ||
// Save the previous value of the `_` variable. | ||
var previousUnderscore = root._; | ||
// Establish the object that gets returned to break out of a loop iteration. | ||
var breaker = {}; | ||
// Establish the object that gets returned to break out of a loop iteration. | ||
var breaker = {}; | ||
// Save bytes in the minified (but not gzipped) version: | ||
var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype; | ||
// Save bytes in the minified (but not gzipped) version: | ||
var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype; | ||
// Create quick reference variables for speed access to core prototypes. | ||
var push = ArrayProto.push, | ||
slice = ArrayProto.slice, | ||
concat = ArrayProto.concat, | ||
toString = ObjProto.toString, | ||
hasOwnProperty = ObjProto.hasOwnProperty; | ||
// Create quick reference variables for speed access to core prototypes. | ||
var push = ArrayProto.push, | ||
slice = ArrayProto.slice, | ||
concat = ArrayProto.concat, | ||
toString = ObjProto.toString, | ||
hasOwnProperty = ObjProto.hasOwnProperty; | ||
// All **ECMAScript 5** native function implementations that we hope to use | ||
// are declared here. | ||
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, | ||
nativeKeys = Object.keys, | ||
nativeBind = FuncProto.bind; | ||
// All **ECMAScript 5** native function implementations that we hope to use | ||
// are declared here. | ||
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, | ||
nativeKeys = Object.keys, | ||
nativeBind = FuncProto.bind; | ||
// Create a safe reference to the Underscore object for use below. | ||
var _ = function(obj) { | ||
if (obj instanceof _) return obj; | ||
if (!(this instanceof _)) return new _(obj); | ||
this._wrapped = obj; | ||
}; | ||
// Create a safe reference to the Underscore object for use below. | ||
var _ = function(obj) { | ||
if (obj instanceof _) return obj; | ||
if (!(this instanceof _)) return new _(obj); | ||
this._wrapped = obj; | ||
}; | ||
// Export the Underscore object for **Node.js**, with | ||
// backwards-compatibility for the old `require()` API. If we're in | ||
// the browser, add `_` as a global object via a string identifier, | ||
// for Closure Compiler "advanced" mode. | ||
if (typeof exports !== 'undefined') { | ||
if (typeof module !== 'undefined' && module.exports) { | ||
exports = module.exports = _; | ||
// Export the Underscore object for **Node.js**, with | ||
// backwards-compatibility for the old `require()` API. If we're in | ||
// the browser, add `_` as a global object via a string identifier, | ||
// for Closure Compiler "advanced" mode. | ||
if (typeof exports !== 'undefined') { | ||
if (typeof module !== 'undefined' && module.exports) { | ||
exports = module.exports = _; | ||
} | ||
exports._ = _; | ||
} else { | ||
root._ = _; | ||
} | ||
exports._ = _; | ||
} else { | ||
root._ = _; | ||
} | ||
// Current version. | ||
_.VERSION = '1.4.3'; | ||
// Current version. | ||
_.VERSION = '1.4.4'; | ||
// Collection Functions | ||
// -------------------- | ||
// Collection Functions | ||
// -------------------- | ||
// The cornerstone, an `each` implementation, aka `forEach`. | ||
// Handles objects with the built-in `forEach`, arrays, and raw objects. | ||
// Delegates to **ECMAScript 5**'s native `forEach` if available. | ||
var each = _.each = _.forEach = function(obj, iterator, context) { | ||
if (obj == null) return; | ||
if (nativeForEach && obj.forEach === nativeForEach) { | ||
obj.forEach(iterator, context); | ||
} else if (obj.length === +obj.length) { | ||
for (var i = 0, l = obj.length; i < l; i++) { | ||
if (iterator.call(context, obj[i], i, obj) === breaker) return; | ||
} | ||
} else { | ||
for (var key in obj) { | ||
if (_.has(obj, key)) { | ||
if (iterator.call(context, obj[key], key, obj) === breaker) return; | ||
// The cornerstone, an `each` implementation, aka `forEach`. | ||
// Handles objects with the built-in `forEach`, arrays, and raw objects. | ||
// Delegates to **ECMAScript 5**'s native `forEach` if available. | ||
var each = _.each = _.forEach = function(obj, iterator, context) { | ||
if (obj == null) return; | ||
if (nativeForEach && obj.forEach === nativeForEach) { | ||
obj.forEach(iterator, context); | ||
} else if (obj.length === +obj.length) { | ||
for (var i = 0, l = obj.length; i < l; i++) { | ||
if (iterator.call(context, obj[i], i, obj) === breaker) return; | ||
} | ||
} else { | ||
for (var key in obj) { | ||
if (_.has(obj, key)) { | ||
if (iterator.call(context, obj[key], key, obj) === breaker) return; | ||
} | ||
} | ||
} | ||
} | ||
} | ||
}; | ||
}; | ||
// Return the results of applying the iterator to each element. | ||
// Delegates to **ECMAScript 5**'s native `map` if available. | ||
_.map = _.collect = function(obj, iterator, context) { | ||
var results = []; | ||
if (obj == null) return results; | ||
if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context); | ||
each(obj, function(value, index, list) { | ||
results[results.length] = iterator.call(context, value, index, list); | ||
}); | ||
return results; | ||
}; | ||
// Return the results of applying the iterator to each element. | ||
// Delegates to **ECMAScript 5**'s native `map` if available. | ||
_.map = _.collect = function(obj, iterator, context) { | ||
var results = []; | ||
if (obj == null) return results; | ||
if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context); | ||
each(obj, function(value, index, list) { | ||
results[results.length] = iterator.call(context, value, index, list); | ||
}); | ||
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`, | ||
// or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available. | ||
_.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) { | ||
var initial = arguments.length > 2; | ||
if (obj == null) obj = []; | ||
if (nativeReduce && obj.reduce === nativeReduce) { | ||
if (context) iterator = _.bind(iterator, context); | ||
return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator); | ||
} | ||
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; | ||
}; | ||
// **Reduce** builds up a single result from a list of values, aka `inject`, | ||
// or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available. | ||
_.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) { | ||
var initial = arguments.length > 2; | ||
if (obj == null) obj = []; | ||
if (nativeReduce && obj.reduce === nativeReduce) { | ||
if (context) iterator = _.bind(iterator, context); | ||
return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator); | ||
} | ||
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; | ||
}; | ||
// The right-associative version of reduce, also known as `foldr`. | ||
// Delegates to **ECMAScript 5**'s native `reduceRight` if available. | ||
_.reduceRight = _.foldr = function(obj, iterator, memo, context) { | ||
var initial = arguments.length > 2; | ||
if (obj == null) obj = []; | ||
if (nativeReduceRight && obj.reduceRight === nativeReduceRight) { | ||
if (context) iterator = _.bind(iterator, context); | ||
return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator); | ||
} | ||
var length = obj.length; | ||
if (length !== +length) { | ||
var keys = _.keys(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; | ||
}; | ||
// The right-associative version of reduce, also known as `foldr`. | ||
// Delegates to **ECMAScript 5**'s native `reduceRight` if available. | ||
_.reduceRight = _.foldr = function(obj, iterator, memo, context) { | ||
var initial = arguments.length > 2; | ||
if (obj == null) obj = []; | ||
if (nativeReduceRight && obj.reduceRight === nativeReduceRight) { | ||
if (context) iterator = _.bind(iterator, context); | ||
return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator); | ||
} | ||
var length = obj.length; | ||
if (length !== +length) { | ||
var keys = _.keys(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 the first value which passes a truth test. Aliased as `detect`. | ||
_.find = _.detect = function(obj, iterator, context) { | ||
var result; | ||
any(obj, function(value, index, list) { | ||
if (iterator.call(context, value, index, list)) { | ||
result = value; | ||
return true; | ||
} | ||
}); | ||
return result; | ||
}; | ||
// Return the first value which passes a truth test. Aliased as `detect`. | ||
_.find = _.detect = function(obj, iterator, context) { | ||
var result; | ||
any(obj, function(value, index, list) { | ||
if (iterator.call(context, value, index, list)) { | ||
result = value; | ||
return true; | ||
} | ||
}); | ||
return result; | ||
}; | ||
// Return all the elements that pass a truth test. | ||
// Delegates to **ECMAScript 5**'s native `filter` if available. | ||
// Aliased as `select`. | ||
_.filter = _.select = function(obj, iterator, context) { | ||
var results = []; | ||
if (obj == null) return results; | ||
if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context); | ||
each(obj, function(value, index, list) { | ||
if (iterator.call(context, value, index, list)) results[results.length] = value; | ||
}); | ||
return results; | ||
}; | ||
// Return all the elements that pass a truth test. | ||
// Delegates to **ECMAScript 5**'s native `filter` if available. | ||
// Aliased as `select`. | ||
_.filter = _.select = function(obj, iterator, context) { | ||
var results = []; | ||
if (obj == null) return results; | ||
if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context); | ||
each(obj, function(value, index, list) { | ||
if (iterator.call(context, value, index, list)) results[results.length] = value; | ||
}); | ||
return results; | ||
}; | ||
// Return all the elements for which a truth test fails. | ||
_.reject = function(obj, iterator, context) { | ||
return _.filter(obj, function(value, index, list) { | ||
return !iterator.call(context, value, index, list); | ||
}, context); | ||
}; | ||
// Return all the elements for which a truth test fails. | ||
_.reject = function(obj, iterator, context) { | ||
return _.filter(obj, function(value, index, list) { | ||
return !iterator.call(context, value, index, list); | ||
}, context); | ||
}; | ||
// Determine whether all of the elements match a truth test. | ||
// 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); | ||
each(obj, function(value, index, list) { | ||
if (!(result = result && iterator.call(context, value, index, list))) return breaker; | ||
}); | ||
return !!result; | ||
}; | ||
// Determine whether all of the elements match a truth test. | ||
// 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); | ||
each(obj, function(value, index, list) { | ||
if (!(result = result && iterator.call(context, value, index, list))) return breaker; | ||
}); | ||
return !!result; | ||
}; | ||
// 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`. | ||
var any = _.some = _.any = function(obj, iterator, context) { | ||
iterator || (iterator = _.identity); | ||
var result = false; | ||
if (obj == null) return result; | ||
if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context); | ||
each(obj, function(value, index, list) { | ||
if (result || (result = iterator.call(context, value, index, list))) return breaker; | ||
}); | ||
return !!result; | ||
}; | ||
// 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`. | ||
var any = _.some = _.any = function(obj, iterator, context) { | ||
iterator || (iterator = _.identity); | ||
var result = false; | ||
if (obj == null) return result; | ||
if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context); | ||
each(obj, function(value, index, list) { | ||
if (result || (result = iterator.call(context, value, index, list))) return breaker; | ||
}); | ||
return !!result; | ||
}; | ||
// Determine if the array or object contains a given value (using `===`). | ||
// Aliased as `include`. | ||
_.contains = _.include = function(obj, target) { | ||
if (obj == null) return false; | ||
if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1; | ||
return any(obj, function(value) { | ||
return value === target; | ||
}); | ||
}; | ||
// Determine if the array or object contains a given value (using `===`). | ||
// Aliased as `include`. | ||
_.contains = _.include = function(obj, target) { | ||
if (obj == null) return false; | ||
if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1; | ||
return any(obj, function(value) { | ||
return value === target; | ||
}); | ||
}; | ||
// Invoke a method (with arguments) on every item in a collection. | ||
_.invoke = function(obj, method) { | ||
var args = slice.call(arguments, 2); | ||
return _.map(obj, function(value) { | ||
return (_.isFunction(method) ? method : value[method]).apply(value, args); | ||
}); | ||
}; | ||
// Invoke a method (with arguments) on every item in a collection. | ||
_.invoke = function(obj, method) { | ||
var args = slice.call(arguments, 2); | ||
var isFunc = _.isFunction(method); | ||
return _.map(obj, function(value) { | ||
return (isFunc ? method : value[method]).apply(value, args); | ||
}); | ||
}; | ||
// Convenience version of a common use case of `map`: fetching a property. | ||
_.pluck = function(obj, key) { | ||
return _.map(obj, function(value){ return value[key]; }); | ||
}; | ||
// Convenience version of a common use case of `map`: fetching a property. | ||
_.pluck = function(obj, key) { | ||
return _.map(obj, function(value){ return value[key]; }); | ||
}; | ||
// Convenience version of a common use case of `filter`: selecting only objects | ||
// with specific `key:value` pairs. | ||
_.where = function(obj, attrs) { | ||
if (_.isEmpty(attrs)) return []; | ||
return _.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 `filter`: selecting only objects | ||
// containing specific `key:value` pairs. | ||
_.where = function(obj, attrs, first) { | ||
if (_.isEmpty(attrs)) return first ? null : []; | ||
return _[first ? 'find' : 'filter'](obj, function(value) { | ||
for (var key in attrs) { | ||
if (attrs[key] !== value[key]) return false; | ||
} | ||
return true; | ||
}); | ||
}; | ||
// Return the maximum element or (element-based computation). | ||
// Can't optimize arrays of integers longer than 65,535 elements. | ||
// See: https://bugs.webkit.org/show_bug.cgi?id=80797 | ||
_.max = function(obj, iterator, context) { | ||
if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) { | ||
return Math.max.apply(Math, obj); | ||
} | ||
if (!iterator && _.isEmpty(obj)) return -Infinity; | ||
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; | ||
}; | ||
// Convenience version of a common use case of `find`: getting the first object | ||
// containing specific `key:value` pairs. | ||
_.findWhere = function(obj, attrs) { | ||
return _.where(obj, attrs, true); | ||
}; | ||
// Return the minimum element (or element-based computation). | ||
_.min = function(obj, iterator, context) { | ||
if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) { | ||
return Math.min.apply(Math, obj); | ||
} | ||
if (!iterator && _.isEmpty(obj)) return Infinity; | ||
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 maximum element or (element-based computation). | ||
// Can't optimize arrays of integers longer than 65,535 elements. | ||
// See: https://bugs.webkit.org/show_bug.cgi?id=80797 | ||
_.max = function(obj, iterator, context) { | ||
if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) { | ||
return Math.max.apply(Math, obj); | ||
} | ||
if (!iterator && _.isEmpty(obj)) return -Infinity; | ||
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. | ||
_.shuffle = function(obj) { | ||
var rand; | ||
var index = 0; | ||
var shuffled = []; | ||
each(obj, function(value) { | ||
rand = _.random(index++); | ||
shuffled[index - 1] = shuffled[rand]; | ||
shuffled[rand] = value; | ||
}); | ||
return shuffled; | ||
}; | ||
// Return the minimum element (or element-based computation). | ||
_.min = function(obj, iterator, context) { | ||
if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) { | ||
return Math.min.apply(Math, obj); | ||
} | ||
if (!iterator && _.isEmpty(obj)) return Infinity; | ||
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; | ||
}; | ||
// An internal function to generate lookup iterators. | ||
var lookupIterator = function(value) { | ||
return _.isFunction(value) ? value : function(obj){ return obj[value]; }; | ||
}; | ||
// Shuffle an array. | ||
_.shuffle = function(obj) { | ||
var rand; | ||
var index = 0; | ||
var shuffled = []; | ||
each(obj, function(value) { | ||
rand = _.random(index++); | ||
shuffled[index - 1] = shuffled[rand]; | ||
shuffled[rand] = value; | ||
}); | ||
return shuffled; | ||
}; | ||
// 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 { | ||
value : value, | ||
index : index, | ||
criteria : iterator.call(context, value, index, list) | ||
}; | ||
}).sort(function(left, right) { | ||
var a = left.criteria; | ||
var b = right.criteria; | ||
if (a !== b) { | ||
if (a > b || a === void 0) return 1; | ||
if (a < b || b === void 0) return -1; | ||
} | ||
return left.index < right.index ? -1 : 1; | ||
}), 'value'); | ||
}; | ||
// An internal function to generate lookup iterators. | ||
var lookupIterator = function(value) { | ||
return _.isFunction(value) ? value : function(obj){ return obj[value]; }; | ||
}; | ||
// An internal function used for aggregate "group by" operations. | ||
var group = function(obj, value, context, behavior) { | ||
var result = {}; | ||
var iterator = lookupIterator(value || _.identity); | ||
each(obj, function(value, index) { | ||
var key = iterator.call(context, value, index, obj); | ||
behavior(result, key, value); | ||
}); | ||
return result; | ||
}; | ||
// 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 { | ||
value : value, | ||
index : index, | ||
criteria : iterator.call(context, value, index, list) | ||
}; | ||
}).sort(function(left, right) { | ||
var a = left.criteria; | ||
var b = right.criteria; | ||
if (a !== b) { | ||
if (a > b || a === void 0) return 1; | ||
if (a < b || b === void 0) return -1; | ||
} | ||
return left.index < right.index ? -1 : 1; | ||
}), 'value'); | ||
}; | ||
// Groups the object's values by a criterion. Pass either a string attribute | ||
// to group by, or a function that returns the criterion. | ||
_.groupBy = function(obj, value, context) { | ||
return group(obj, value, context, function(result, key, value) { | ||
(_.has(result, key) ? result[key] : (result[key] = [])).push(value); | ||
}); | ||
}; | ||
// An internal function used for aggregate "group by" operations. | ||
var group = function(obj, value, context, behavior) { | ||
var result = {}; | ||
var iterator = lookupIterator(value || _.identity); | ||
each(obj, function(value, index) { | ||
var key = iterator.call(context, value, index, obj); | ||
behavior(result, key, value); | ||
}); | ||
return result; | ||
}; | ||
// 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 | ||
// criterion. | ||
_.countBy = function(obj, value, context) { | ||
return group(obj, value, context, function(result, key) { | ||
if (!_.has(result, key)) result[key] = 0; | ||
result[key]++; | ||
}); | ||
}; | ||
// Groups the object's values by a criterion. Pass either a string attribute | ||
// to group by, or a function that returns the criterion. | ||
_.groupBy = function(obj, value, context) { | ||
return group(obj, value, context, function(result, key, value) { | ||
(_.has(result, key) ? result[key] : (result[key] = [])).push(value); | ||
}); | ||
}; | ||
// 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. | ||
_.sortedIndex = function(array, obj, iterator, context) { | ||
iterator = iterator == null ? _.identity : lookupIterator(iterator); | ||
var value = iterator.call(context, obj); | ||
var low = 0, high = array.length; | ||
while (low < high) { | ||
var mid = (low + high) >>> 1; | ||
iterator.call(context, array[mid]) < value ? low = mid + 1 : high = mid; | ||
} | ||
return low; | ||
}; | ||
// 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 | ||
// criterion. | ||
_.countBy = function(obj, value, context) { | ||
return group(obj, value, context, function(result, key) { | ||
if (!_.has(result, key)) result[key] = 0; | ||
result[key]++; | ||
}); | ||
}; | ||
// Safely convert anything iterable into a real, live array. | ||
_.toArray = function(obj) { | ||
if (!obj) return []; | ||
if (_.isArray(obj)) return slice.call(obj); | ||
if (obj.length === +obj.length) return _.map(obj, _.identity); | ||
return _.values(obj); | ||
}; | ||
// 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. | ||
_.sortedIndex = function(array, obj, iterator, context) { | ||
iterator = iterator == null ? _.identity : lookupIterator(iterator); | ||
var value = iterator.call(context, obj); | ||
var low = 0, high = array.length; | ||
while (low < high) { | ||
var mid = (low + high) >>> 1; | ||
iterator.call(context, array[mid]) < value ? low = mid + 1 : high = mid; | ||
} | ||
return low; | ||
}; | ||
// Return the number of elements in an object. | ||
_.size = function(obj) { | ||
if (obj == null) return 0; | ||
return (obj.length === +obj.length) ? obj.length : _.keys(obj).length; | ||
}; | ||
// Safely convert anything iterable into a real, live array. | ||
_.toArray = function(obj) { | ||
if (!obj) return []; | ||
if (_.isArray(obj)) return slice.call(obj); | ||
if (obj.length === +obj.length) return _.map(obj, _.identity); | ||
return _.values(obj); | ||
}; | ||
// Array Functions | ||
// --------------- | ||
// Return the number of elements in an object. | ||
_.size = function(obj) { | ||
if (obj == null) return 0; | ||
return (obj.length === +obj.length) ? obj.length : _.keys(obj).length; | ||
}; | ||
// Get the first element of an array. Passing **n** will return the first N | ||
// values in the array. Aliased as `head` and `take`. The **guard** check | ||
// allows it to work with `_.map`. | ||
_.first = _.head = _.take = function(array, n, guard) { | ||
if (array == null) return void 0; | ||
return (n != null) && !guard ? slice.call(array, 0, n) : array[0]; | ||
}; | ||
// Array Functions | ||
// --------------- | ||
// Returns everything but the last entry of the array. Especially useful on | ||
// the arguments object. Passing **n** will return all the values in | ||
// the array, excluding the last N. The **guard** check allows it to work with | ||
// `_.map`. | ||
_.initial = function(array, n, guard) { | ||
return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n)); | ||
}; | ||
// Get the first element of an array. Passing **n** will return the first N | ||
// values in the array. Aliased as `head` and `take`. The **guard** check | ||
// allows it to work with `_.map`. | ||
_.first = _.head = _.take = function(array, n, guard) { | ||
if (array == null) return void 0; | ||
return (n != null) && !guard ? slice.call(array, 0, n) : array[0]; | ||
}; | ||
// 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`. | ||
_.last = function(array, n, guard) { | ||
if (array == null) return void 0; | ||
if ((n != null) && !guard) { | ||
return slice.call(array, Math.max(array.length - n, 0)); | ||
} else { | ||
return array[array.length - 1]; | ||
} | ||
}; | ||
// Returns everything but the last entry of the array. Especially useful on | ||
// the arguments object. Passing **n** will return all the values in | ||
// the array, excluding the last N. The **guard** check allows it to work with | ||
// `_.map`. | ||
_.initial = function(array, n, guard) { | ||
return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n)); | ||
}; | ||
// 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 = _.drop = function(array, n, guard) { | ||
return slice.call(array, (n == null) || guard ? 1 : 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`. | ||
_.last = function(array, n, guard) { | ||
if (array == null) return void 0; | ||
if ((n != null) && !guard) { | ||
return slice.call(array, Math.max(array.length - n, 0)); | ||
} else { | ||
return array[array.length - 1]; | ||
} | ||
}; | ||
// Trim out all falsy values from an array. | ||
_.compact = function(array) { | ||
return _.filter(array, _.identity); | ||
}; | ||
// 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 = _.drop = function(array, n, guard) { | ||
return slice.call(array, (n == null) || guard ? 1 : n); | ||
}; | ||
// Internal implementation of a recursive `flatten` function. | ||
var flatten = function(input, shallow, output) { | ||
each(input, function(value) { | ||
if (_.isArray(value)) { | ||
shallow ? push.apply(output, value) : flatten(value, shallow, output); | ||
} else { | ||
output.push(value); | ||
} | ||
}); | ||
return output; | ||
}; | ||
// Trim out all falsy values from an array. | ||
_.compact = function(array) { | ||
return _.filter(array, _.identity); | ||
}; | ||
// Return a completely flattened version of an array. | ||
_.flatten = function(array, shallow) { | ||
return flatten(array, shallow, []); | ||
}; | ||
// Internal implementation of a recursive `flatten` function. | ||
var flatten = function(input, shallow, output) { | ||
each(input, function(value) { | ||
if (_.isArray(value)) { | ||
shallow ? push.apply(output, value) : flatten(value, shallow, output); | ||
} else { | ||
output.push(value); | ||
} | ||
}); | ||
return output; | ||
}; | ||
// Return a version of the array that does not contain the specified value(s). | ||
_.without = function(array) { | ||
return _.difference(array, slice.call(arguments, 1)); | ||
}; | ||
// Return a completely flattened version of an array. | ||
_.flatten = function(array, shallow) { | ||
return flatten(array, shallow, []); | ||
}; | ||
// Produce a duplicate-free version of the array. If the array has already | ||
// been sorted, you have the option of using a faster algorithm. | ||
// Aliased as `unique`. | ||
_.uniq = _.unique = function(array, isSorted, iterator, context) { | ||
if (_.isFunction(isSorted)) { | ||
context = iterator; | ||
iterator = isSorted; | ||
isSorted = false; | ||
} | ||
var initial = iterator ? _.map(array, iterator, context) : array; | ||
var results = []; | ||
var seen = []; | ||
each(initial, function(value, index) { | ||
if (isSorted ? (!index || seen[seen.length - 1] !== value) : !_.contains(seen, value)) { | ||
seen.push(value); | ||
results.push(array[index]); | ||
} | ||
}); | ||
return results; | ||
}; | ||
// Return a version of the array that does not contain the specified value(s). | ||
_.without = function(array) { | ||
return _.difference(array, slice.call(arguments, 1)); | ||
}; | ||
// Produce an array that contains the union: each distinct element from all of | ||
// the passed-in arrays. | ||
_.union = function() { | ||
return _.uniq(concat.apply(ArrayProto, arguments)); | ||
}; | ||
// Produce a duplicate-free version of the array. If the array has already | ||
// been sorted, you have the option of using a faster algorithm. | ||
// Aliased as `unique`. | ||
_.uniq = _.unique = function(array, isSorted, iterator, context) { | ||
if (_.isFunction(isSorted)) { | ||
context = iterator; | ||
iterator = isSorted; | ||
isSorted = false; | ||
} | ||
var initial = iterator ? _.map(array, iterator, context) : array; | ||
var results = []; | ||
var seen = []; | ||
each(initial, function(value, index) { | ||
if (isSorted ? (!index || seen[seen.length - 1] !== value) : !_.contains(seen, value)) { | ||
seen.push(value); | ||
results.push(array[index]); | ||
} | ||
}); | ||
return results; | ||
}; | ||
// Produce an array that contains every item shared between all the | ||
// passed-in arrays. | ||
_.intersection = function(array) { | ||
var rest = slice.call(arguments, 1); | ||
return _.filter(_.uniq(array), function(item) { | ||
return _.every(rest, function(other) { | ||
return _.indexOf(other, item) >= 0; | ||
}); | ||
}); | ||
}; | ||
// Produce an array that contains the union: each distinct element from all of | ||
// the passed-in arrays. | ||
_.union = function() { | ||
return _.uniq(concat.apply(ArrayProto, arguments)); | ||
}; | ||
// Take the difference between one array and a number of other arrays. | ||
// Only the elements present in just the first array will remain. | ||
_.difference = function(array) { | ||
var rest = concat.apply(ArrayProto, slice.call(arguments, 1)); | ||
return _.filter(array, function(value){ return !_.contains(rest, value); }); | ||
}; | ||
// Produce an array that contains every item shared between all the | ||
// passed-in arrays. | ||
_.intersection = function(array) { | ||
var rest = slice.call(arguments, 1); | ||
return _.filter(_.uniq(array), function(item) { | ||
return _.every(rest, function(other) { | ||
return _.indexOf(other, item) >= 0; | ||
}); | ||
}); | ||
}; | ||
// Zip together multiple lists into a single array -- elements that share | ||
// an index go together. | ||
_.zip = function() { | ||
var args = slice.call(arguments); | ||
var length = _.max(_.pluck(args, 'length')); | ||
var results = new Array(length); | ||
for (var i = 0; i < length; i++) { | ||
results[i] = _.pluck(args, "" + i); | ||
} | ||
return results; | ||
}; | ||
// Take the difference between one array and a number of other arrays. | ||
// Only the elements present in just the first array will remain. | ||
_.difference = function(array) { | ||
var rest = concat.apply(ArrayProto, slice.call(arguments, 1)); | ||
return _.filter(array, function(value){ return !_.contains(rest, value); }); | ||
}; | ||
// 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) { | ||
if (list == null) return {}; | ||
var result = {}; | ||
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; | ||
}; | ||
// Zip together multiple lists into a single array -- elements that share | ||
// an index go together. | ||
_.zip = function() { | ||
var args = slice.call(arguments); | ||
var length = _.max(_.pluck(args, 'length')); | ||
var results = new Array(length); | ||
for (var i = 0; i < length; i++) { | ||
results[i] = _.pluck(args, "" + i); | ||
} | ||
return results; | ||
}; | ||
// If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**), | ||
// we need this function. Return the position of the first occurrence of an | ||
// 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` | ||
// for **isSorted** to use binary search. | ||
_.indexOf = function(array, item, isSorted) { | ||
if (array == null) return -1; | ||
var i = 0, l = array.length; | ||
if (isSorted) { | ||
if (typeof isSorted == 'number') { | ||
i = (isSorted < 0 ? Math.max(0, l + isSorted) : isSorted); | ||
} else { | ||
i = _.sortedIndex(array, item); | ||
return array[i] === item ? i : -1; | ||
} | ||
} | ||
if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item, isSorted); | ||
for (; i < l; i++) if (array[i] === item) return i; | ||
return -1; | ||
}; | ||
// 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) { | ||
if (list == null) return {}; | ||
var result = {}; | ||
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; | ||
}; | ||
// Delegates to **ECMAScript 5**'s native `lastIndexOf` if available. | ||
_.lastIndexOf = function(array, item, from) { | ||
if (array == null) return -1; | ||
var hasIndex = from != null; | ||
if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) { | ||
return hasIndex ? array.lastIndexOf(item, from) : array.lastIndexOf(item); | ||
} | ||
var i = (hasIndex ? from : array.length); | ||
while (i--) if (array[i] === item) return i; | ||
return -1; | ||
}; | ||
// If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**), | ||
// we need this function. Return the position of the first occurrence of an | ||
// 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` | ||
// for **isSorted** to use binary search. | ||
_.indexOf = function(array, item, isSorted) { | ||
if (array == null) return -1; | ||
var i = 0, l = array.length; | ||
if (isSorted) { | ||
if (typeof isSorted == 'number') { | ||
i = (isSorted < 0 ? Math.max(0, l + isSorted) : isSorted); | ||
} else { | ||
i = _.sortedIndex(array, item); | ||
return array[i] === item ? i : -1; | ||
} | ||
} | ||
if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item, isSorted); | ||
for (; i < l; i++) if (array[i] === item) return i; | ||
return -1; | ||
}; | ||
// Generate an integer Array containing an arithmetic progression. A port of | ||
// the native Python `range()` function. See | ||
// [the Python documentation](http://docs.python.org/library/functions.html#range). | ||
_.range = function(start, stop, step) { | ||
if (arguments.length <= 1) { | ||
stop = start || 0; | ||
start = 0; | ||
} | ||
step = arguments[2] || 1; | ||
// Delegates to **ECMAScript 5**'s native `lastIndexOf` if available. | ||
_.lastIndexOf = function(array, item, from) { | ||
if (array == null) return -1; | ||
var hasIndex = from != null; | ||
if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) { | ||
return hasIndex ? array.lastIndexOf(item, from) : array.lastIndexOf(item); | ||
} | ||
var i = (hasIndex ? from : array.length); | ||
while (i--) if (array[i] === item) return i; | ||
return -1; | ||
}; | ||
var len = Math.max(Math.ceil((stop - start) / step), 0); | ||
var idx = 0; | ||
var range = new Array(len); | ||
// Generate an integer Array containing an arithmetic progression. A port of | ||
// the native Python `range()` function. See | ||
// [the Python documentation](http://docs.python.org/library/functions.html#range). | ||
_.range = function(start, stop, step) { | ||
if (arguments.length <= 1) { | ||
stop = start || 0; | ||
start = 0; | ||
} | ||
step = arguments[2] || 1; | ||
while(idx < len) { | ||
range[idx++] = start; | ||
start += step; | ||
} | ||
var len = Math.max(Math.ceil((stop - start) / step), 0); | ||
var idx = 0; | ||
var range = new Array(len); | ||
return range; | ||
}; | ||
while(idx < len) { | ||
range[idx++] = start; | ||
start += step; | ||
} | ||
// Function (ahem) Functions | ||
// ------------------ | ||
return range; | ||
}; | ||
// Reusable constructor function for prototype setting. | ||
var ctor = function(){}; | ||
// Function (ahem) Functions | ||
// ------------------ | ||
// Create a function bound to a given object (assigning `this`, and arguments, | ||
// optionally). Binding with arguments is also known as `curry`. | ||
// Delegates to **ECMAScript 5**'s native `Function.bind` if available. | ||
// We check for `func.bind` first, to fail fast when `func` is undefined. | ||
_.bind = function(func, context) { | ||
var args, bound; | ||
if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1)); | ||
if (!_.isFunction(func)) throw new TypeError; | ||
args = slice.call(arguments, 2); | ||
return bound = function() { | ||
if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments))); | ||
ctor.prototype = func.prototype; | ||
var self = new ctor; | ||
ctor.prototype = null; | ||
var result = func.apply(self, args.concat(slice.call(arguments))); | ||
if (Object(result) === result) return result; | ||
return self; | ||
// Create a function bound to a given object (assigning `this`, and arguments, | ||
// optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if | ||
// available. | ||
_.bind = function(func, context) { | ||
if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1)); | ||
var args = slice.call(arguments, 2); | ||
return function() { | ||
return func.apply(context, args.concat(slice.call(arguments))); | ||
}; | ||
}; | ||
}; | ||
// Bind all of an object's methods to that object. Useful for ensuring that | ||
// all callbacks defined on an object belong to it. | ||
_.bindAll = function(obj) { | ||
var funcs = slice.call(arguments, 1); | ||
if (funcs.length == 0) funcs = _.functions(obj); | ||
each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); }); | ||
return obj; | ||
}; | ||
// Partially apply a function by creating a version that has had some of its | ||
// arguments pre-filled, without changing its dynamic `this` context. | ||
_.partial = function(func) { | ||
var args = slice.call(arguments, 1); | ||
return function() { | ||
return func.apply(this, args.concat(slice.call(arguments))); | ||
}; | ||
}; | ||
// Memoize an expensive function by storing its results. | ||
_.memoize = function(func, hasher) { | ||
var memo = {}; | ||
hasher || (hasher = _.identity); | ||
return function() { | ||
var key = hasher.apply(this, arguments); | ||
return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments)); | ||
// Bind all of an object's methods to that object. Useful for ensuring that | ||
// all callbacks defined on an object belong to it. | ||
_.bindAll = function(obj) { | ||
var funcs = slice.call(arguments, 1); | ||
if (funcs.length === 0) funcs = _.functions(obj); | ||
each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); }); | ||
return obj; | ||
}; | ||
}; | ||
// Delays a function for the given number of milliseconds, and then calls | ||
// it with the arguments supplied. | ||
_.delay = function(func, wait) { | ||
var args = slice.call(arguments, 2); | ||
return setTimeout(function(){ return func.apply(null, args); }, wait); | ||
}; | ||
// Memoize an expensive function by storing its results. | ||
_.memoize = function(func, hasher) { | ||
var memo = {}; | ||
hasher || (hasher = _.identity); | ||
return function() { | ||
var key = hasher.apply(this, arguments); | ||
return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments)); | ||
}; | ||
}; | ||
// Defers a function, scheduling it to run after the current call stack has | ||
// cleared. | ||
_.defer = function(func) { | ||
return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1))); | ||
}; | ||
// Delays a function for the given number of milliseconds, and then calls | ||
// it with the arguments supplied. | ||
_.delay = function(func, wait) { | ||
var args = slice.call(arguments, 2); | ||
return setTimeout(function(){ return func.apply(null, args); }, wait); | ||
}; | ||
// Returns a function, that, when invoked, will only be triggered at most once | ||
// during a given window of time. | ||
_.throttle = function(func, wait) { | ||
var context, args, timeout, result; | ||
var previous = 0; | ||
var later = function() { | ||
previous = new Date; | ||
timeout = null; | ||
result = func.apply(context, args); | ||
// Defers a function, scheduling it to run after the current call stack has | ||
// cleared. | ||
_.defer = function(func) { | ||
return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1))); | ||
}; | ||
return function() { | ||
var now = new Date; | ||
var remaining = wait - (now - previous); | ||
context = this; | ||
args = arguments; | ||
if (remaining <= 0) { | ||
clearTimeout(timeout); | ||
timeout = null; | ||
previous = now; | ||
result = func.apply(context, args); | ||
} else if (!timeout) { | ||
timeout = setTimeout(later, remaining); | ||
} | ||
return result; | ||
// Returns a function, that, when invoked, will only be triggered at most once | ||
// during a given window of time. | ||
_.throttle = function(func, wait) { | ||
var context, args, timeout, result; | ||
var previous = 0; | ||
var later = function() { | ||
previous = new Date; | ||
timeout = null; | ||
result = func.apply(context, args); | ||
}; | ||
return function() { | ||
var now = new Date; | ||
var remaining = wait - (now - previous); | ||
context = this; | ||
args = arguments; | ||
if (remaining <= 0) { | ||
clearTimeout(timeout); | ||
timeout = null; | ||
previous = now; | ||
result = func.apply(context, args); | ||
} else if (!timeout) { | ||
timeout = setTimeout(later, remaining); | ||
} | ||
return result; | ||
}; | ||
}; | ||
}; | ||
// Returns a function, that, as long as it continues to be invoked, will not | ||
// be triggered. The function will be called after it stops being called for | ||
// N milliseconds. If `immediate` is passed, trigger the function on the | ||
// leading edge, instead of the trailing. | ||
_.debounce = function(func, wait, immediate) { | ||
var timeout, result; | ||
return function() { | ||
var context = this, args = arguments; | ||
var later = function() { | ||
timeout = null; | ||
if (!immediate) result = func.apply(context, args); | ||
}; | ||
var callNow = immediate && !timeout; | ||
clearTimeout(timeout); | ||
timeout = setTimeout(later, wait); | ||
if (callNow) result = func.apply(context, args); | ||
return result; | ||
// Returns a function, that, as long as it continues to be invoked, will not | ||
// be triggered. The function will be called after it stops being called for | ||
// N milliseconds. If `immediate` is passed, trigger the function on the | ||
// leading edge, instead of the trailing. | ||
_.debounce = function(func, wait, immediate) { | ||
var timeout, result; | ||
return function() { | ||
var context = this, args = arguments; | ||
var later = function() { | ||
timeout = null; | ||
if (!immediate) result = func.apply(context, args); | ||
}; | ||
var callNow = immediate && !timeout; | ||
clearTimeout(timeout); | ||
timeout = setTimeout(later, wait); | ||
if (callNow) result = func.apply(context, args); | ||
return result; | ||
}; | ||
}; | ||
}; | ||
// Returns a function that will be executed at most one time, no matter how | ||
// often you call it. Useful for lazy initialization. | ||
_.once = function(func) { | ||
var ran = false, memo; | ||
return function() { | ||
if (ran) return memo; | ||
ran = true; | ||
memo = func.apply(this, arguments); | ||
func = null; | ||
return memo; | ||
// Returns a function that will be executed at most one time, no matter how | ||
// often you call it. Useful for lazy initialization. | ||
_.once = function(func) { | ||
var ran = false, memo; | ||
return function() { | ||
if (ran) return memo; | ||
ran = true; | ||
memo = func.apply(this, arguments); | ||
func = null; | ||
return memo; | ||
}; | ||
}; | ||
}; | ||
// Returns the first function passed as an argument to the second, | ||
// allowing you to adjust arguments, run code before and after, and | ||
// conditionally execute the original function. | ||
_.wrap = function(func, wrapper) { | ||
return function() { | ||
var args = [func]; | ||
push.apply(args, arguments); | ||
return wrapper.apply(this, args); | ||
// Returns the first function passed as an argument to the second, | ||
// allowing you to adjust arguments, run code before and after, and | ||
// conditionally execute the original function. | ||
_.wrap = function(func, wrapper) { | ||
return function() { | ||
var args = [func]; | ||
push.apply(args, arguments); | ||
return wrapper.apply(this, args); | ||
}; | ||
}; | ||
}; | ||
// Returns a function that is the composition of a list of functions, each | ||
// consuming the return value of the function that follows. | ||
_.compose = function() { | ||
var funcs = arguments; | ||
return function() { | ||
var args = arguments; | ||
for (var i = funcs.length - 1; i >= 0; i--) { | ||
args = [funcs[i].apply(this, args)]; | ||
} | ||
return args[0]; | ||
// Returns a function that is the composition of a list of functions, each | ||
// consuming the return value of the function that follows. | ||
_.compose = function() { | ||
var funcs = arguments; | ||
return function() { | ||
var args = arguments; | ||
for (var i = funcs.length - 1; i >= 0; i--) { | ||
args = [funcs[i].apply(this, args)]; | ||
} | ||
return args[0]; | ||
}; | ||
}; | ||
}; | ||
// Returns a function that will only be executed after being called N times. | ||
_.after = function(times, func) { | ||
if (times <= 0) return func(); | ||
return function() { | ||
if (--times < 1) { | ||
return func.apply(this, arguments); | ||
} | ||
// Returns a function that will only be executed after being called N times. | ||
_.after = function(times, func) { | ||
if (times <= 0) return func(); | ||
return function() { | ||
if (--times < 1) { | ||
return func.apply(this, arguments); | ||
} | ||
}; | ||
}; | ||
}; | ||
// Object Functions | ||
// ---------------- | ||
// Object Functions | ||
// ---------------- | ||
// Retrieve the names of an object's properties. | ||
// Delegates to **ECMAScript 5**'s native `Object.keys` | ||
_.keys = nativeKeys || function(obj) { | ||
if (obj !== Object(obj)) throw new TypeError('Invalid object'); | ||
var keys = []; | ||
for (var key in obj) if (_.has(obj, key)) keys[keys.length] = key; | ||
return keys; | ||
}; | ||
// Retrieve the names of an object's properties. | ||
// Delegates to **ECMAScript 5**'s native `Object.keys` | ||
_.keys = nativeKeys || function(obj) { | ||
if (obj !== Object(obj)) throw new TypeError('Invalid object'); | ||
var keys = []; | ||
for (var key in obj) if (_.has(obj, key)) keys[keys.length] = key; | ||
return keys; | ||
}; | ||
// Retrieve the values of an object's properties. | ||
_.values = function(obj) { | ||
var values = []; | ||
for (var key in obj) if (_.has(obj, key)) values.push(obj[key]); | ||
return values; | ||
}; | ||
// Retrieve the values of an object's properties. | ||
_.values = function(obj) { | ||
var values = []; | ||
for (var key in obj) if (_.has(obj, key)) values.push(obj[key]); | ||
return values; | ||
}; | ||
// Convert an object into a list of `[key, value]` pairs. | ||
_.pairs = function(obj) { | ||
var pairs = []; | ||
for (var key in obj) if (_.has(obj, key)) pairs.push([key, obj[key]]); | ||
return pairs; | ||
}; | ||
// Convert an object into a list of `[key, value]` pairs. | ||
_.pairs = function(obj) { | ||
var pairs = []; | ||
for (var key in obj) if (_.has(obj, key)) pairs.push([key, obj[key]]); | ||
return pairs; | ||
}; | ||
// Invert the keys and values of an object. The values must be serializable. | ||
_.invert = function(obj) { | ||
var result = {}; | ||
for (var key in obj) if (_.has(obj, key)) result[obj[key]] = key; | ||
return result; | ||
}; | ||
// Invert the keys and values of an object. The values must be serializable. | ||
_.invert = function(obj) { | ||
var result = {}; | ||
for (var key in obj) if (_.has(obj, key)) result[obj[key]] = key; | ||
return result; | ||
}; | ||
// Return a sorted list of the function names available on the object. | ||
// Aliased as `methods` | ||
_.functions = _.methods = function(obj) { | ||
var names = []; | ||
for (var key in obj) { | ||
if (_.isFunction(obj[key])) names.push(key); | ||
} | ||
return names.sort(); | ||
}; | ||
// Extend a given object with all the properties in passed-in object(s). | ||
_.extend = function(obj) { | ||
each(slice.call(arguments, 1), function(source) { | ||
if (source) { | ||
for (var prop in source) { | ||
obj[prop] = source[prop]; | ||
// Return a sorted list of the function names available on the object. | ||
// Aliased as `methods` | ||
_.functions = _.methods = function(obj) { | ||
var names = []; | ||
for (var key in obj) { | ||
if (_.isFunction(obj[key])) names.push(key); | ||
} | ||
} | ||
}); | ||
return obj; | ||
}; | ||
return names.sort(); | ||
}; | ||
// Return a copy of the object only containing the whitelisted properties. | ||
_.pick = function(obj) { | ||
var copy = {}; | ||
var keys = concat.apply(ArrayProto, slice.call(arguments, 1)); | ||
each(keys, function(key) { | ||
if (key in obj) copy[key] = obj[key]; | ||
}); | ||
return copy; | ||
}; | ||
// Extend a given object with all the properties in passed-in object(s). | ||
_.extend = function(obj) { | ||
each(slice.call(arguments, 1), function(source) { | ||
if (source) { | ||
for (var prop in source) { | ||
obj[prop] = source[prop]; | ||
} | ||
} | ||
}); | ||
return obj; | ||
}; | ||
// Return a copy of the object without the blacklisted properties. | ||
_.omit = function(obj) { | ||
var copy = {}; | ||
var keys = concat.apply(ArrayProto, slice.call(arguments, 1)); | ||
for (var key in obj) { | ||
if (!_.contains(keys, key)) copy[key] = obj[key]; | ||
} | ||
return copy; | ||
}; | ||
// Return a copy of the object only containing the whitelisted properties. | ||
_.pick = function(obj) { | ||
var copy = {}; | ||
var keys = concat.apply(ArrayProto, slice.call(arguments, 1)); | ||
each(keys, function(key) { | ||
if (key in obj) copy[key] = obj[key]; | ||
}); | ||
return copy; | ||
}; | ||
// Fill in a given object with default properties. | ||
_.defaults = function(obj) { | ||
each(slice.call(arguments, 1), function(source) { | ||
if (source) { | ||
for (var prop in source) { | ||
if (obj[prop] == null) obj[prop] = source[prop]; | ||
// Return a copy of the object without the blacklisted properties. | ||
_.omit = function(obj) { | ||
var copy = {}; | ||
var keys = concat.apply(ArrayProto, slice.call(arguments, 1)); | ||
for (var key in obj) { | ||
if (!_.contains(keys, key)) copy[key] = obj[key]; | ||
} | ||
} | ||
}); | ||
return obj; | ||
}; | ||
return copy; | ||
}; | ||
// Create a (shallow-cloned) duplicate of an object. | ||
_.clone = function(obj) { | ||
if (!_.isObject(obj)) return obj; | ||
return _.isArray(obj) ? obj.slice() : _.extend({}, obj); | ||
}; | ||
// Fill in a given object with default properties. | ||
_.defaults = function(obj) { | ||
each(slice.call(arguments, 1), function(source) { | ||
if (source) { | ||
for (var prop in source) { | ||
if (obj[prop] == null) obj[prop] = source[prop]; | ||
} | ||
} | ||
}); | ||
return obj; | ||
}; | ||
// Invokes interceptor with the obj, and then returns obj. | ||
// The primary purpose of this method is to "tap into" a method chain, in | ||
// order to perform operations on intermediate results within the chain. | ||
_.tap = function(obj, interceptor) { | ||
interceptor(obj); | ||
return obj; | ||
}; | ||
// Create a (shallow-cloned) duplicate of an object. | ||
_.clone = function(obj) { | ||
if (!_.isObject(obj)) return obj; | ||
return _.isArray(obj) ? obj.slice() : _.extend({}, obj); | ||
}; | ||
// Internal recursive comparison function for `isEqual`. | ||
var eq = function(a, b, aStack, bStack) { | ||
// 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. | ||
if (a === b) return a !== 0 || 1 / a == 1 / b; | ||
// A strict comparison is necessary because `null == undefined`. | ||
if (a == null || b == null) return a === b; | ||
// Unwrap any wrapped objects. | ||
if (a instanceof _) a = a._wrapped; | ||
if (b instanceof _) b = b._wrapped; | ||
// Compare `[[Class]]` names. | ||
var className = toString.call(a); | ||
if (className != toString.call(b)) return false; | ||
switch (className) { | ||
// Strings, numbers, dates, and booleans are compared by value. | ||
case '[object String]': | ||
// Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is | ||
// equivalent to `new String("5")`. | ||
return a == String(b); | ||
case '[object Number]': | ||
// `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for | ||
// other numeric values. | ||
return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b); | ||
case '[object Date]': | ||
case '[object Boolean]': | ||
// Coerce dates and booleans to numeric primitive values. Dates are compared by their | ||
// millisecond representations. Note that invalid dates with millisecond representations | ||
// of `NaN` are not equivalent. | ||
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; | ||
// Assume equality for cyclic structures. The algorithm for detecting cyclic | ||
// structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. | ||
var length = aStack.length; | ||
while (length--) { | ||
// Linear search. Performance is inversely proportional to the number of | ||
// unique nested structures. | ||
if (aStack[length] == a) return bStack[length] == b; | ||
} | ||
// Add the first object to the stack of traversed objects. | ||
aStack.push(a); | ||
bStack.push(b); | ||
var size = 0, result = true; | ||
// Recursively compare objects and arrays. | ||
if (className == '[object Array]') { | ||
// Compare array lengths to determine if a deep comparison is necessary. | ||
size = a.length; | ||
result = size == b.length; | ||
if (result) { | ||
// Deep compare the contents, ignoring non-numeric properties. | ||
while (size--) { | ||
if (!(result = eq(a[size], b[size], aStack, bStack))) break; | ||
// Invokes interceptor with the obj, and then returns obj. | ||
// The primary purpose of this method is to "tap into" a method chain, in | ||
// order to perform operations on intermediate results within the chain. | ||
_.tap = function(obj, interceptor) { | ||
interceptor(obj); | ||
return obj; | ||
}; | ||
// Internal recursive comparison function for `isEqual`. | ||
var eq = function(a, b, aStack, bStack) { | ||
// 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. | ||
if (a === b) return a !== 0 || 1 / a == 1 / b; | ||
// A strict comparison is necessary because `null == undefined`. | ||
if (a == null || b == null) return a === b; | ||
// Unwrap any wrapped objects. | ||
if (a instanceof _) a = a._wrapped; | ||
if (b instanceof _) b = b._wrapped; | ||
// Compare `[[Class]]` names. | ||
var className = toString.call(a); | ||
if (className != toString.call(b)) return false; | ||
switch (className) { | ||
// Strings, numbers, dates, and booleans are compared by value. | ||
case '[object String]': | ||
// Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is | ||
// equivalent to `new String("5")`. | ||
return a == String(b); | ||
case '[object Number]': | ||
// `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for | ||
// other numeric values. | ||
return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b); | ||
case '[object Date]': | ||
case '[object Boolean]': | ||
// Coerce dates and booleans to numeric primitive values. Dates are compared by their | ||
// millisecond representations. Note that invalid dates with millisecond representations | ||
// of `NaN` are not equivalent. | ||
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; | ||
} | ||
} | ||
} else { | ||
// Objects with different constructors are not equivalent, but `Object`s | ||
// from different frames are. | ||
var aCtor = a.constructor, bCtor = b.constructor; | ||
if (aCtor !== bCtor && !(_.isFunction(aCtor) && (aCtor instanceof aCtor) && | ||
_.isFunction(bCtor) && (bCtor instanceof bCtor))) { | ||
return false; | ||
} | ||
// Deep compare objects. | ||
for (var key in a) { | ||
if (_.has(a, key)) { | ||
// Count the expected number of properties. | ||
size++; | ||
// Deep compare each member. | ||
if (!(result = _.has(b, key) && eq(a[key], b[key], aStack, bStack))) break; | ||
if (typeof a != 'object' || typeof b != 'object') return false; | ||
// Assume equality for cyclic structures. The algorithm for detecting cyclic | ||
// structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. | ||
var length = aStack.length; | ||
while (length--) { | ||
// Linear search. Performance is inversely proportional to the number of | ||
// unique nested structures. | ||
if (aStack[length] == a) return bStack[length] == b; | ||
} | ||
} | ||
// Ensure that both objects contain the same number of properties. | ||
if (result) { | ||
for (key in b) { | ||
if (_.has(b, key) && !(size--)) break; | ||
// Add the first object to the stack of traversed objects. | ||
aStack.push(a); | ||
bStack.push(b); | ||
var size = 0, result = true; | ||
// Recursively compare objects and arrays. | ||
if (className == '[object Array]') { | ||
// Compare array lengths to determine if a deep comparison is necessary. | ||
size = a.length; | ||
result = size == b.length; | ||
if (result) { | ||
// Deep compare the contents, ignoring non-numeric properties. | ||
while (size--) { | ||
if (!(result = eq(a[size], b[size], aStack, bStack))) break; | ||
} | ||
} | ||
} else { | ||
// Objects with different constructors are not equivalent, but `Object`s | ||
// from different frames are. | ||
var aCtor = a.constructor, bCtor = b.constructor; | ||
if (aCtor !== bCtor && !(_.isFunction(aCtor) && (aCtor instanceof aCtor) && | ||
_.isFunction(bCtor) && (bCtor instanceof bCtor))) { | ||
return false; | ||
} | ||
// Deep compare objects. | ||
for (var key in a) { | ||
if (_.has(a, key)) { | ||
// Count the expected number of properties. | ||
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) { | ||
for (key in b) { | ||
if (_.has(b, key) && !(size--)) break; | ||
} | ||
result = !size; | ||
} | ||
} | ||
result = !size; | ||
} | ||
} | ||
// Remove the first object from the stack of traversed objects. | ||
aStack.pop(); | ||
bStack.pop(); | ||
return result; | ||
}; | ||
// Remove the first object from the stack of traversed objects. | ||
aStack.pop(); | ||
bStack.pop(); | ||
return result; | ||
}; | ||
// Perform a deep comparison to check if two objects are equal. | ||
_.isEqual = function(a, b) { | ||
return eq(a, b, [], []); | ||
}; | ||
// Perform a deep comparison to check if two objects are equal. | ||
_.isEqual = function(a, b) { | ||
return eq(a, b, [], []); | ||
}; | ||
// Is a given array, string, or object empty? | ||
// An "empty" object has no enumerable own-properties. | ||
_.isEmpty = function(obj) { | ||
if (obj == null) return true; | ||
if (_.isArray(obj) || _.isString(obj)) return obj.length === 0; | ||
for (var key in obj) if (_.has(obj, key)) return false; | ||
return true; | ||
}; | ||
// Is a given array, string, or object empty? | ||
// An "empty" object has no enumerable own-properties. | ||
_.isEmpty = function(obj) { | ||
if (obj == null) return true; | ||
if (_.isArray(obj) || _.isString(obj)) return obj.length === 0; | ||
for (var key in obj) if (_.has(obj, key)) return false; | ||
return true; | ||
}; | ||
// Is a given value a DOM element? | ||
_.isElement = function(obj) { | ||
return !!(obj && obj.nodeType === 1); | ||
}; | ||
// Is a given value an array? | ||
// Delegates to ECMA5's native Array.isArray | ||
_.isArray = nativeIsArray || function(obj) { | ||
return toString.call(obj) == '[object Array]'; | ||
}; | ||
// Is a given variable an object? | ||
_.isObject = function(obj) { | ||
return obj === Object(obj); | ||
}; | ||
// Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp. | ||
each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'], function(name) { | ||
_['is' + name] = function(obj) { | ||
return toString.call(obj) == '[object ' + name + ']'; | ||
// Is a given value a DOM element? | ||
_.isElement = function(obj) { | ||
return !!(obj && obj.nodeType === 1); | ||
}; | ||
}); | ||
// Define a fallback version of the method in browsers (ahem, IE), where | ||
// there isn't any inspectable "Arguments" type. | ||
if (!_.isArguments(arguments)) { | ||
_.isArguments = function(obj) { | ||
return !!(obj && _.has(obj, 'callee')); | ||
// Is a given value an array? | ||
// Delegates to ECMA5's native Array.isArray | ||
_.isArray = nativeIsArray || function(obj) { | ||
return toString.call(obj) == '[object Array]'; | ||
}; | ||
} | ||
// Optimize `isFunction` if appropriate. | ||
if (typeof (/./) !== 'function') { | ||
_.isFunction = function(obj) { | ||
return typeof obj === 'function'; | ||
// Is a given variable an object? | ||
_.isObject = function(obj) { | ||
return obj === Object(obj); | ||
}; | ||
} | ||
// Is a given object a finite number? | ||
_.isFinite = function(obj) { | ||
return isFinite(obj) && !isNaN(parseFloat(obj)); | ||
}; | ||
// Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp. | ||
each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'], function(name) { | ||
_['is' + name] = function(obj) { | ||
return toString.call(obj) == '[object ' + name + ']'; | ||
}; | ||
}); | ||
// Is the given value `NaN`? (NaN is the only number which does not equal itself). | ||
_.isNaN = function(obj) { | ||
return _.isNumber(obj) && obj != +obj; | ||
}; | ||
// Define a fallback version of the method in browsers (ahem, IE), where | ||
// there isn't any inspectable "Arguments" type. | ||
if (!_.isArguments(arguments)) { | ||
_.isArguments = function(obj) { | ||
return !!(obj && _.has(obj, 'callee')); | ||
}; | ||
} | ||
// Is a given value a boolean? | ||
_.isBoolean = function(obj) { | ||
return obj === true || obj === false || toString.call(obj) == '[object Boolean]'; | ||
}; | ||
// Optimize `isFunction` if appropriate. | ||
if (typeof (/./) !== 'function') { | ||
_.isFunction = function(obj) { | ||
return typeof obj === 'function'; | ||
}; | ||
} | ||
// Is a given value equal to null? | ||
_.isNull = function(obj) { | ||
return obj === null; | ||
}; | ||
// Is a given object a finite number? | ||
_.isFinite = function(obj) { | ||
return isFinite(obj) && !isNaN(parseFloat(obj)); | ||
}; | ||
// Is a given variable undefined? | ||
_.isUndefined = function(obj) { | ||
return obj === void 0; | ||
}; | ||
// Is the given value `NaN`? (NaN is the only number which does not equal itself). | ||
_.isNaN = function(obj) { | ||
return _.isNumber(obj) && obj != +obj; | ||
}; | ||
// Shortcut function for checking if an object has a given property directly | ||
// on itself (in other words, not on a prototype). | ||
_.has = function(obj, key) { | ||
return hasOwnProperty.call(obj, key); | ||
}; | ||
// Is a given value a boolean? | ||
_.isBoolean = function(obj) { | ||
return obj === true || obj === false || toString.call(obj) == '[object Boolean]'; | ||
}; | ||
// Utility Functions | ||
// ----------------- | ||
// Is a given value equal to null? | ||
_.isNull = function(obj) { | ||
return obj === null; | ||
}; | ||
// Run Underscore.js in *noConflict* mode, returning the `_` variable to its | ||
// previous owner. Returns a reference to the Underscore object. | ||
_.noConflict = function() { | ||
root._ = previousUnderscore; | ||
return this; | ||
}; | ||
// Is a given variable undefined? | ||
_.isUndefined = function(obj) { | ||
return obj === void 0; | ||
}; | ||
// Keep the identity function around for default iterators. | ||
_.identity = function(value) { | ||
return value; | ||
}; | ||
// Shortcut function for checking if an object has a given property directly | ||
// on itself (in other words, not on a prototype). | ||
_.has = function(obj, key) { | ||
return hasOwnProperty.call(obj, key); | ||
}; | ||
// Run a function **n** times. | ||
_.times = function(n, iterator, context) { | ||
var accum = Array(n); | ||
for (var i = 0; i < n; i++) accum[i] = iterator.call(context, i); | ||
return accum; | ||
}; | ||
// Utility Functions | ||
// ----------------- | ||
// Return a random integer between min and max (inclusive). | ||
_.random = function(min, max) { | ||
if (max == null) { | ||
max = min; | ||
min = 0; | ||
} | ||
return min + (0 | Math.random() * (max - min + 1)); | ||
}; | ||
// Run Underscore.js in *noConflict* mode, returning the `_` variable to its | ||
// previous owner. Returns a reference to the Underscore object. | ||
_.noConflict = function() { | ||
root._ = previousUnderscore; | ||
return this; | ||
}; | ||
// List of HTML entities for escaping. | ||
var entityMap = { | ||
escape: { | ||
'&': '&', | ||
'<': '<', | ||
'>': '>', | ||
'"': '"', | ||
"'": ''', | ||
'/': '/' | ||
} | ||
}; | ||
entityMap.unescape = _.invert(entityMap.escape); | ||
// Keep the identity function around for default iterators. | ||
_.identity = function(value) { | ||
return value; | ||
}; | ||
// 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') | ||
}; | ||
// Run a function **n** times. | ||
_.times = function(n, iterator, context) { | ||
var accum = Array(n); | ||
for (var i = 0; i < n; i++) accum[i] = iterator.call(context, i); | ||
return accum; | ||
}; | ||
// 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]; | ||
}); | ||
// Return a random integer between min and max (inclusive). | ||
_.random = function(min, max) { | ||
if (max == null) { | ||
max = min; | ||
min = 0; | ||
} | ||
return min + Math.floor(Math.random() * (max - min + 1)); | ||
}; | ||
}); | ||
// If the value of the named property is a function then invoke it; | ||
// otherwise, return it. | ||
_.result = function(object, property) { | ||
if (object == null) return null; | ||
var value = object[property]; | ||
return _.isFunction(value) ? value.call(object) : value; | ||
}; | ||
// List of HTML entities for escaping. | ||
var entityMap = { | ||
escape: { | ||
'&': '&', | ||
'<': '<', | ||
'>': '>', | ||
'"': '"', | ||
"'": ''', | ||
'/': '/' | ||
} | ||
}; | ||
entityMap.unescape = _.invert(entityMap.escape); | ||
// 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)); | ||
}; | ||
// 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') | ||
}; | ||
// 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]; | ||
}); | ||
}; | ||
}); | ||
}; | ||
// Generate a unique integer id (unique within the entire client session). | ||
// Useful for temporary DOM ids. | ||
var idCounter = 0; | ||
_.uniqueId = function(prefix) { | ||
var id = '' + ++idCounter; | ||
return prefix ? prefix + id : id; | ||
}; | ||
// If the value of the named property is a function then invoke it; | ||
// otherwise, return it. | ||
_.result = function(object, property) { | ||
if (object == null) return null; | ||
var value = object[property]; | ||
return _.isFunction(value) ? value.call(object) : value; | ||
}; | ||
// By default, Underscore uses ERB-style template delimiters, change the | ||
// following template settings to use alternative delimiters. | ||
_.templateSettings = { | ||
evaluate : /<%([\s\S]+?)%>/g, | ||
interpolate : /<%=([\s\S]+?)%>/g, | ||
escape : /<%-([\s\S]+?)%>/g | ||
}; | ||
// 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)); | ||
}; | ||
}); | ||
}; | ||
// When customizing `templateSettings`, if you don't want to define an | ||
// interpolation, evaluation or escaping regex, we need one that is | ||
// guaranteed not to match. | ||
var noMatch = /(.)^/; | ||
// Generate a unique integer id (unique within the entire client session). | ||
// Useful for temporary DOM ids. | ||
var idCounter = 0; | ||
_.uniqueId = function(prefix) { | ||
var id = ++idCounter + ''; | ||
return prefix ? prefix + id : id; | ||
}; | ||
// Certain characters need to be escaped so that they can be put into a | ||
// string literal. | ||
var escapes = { | ||
"'": "'", | ||
'\\': '\\', | ||
'\r': 'r', | ||
'\n': 'n', | ||
'\t': 't', | ||
'\u2028': 'u2028', | ||
'\u2029': 'u2029' | ||
}; | ||
// By default, Underscore uses ERB-style template delimiters, change the | ||
// following template settings to use alternative delimiters. | ||
_.templateSettings = { | ||
evaluate : /<%([\s\S]+?)%>/g, | ||
interpolate : /<%=([\s\S]+?)%>/g, | ||
escape : /<%-([\s\S]+?)%>/g | ||
}; | ||
var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g; | ||
// When customizing `templateSettings`, if you don't want to define an | ||
// interpolation, evaluation or escaping regex, we need one that is | ||
// guaranteed not to match. | ||
var noMatch = /(.)^/; | ||
// JavaScript micro-templating, similar to John Resig's implementation. | ||
// Underscore templating handles arbitrary delimiters, preserves whitespace, | ||
// and correctly escapes quotes within interpolated code. | ||
_.template = function(text, data, settings) { | ||
settings = _.defaults({}, settings, _.templateSettings); | ||
// Certain characters need to be escaped so that they can be put into a | ||
// string literal. | ||
var escapes = { | ||
"'": "'", | ||
'\\': '\\', | ||
'\r': 'r', | ||
'\n': 'n', | ||
'\t': 't', | ||
'\u2028': 'u2028', | ||
'\u2029': 'u2029' | ||
}; | ||
// Combine delimiters into one regular expression via alternation. | ||
var matcher = new RegExp([ | ||
(settings.escape || noMatch).source, | ||
(settings.interpolate || noMatch).source, | ||
(settings.evaluate || noMatch).source | ||
].join('|') + '|$', 'g'); | ||
var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g; | ||
// Compile the template source, escaping string literals appropriately. | ||
var index = 0; | ||
var source = "__p+='"; | ||
text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { | ||
source += text.slice(index, offset) | ||
.replace(escaper, function(match) { return '\\' + escapes[match]; }); | ||
// JavaScript micro-templating, similar to John Resig's implementation. | ||
// Underscore templating handles arbitrary delimiters, preserves whitespace, | ||
// and correctly escapes quotes within interpolated code. | ||
_.template = function(text, data, settings) { | ||
var render; | ||
settings = _.defaults({}, settings, _.templateSettings); | ||
if (escape) { | ||
source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'"; | ||
} | ||
if (interpolate) { | ||
source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'"; | ||
} | ||
if (evaluate) { | ||
source += "';\n" + evaluate + "\n__p+='"; | ||
} | ||
index = offset + match.length; | ||
return match; | ||
}); | ||
source += "';\n"; | ||
// Combine delimiters into one regular expression via alternation. | ||
var matcher = new RegExp([ | ||
(settings.escape || noMatch).source, | ||
(settings.interpolate || noMatch).source, | ||
(settings.evaluate || noMatch).source | ||
].join('|') + '|$', 'g'); | ||
// If a variable is not specified, place data values in local scope. | ||
if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; | ||
// Compile the template source, escaping string literals appropriately. | ||
var index = 0; | ||
var source = "__p+='"; | ||
text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { | ||
source += text.slice(index, offset) | ||
.replace(escaper, function(match) { return '\\' + escapes[match]; }); | ||
source = "var __t,__p='',__j=Array.prototype.join," + | ||
"print=function(){__p+=__j.call(arguments,'');};\n" + | ||
source + "return __p;\n"; | ||
if (escape) { | ||
source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'"; | ||
} | ||
if (interpolate) { | ||
source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'"; | ||
} | ||
if (evaluate) { | ||
source += "';\n" + evaluate + "\n__p+='"; | ||
} | ||
index = offset + match.length; | ||
return match; | ||
}); | ||
source += "';\n"; | ||
try { | ||
var render = new Function(settings.variable || 'obj', '_', source); | ||
} catch (e) { | ||
e.source = source; | ||
throw e; | ||
} | ||
// If a variable is not specified, place data values in local scope. | ||
if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; | ||
if (data) return render(data, _); | ||
var template = function(data) { | ||
return render.call(this, data, _); | ||
}; | ||
source = "var __t,__p='',__j=Array.prototype.join," + | ||
"print=function(){__p+=__j.call(arguments,'');};\n" + | ||
source + "return __p;\n"; | ||
// Provide the compiled function source as a convenience for precompilation. | ||
template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}'; | ||
try { | ||
render = new Function(settings.variable || 'obj', '_', source); | ||
} catch (e) { | ||
e.source = source; | ||
throw e; | ||
} | ||
return template; | ||
}; | ||
if (data) return render(data, _); | ||
var template = function(data) { | ||
return render.call(this, data, _); | ||
}; | ||
// Add a "chain" function, which will delegate to the wrapper. | ||
_.chain = function(obj) { | ||
return _(obj).chain(); | ||
}; | ||
// Provide the compiled function source as a convenience for precompilation. | ||
template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}'; | ||
// OOP | ||
// --------------- | ||
// If Underscore is called as a function, it returns a wrapped object that | ||
// can be used OO-style. This wrapper holds altered versions of all the | ||
// underscore functions. Wrapped objects may be chained. | ||
return template; | ||
}; | ||
// Helper function to continue chaining intermediate results. | ||
var result = function(obj) { | ||
return this._chain ? _(obj).chain() : obj; | ||
}; | ||
// Add a "chain" function, which will delegate to the wrapper. | ||
_.chain = function(obj) { | ||
return _(obj).chain(); | ||
}; | ||
// Add all of the Underscore functions to the wrapper object. | ||
_.mixin(_); | ||
// OOP | ||
// --------------- | ||
// If Underscore is called as a function, it returns a wrapped object that | ||
// can be used OO-style. This wrapper holds altered versions of all the | ||
// underscore functions. Wrapped objects may be chained. | ||
// Add all mutator Array functions to the wrapper. | ||
each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { | ||
var method = ArrayProto[name]; | ||
_.prototype[name] = function() { | ||
var obj = this._wrapped; | ||
method.apply(obj, arguments); | ||
if ((name == 'shift' || name == 'splice') && obj.length === 0) delete obj[0]; | ||
return result.call(this, obj); | ||
// Helper function to continue chaining intermediate results. | ||
var result = function(obj) { | ||
return this._chain ? _(obj).chain() : obj; | ||
}; | ||
}); | ||
// Add all accessor Array functions to the wrapper. | ||
each(['concat', 'join', 'slice'], function(name) { | ||
var method = ArrayProto[name]; | ||
_.prototype[name] = function() { | ||
return result.call(this, method.apply(this._wrapped, arguments)); | ||
}; | ||
}); | ||
// Add all of the Underscore functions to the wrapper object. | ||
_.mixin(_); | ||
_.extend(_.prototype, { | ||
// Add all mutator Array functions to the wrapper. | ||
each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { | ||
var method = ArrayProto[name]; | ||
_.prototype[name] = function() { | ||
var obj = this._wrapped; | ||
method.apply(obj, arguments); | ||
if ((name == 'shift' || name == 'splice') && obj.length === 0) delete obj[0]; | ||
return result.call(this, obj); | ||
}; | ||
}); | ||
// Start chaining a wrapped Underscore object. | ||
chain: function() { | ||
this._chain = true; | ||
return this; | ||
}, | ||
// Add all accessor Array functions to the wrapper. | ||
each(['concat', 'join', 'slice'], function(name) { | ||
var method = ArrayProto[name]; | ||
_.prototype[name] = function() { | ||
return result.call(this, method.apply(this._wrapped, arguments)); | ||
}; | ||
}); | ||
// Extracts the result from a wrapped and chained object. | ||
value: function() { | ||
return this._wrapped; | ||
} | ||
_.extend(_.prototype, { | ||
}); | ||
// Start chaining a wrapped Underscore object. | ||
chain: function() { | ||
this._chain = true; | ||
return this; | ||
}, | ||
// Extracts the result from a wrapped and chained object. | ||
value: function() { | ||
return this._wrapped; | ||
} | ||
}); | ||
}).call(this); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
927114
18837
608
35