prototype/src/lang/enumerable.js

385 lines
11 KiB
JavaScript

/** section: lang
* mixin Enumerable
**/
var $break = { };
var Enumerable = (function() {
/**
* Enumerable#each(iterator[, context]) -> Enumerable
* - iterator (Function): A `Function` that expects an item in the
* collection as the first argument and a numerical index as the second.
* - context (Object): The scope in which to call `iterator`. Affects what
* the keyword `this` means inside `iterator`.
*
* Calls `iterator` for each item in the collection.
**/
function each(iterator, context) {
var index = 0;
try {
this._each(function(value) {
iterator.call(context, value, index++);
});
} catch (e) {
if (e != $break) throw e;
}
return this;
}
/**
* Enumerable#eachSlice(number[, iterator = Prototype.K[, context]]) -> Enumerable
*
* TODO: Enumerable#eachSlice
* http://prototypejs.org/api/enumerable#method-eachslice
**/
function eachSlice(number, iterator, context) {
var index = -number, slices = [], array = this.toArray();
if (number < 1) return array;
while ((index += number) < array.length)
slices.push(array.slice(index, index+number));
return slices.collect(iterator, context);
}
/**
* Enumerable#all([iterator = Prototype.K[, context]]) -> Boolean
*
* Determines whether all the elements are boolean-equivalent to `true`,
* either directly or through computation by the provided iterator.
**/
function all(iterator, context) {
iterator = iterator || Prototype.K;
var result = true;
this.each(function(value, index) {
result = result && !!iterator.call(context, value, index);
if (!result) throw $break;
});
return result;
}
/**
* Enumerable#any([iterator = Prototype.K[, context]]) -> Boolean
*
* Determines whether at least one element is boolean-equivalent to `true`,
* either directly or through computation by the provided iterator.
**/
function any(iterator, context) {
iterator = iterator || Prototype.K;
var result = false;
this.each(function(value, index) {
if (result = !!iterator.call(context, value, index))
throw $break;
});
return result;
}
/** alias of: Enumerable#map
* Enumerable#collect([iterator = Prototype.K[, context]]) -> Array
*
* Returns the results of applying the iterator to each element.
* Aliased as [[Enumerable#map]].
**/
function collect(iterator, context) {
iterator = iterator || Prototype.K;
var results = [];
this.each(function(value, index) {
results.push(iterator.call(context, value, index));
});
return results;
}
/** alias of: Enumerable#find
* Enumerable#detect(iterator[, context]) -> firstElement | undefined
*
* Finds the first element for which the iterator returns a “truthy” value.
* Aliased by the [[Enumerable#find]] method.
**/
function detect(iterator, context) {
var result;
this.each(function(value, index) {
if (iterator.call(context, value, index)) {
result = value;
throw $break;
}
});
return result;
}
/** alias of: select
* Enumerable#findAll(iterator[, context]) -> Array
*
* Returns all the elements for which the iterator returned “truthy” value.
* Aliased as [[Enumerable#select]].
**/
function findAll(iterator, context) {
var results = [];
this.each(function(value, index) {
if (iterator.call(context, value, index))
results.push(value);
});
return results;
}
/**
* Enumerable#grep(regex[, iterator = Prototype.K[, context]]) -> Array
*
* Returns all the elements that match the filter. If an iterator is provided,
* it is used to produce the returned value for each selected element.
**/
function grep(filter, iterator, context) {
iterator = iterator || Prototype.K;
var results = [];
if (Object.isString(filter))
filter = new RegExp(RegExp.escape(filter));
this.each(function(value, index) {
if (filter.match(value))
results.push(iterator.call(context, value, index));
});
return results;
}
/** alias of: Enumerable#member
* Enumerable#include(object) -> Boolean
*
* Determines whether a given object is in the Enumerable or not,
* based on the `==` comparison operator. Aliased as [[Enumerable#member]].
**/
function include(object) {
if (Object.isFunction(this.indexOf))
if (this.indexOf(object) != -1) return true;
var found = false;
this.each(function(value) {
if (value == object) {
found = true;
throw $break;
}
});
return found;
}
/**
* Enumerable#inGroupsOf(size[, filler = null]) -> [group...]
*
* Groups items in fixed-size chunks, using a specific value to fill up
* the last chunk if necessary.
**/
function inGroupsOf(number, fillWith) {
fillWith = Object.isUndefined(fillWith) ? null : fillWith;
return this.eachSlice(number, function(slice) {
while(slice.length < number) slice.push(fillWith);
return slice;
});
}
/**
* Enumerable#inject(accumulator, iterator[, context]) -> accumulatedValue
*
* Incrementally builds a result value based on the successive results
* of the iterator.
* This can be used for array construction, numerical sums/averages, etc.
**/
function inject(memo, iterator, context) {
this.each(function(value, index) {
memo = iterator.call(context, memo, value, index);
});
return memo;
}
/**
* Enumerable#invoke(methodName[, arg...]) -> Array
*
* Invokes the same method, with the same arguments, for all items in a collection.
* Returns the results of the method calls.
**/
function invoke(method) {
var args = $A(arguments).slice(1);
return this.map(function(value) {
return value[method].apply(value, args);
});
}
/**
* Enumerable#max([iterator = Prototype.K[, context]]) -> maxValue
*
* Returns the maximum element (or element-based computation), or undefined if
* the enumeration is empty.
* Elements are either compared directly, or by first applying the iterator
* and comparing returned values.
**/
function max(iterator, context) {
iterator = iterator || Prototype.K;
var result;
this.each(function(value, index) {
value = iterator.call(context, value, index);
if (result == null || value >= result)
result = value;
});
return result;
}
/**
* Enumerable#min([iterator = Prototype.K[, context]]) -> minValue
*
* Returns the minimum element (or element-based computation), or undefined if
* the enumeration is empty.
* Elements are either compared directly, or by first applying the iterator
* and comparing returned values.
**/
function min(iterator, context) {
iterator = iterator || Prototype.K;
var result;
this.each(function(value, index) {
value = iterator.call(context, value, index);
if (result == null || value < result)
result = value;
});
return result;
}
/**
* Enumerable#partition([iterator = Prototype.K[, context]]) -> [TrueArray, FalseArray]
*
* Partitions the elements in two groups: those regarded as true, and those
* considered false.
* By default, regular JavaScript boolean equivalence is used, but an iterator
* can be provided, that computes a boolean representation of the elements.
**/
function partition(iterator, context) {
iterator = iterator || Prototype.K;
var trues = [], falses = [];
this.each(function(value, index) {
(iterator.call(context, value, index) ?
trues : falses).push(value);
});
return [trues, falses];
}
/**
* Enumerable#pluck(propertyName) -> Array
*
* Optimization for a common use-case of collect: fetching the same property
* for all the elements. Returns the property values.
**/
function pluck(property) {
var results = [];
this.each(function(value) {
results.push(value[property]);
});
return results;
}
/**
* Enumerable#reject(iterator[, context]) -> Array
*
* Returns all the elements for which the iterator returned a “falsy” value.
**/
function reject(iterator, context) {
var results = [];
this.each(function(value, index) {
if (!iterator.call(context, value, index))
results.push(value);
});
return results;
}
/**
* Enumerable#sortBy(iterator[, context]) -> Array
*
* Provides a custom-sorted view of the elements based on the criteria computed,
* for each element, by the iterator.
**/
function sortBy(iterator, context) {
return this.map(function(value, index) {
return {
value: value,
criteria: iterator.call(context, value, index)
};
}).sort(function(left, right) {
var a = left.criteria, b = right.criteria;
return a < b ? -1 : a > b ? 1 : 0;
}).pluck('value');
}
/** alias of: Enumerable#entries
* Enumerable#toArray() -> Array
*
* Returns an Array representation of the enumeration.
* Aliased as [[Enumerable#entries]].
**/
function toArray() {
return this.map();
}
/**
* Enumerable#zip(sequence...[, iterator = Prototype.K]) -> Array
*
* Zips together (think of the zip on a pair of trousers) 2+ sequences,
* providing an array of tuples.
* Each tuple contains one value per original sequence.
* Tuples can be converted to something else by applying the optional iterator on them.
**/
function zip() {
var iterator = Prototype.K, args = $A(arguments);
if (Object.isFunction(args.last()))
iterator = args.pop();
var collections = [this].concat(args).map($A);
return this.map(function(value, index) {
return iterator(collections.pluck(index));
});
}
/**
* Enumerable#size() -> Number
*
* Returns the size of the enumeration.
**/
function size() {
return this.toArray().length;
}
/** related to: Object.inspect
* Enumerable#inspect() -> String
*
* TODO: Enumerable#inspect
**/
function inspect() {
return '#<Enumerable:' + this.toArray().inspect() + '>';
}
return {
each: each,
eachSlice: eachSlice,
all: all,
every: all,
any: any,
some: any,
collect: collect,
map: collect,
detect: detect,
findAll: findAll,
select: findAll,
filter: findAll,
grep: grep,
include: include,
member: include,
inGroupsOf: inGroupsOf,
inject: inject,
invoke: invoke,
max: max,
min: min,
partition: partition,
pluck: pluck,
reject: reject,
sortBy: sortBy,
toArray: toArray,
entries: toArray,
zip: zip,
size: size,
inspect: inspect,
find: detect
};
})();