diff --git a/index.html b/index.html index db3ee81b0..11f706290 100644 --- a/index.html +++ b/index.html @@ -33,9 +33,14 @@ h1, h2, h3, h4, h5, h6 { margin-top: 40px; } - b.method_name { + b.header { font-size: 18px; } + span.alias { + font-size: 14px; + font-style: italic; + margin-left: 20px; + } table, tr, td { margin: 0; padding: 0; } @@ -76,7 +81,7 @@

- Underscore provides 42-odd functions that support both the usual + Underscore provides 44-odd functions that support both the usual functional suspects: map, select, invoke — as well as more specialized helpers: function binding, javascript templating, deep equality testing, and so on. It delegates to built-in @@ -102,11 +107,11 @@

- + - +
Development Version (0.1.1)Development Version (0.2.0) 16kb, Uncompressed with Comments
Production Version (0.1.1)Production Version (0.2.0) 4kb, Packed and Gzipped
@@ -118,7 +123,7 @@ Collections
each, map, - inject, detect, select, reject, all, + reduce, detect, select, reject, all, any, include, invoke, pluck, max, min, sortBy, sortedIndex, toArray, size @@ -129,14 +134,15 @@
first, last, compact, flatten, without, uniq, - intersect, zip, indexOf + intersect, zip, indexOf, + lastIndexOf

Functions
bind, bindAll, delay, - defer, wrap + defer, wrap, compose

@@ -160,7 +166,8 @@

Collection Functions (Arrays or Objects)

- each_.each(list, iterator, [context]) + each_.each(list, iterator, [context]) + Alias: forEach
Iterates over a list of elements, yielding each in turn to an iterator function. The iterator is bound to the context object, if one is @@ -174,7 +181,7 @@ _.each([1, 2, 3], function(num){ alert(num); }); => alerts each number in turn...

- map_.map(list, iterator, [context]) + map_.map(list, iterator, [context])
Produces a new array of values by mapping each value in list through a transformation function (iterator). If the native @@ -184,21 +191,22 @@ _.each([1, 2, 3], function(num){ alert(num); }); _.map([1, 2, 3], function(num){ return num * 3 }); => [3, 6, 9] -

- inject_.inject(list, memo, iterator, [context]) +

+ reduce_.reduce(list, memo, iterator, [context]) + Alias: inject
- Also known as reduce and foldl, inject reduces a + Also known as inject and foldl, reduce boils down a list of values into a single value. Memo is the initial state of the reduction, and each successive step of it should be returned by iterator.

-var sum = _.inject([1, 2, 3], 0, function(memo, num){ return memo + num });
+var sum = _.reduce([1, 2, 3], 0, function(memo, num){ return memo + num });
 => 6
 

- detect_.detect(list, iterator, [context]) + detect_.detect(list, iterator, [context])
Looks through each value in the list, returning the first one that passes a truth test (iterator). The function returns as @@ -211,7 +219,8 @@ var even = _.detect([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });

- select_.select(list, iterator, [context]) + select_.select(list, iterator, [context]) + Alias: filter
Looks through each value in the list, returning an array of all the values that pass a truth test (iterator). Delegates to the @@ -223,7 +232,7 @@ var evens = _.select([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });

- reject_.reject(list, iterator, [context]) + reject_.reject(list, iterator, [context])
Returns the values in list without the elements that the truth test (iterator) passes. The opposite of select. @@ -234,7 +243,8 @@ var odds = _.reject([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });

- all_.all(list, [iterator], [context]) + all_.all(list, [iterator], [context]) + Alias: every
Returns true if all of the values in the list pass the iterator truth test. If an iterator is not provided, the truthy value of @@ -247,7 +257,8 @@ _.all([true, 1, null, 'yes']);

- any_.any(list, [iterator], [context]) + any_.any(list, [iterator], [context]) + Alias: some
Returns true if any of the values in the list pass the iterator truth test. Short-circuits and stops traversing the list @@ -260,7 +271,7 @@ _.any([null, 0, 'yes', false]);

- include_.include(list, value) + include_.include(list, value)
Returns true if the value is present in the list, using === to test equality. Uses indexOf internally, if list @@ -272,7 +283,7 @@ _.include([1, 2, 3], 3);

- invoke_.invoke(list, methodName, [*arguments]) + invoke_.invoke(list, methodName, [*arguments])
Calls the method named by methodName on each value in the list. Any extra arguments passed to invoke will be forwarded on to the @@ -284,7 +295,7 @@ _.invoke([[5, 1, 7], [3, 2, 1]], 'sort');

- pluck_.pluck(list, propertyName) + pluck_.pluck(list, propertyName)
An optimized version of what is perhaps the most common use-case for map: returning a list of property values. @@ -296,7 +307,7 @@ _.pluck(stooges, 'name');

- max_.max(list, [iterator], [context]) + max_.max(list, [iterator], [context])
Returns the maximum value in list. If iterator is passed, it will be used on each value to generate the criterion by which the @@ -309,7 +320,7 @@ _.max(stooges, function(stooge){ return stooge.age; });

- min_.min(list, [iterator], [context]) + min_.min(list, [iterator], [context])
Returns the minimum value in list. If iterator is passed, it will be used on each value to generate the criterion by which the @@ -322,7 +333,7 @@ _.min(numbers);

- sortBy_.sortBy(list, iterator, [context]) + sortBy_.sortBy(list, iterator, [context])
Returns a sorted list, ranked by the results of running each value through iterator. @@ -333,7 +344,7 @@ _.sortBy([1, 2, 3, 4, 5, 6], function(num){ return Math.sin(num); });

- sortedIndex_.sortedIndex(list, value, [iterator]) + sortedIndex_.sortedIndex(list, value, [iterator])
Uses a binary search to determine the index at which the value should be inserted into the list in order to maintain the list's @@ -346,7 +357,7 @@ _.sortedIndex([10, 20, 30, 40, 50], 35);

- toArray_.toArray(list) + toArray_.toArray(list)
Converts the list (anything that can be iterated over), into a real Array. Useful for transmuting the arguments object. @@ -357,7 +368,7 @@ _.sortedIndex([10, 20, 30, 40, 50], 35);

- size_.size(list) + size_.size(list)
Return the number of values in the list.

@@ -369,7 +380,7 @@ _.size({one : 1, two : 2, three : 3});

Array Functions

- first_.first(array) + first_.first(array)
Convenience to return the first element of an array (identical to array[0]).

@@ -379,7 +390,7 @@ _.first([3, 2, 1]);

- last_.last(array) + last_.last(array)
Returns the last element of an array.

@@ -389,7 +400,7 @@ _.last([3, 2, 1]);

- compact_.compact(array) + compact_.compact(array)
Returns a copy of the array with all falsy values removed. In Javascript, false, null, 0, "", @@ -401,7 +412,7 @@ _.compact([0, 1, false, 2, '', 3]);

- flatten_.flatten(array) + flatten_.flatten(array)
Flattens a nested array (the nesting can be to any depth).

@@ -411,7 +422,7 @@ _.flatten([1, [2], [3, [[[4]]]]]);

- without_.without(array, [*values]) + without_.without(array, [*values])
Returns a copy of the array with all instances of the values removed. === is used for the equality test. @@ -422,7 +433,7 @@ _.without([1, 2, 1, 0, 3, 1, 4], 0, 1);

- uniq_.uniq(array, [isSorted]) + uniq_.uniq(array, [isSorted])
Produces a duplicate-free version of the array, using === to test object equality. If you know in advance that the array is sorted, @@ -434,7 +445,7 @@ _.uniq([1, 2, 1, 3, 1, 4]);

- intersect_.intersect(*arrays) + intersect_.intersect(*arrays)
Computes the list of values that are the intersection of all the arrays. Each value in the result is present in each of the arrays. @@ -445,7 +456,7 @@ _.intersect([1, 2, 3], [101, 2, 1, 10], [2, 1]);

- zip_.zip(*arrays) + zip_.zip(*arrays)
Merges together the values of each of the arrays with the values at the corresponding position. Useful when you have separate @@ -457,7 +468,7 @@ _.zip(['moe', 'larry', 'curly'], [30, 40, 50], [true, false, false]);

- indexOf_.indexOf(array, value) + indexOf_.indexOf(array, value)
Returns the index at which value can be found in the array, or -1 if value is not present in the array. Uses the native @@ -466,12 +477,24 @@ _.zip(['moe', 'larry', 'curly'], [30, 40, 50], [true, false, false]);

 _.indexOf([1, 2, 3], 2);
 => 1
+
+ +

+ lastIndexOf_.lastIndexOf(array, value) +
+ Returns the index of the last occurrence of value in the array, + or -1 if value is not present. Uses the native lastIndexOf + function if possible. +

+
+_.lastIndexOf([1, 2, 3, 1, 2, 3], 2);
+=> 4
 

Function (uh, ahem) Functions

- bind_.bind(function, context, [*arguments]) + bind_.bind(function, context, [*arguments])
Bind a function to a context object, meaning that whenever the function is called, the value of this will be the context. @@ -486,7 +509,7 @@ func();

- bindAll_.bindAll(*methodNames, context) + bindAll_.bindAll(*methodNames, context)
Binds a number of methods on the context object, specified by methodNames, to be run in the context of that object whenever they @@ -506,7 +529,7 @@ jQuery('#underscore_button').bind('click', buttonView.onClick);

- delay_.delay(function, wait, [*arguments]) + delay_.delay(function, wait, [*arguments])
Much like setTimeout, invokes function after wait milliseconds. If you pass the optional arguments, they will be @@ -519,7 +542,7 @@ _.delay(log, 1000, 'logged later');

- defer_.defer(function) + defer_.defer(function)
Defers invoking the function until the current call stack has cleared, similar to using setTimeout with a delay of 0. Useful for performing @@ -532,7 +555,7 @@ _.defer(function(){ alert('deferred'); });

- wrap_.wrap(function, wrapper) + wrap_.wrap(function, wrapper)
Wraps the first function inside of the wrapper function, passing it as the first argument. This allows the wrapper to @@ -545,13 +568,29 @@ hello = _.wrap(hello, function(func) { return "before, " + func("moe") + ", after"; }); hello(); -=> before, hello: moe, after +=> 'before, hello: moe, after' + + +

+ compose_.compose(*functions) +
+ Returns the composition of a list of functions, where each function + consumes the return value of the function that follows. In math terms, + composing the functions f(), g(), and h() produces + f(g(h())). +

+
+var greet    = function(name){ return "hi: " + name; };
+var exclaim  = function(statement){ return statement + "!"; };
+var welcome = _.compose(greet, exclaim);
+welcome('moe');
+=> 'hi: moe!'
 

Object Functions

- keys_.keys(object) + keys_.keys(object)
Retrieve all the names of the object's properties.

@@ -561,7 +600,7 @@ _.keys({one : 1, two : 2, three : 3});

- values_.values(object) + values_.values(object)
Return all of the values of the object's properties.

@@ -571,7 +610,7 @@ _.values({one : 1, two : 2, three : 3});

- extend_.extend(destination, source) + extend_.extend(destination, source)
Copy all of the properties in the source object over to the destination object. @@ -582,7 +621,7 @@ _.extend({name : 'moe'}, {age : 50});

- clone_.clone(object) + clone_.clone(object)
Create a shallow-copied clone of the object. Any nested objects or arrays will be copied by reference, not duplicated. @@ -593,7 +632,7 @@ _.clone({name : 'moe'});

- isEqual_.isEqual(object, other) + isEqual_.isEqual(object, other)
Performs an optimized deep comparison between the two objects, to determine if they should be considered equal. @@ -608,7 +647,7 @@ _.isEqual(moe, clone);

- isElement_.isElement(object) + isElement_.isElement(object)
Returns true if object is a DOM element.

@@ -618,7 +657,7 @@ _.isElement(jQuery('body')[0]);

- isArray_.isArray(object) + isArray_.isArray(object)
Returns true if object is an Array.

@@ -630,7 +669,7 @@ _.isArray([1,2,3]);

- isFunction_.isFunction(object) + isFunction_.isFunction(object)
Returns true if object is a Function.

@@ -640,7 +679,7 @@ _.isFunction(alert);

- isUndefined_.isUndefined(variable) + isUndefined_.isUndefined(variable)
Returns true if variable is undefined.

@@ -652,7 +691,7 @@ _.isUndefined(window.missingVariable);

Utility Functions

- noConflict_.noConflict() + noConflict_.noConflict()
Give control of the "_" variable back to its previous owner. Returns a reference to the Underscore object. @@ -661,7 +700,7 @@ _.isUndefined(window.missingVariable); var underscore = _.noConflict();

- uniqueId_.uniqueId([prefix]) + uniqueId_.uniqueId([prefix])
Generate a globally-unique id for client-side models or DOM elements that need one. If prefix is passed, the id will be appended to it. @@ -672,7 +711,7 @@ _.uniqueId('contact_');

- template_.template(templateString, [context]) + template_.template(templateString, [context])
Compiles Javascript templates into functions that can be evaluated for rendering. Useful for rendering complicated bits of HTML from JSON @@ -694,6 +733,26 @@ _.template(list, {people : ['moe', 'curly', 'larry']}); => "<li>moe</li><li>curly</li><li>larry</li>" +

Change Log

+ +

+ 0.2.0
+ Added compose and lastIndexOf, renamed inject to + reduce, added aliases for inject, filter, + every, some, and forEach. +

+ +

+ 0.1.1
+ Added noConflict, so that the "Underscore" object can be assigned to + other variables. +

+ +

+ 0.1.0
+ Initial release of Underscore.js. +

+

A DocumentCloud Project diff --git a/package.json b/package.json new file mode 100644 index 000000000..0f55aaa87 --- /dev/null +++ b/package.json @@ -0,0 +1,12 @@ +// ServerJS package specification. +{ + "name": "underscore", + "description": "Functional programming aid for Javascript. Works well with jQuery.", + "url": "http://documentcloud.github.com/underscore/", + "keywords": ["util", "functional", "server", "client", "browser"], + "author": "Jeremy Ashkenas ", + "maintainer": "Kris Kowal ", + "contributors": [], + "dependencies": [], + "lib", "." +} diff --git a/test/arrays.js b/test/arrays.js index e59397996..dd54c7266 100644 --- a/test/arrays.js +++ b/test/arrays.js @@ -49,4 +49,11 @@ $(document).ready(function() { equals(_.indexOf(numbers, 2), 1, 'can compute indexOf, even without the native function'); }); + test("arrays: lastIndexOf", function() { + var numbers = [1, 0, 1, 0, 0, 1, 0, 0, 0]; + numbers.lastIndexOf = null; + equals(_.lastIndexOf(numbers, 1), 5, 'can compute lastIndexOf, even without the native function'); + equals(_.lastIndexOf(numbers, 0), 8, 'lastIndexOf the other element'); + }); + }); diff --git a/test/collections.js b/test/collections.js index bed210ac2..f97e6e520 100644 --- a/test/collections.js +++ b/test/collections.js @@ -14,6 +14,10 @@ $(document).ready(function() { var answers = []; _.each([1, 2, 3], function(num) { answers.push(num * this.multiplier);}, {multiplier : 5}); equals(answers.join(', '), '5, 10, 15', 'context object property accessed'); + + answers = []; + _.forEach([1, 2, 3], function(num){ answers.push(num); }); + equals(answers.join(', '), '1, 2, 3', 'aliased as "forEach"'); }); test('collections: map', function() { @@ -24,9 +28,12 @@ $(document).ready(function() { equals(tripled.join(', '), '3, 6, 9', 'tripled numbers with context'); }); - test('collections: inject', function() { - var sum = _.inject([1,2,3], 0, function(sum, num){ return sum + num; }); + test('collections: reduce', function() { + var sum = _.reduce([1, 2, 3], 0, function(sum, num){ return sum + num; }); equals(sum, 6, 'can sum up an array'); + + sum = _.inject([1, 2, 3], 0, function(sum, num){ return sum + num; }); + equals(sum, 6, 'aliased as "inject"'); }); test('collections: detect', function() { @@ -35,12 +42,15 @@ $(document).ready(function() { }); test('collections: select', function() { - var evens = _.select([1,2,3,4,5,6], function(num){ return num % 2 == 0; }); + var evens = _.select([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; }); equals(evens.join(', '), '2, 4, 6', 'selected each even number'); + + evens = _.filter([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; }); + equals(evens.join(', '), '2, 4, 6', 'aliased as "filter"'); }); test('collections: reject', function() { - var odds = _.reject([1,2,3,4,5,6], function(num){ return num % 2 == 0; }); + var odds = _.reject([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; }); equals(odds.join(', '), '1, 3, 5', 'rejected each even number'); }); @@ -50,6 +60,7 @@ $(document).ready(function() { ok(!_.all([true, false, true]), 'one false value'); ok(_.all([0, 10, 28], function(num){ return num % 2 == 0; }), 'even numbers'); ok(!_.all([0, 11, 28], function(num){ return num % 2 == 0; }), 'an odd number'); + ok(_.every([true, true, true]), 'aliased as "every"'); }); test('collections: any', function() { @@ -58,6 +69,7 @@ $(document).ready(function() { ok(_.any([false, false, true]), 'one true value'); ok(!_.any([1, 11, 29], function(num){ return num % 2 == 0; }), 'all odd numbers'); ok(_.any([1, 10, 29], function(num){ return num % 2 == 0; }), 'an even number'); + ok(_.some([false, false, true]), 'aliased as "some"'); }); test('collections: include', function() { diff --git a/test/functions.js b/test/functions.js index 039f7a746..9e401e4c7 100644 --- a/test/functions.js +++ b/test/functions.js @@ -48,4 +48,14 @@ $(document).ready(function() { equals(backwards('moe'), 'hi: moe eom', 'wrapped the saluation function'); }); + test("functions: compose", function() { + var greet = function(name){ return "hi: " + name; }; + var exclaim = function(sentence){ return sentence + '!'; }; + var composed = _.compose(exclaim, greet); + equals(composed('moe'), 'hi: moe!', 'can compose a function that takes another'); + + composed = _.compose(greet, exclaim); + equals(composed('moe'), 'hi: moe!', 'in this case, the functions are also commutative'); + }); + }); diff --git a/test/utility.js b/test/utility.js index 9a8eb516f..af9680bb5 100644 --- a/test/utility.js +++ b/test/utility.js @@ -5,6 +5,8 @@ $(document).ready(function() { test("utility: noConflict", function() { var underscore = _.noConflict(); ok(underscore.isUndefined(_), "The '_' variable has been returned to its previous state."); + var intersection = underscore.intersect([-1, 0, 1, 2], [1, 2, 3, 4]); + equals(intersection.join(', '), '1, 2', 'but the intersection function still works'); window._ = underscore; }); diff --git a/underscore-min.js b/underscore-min.js index 096ff0aac..98d0437b5 100644 --- a/underscore-min.js +++ b/underscore-min.js @@ -1 +1 @@ -window.Underscore={VERSION:"0.1.1",PREVIOUS_UNDERSCORE:window._,each:function(c,f,a){var g=0;try{if(c.forEach){c.forEach(f,a)}else{if(c.length){for(var d=0;d=a.computed){a={value:g,computed:f}}});return a.value},min:function(d,c,b){if(!c&&_.isArray(d)){return Math.min.apply(Math,d)}var a;_.each(d,function(g,e){var f=c?c.call(b,g,e):g;if(a==null||fd?1:0}),"value")},sortedIndex:function(f,e,c){c=c||function(g){return g};var a=0,d=f.length;while(a>1;c(f[b])=0})})},zip:function(){var a=_.toArray(arguments);var d=_.max(_.pluck(a,"length"));var c=new Array(d);for(var b=0;b)[^\t]*)'/g,"$1\r").replace(/\t=(.*?)%>/g,"',$1,'").split("\t").join("');").split("%>").join("p.push('").split("\r").join("\\'")+"');}return p.join('');");return b?a(b):a}};window._=Underscore; \ No newline at end of file +(function(){var a=(typeof window!="undefined")?window:exports;var c=a._;var b=a._={};b.VERSION="0.2.0";b.each=function(g,j,d){var k=0;try{if(g.forEach){g.forEach(j,d)}else{if(g.length){for(var h=0;h=d.computed){d={value:k,computed:j}}});return d.value};b.min=function(g,f,e){if(!f&&b.isArray(g)){return Math.min.apply(Math,g)}var d;b.each(g,function(k,h){var j=f?f.call(e,k,h):k;if(d==null||jg?1:0}),"value")};b.sortedIndex=function(j,h,f){f=f||function(k){return k};var d=0,g=j.length;while(d>1;f(j[e])=0})})};b.zip=function(){var d=b.toArray(arguments);var g=b.max(b.pluck(d,"length"));var f=new Array(g);for(var e=0;e=0;i--){if(e[i]===d){return i}}return -1};b.bind=function(f,e){if(!e){return f}var d=b.toArray(arguments).slice(2);return function(){var g=d.concat(b.toArray(arguments));return f.apply(e,g)}};b.bindAll=function(){var d=b.toArray(arguments);var e=d.pop();b.each(d,function(f){e[f]=b.bind(e[f],e)})};b.delay=function(e,f){var d=b.toArray(arguments).slice(2);return setTimeout(function(){return e.apply(e,d)},f)};b.defer=function(d){return b.delay.apply(b,[d,1].concat(b.toArray(arguments).slice(1)))};b.wrap=function(d,e){return function(){var f=[d].concat(b.toArray(arguments));return e.apply(e,f)}};b.compose=function(){var d=b.toArray(arguments);return function(){for(var e=d.length-1;e>=0;e--){arguments=[d[e].apply(this,arguments)]}return arguments[0]}};b.keys=function(d){return b.pluck(d,"key")};b.values=function(d){return b.pluck(d,"value")};b.extend=function(d,f){for(var e in f){d[e]=f[e]}return d};b.clone=function(d){return b.extend({},d)};b.isEqual=function(e,d){if(e===d){return true}var h=typeof(e),k=typeof(d);if(h!=k){return false}if(e==d){return true}if(e.isEqual){return e.isEqual(d)}if(h!=="object"){return false}var f=b.keys(e),j=b.keys(d);if(f.length!=j.length){return false}for(var g in e){if(!b.isEqual(e[g],d[g])){return false}}return true};b.isElement=function(d){return !!(d&&d.nodeType==1)};b.isArray=function(d){return Object.prototype.toString.call(d)=="[object Array]"};b.isFunction=function(d){return typeof d=="function"};b.isUndefined=function(d){return typeof d=="undefined"};b.noConflict=function(){a._=c;return this};b.uniqueId=function(d){var e=this._idCounter=(this._idCounter||0)+1;return d?d+e:e};b.template=function(f,e){var d=new Function("obj","var p=[],print=function(){p.push.apply(p,arguments);};with(obj){p.push('"+f.replace(/[\r\t\n]/g," ").split("<%").join("\t").replace(/((^|%>)[^\t]*)'/g,"$1\r").replace(/\t=(.*?)%>/g,"',$1,'").split("\t").join("');").split("%>").join("p.push('").split("\r").join("\\'")+"');}return p.join('');");return e?d(e):d};b.forEach=b.each;b.inject=b.reduce;b.filter=b.select;b.every=b.all;b.some=b.any;if(!b.isUndefined(exports)){exports=b}})(); \ No newline at end of file diff --git a/underscore.js b/underscore.js index 0091abd90..7b48ab101 100644 --- a/underscore.js +++ b/underscore.js @@ -5,17 +5,22 @@ // Oliver Steele's Functional, And John Resig's Micro-Templating. // For all details and documentation: // http://documentcloud.github.com/underscore/ -window.Underscore = { + +(function() { - VERSION : '0.1.1', + var root = (typeof window != 'undefined') ? window : exports; - PREVIOUS_UNDERSCORE : window._, + var previousUnderscore = root._; + var _ = root._ = {}; + + _.VERSION = '0.2.0'; + /*------------------------ Collection Functions: ---------------------------*/ // The cornerstone, an each implementation. // Handles objects implementing forEach, each, arrays, and raw objects. - each : function(obj, iterator, context) { + _.each = function(obj, iterator, context) { var index = 0; try { if (obj.forEach) { @@ -37,30 +42,30 @@ window.Underscore = { if (e != '__break__') throw e; } return obj; - }, + }; // Return the results of applying the iterator to each element. Use Javascript // 1.6's version of map, if possible. - map : function(obj, iterator, context) { + _.map = function(obj, iterator, context) { if (obj && obj.map) return obj.map(iterator, context); var results = []; _.each(obj, function(value, index) { results.push(iterator.call(context, value, index)); }); return results; - }, + }; - // Inject builds up a single result from a list of values. Also known as - // reduce, or foldl. - inject : function(obj, memo, iterator, context) { + // Reduce builds up a single result from a list of values. Also known as + // inject, or foldl. + _.reduce = function(obj, memo, iterator, context) { _.each(obj, function(value, index) { memo = iterator.call(context, memo, value, index); }); return memo; - }, + }; // Return the first value which passes a truth test. - detect : function(obj, iterator, context) { + _.detect = function(obj, iterator, context) { var result; _.each(obj, function(value, index) { if (iterator.call(context, value, index)) { @@ -69,31 +74,31 @@ window.Underscore = { } }); return result; - }, + }; // Return all the elements that pass a truth test. Use Javascript 1.6's // filter(), if it exists. - select : function(obj, iterator, context) { + _.select = function(obj, iterator, context) { if (obj.filter) return obj.filter(iterator, context); var results = []; _.each(obj, function(value, index) { if (iterator.call(context, value, index)) results.push(value); }); return results; - }, + }; // Return all the elements for which a truth test fails. - reject : function(obj, iterator, context) { + _.reject = function(obj, iterator, context) { var results = []; _.each(obj, function(value, index) { if (!iterator.call(context, value, index)) results.push(value); }); return results; - }, + }; // Determine whether all of the elements match a truth test. Delegate to // Javascript 1.6's every(), if it is present. - all : function(obj, iterator, context) { + _.all = function(obj, iterator, context) { iterator = iterator || function(v){ return v; }; if (obj.every) return obj.every(iterator, context); var result = true; @@ -102,11 +107,11 @@ window.Underscore = { if (!result) throw '__break__'; }); return result; - }, + }; // Determine if at least one element in the object matches a truth test. Use // Javascript 1.6's some(), if it exists. - any : function(obj, iterator, context) { + _.any = function(obj, iterator, context) { iterator = iterator || function(v) { return v; }; if (obj.some) return obj.some(iterator, context); var result = false; @@ -114,11 +119,11 @@ window.Underscore = { if (result = !!iterator.call(context, value, index)) throw '__break__'; }); return result; - }, + }; // Determine if a given value is included in the array or object, // based on '==='. - include : function(obj, target) { + _.include = function(obj, target) { if (_.isArray(obj)) return _.indexOf(obj, target) != -1; var found = false; _.each(obj, function(pair) { @@ -128,25 +133,25 @@ window.Underscore = { } }); return found; - }, + }; // Invoke a method with arguments on every item in a collection. - invoke : function(obj, method) { + _.invoke = function(obj, method) { var args = _.toArray(arguments).slice(2); return _.map(obj, function(value) { return (method ? value[method] : value).apply(value, args); }); - }, + }; // Optimized version of a common use case of map: fetching a property. - pluck : function(obj, key) { + _.pluck = function(obj, key) { var results = []; _.each(obj, function(value){ results.push(value[key]); }); return results; - }, + }; // Return the maximum item or (item-based computation). - max : function(obj, iterator, context) { + _.max = function(obj, iterator, context) { if (!iterator && _.isArray(obj)) return Math.max.apply(Math, obj); var result; _.each(obj, function(value, index) { @@ -154,10 +159,10 @@ window.Underscore = { if (result == null || computed >= result.computed) result = {value : value, computed : computed}; }); return result.value; - }, + }; // Return the minimum element (or element-based computation). - min : function(obj, iterator, context) { + _.min = function(obj, iterator, context) { if (!iterator && _.isArray(obj)) return Math.min.apply(Math, obj); var result; _.each(obj, function(value, index) { @@ -165,10 +170,10 @@ window.Underscore = { if (result == null || computed < result.computed) result = {value : value, computed : computed}; }); return result.value; - }, + }; // Sort the object's values by a criteria produced by an iterator. - sortBy : function(obj, iterator, context) { + _.sortBy = function(obj, iterator, context) { return _.pluck(_.map(obj, function(value, index) { return { value : value, @@ -178,11 +183,11 @@ window.Underscore = { var a = left.criteria, b = right.criteria; return a < b ? -1 : a > b ? 1 : 0; }), 'value'); - }, + }; // Use a comparator function to figure out at what index an object should // be inserted so as to maintain order. Uses binary search. - sortedIndex : function(array, obj, iterator) { + _.sortedIndex = function(array, obj, iterator) { iterator = iterator || function(val) { return val; }; var low = 0, high = array.length; while (low < high) { @@ -190,163 +195,182 @@ window.Underscore = { iterator(array[mid]) < iterator(obj) ? low = mid + 1 : high = mid; } return low; - }, + }; // Convert anything iterable into a real, live array. - toArray : function(iterable) { + _.toArray = function(iterable) { if (!iterable) return []; if (_.isArray(iterable)) return iterable; return _.map(iterable, function(val){ return val; }); - }, + }; // Return the number of elements in an object. - size : function(obj) { + _.size = function(obj) { return _.toArray(obj).length; - }, + }; /*-------------------------- Array Functions: ------------------------------*/ // Get the first element of an array. - first : function(array) { + _.first = function(array) { return array[0]; - }, + }; // Get the last element of an array. - last : function(array) { + _.last = function(array) { return array[array.length - 1]; - }, + }; // Trim out all falsy values from an array. - compact : function(array) { + _.compact = function(array) { return _.select(array, function(value){ return !!value; }); - }, + }; // Return a completely flattened version of an array. - flatten : function(array) { - return _.inject(array, [], function(memo, value) { + _.flatten = function(array) { + return _.reduce(array, [], function(memo, value) { if (_.isArray(value)) return memo.concat(_.flatten(value)); memo.push(value); return memo; }); - }, + }; // Return a version of the array that does not contain the specified value(s). - without : function(array) { + _.without = function(array) { var values = array.slice.call(arguments, 0); return _.select(array, function(value){ return !_.include(values, value); }); - }, + }; // Produce a duplicate-free version of the array. If the array has already // been sorted, you have the option of using a faster algorithm. - uniq : function(array, isSorted) { - return _.inject(array, [], function(memo, el, i) { + _.uniq = function(array, isSorted) { + return _.reduce(array, [], function(memo, el, i) { if (0 == i || (isSorted ? _.last(memo) != el : !_.include(memo, el))) memo.push(el); return memo; }); - }, + }; // Produce an array that contains every item shared between all the // passed-in arrays. - intersect : function(array) { + _.intersect = function(array) { var rest = _.toArray(arguments).slice(1); return _.select(_.uniq(array), function(item) { return _.all(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() { + _.zip = function() { var args = _.toArray(arguments); var length = _.max(_.pluck(args, 'length')); var results = new Array(length); for (var i=0; i=0; i--) if (array[i] === item) return i; + return -1; + }; /* ----------------------- Function Functions: -----------------------------*/ // Create a function bound to a given object (assigning 'this', and arguments, // optionally). Binding with arguments is also known as 'curry'. - bind : function(func, context) { + _.bind = function(func, context) { if (!context) return func; var args = _.toArray(arguments).slice(2); return function() { var a = args.concat(_.toArray(arguments)); return func.apply(context, a); }; - }, + }; // 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() { + _.bindAll = function() { var args = _.toArray(arguments); var context = args.pop(); _.each(args, function(methodName) { context[methodName] = _.bind(context[methodName], context); }); - }, + }; // Delays a function for the given number of milliseconds, and then calls // it with the arguments supplied. - delay : function(func, wait) { + _.delay = function(func, wait) { var args = _.toArray(arguments).slice(2); - return window.setTimeout(function(){ return func.apply(func, args); }, wait); - }, + return setTimeout(function(){ return func.apply(func, args); }, wait); + }; // Defers a function, scheduling it to run after the current call stack has // cleared. - defer : function(func) { + _.defer = function(func) { return _.delay.apply(_, [func, 1].concat(_.toArray(arguments).slice(1))); - }, + }; // 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) { + _.wrap = function(func, wrapper) { return function() { var args = [func].concat(_.toArray(arguments)); return wrapper.apply(wrapper, 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 = _.toArray(arguments); + return function() { + for (var i=funcs.length-1; i >= 0; i--) { + arguments = [funcs[i].apply(this, arguments)]; + } + return arguments[0]; + }; + }; /* ------------------------- Object Functions: ---------------------------- */ // Retrieve the names of an object's properties. - keys : function(obj) { + _.keys = function(obj) { return _.pluck(obj, 'key'); - }, + }; // Retrieve the values of an object's properties. - values : function(obj) { + _.values = function(obj) { return _.pluck(obj, 'value'); - }, + }; // Extend a given object with all of the properties in a source object. - extend : function(destination, source) { + _.extend = function(destination, source) { for (var property in source) destination[property] = source[property]; return destination; - }, + }; // Create a (shallow-cloned) duplicate of an object. - clone : function(obj) { + _.clone = function(obj) { return _.extend({}, obj); - }, + }; // Perform a deep comparison to check if two objects are equal. - isEqual : function(a, b) { + _.isEqual = function(a, b) { // Check object identity. if (a === b) return true; // Different types? @@ -365,47 +389,47 @@ window.Underscore = { // Recursive comparison of contents. for (var key in a) if (!_.isEqual(a[key], b[key])) return false; return true; - }, + }; // Is a given value a DOM element? - isElement : function(obj) { + _.isElement = function(obj) { return !!(obj && obj.nodeType == 1); - }, + }; // Is a given value a real Array? - isArray : function(obj) { + _.isArray = function(obj) { return Object.prototype.toString.call(obj) == '[object Array]'; - }, + }; // Is a given value a Function? - isFunction : function(obj) { + _.isFunction = function(obj) { return typeof obj == 'function'; - }, + }; // Is a given variable undefined? - isUndefined : function(obj) { + _.isUndefined = function(obj) { return typeof obj == 'undefined'; - }, + }; /* -------------------------- Utility Functions: -------------------------- */ // Run Underscore.js in noConflict mode, returning the '_' variable to its // previous owner. Returns a reference to the Underscore object. - noConflict : function() { - window._ = Underscore.PREVIOUS_UNDERSCORE; + _.noConflict = function() { + root._ = previousUnderscore; return this; - }, + }; // Generate a unique integer id (unique within the entire client session). // Useful for temporary DOM ids. - uniqueId : function(prefix) { + _.uniqueId = function(prefix) { var id = this._idCounter = (this._idCounter || 0) + 1; return prefix ? prefix + id : id; - }, + }; // Javascript templating a-la ERB, pilfered from John Resig's // "Secrets of the Javascript Ninja", page 83. - template : function(str, data) { + _.template = function(str, data) { var fn = new Function('obj', 'var p=[],print=function(){p.push.apply(p,arguments);};' + 'with(obj){p.push(\'' + @@ -419,8 +443,18 @@ window.Underscore = { .split("\r").join("\\'") + "');}return p.join('');"); return data ? fn(data) : fn; - } + }; -}; + /*------------------------------- Aliases ----------------------------------*/ -window._ = Underscore; + _.forEach = _.each; + _.inject = _.reduce; + _.filter = _.select; + _.every = _.all; + _.some = _.any; + + /*------------------------- Export for ServerJS ----------------------------*/ + + if (!_.isUndefined(exports)) exports = _; + +})();