Underscore.cfc

Underscore.cfc is a port of Underscore.js for Coldfusion. It is a utility-belt library that provides a lot of the functional programming support that you would expect in Prototype.js (or Ruby).

Underscore.cfc provides dozens of functions that support both the usual functional suspects: map, select, invoke - as well as more specialized helpers: function binding, sorting, deep equality testing, and so on. It delegates to built-in functions where applicable.

Underscore.cfc is compatible with Adobe Coldfusion 10 and Railo 4.

The project is hosted on GitHub. Contributions are welcome.

Download

Version 2.4 - ~40kb, Includes all development files

Collection Functions (Arrays, Structs, Queries, or Objects)

each _.each(collection, iterator, [context]) : void
Iterates over a collection of elements, yielding each in turn to an iterator function. The iterator is bound to the context object (component or struct), if one is passed. Each invocation of iterator is called with three arguments: (element, index, collection, this). If collection is an object/struct, iterator's arguments will be (value, key, collection, this).

_.each([1, 2, 3], function(num){ writeDump(num); }); 
=> dumps each number in turn...
_.each({one : 1, two : 2, three : 3}, function(num, key){ writeDump(num); });
=> dumps each number in turn...

map _.map(collection, iterator, [context]) : array
Produces a new array of values by mapping each value in collection through a transformation function (iterator). If collection is an object/struct, iterator's arguments will be (value, key, collection, this).

_.map([1, 2, 3], function(num){ return num * 3; }); 
=> [3, 6, 9]
_.map({one : 1, two : 2, three : 3}, function(num, key){ return num * 3; });
=> [3, 6, 9]

reduce _.reduce(collection, iterator, memo, [context]) : any
Also known as inject and foldl, reduce boils down a collection 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.

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

reduceRight _.reduceRight(collection, [iterator], memo, [context])
The right-associative version of reduce.

list = [[0, 1], [2, 3], [4, 5]];
flat = _.reduceRight(list, function(a, b) { return _.concat(a, b); }, []);
=> [4, 5, 2, 3, 0, 1]

find _.find(collection, iterator, [context]) : any
Looks through each value in the collection, returning the first one that passes a truth test (iterator). The function returns as soon as it finds an acceptable element, and doesn't traverse the entire collection.

even = _.find([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });
=> 2

filter _.filter(collection, iterator, [context]) : array
Looks through each value in the collection, returning an array of all the values that pass a truth test (iterator).

evens = _.filter([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });
=> [2, 4, 6]

where _.where(list, properties) : array
Looks through each value in the list, returning an array of all the values that contain all of the key-value pairs listed in properties.

_.where(listOfPlays, {author: "Shakespeare", year: 1611});
=> [{title: "Cymbeline", author: "Shakespeare", year: 1611},
{title: "The Tempest", author: "Shakespeare", year: 1611}]

findWhere _.findWhere(collection, properties) : any
Looks through the collection and returns the first value that matches all of the key-value pairs listed in properties.

_.findWhere(publicServicePulitzers, {newsroom: "The New York Times"});
=> {year: 1918, newsroom: "The New York Times",
reason: "For its public service in publishing in full so many official reports,
documents and speeches by European statesmen relating to the progress and
conduct of the war."}

reject _.reject(collection, iterator, [context]) : array
Returns the values in collection without the elements that the truth test (iterator) passes. The opposite of filter.

odds = _.reject([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });
=> [1, 3, 5]

all _.all(collection, iterator, [context]) : boolean
Returns true if all of the values in the collection pass the iterator truth test.

_.all([true, 1, 'no'], _.identity);
=> false

any _.any(collection, [iterator], [context]) : boolean
Returns true if any of the values in the collection pass the iterator truth test. Short-circuits and stops traversing the collection if a true element is found.

_.any([0, 'yes', false]);
=> true

include _.include(collection, value) : boolean
Returns true if the value is present in the collection.

_.include([1, 2, 3], 3);
=> true

invoke _.invoke(collection, methodName, [arguments]) : array
Calls the method named by methodName on each value in the collection. The arguments struct passed to invoke will be forwarded on to the method invocation.

_.invoke([{fun: function(){ return 1; }}], 'fun');
=> [1]

pluck _.pluck(collection, propertyName) : array
A convenient version of what is perhaps the most common use-case for map: extracting a collection of property values.

stooges = [{name : 'moe', age : 40}, {name : 'larry', age : 50}, {name : 'curly', age : 60}];
_.pluck(stooges, 'name');
=> ["moe", "larry", "curly"]

max _.max(collection, [iterator], [context]) : any
Returns the maximum value in collection. If iterator is passed, it will be used on each value to generate the criterion by which the value is ranked.

stooges = [{name : 'moe', age : 40}, {name : 'larry', age : 50}, {name : 'curly', age : 60}];
_.max(stooges, function(stooge){ return stooge.age; });
=> {name : 'curly', age : 60};

min _.min(collection, [iterator], [context]) : any
Returns the minimum value in collection. If iterator is passed, it will be used on each value to generate the criterion by which the value is ranked.

numbers = [10, 5, 100, 2, 1000];
_.min(numbers);
=> 2

sortBy _.sortBy(collection, [iterator], [context]) : array
Returns a sorted copy of collection, ranked in ascending order by the results of running each value through iterator. Iterator may also be the string name of the object key to sort by. Delegates to arraySort().

_.sortBy([6, 2, 4, 3, 5, 1], function(num){ return num; });
=> [1, 2, 3, 4, 5, 6]

groupBy _.groupBy(collection, iterator) : struct
Splits a collection into sets, grouped by the result of running each value through iterator. If iterator is a string instead of a function, groups by the property named by iterator on each of the values.

_.groupBy([1.3, 2.1, 2.4], function(num){ return fix(num); });
=> {1: [1.3], 2: [2.1, 2.4]}

_.groupBy(['one', 'two', 'three'], function(num) { return len(num); });
=> {3: ["one", "two"], 5: ["three"]}

countBy _.countBy(collection, iterator) : struct
Sorts a collection into groups and returns a count for the number of objects in each group. Similar to groupBy, but instead of returning a list of values, returns a count for the number of values in that group.

_.countBy([1, 2, 3, 4, 5], function(num) { return num % 2 == 0 ? 'even' : 'odd'; });
=> {odd: 3, even: 2}

sortedIndex _.sortedIndex(collection, value, [iterator]) : numeric
Uses a binary search to determine the index at which the value should be inserted into the collection in order to maintain the collection's sorted order. If an iterator is passed, it will be used to compute the sort ranking of each value.

_.sortedIndex([10, 20, 30, 40, 50], 35);
=> 4

shuffle _.shuffle(array) : array
Returns a shuffled copy of the array, using a version of the Fisher-Yates shuffle.

_.shuffle([1, 2, 3, 4, 5, 6]);
=> [4, 1, 6, 3, 5, 2]

toArray _.toArray(collection) : array
Converts the collection (object, struct, query, xml, or cf-list), into an array.

_.toArray({a:10,b:20});
=> [10, 20]

toQuery _.toQuery(array, [columnNames], [columnTypes]) : query
Converts an array of structs to a Coldfusion query. Columns are created dynamically unless a comma-delimited list of column names are provided. Column types are "varchar" unless a comma-delimited list of column types is provided. Delegates to native QueryNew().

_.toQuery([{someColumn: "row 1"}]); 
=> (result is a query with one column titled "someColumn" and one row containing "row 1

toXml _.toXml(collection, [*elementNames]) : xml
Converts a collection to an XML object. Element names default to variable types. If provided, element names will be assigned to unnamed elements (any element without a key) in the order they are listed.

_.toXml([1, 2]); 
=> <array><element>1</element><element>2</element></array>

_.toXml([3], 'myArray', 'number');
=> <myArray><number>3</number><myArray>

size _.size(collection) : numeric
Return the number of values in the collection. Note: A simple value will be considered a single list item.

_.size({one : 1, two : 2, three : 3});
=> 3
_.size(99);
=> 1
_.size("101");
=> 1
_.size();
=> 0

Array Functions

first _.first(array, [n]) : any
Returns the first element of an array. Passing n will return the first n elements of the array.

_.first([5, 4, 3, 2, 1]);
=> 5

initial _.initial(array, [n]) : array
Returns everything but the last entry of the array. Especially useful on the arguments object. Pass n to exclude the last n elements from the result. Note: CF arrays start at an index of 1

_.initial([5, 4, 3, 2, 1]);
=> [5, 4, 3, 2]

last _.last(array, [n]) : any
Returns the last element of an array. Passing n will return the last n elements of the array.

_.last([5, 4, 3, 2, 1]);
=> 1

rest _.rest(array, [index]) : array
Returns the rest of the elements in an array. Pass an index to return the values of the array from that index onward.

_.rest([5, 4, 3, 2, 1]);
=> [4, 3, 2, 1]

compact _.compact(array) : array
Returns a copy of the array with all falsy values removed. In Coldfusion, false, 0, and "" are all falsy.

_.compact([0, 1, false, 2, '', 3]);
=> [1, 2, 3]

flatten _.flatten(array, [shallow]) : array
Flattens a nested array (the nesting can be to any depth). If you pass shallow, the array will only be flattened a single level.

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

_.flatten([1, [2], [3, [[4]]]], true);
=> [1, 2, 3, [[4]]];

without _.without(array, [values]) : array
Returns a copy of the array with all instances of the values removed.

_.without([1, 2, 1, 0, 3, 1, 4], [0, 1]);
=> [2, 3, 4]

union _.union(*arrays) : array
Computes the union of the passed-in arrays: the collection of unique items, in order, that are present in one or more of the arrays.

_.union([1, 2, 3], [101, 2, 1, 10], [2, 1]);
=> [1, 2, 3, 101, 10]

intersection _.intersection(*arrays) : array
Computes the collection of values that are the intersection of all the arrays. Each value in the result is present in each of the arrays.

_.intersection([1, 2, 3], [101, 2, 1, 10], [2, 1]);
=> [1, 2]

difference _.difference(array, others) : array
Similar to without, but returns the values from array that are not present in the other arrays.

_.difference([1, 2, 3, 4, 5], [5, 2, 10]);
=> [1, 3, 4]

uniq _.uniq(array, [isSorted], [iterator]) : array
Produces a duplicate-free version of the array. If you know in advance that the array is sorted, passing true for isSorted will run a much faster algorithm. If you want to compute unique items based on a transformation, pass an iterator function.

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

zip _.zip(*arrays) : array
Merges together the values of each of the arrays with the values at the corresponding position. Useful when you have separate data sources that are coordinated through matching array indexes.

_.zip(['moe', 'larry', 'curly'], [30, 40, 50], [true, false, false]);
=> [["moe", 30, true], ["larry", 40, false], ["curly", 50, false]]

object _.object(array, [values])
Converts an array into a struct. Pass either a single array of [key, value] pairs, or an array of keys, and an array of values.

_.object([['MYKEY', 'myVal']]);
=> {MYKEY: 'myVal'}

_.object(['KEY'], ['myVal']);
=> {KEY: 'value'}

indexOf _.indexOf(array, value, [isSorted/fromIndex]) : numeric
Returns the index at which value can be found in the array, or 0 if value is not present in the array. Uses the native ArrayFind() function. If you're working with a large array, and you know that the array is already sorted, pass true for isSorted to use a faster binary search.

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

lastIndexOf _.lastIndexOf(array, value) : numeric
Returns the index of the last occurrence of value in the array, or 0 if value is not present.

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

range _.range([start], stop, [step]) : array
A function to create flexibly-numbered arrays of integers, handy for each and map loops. start, if omitted, defaults to 0; step defaults to 1. Returns an array of integers from start to stop, incremented (or decremented) by step, exclusive.

_.range(10);
=> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
_.range(1, 11);
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
_.range(0, 30, 5);
=> [0, 5, 10, 15, 20, 25]
_.range(0, -10, -1);
=> [0, -1, -2, -3, -4, -5, -6, -7, -8, -9]
_.range(0);
=> []

concat _.concat(*arrays) : array
Concatenates any number of arrays together an returns the result. Delegates to ArrayAppend().

_.concat([1, 2, 3], [4, 5, 6]);
=> [1, 2, 3, 4, 5, 6];

reverse _.reverse(array) : array
Returns a copy of the array in reverse order.

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

takeWhile _.takeWhile(array) : array
Appends values to a new array as long as the iterator is true.

_.takeWhile([1, 2, 3, 4, 1, 2], function(val) { return val < 3; });
=> [1, 2]

splice _.splice(array, index, howMany, [*items]) : array
Returns a copy of the array with howMany elements removed. Optionally inserts items at the index. Note: differs from Javascript splice() in that it does not return the removed elements.

_.splice([10, 90, 30], 2, 2);
=> [10]
_.splice([10, 90, 30], 2, 1, 20);
=> [10, 20, 30]

push _.push(array, *values) : array
Returns a new array with values appended to the end of it. Does not modify the original array.

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

unshift _.unshift(array, *values) : array
Returns a new array with values prepended to the array. Does not modify the original array.

_.unshift(["end"], "start", "middle");
=> ["start", "middle", "end"]

join _.join(array, [separator]) : string
Returns a string with all array elements joined together. Default separator is a single space.

_.join([1, 2], " and ");
=> "1 and 2

split _.split(string, [delimiters], [includeEmptyFields], [multiCharacterDelimiter]) : array
Returns an array of strings from the string, separating the string at each of the delimeters passed to it (default is comma). Note: This is simply an alias for listToArray().

_.split("hello", '');
=> ['h', 'e', 'l', 'l', 'o']

slice _.slice(array, [from], [to]) : array
Returns a subsection of the array. Negative values for to and from offset from the end of the array.

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

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

_.slice([1, 2, 3, 4], 2, -1);
=> [2, 3]

_.slice([1, 2, 3, 4], -3, -1);
=> [2, 3]

Function (uh, ahem) Functions

bind _.bind(function, object, [*arguments]) : function
Bind a function to a structure, meaning that whenever the function is called, the value of "this" will be the structure. Optionally, bind arguments to the function to pre-fill them, also known as partial application.

func = function(args, this){ return args.greeting & ': ' & this.name; };
func = _.bind(func, {name : 'moe'}, {greeting: 'hi'});
func();
=> 'hi: moe'

bindAll _.bindAll(object, [*methodNames]) : any
Bind all of an object's methods to that object. Useful for ensuring that all callbacks defined on an object belong to it.

greeter = {hello: 'Hello, ', greet: function(this){ return this.hello & 'World!'; }};
_.bindAll(greeter);
greeter.greet();
=> 'Hello, World!'

memoize _.memoize(function, [hashFunction]) : function
Memoizes a given function by caching the computed result. Useful for speeding up slow-running computations. If passed an optional hashFunction, it will be used to compute the hash key for storing the result, based on the arguments to the original function. The default hashFunction just uses the first argument to the memoized function as the key.

fibonacci = _.memoize(function(n) {  return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2); });

delay _.delay(function, wait, arguments) : any
Delays a function for the given number of milliseconds, and then calls it with the arguments supplied in the args struct.

_.delay(function (msg) {return msg;}, 1000, {msg = "hi"});
=> "hi" // appears after one second

once _.once(function) : function
Returns a function that will be executed at most one time, no matter how often you call it. Useful for lazy initialization.

i = 0;
once = _.once(function () { i = i+1; return i; });
once();
=> 1
once();
=> 1

debounce _.debounce(function, wait, immediate) : function
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. (Immediate requires a Wait cooldown period beteween calls.)

keepCalm = _.debounce(function(){}, 300, true);
for (var i = 0; i<10; i++){ keepCalm(); }
=>//function argument is called only once

after _.after(count, function) : any
Returns a function that will only be executed after being called N times. When count <= 0, the result of calling the function immediately is returned.

func = function () { writeOutput("hi"); };
callFuncAfterTwo = _.after(2, func);
callFuncAfterTwo();
=> // nothing
callFuncAfterTwo();
=> 'hi'

wrap _.wrap(function, wrapper) : function
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.

hello = function(name) { return "hello: " & name; };
hello = _.wrap(hello, function(func) {
return "before, " & func("moe") & ", after";
});
hello();
=> 'before, hello: moe, after'

compose _.compose(*functions) : function
Returns a function that is the composition of a list of functions, 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())).

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

Object/Struct Functions

keys _.keys(object) : array
Retrieve all the names of the object's properties.

_.keys({one : 1, two : 2, three : 3});
=> ["one", "two", "three"]

values _.values(object) : array
Returns true if any of the values in the object pass the iterator truth test. Short-circuits and stops traversing the object if a true element is found.

_.values({one : 1, two : 2, three : 3});
=> [1, 2, 3]

pairs _.pairs(object) : array
Convert an object into a list of [key, value] pairs. Notes: Resulting item order is not guaranteed. Key capitalization will depend on your CF engine.

_.pairs({ONE: 1, TWO: 2, THREE: 3});
=> [["ONE", 1], ["TWO", 2], ["THREE", 3]]

invert _.invert(object) : object
Returns a copy of the object where the keys have become the values and the values the keys. For this to work, all of your object's values should be unique and string serializable. Note: Capitalization will depend on your CF engine.

_.invert({Moe: "Moses", Larry: "Louis", Curly: "Jerome"});
=> {Moses: "Moe", Louis: "Larry", Jerome: "Curly"};

functions _.functions(object) : array
Returns a sorted array of the names of every method in an object -- that is to say, the name of every function property of the object.

_.functions(_);
=> ["all", "any", "bind", "bindAll", "clone", "compact", "compose" ...

extend _.extend(destination, *sources) : any
Copy all of the properties in the source objects over to the destination object, and return the destination object. It's in-order, so the last source will override properties of the same name in previous arguments.

_.extend({name : 'moe'}, {age : 50});
=> {name : 'moe', age : 50}

pick _.pick(object, *keys) : struct
Return a copy of the object, filtered to only have values for the whitelisted keys (or array of valid keys).

_.pick({name : 'moe', age: 50, userid : 'moe1'}, 'name', 'age');
=> {name : 'moe', age : 50}

omit _.omit(object, *keys) : struct
Return a copy of the object, filtered to omit the blacklisted keys (or array of keys).

_.omit({NAME: 'moe', AGE: 50, USERID: 'moe1'}, 'userid');
=> {NAME: 'moe', AGE: 50}

defaults _.defaults(object, *defaults) : any
Fill in missing properties in object with default values from the defaults objects, and return the object. As soon as the property is filled, further defaults will have no effect.

iceCream = {flavor : "chocolate"};
_.defaults(iceCream, {flavor : "vanilla", sprinkles : "lots"});
=> {flavor : "chocolate", sprinkles : "lots"}

clone _.clone(object) : any
Create a shallow-copied clone of the object. Any nested structs or objects will be copied by reference, not duplicated.

_.clone({name : 'moe'});
=> {name : 'moe'}

has _.has(object, key) : boolean
Does the object contain the given key? Delegates to _.include() for arrays or native structKeyExists() for objects.

_.has({a: 1, b: 2, c: 3}, "b");
=> true

isEqual _.isEqual(object, other)
Performs a deep comparison between the two objects, to determine if they should be considered equal.

moe = {name : 'moe', luckyNumbers : [13, 27, 34]};
clone = {name : 'moe', luckyNumbers : [13, 27, 34]};
_.isEqual(moe, clone);
=> true

isEmpty _.isEmpty(object) : boolean
Returns true if object contains no values. Delegates to ArrayLen for arrays, structIsEmpty() otherwise.

_.isEmpty([1, 2, 3]);
=> false
_.isEmpty({});
=> true

isArray _.isArray(object) : boolean
Returns true if object is an Array. Delegates to native isArray();

_.isArray({one: 1});
=> false
_.isArray([1,2,3]);
=> true

isObject _.isObject(object) : boolean
Returns true if value is an Object. Delegates to native isObject()

_.isObject(new Component());
=> true
_.isObject({});
=> false

isFunction _.isFunction(object) : boolean
Returns true if object is a Function. Delegates to native isClosure() || isCustomFunction()

_.isFunction(function(){return 1;});
=> true

isString _.isString(object) : boolean
Returns true if object is a String. Uses Java String type comparison.

_.isString("moe");
=> true
_.isString(1);
=> true//Coldfusion converts numbers to strings

isNumber _.isNumber(object) : boolean
Returns true if object is of a Java numeric type.

_.isNumber(1);
=> false//Coldfusion converts numbers to strings
_.isNumber(JavaCast("int", 1));
=> true

isBoolean _.isBoolean(object) : boolean
Returns true if object is a boolean. Delegates to native isBoolean()

_.isBoolean(false);
=> true

isDate _.isDate(object) : boolean
Returns true if object is a date. Delegates to native isDate()

_.isDate(now());
=> true

Utility Functions

times _.times(n, iterator) : void
Invokes the given iterator function n times.

_.times(3, function(){ genie.grantWish(); });

random _.random(min, max) : numeric
Returns a random integer between min and max, inclusive. If you only pass one argument, it will return a number between 0 and that number. Delegates to RandRange().

_.random(0, 100);
=> 42

mixin _.mixin(object) : void
Allows you to extend Underscore with your own utility functions. Pass a struct of {name: function} definitions to have your functions added to the Underscore object, as well as the OOP wrapper.

_.mixin({ 
upper: function(string) { return uCase(string); }
});
_.upper("fabio");
=> "Fabio"

uniqueId _.uniqueId([prefix]) : string
Generates an identifier that is unique for this instance of Underscore

_.uniqueId('c');
=> 'c1

escape _.escape(input) : string
Escapes a string for insertion into HTML, replacing &, <, >, and " characters. Delegates to HTMLeditFormat().

_.escape('Curly, Larry & Moe');
=> "Curly, Larry & Moe

result _.result(object, property) : any
If the value of the named property is a function then invoke it; otherwise, return it.

object = {cheese: 'crumpets', stuff: function(){ return 'nonsense'; }};
_.result(object, 'cheese');
=> "crumpets"
_.result(object, 'stuff');
=> "nonsense"

Change Log

2.4 -- Feb 3rd, 2014

2.3 -- December 12th, 2012

2.2 -- August 10th, 2012

2.1 -- July 9th, 2012

2.0 -- July 4th, 2012

1.0 -- June 30, 2012

Fork me on GitHub