UNPKG

angular-ui-form-validation

Version:

A plugin for performing validation in angularjs without writing lots of boilerplate code or duplicating logic.

1,665 lines (1,503 loc) 217 kB
/* * @name Lazy.js * * @fileOverview * Lazy.js is a lazy evaluation library for JavaScript. * * This has been done before. For examples see: * * - [wu.js](http://fitzgen.github.io/wu.js/) * - [Linq.js](http://linqjs.codeplex.com/) * - [from.js](https://github.com/suckgamoni/fromjs/) * - [IxJS](http://rx.codeplex.com/) * - [sloth.js](http://rfw.name/sloth.js/) * * However, at least at present, Lazy.js is faster (on average) than any of * those libraries. It is also more complete, with nearly all of the * functionality of [Underscore](http://underscorejs.org/) and * [Lo-Dash](http://lodash.com/). * * Finding your way around the code * -------------------------------- * * At the heart of Lazy.js is the {@link Sequence} object. You create an initial * sequence using {@link Lazy}, which can accept an array, object, or string. * You can then "chain" together methods from this sequence, creating a new * sequence with each call. * * Here's an example: * * var data = getReallyBigArray(); * * var statistics = Lazy(data) * .map(transform) * .filter(validate) * .reduce(aggregate); * * {@link Sequence} is the foundation of other, more specific sequence types. * * An {@link ArrayLikeSequence} provides indexed access to its elements. * * An {@link ObjectLikeSequence} consists of key/value pairs. * * A {@link StringLikeSequence} is like a string (duh): actually, it is an * {@link ArrayLikeSequence} whose elements happen to be characters. * * An {@link AsyncSequence} is special: it iterates over its elements * asynchronously (so calling `each` generally begins an asynchronous loop and * returns immediately). * * For more information * -------------------- * * I wrote a blog post that explains a little bit more about Lazy.js, which you * can read [here](http://philosopherdeveloper.com/posts/introducing-lazy-js.html). * * You can also [create an issue on GitHub](https://github.com/dtao/lazy.js/issues) * if you have any issues with the library. I work through them eventually. * * [@dtao](https://github.com/dtao) */ (function(context) { /** * Wraps an object and returns a {@link Sequence}. For `null` or `undefined`, * simply returns an empty sequence (see {@link Lazy.strict} for a stricter * implementation). * * - For **arrays**, Lazy will create a sequence comprising the elements in * the array (an {@link ArrayLikeSequence}). * - For **objects**, Lazy will create a sequence of key/value pairs * (an {@link ObjectLikeSequence}). * - For **strings**, Lazy will create a sequence of characters (a * {@link StringLikeSequence}). * * @public * @param {Array|Object|string} source An array, object, or string to wrap. * @returns {Sequence} The wrapped lazy object. * * @exampleHelpers * // Utility functions to provide to all examples * function increment(x) { return x + 1; } * function isEven(x) { return x % 2 === 0; } * function isPositive(x) { return x > 0; } * function isNegative(x) { return x < 0; } * * @examples * Lazy([1, 2, 4]) // instanceof Lazy.ArrayLikeSequence * Lazy({ foo: "bar" }) // instanceof Lazy.ObjectLikeSequence * Lazy("hello, world!") // instanceof Lazy.StringLikeSequence * Lazy() // sequence: [] * Lazy(null) // sequence: [] */ function Lazy(source) { if (source instanceof Array) { return new ArrayWrapper(source); } else if (typeof source === "string") { return new StringWrapper(source); } else if (source instanceof Sequence) { return source; } if (Lazy.extensions) { var extensions = Lazy.extensions, length = extensions.length, result; while (!result && length--) { result = extensions[length](source); } if (result) { return result; } } return new ObjectWrapper(source); } Lazy.VERSION = '0.3.2'; /*** Utility methods of questionable value ***/ Lazy.noop = function noop() {}; Lazy.identity = function identity(x) { return x; }; /** * Provides a stricter version of {@link Lazy} which throws an error when * attempting to wrap `null`, `undefined`, or numeric or boolean values as a * sequence. * * @public * @returns {Function} A stricter version of the {@link Lazy} helper function. * * @examples * var Strict = Lazy.strict(); * * Strict() // throws * Strict(null) // throws * Strict(true) // throws * Strict(5) // throws * Strict([1, 2, 3]) // instanceof Lazy.ArrayLikeSequence * Strict({ foo: "bar" }) // instanceof Lazy.ObjectLikeSequence * Strict("hello, world!") // instanceof Lazy.StringLikeSequence * * // Let's also ensure the static functions are still there. * Strict.range(3) // sequence: [0, 1, 2] * Strict.generate(Date.now) // instanceof Lazy.GeneratedSequence */ Lazy.strict = function strict() { function StrictLazy(source) { if (source == null) { throw "You cannot wrap null or undefined using Lazy."; } if (typeof source === "number" || typeof source === "boolean") { throw "You cannot wrap primitive values using Lazy."; } return Lazy(source); }; Lazy(Lazy).each(function(property, name) { StrictLazy[name] = property; }); return StrictLazy; }; /** * The `Sequence` object provides a unified API encapsulating the notion of * zero or more consecutive elements in a collection, stream, etc. * * Lazy evaluation * --------------- * * Generally speaking, creating a sequence should not be an expensive operation, * and should not iterate over an underlying source or trigger any side effects. * This means that chaining together methods that return sequences incurs only * the cost of creating the `Sequence` objects themselves and not the cost of * iterating an underlying data source multiple times. * * The following code, for example, creates 4 sequences and does nothing with * `source`: * * var seq = Lazy(source) // 1st sequence * .map(func) // 2nd * .filter(pred) // 3rd * .reverse(); // 4th * * Lazy's convention is to hold off on iterating or otherwise *doing* anything * (aside from creating `Sequence` objects) until you call `each`: * * seq.each(function(x) { }); * * Defining custom sequences * ------------------------- * * Defining your own type of sequence is relatively simple: * * 1. Pass a *method name* and an object containing *function overrides* to * {@link Sequence.define}. If the object includes a function called `init`, * this function will be called upon initialization. * 2. The object should include at least either a `getIterator` method or an * `each` method. The former supports both asynchronous and synchronous * iteration, but is slightly more cumbersome to implement. The latter * supports synchronous iteration and can be automatically implemented in * terms of the former. You can also implement both if you want, e.g. to * optimize performance. For more info, see {@link Iterator} and * {@link AsyncSequence}. * * As a trivial example, the following code defines a new method, `sample`, * which randomly may or may not include each element from its parent. * * Lazy.Sequence.define("sample", { * each: function(fn) { * return this.parent.each(function(e) { * // 50/50 chance of including this element. * if (Math.random() > 0.5) { * return fn(e); * } * }); * } * }); * * (Of course, the above could also easily have been implemented using * {@link #filter} instead of creating a custom sequence. But I *did* say this * was a trivial example, to be fair.) * * Now it will be possible to create this type of sequence from any parent * sequence by calling the method name you specified. In other words, you can * now do this: * * Lazy(arr).sample(); * Lazy(arr).map(func).sample(); * Lazy(arr).map(func).filter(pred).sample(); * * Etc., etc. * * @public * @constructor */ function Sequence() {} /** * Create a new constructor function for a type inheriting from `Sequence`. * * @public * @param {string|Array.<string>} methodName The name(s) of the method(s) to be * used for constructing the new sequence. The method will be attached to * the `Sequence` prototype so that it can be chained with any other * sequence methods, like {@link #map}, {@link #filter}, etc. * @param {Object} overrides An object containing function overrides for this * new sequence type. **Must** include either `getIterator` or `each` (or * both). *May* include an `init` method as well. For these overrides, * `this` will be the new sequence, and `this.parent` will be the base * sequence from which the new sequence was constructed. * @returns {Function} A constructor for a new type inheriting from `Sequence`. * * @examples * // This sequence type logs every element to the specified logger as it * // iterates over it. * Lazy.Sequence.define("verbose", { * init: function(logger) { * this.logger = logger; * }, * * each: function(fn) { * var logger = this.logger; * return this.parent.each(function(e, i) { * logger(e); * return fn(e, i); * }); * } * }); * * Lazy([1, 2, 3]).verbose(logger).each(Lazy.noop) // calls logger 3 times */ Sequence.define = function define(methodName, overrides) { if (!overrides || (!overrides.getIterator && !overrides.each)) { throw "A custom sequence must implement *at least* getIterator or each!"; } return defineSequenceType(Sequence, methodName, overrides); }; /** * Gets the number of elements in the sequence. In some cases, this may * require eagerly evaluating the sequence. * * @public * @returns {number} The number of elements in the sequence. * * @examples * Lazy([1, 2, 3]).size(); // => 3 * Lazy([1, 2]).map(Lazy.identity).size(); // => 2 * Lazy([1, 2, 3]).reject(isEven).size(); // => 2 * Lazy([1, 2, 3]).take(1).size(); // => 1 * Lazy({ foo: 1, bar: 2 }).size(); // => 2 * Lazy('hello').size(); // => 5 */ Sequence.prototype.size = function size() { return this.getIndex().length(); }; /** * Creates an {@link Iterator} object with two methods, `moveNext` -- returning * true or false -- and `current` -- returning the current value. * * This method is used when asynchronously iterating over sequences. Any type * inheriting from `Sequence` must implement this method or it can't support * asynchronous iteration. * * Note that **this method is not intended to be used directly by application * code.** Rather, it is intended as a means for implementors to potentially * define custom sequence types that support either synchronous or * asynchronous iteration. * * @public * @returns {Iterator} An iterator object. * * @examples * var iterator = Lazy([1, 2]).getIterator(); * * iterator.moveNext(); // => true * iterator.current(); // => 1 * iterator.moveNext(); // => true * iterator.current(); // => 2 * iterator.moveNext(); // => false */ Sequence.prototype.getIterator = function getIterator() { return new Iterator(this); }; /** * Gets the root sequence underlying the current chain of sequences. */ Sequence.prototype.root = function root() { return this.parent.root(); }; /** * Evaluates the sequence and produces an appropriate value (an array in most * cases, an object for {@link ObjectLikeSequence}s or a string for * {@link StringLikeSequence}s). */ Sequence.prototype.value = function value() { return this.toArray(); }; /** * Applies the current transformation chain to a given source. * * @examples * var sequence = Lazy([]) * .map(function(x) { return x * -1; }) * .filter(function(x) { return x % 2 === 0; }); * * sequence.apply([1, 2, 3, 4]); // => [-2, -4] */ Sequence.prototype.apply = function apply(source) { var root = this.root(), previousSource = root.source, result; try { root.source = source; result = this.value(); } finally { root.source = previousSource; } return result; }; /** * The Iterator object provides an API for iterating over a sequence. * * The purpose of the `Iterator` type is mainly to offer an agnostic way of * iterating over a sequence -- either synchronous (i.e. with a `while` loop) * or asynchronously (with recursive calls to either `setTimeout` or --- if * available --- `setImmediate`). It is not intended to be used directly by * application code. * * @public * @constructor * @param {Sequence} sequence The sequence to iterate over. */ function Iterator(sequence) { this.sequence = sequence; this.index = -1; } /** * Gets the current item this iterator is pointing to. * * @public * @returns {*} The current item. */ Iterator.prototype.current = function current() { return this.cachedIndex && this.cachedIndex.get(this.index); }; /** * Moves the iterator to the next item in a sequence, if possible. * * @public * @returns {boolean} True if the iterator is able to move to a new item, or else * false. */ Iterator.prototype.moveNext = function moveNext() { var cachedIndex = this.cachedIndex; if (!cachedIndex) { cachedIndex = this.cachedIndex = this.sequence.getIndex(); } if (this.index >= cachedIndex.length() - 1) { return false; } ++this.index; return true; }; /** * Creates an array snapshot of a sequence. * * Note that for indefinite sequences, this method may raise an exception or * (worse) cause the environment to hang. * * @public * @returns {Array} An array containing the current contents of the sequence. * * @examples * Lazy([1, 2, 3]).toArray() // => [1, 2, 3] */ Sequence.prototype.toArray = function toArray() { return this.reduce(function(arr, element) { arr.push(element); return arr; }, []); }; /** * Provides an indexed view into the sequence. * * For sequences that are already indexed, this will simply return the * sequence. For non-indexed sequences, this will eagerly evaluate the * sequence and cache the result (so subsequent calls will not create * additional arrays). * * @returns {ArrayLikeSequence} A sequence containing the current contents of * the sequence. * * @examples * Lazy([1, 2, 3]).filter(isEven) // instanceof Lazy.Sequence * Lazy([1, 2, 3]).filter(isEven).getIndex() // instanceof Lazy.ArrayLikeSequence */ Sequence.prototype.getIndex = function getIndex() { if (!this.cachedIndex) { this.cachedIndex = new ArrayWrapper(this.toArray()); } return this.cachedIndex; }; /** * Provides an indexed, memoized view into the sequence. This will cache the * result whenever the sequence is first iterated, so that subsequent * iterations will access the same element objects. * * @public * @returns {ArrayLikeSequence} An indexed, memoized sequence containing this * sequence's elements, cached after the first iteration. * * @example * function createObject() { return new Object(); } * * var plain = Lazy.generate(createObject, 10), * memoized = Lazy.generate(createObject, 10).memoize(); * * plain.toArray()[0] === plain.toArray()[0]; // => false * memoized.toArray()[0] === memoized.toArray()[0]; // => true */ Sequence.prototype.memoize = function memoize() { return new MemoizedSequence(this); }; /** * @constructor */ function MemoizedSequence(parent) { this.parent = parent; } // MemoizedSequence needs to have its prototype set up after ArrayLikeSequence /** * Creates an object from a sequence of key/value pairs. * * @public * @returns {Object} An object with keys and values corresponding to the pairs * of elements in the sequence. * * @examples * var details = [ * ["first", "Dan"], * ["last", "Tao"], * ["age", 29] * ]; * * Lazy(details).toObject() // => { first: "Dan", last: "Tao", age: 29 } */ Sequence.prototype.toObject = function toObject() { return this.reduce(function(object, pair) { object[pair[0]] = pair[1]; return object; }, {}); }; /** * Iterates over this sequence and executes a function for every element. * * @public * @aka forEach * @param {Function} fn The function to call on each element in the sequence. * Return false from the function to end the iteration. * * @examples * Lazy([1, 2, 3, 4]).each(fn) // calls fn 4 times */ Sequence.prototype.each = function each(fn) { var iterator = this.getIterator(), i = -1; while (iterator.moveNext()) { if (fn(iterator.current(), ++i) === false) { return false; } } return true; }; Sequence.prototype.forEach = function forEach(fn) { return this.each(fn); }; /** * Creates a new sequence whose values are calculated by passing this sequence's * elements through some mapping function. * * @public * @aka collect * @param {Function} mapFn The mapping function used to project this sequence's * elements onto a new sequence. * @returns {Sequence} The new sequence. * * @examples * Lazy([]).map(increment) // sequence: [] * Lazy([1, 2, 3]).map(increment) // sequence: [2, 3, 4] * * @benchmarks * function increment(x) { return x + 1; } * * var smArr = Lazy.range(10).toArray(), * lgArr = Lazy.range(100).toArray(); * * Lazy(smArr).map(increment).each(Lazy.noop) // lazy - 10 elements * Lazy(lgArr).map(increment).each(Lazy.noop) // lazy - 100 elements * _.each(_.map(smArr, increment), _.noop) // lodash - 10 elements * _.each(_.map(lgArr, increment), _.noop) // lodash - 100 elements */ Sequence.prototype.map = function map(mapFn) { return new MappedSequence(this, createCallback(mapFn)); }; Sequence.prototype.collect = function collect(mapFn) { return this.map(mapFn); }; /** * @constructor */ function MappedSequence(parent, mapFn) { this.parent = parent; this.mapFn = mapFn; } MappedSequence.prototype = new Sequence(); MappedSequence.prototype.getIterator = function getIterator() { return new MappingIterator(this.parent, this.mapFn); }; MappedSequence.prototype.each = function each(fn) { var mapFn = this.mapFn; return this.parent.each(function(e, i) { return fn(mapFn(e, i), i); }); }; /** * @constructor */ function MappingIterator(sequence, mapFn) { this.iterator = sequence.getIterator(); this.mapFn = mapFn; this.index = -1; } MappingIterator.prototype.current = function current() { return this.mapFn(this.iterator.current(), this.index); }; MappingIterator.prototype.moveNext = function moveNext() { if (this.iterator.moveNext()) { ++this.index; return true; } return false; }; /** * Creates a new sequence whose values are calculated by accessing the specified * property from each element in this sequence. * * @public * @param {string} propertyName The name of the property to access for every * element in this sequence. * @returns {Sequence} The new sequence. * * @examples * var people = [ * { first: "Dan", last: "Tao" }, * { first: "Bob", last: "Smith" } * ]; * * Lazy(people).pluck("last") // sequence: ["Tao", "Smith"] */ Sequence.prototype.pluck = function pluck(property) { return this.map(property); }; /** * Creates a new sequence whose values are calculated by invoking the specified * function on each element in this sequence. * * @public * @param {string} methodName The name of the method to invoke for every element * in this sequence. * @returns {Sequence} The new sequence. * * @examples * function Person(first, last) { * this.fullName = function fullName() { * return first + " " + last; * }; * } * * var people = [ * new Person("Dan", "Tao"), * new Person("Bob", "Smith") * ]; * * Lazy(people).invoke("fullName") // sequence: ["Dan Tao", "Bob Smith"] */ Sequence.prototype.invoke = function invoke(methodName) { return this.map(function(e) { return e[methodName](); }); }; /** * Creates a new sequence whose values are the elements of this sequence which * satisfy the specified predicate. * * @public * @aka select * @param {Function} filterFn The predicate to call on each element in this * sequence, which returns true if the element should be included. * @returns {Sequence} The new sequence. * * @examples * var numbers = [1, 2, 3, 4, 5, 6]; * * Lazy(numbers).filter(isEven) // sequence: [2, 4, 6] * * @benchmarks * function isEven(x) { return x % 2 === 0; } * * var smArr = Lazy.range(10).toArray(), * lgArr = Lazy.range(100).toArray(); * * Lazy(smArr).filter(isEven).each(Lazy.noop) // lazy - 10 elements * Lazy(lgArr).filter(isEven).each(Lazy.noop) // lazy - 100 elements * _.each(_.filter(smArr, isEven), _.noop) // lodash - 10 elements * _.each(_.filter(lgArr, isEven), _.noop) // lodash - 100 elements */ Sequence.prototype.filter = function filter(filterFn) { return new FilteredSequence(this, createCallback(filterFn)); }; Sequence.prototype.select = function select(filterFn) { return this.filter(filterFn); }; /** * @constructor */ function FilteredSequence(parent, filterFn) { this.parent = parent; this.filterFn = filterFn; } FilteredSequence.prototype = new Sequence(); FilteredSequence.prototype.getIterator = function getIterator() { return new FilteringIterator(this.parent, this.filterFn); }; FilteredSequence.prototype.each = function each(fn) { var filterFn = this.filterFn; return this.parent.each(function(e, i) { if (filterFn(e, i)) { return fn(e, i); } }); }; FilteredSequence.prototype.reverse = function reverse() { return this.parent.reverse().filter(this.filterFn); }; /** * @constructor */ function FilteringIterator(sequence, filterFn) { this.iterator = sequence.getIterator(); this.filterFn = filterFn; this.index = 0; } FilteringIterator.prototype.current = function current() { return this.value; }; FilteringIterator.prototype.moveNext = function moveNext() { var iterator = this.iterator, filterFn = this.filterFn, value; while (iterator.moveNext()) { value = iterator.current(); if (filterFn(value, this.index++)) { this.value = value; return true; } } this.value = undefined; return false; }; /** * Creates a new sequence whose values exclude the elements of this sequence * identified by the specified predicate. * * @public * @param {Function} rejectFn The predicate to call on each element in this * sequence, which returns true if the element should be omitted. * @returns {Sequence} The new sequence. * * @examples * Lazy([1, 2, 3, 4, 5]).reject(isEven) // sequence: [1, 3, 5] * Lazy([{ foo: 1 }, { bar: 2 }]).reject('foo') // sequence: [{ bar: 2 }] * Lazy([{ foo: 1 }, { foo: 2 }]).reject({ foo: 2 }) // sequence: [{ foo: 1 }] */ Sequence.prototype.reject = function reject(rejectFn) { rejectFn = createCallback(rejectFn); return this.filter(function(e) { return !rejectFn(e); }); }; /** * Creates a new sequence whose values have the specified type, as determined * by the `typeof` operator. * * @public * @param {string} type The type of elements to include from the underlying * sequence, i.e. where `typeof [element] === [type]`. * @returns {Sequence} The new sequence, comprising elements of the specified * type. * * @examples * Lazy([1, 2, 'foo', 'bar']).ofType('number') // sequence: [1, 2] * Lazy([1, 2, 'foo', 'bar']).ofType('string') // sequence: ['foo', 'bar'] * Lazy([1, 2, 'foo', 'bar']).ofType('boolean') // sequence: [] */ Sequence.prototype.ofType = function ofType(type) { return this.filter(function(e) { return typeof e === type; }); }; /** * Creates a new sequence whose values are the elements of this sequence with * property names and values matching those of the specified object. * * @public * @param {Object} properties The properties that should be found on every * element that is to be included in this sequence. * @returns {Sequence} The new sequence. * * @examples * var people = [ * { first: "Dan", last: "Tao" }, * { first: "Bob", last: "Smith" } * ]; * * Lazy(people).where({ first: "Dan" }) // sequence: [{ first: "Dan", last: "Tao" }] * * @benchmarks * var animals = ["dog", "cat", "mouse", "horse", "pig", "snake"]; * * Lazy(animals).where({ length: 3 }).each(Lazy.noop) // lazy * _.each(_.where(animals, { length: 3 }), _.noop) // lodash */ Sequence.prototype.where = function where(properties) { return this.filter(properties); }; /** * Creates a new sequence with the same elements as this one, but to be iterated * in the opposite order. * * Note that in some (but not all) cases, the only way to create such a sequence * may require iterating the entire underlying source when `each` is called. * * @public * @returns {Sequence} The new sequence. * * @examples * Lazy([1, 2, 3]).reverse() // sequence: [3, 2, 1] * Lazy([]).reverse() // sequence: [] */ Sequence.prototype.reverse = function reverse() { return new ReversedSequence(this); }; /** * @constructor */ function ReversedSequence(parent) { this.parent = parent; } ReversedSequence.prototype = new Sequence(); ReversedSequence.prototype.getIterator = function getIterator() { return new ReversedIterator(this.parent); }; /** * @constuctor */ function ReversedIterator(sequence) { this.sequence = sequence; } ReversedIterator.prototype.current = function current() { return this.sequence.getIndex().get(this.index); }; ReversedIterator.prototype.moveNext = function moveNext() { var indexed = this.sequence.getIndex(), length = indexed.length(); if (typeof this.index === "undefined") { this.index = length; } return (--this.index >= 0); }; /** * Creates a new sequence with all of the elements of this one, plus those of * the given array(s). * * @public * @param {...*} var_args One or more values (or arrays of values) to use for * additional items after this sequence. * @returns {Sequence} The new sequence. * * @examples * var left = [1, 2, 3]; * var right = [4, 5, 6]; * * Lazy(left).concat(right) // sequence: [1, 2, 3, 4, 5, 6] * Lazy(left).concat(Lazy(right)) // sequence: [1, 2, 3, 4, 5, 6] * Lazy(left).concat(right, [7, 8]) // sequence: [1, 2, 3, 4, 5, 6, 7, 8] */ Sequence.prototype.concat = function concat(var_args) { return new ConcatenatedSequence(this, arraySlice.call(arguments, 0)); }; /** * @constructor */ function ConcatenatedSequence(parent, arrays) { this.parent = parent; this.arrays = arrays; } ConcatenatedSequence.prototype = new Sequence(); ConcatenatedSequence.prototype.each = function each(fn) { var done = false, i = 0; this.parent.each(function(e) { if (fn(e, i++) === false) { done = true; return false; } }); if (!done) { Lazy(this.arrays).flatten().each(function(e) { if (fn(e, i++) === false) { return false; } }); } }; /** * Creates a new sequence comprising the first N elements from this sequence, OR * (if N is `undefined`) simply returns the first element of this sequence. * * @public * @aka head, take * @param {number=} count The number of elements to take from this sequence. If * this value exceeds the length of the sequence, the resulting sequence * will be essentially the same as this one. * @returns {*} The new sequence (or the first element from this sequence if * no count was given). * * @examples * function powerOfTwo(exp) { * return Math.pow(2, exp); * } * * Lazy.generate(powerOfTwo).first() // => 1 * Lazy.generate(powerOfTwo).first(5) // sequence: [1, 2, 4, 8, 16] * Lazy.generate(powerOfTwo).skip(2).first() // => 4 * Lazy.generate(powerOfTwo).skip(2).first(2) // sequence: [4, 8] */ Sequence.prototype.first = function first(count) { if (typeof count === "undefined") { return getFirst(this); } return new TakeSequence(this, count); }; Sequence.prototype.head = Sequence.prototype.take = function (count) { return this.first(count); }; /** * @constructor */ function TakeSequence(parent, count) { this.parent = parent; this.count = count; } TakeSequence.prototype = new Sequence(); TakeSequence.prototype.getIterator = function getIterator() { return new TakeIterator(this.parent, this.count); }; TakeSequence.prototype.each = function each(fn) { var count = this.count, i = 0; this.parent.each(function(e) { var result; if (i < count) { result = fn(e, i); } if (++i >= count) { return false; } return result; }); }; /** * @constructor */ function TakeIterator(sequence, count) { this.iterator = sequence.getIterator(); this.count = count; } TakeIterator.prototype.current = function current() { return this.iterator.current(); }; TakeIterator.prototype.moveNext = function moveNext() { return ((--this.count >= 0) && this.iterator.moveNext()); }; /** * Creates a new sequence comprising the elements from the head of this sequence * that satisfy some predicate. Once an element is encountered that doesn't * satisfy the predicate, iteration will stop. * * @public * @param {Function} predicate * @returns {Sequence} The new sequence * * @examples * function lessThan(x) { * return function(y) { * return y < x; * }; * } * * Lazy([1, 2, 3, 4]).takeWhile(lessThan(3)) // sequence: [1, 2] * Lazy([1, 2, 3, 4]).takeWhile(lessThan(0)) // sequence: [] */ Sequence.prototype.takeWhile = function takeWhile(predicate) { return new TakeWhileSequence(this, predicate); }; /** * @constructor */ function TakeWhileSequence(parent, predicate) { this.parent = parent; this.predicate = predicate; } TakeWhileSequence.prototype = new Sequence(); TakeWhileSequence.prototype.each = function each(fn) { var predicate = this.predicate; this.parent.each(function(e) { return predicate(e) && fn(e); }); }; /** * Creates a new sequence comprising all but the last N elements of this * sequence. * * @public * @param {number=} count The number of items to omit from the end of the * sequence (defaults to 1). * @returns {Sequence} The new sequence. * * @examples * Lazy([1, 2, 3, 4]).initial() // sequence: [1, 2, 3] * Lazy([1, 2, 3, 4]).initial(2) // sequence: [1, 2] * Lazy([1, 2, 3]).filter(Lazy.identity).initial() // sequence: [1, 2] */ Sequence.prototype.initial = function initial(count) { if (typeof count === "undefined") { count = 1; } return this.take(this.getIndex().length() - count); }; /** * Creates a new sequence comprising the last N elements of this sequence, OR * (if N is `undefined`) simply returns the last element of this sequence. * * @public * @param {number=} count The number of items to take from the end of the * sequence. * @returns {*} The new sequence (or the last element from this sequence * if no count was given). * * @examples * Lazy([1, 2, 3]).last() // => 3 * Lazy([1, 2, 3]).last(2) // sequence: [2, 3] * Lazy([1, 2, 3]).filter(isEven).last(2) // sequence: [2] */ Sequence.prototype.last = function last(count) { if (typeof count === "undefined") { return this.reverse().first(); } return this.reverse().take(count).reverse(); }; /** * Returns the first element in this sequence with property names and values * matching those of the specified object. * * @public * @param {Object} properties The properties that should be found on some * element in this sequence. * @returns {*} The found element, or `undefined` if none exists in this * sequence. * * @examples * var words = ["foo", "bar"]; * * Lazy(words).findWhere({ 0: "f" }); // => "foo" * Lazy(words).findWhere({ 0: "z" }); // => undefined */ Sequence.prototype.findWhere = function findWhere(properties) { return this.where(properties).first(); }; /** * Creates a new sequence comprising all but the first N elements of this * sequence. * * @public * @aka skip, tail, rest * @param {number=} count The number of items to omit from the beginning of the * sequence (defaults to 1). * @returns {Sequence} The new sequence. * * @examples * Lazy([1, 2, 3, 4]).rest() // sequence: [2, 3, 4] * Lazy([1, 2, 3, 4]).rest(0) // sequence: [1, 2, 3, 4] * Lazy([1, 2, 3, 4]).rest(2) // sequence: [3, 4] * Lazy([1, 2, 3, 4]).rest(5) // sequence: [] */ Sequence.prototype.rest = function rest(count) { return new DropSequence(this, count); }; Sequence.prototype.skip = Sequence.prototype.tail = Sequence.prototype.drop = function drop(count) { return this.rest(count); }; /** * @constructor */ function DropSequence(parent, count) { this.parent = parent; this.count = typeof count === "number" ? count : 1; } DropSequence.prototype = new Sequence(); DropSequence.prototype.each = function each(fn) { var count = this.count, dropped = 0, i = 0; this.parent.each(function(e) { if (dropped++ < count) { return; } return fn(e, i++); }); }; /** * Creates a new sequence comprising the elements from this sequence *after* * those that satisfy some predicate. The sequence starts with the first * element that does not match the predicate. * * @public * @aka skipWhile * @param {Function} predicate * @returns {Sequence} The new sequence */ Sequence.prototype.dropWhile = function dropWhile(predicate) { return new DropWhileSequence(this, predicate); }; Sequence.prototype.skipWhile = function skipWhile(predicate) { return this.dropWhile(predicate); }; /** * @constructor */ function DropWhileSequence(parent, predicate) { this.parent = parent; this.predicate = predicate; } DropWhileSequence.prototype = new Sequence(); DropWhileSequence.prototype.each = function each(fn) { var predicate = this.predicate, done = false; this.parent.each(function(e) { if (!done) { if (predicate(e)) { return; } done = true; } return fn(e); }); }; /** * Creates a new sequence with the same elements as this one, but ordered * according to the values returned by the specified function. * * @public * @param {Function} sortFn The function to call on the elements in this * sequence, in order to sort them. * @returns {Sequence} The new sequence. * * @examples * function population(country) { * return country.pop; * } * * function area(country) { * return country.sqkm; * } * * var countries = [ * { name: "USA", pop: 320000000, sqkm: 9600000 }, * { name: "Brazil", pop: 194000000, sqkm: 8500000 }, * { name: "Nigeria", pop: 174000000, sqkm: 924000 }, * { name: "China", pop: 1350000000, sqkm: 9700000 }, * { name: "Russia", pop: 143000000, sqkm: 17000000 }, * { name: "Australia", pop: 23000000, sqkm: 7700000 } * ]; * * Lazy(countries).sortBy(population).last(3).pluck('name') // sequence: ["Brazil", "USA", "China"] * Lazy(countries).sortBy(area).last(3).pluck('name') // sequence: ["USA", "China", "Russia"] * * @benchmarks * var randoms = Lazy.generate(Math.random).take(100).toArray(); * * Lazy(randoms).sortBy(Lazy.identity).each(Lazy.noop) // lazy * _.each(_.sortBy(randoms, Lazy.identity), _.noop) // lodash */ Sequence.prototype.sortBy = function sortBy(sortFn) { return new SortedSequence(this, sortFn); }; /** * @constructor */ function SortedSequence(parent, sortFn) { this.parent = parent; this.sortFn = sortFn; } SortedSequence.prototype = new Sequence(); SortedSequence.prototype.each = function each(fn) { var sortFn = createCallback(this.sortFn), sorted = this.parent.toArray(), i = -1; sorted.sort(function(x, y) { return compare(x, y, sortFn); }); return forEach(sorted, fn); }; /** * Creates a new {@link ObjectLikeSequence} comprising the elements in this * one, grouped together according to some key. The value associated with each * key in the resulting object-like sequence is an array containing all of * the elements in this sequence with that key. * * @public * @param {Function|string} keyFn The function to call on the elements in this * sequence to obtain a key by which to group them, or a string representing * a parameter to read from all the elements in this sequence. * @returns {Sequence} The new sequence. * * @examples * function oddOrEven(x) { * return x % 2 === 0 ? 'even' : 'odd'; * } * * var numbers = [1, 2, 3, 4, 5]; * * Lazy(numbers).groupBy(oddOrEven) // sequence: { odd: [1, 3, 5], even: [2, 4] } * Lazy(numbers).groupBy(oddOrEven).get("odd") // => [1, 3, 5] * Lazy(numbers).groupBy(oddOrEven).get("foo") // => undefined */ Sequence.prototype.groupBy = function groupBy(keyFn) { return new GroupedSequence(this, keyFn); }; /** * @constructor */ function GroupedSequence(parent, keyFn) { this.parent = parent; this.keyFn = keyFn; } // GroupedSequence must have its prototype set after ObjectLikeSequence has // been fully initialized. /** * Creates a new {@link ObjectLikeSequence} containing the unique keys of all * the elements in this sequence, each paired with the number of elements * in this sequence having that key. * * @public * @param {Function|string} keyFn The function to call on the elements in this * sequence to obtain a key by which to count them, or a string representing * a parameter to read from all the elements in this sequence. * @returns {Sequence} The new sequence. * * @examples * function oddOrEven(x) { * return x % 2 === 0 ? 'even' : 'odd'; * } * * var numbers = [1, 2, 3, 4, 5]; * * Lazy(numbers).countBy(oddOrEven) // sequence: { odd: 3, even: 2 } * Lazy(numbers).countBy(oddOrEven).get("odd") // => 3 * Lazy(numbers).countBy(oddOrEven).get("foo") // => undefined */ Sequence.prototype.countBy = function countBy(keyFn) { return new CountedSequence(this, keyFn); }; /** * @constructor */ function CountedSequence(parent, keyFn) { this.parent = parent; this.keyFn = keyFn; } // CountedSequence, like GroupedSequence, must have its prototype set after // ObjectLikeSequence has been fully initialized. /** * Creates a new sequence with every unique element from this one appearing * exactly once (i.e., with duplicates removed). * * @public * @aka unique * @returns {Sequence} The new sequence. * * @examples * Lazy([1, 2, 2, 3, 3, 3]).uniq() // sequence: [1, 2, 3] * * @benchmarks * function randomOf(array) { * return function() { * return array[Math.floor(Math.random() * array.length)]; * }; * } * * var mostUnique = Lazy.generate(randomOf(_.range(100)), 100).toArray(), * someUnique = Lazy.generate(randomOf(_.range(50)), 100).toArray(), * mostDupes = Lazy.generate(randomOf(_.range(5)), 100).toArray(); * * Lazy(mostUnique).uniq().each(Lazy.noop) // lazy - mostly unique elements * Lazy(someUnique).uniq().each(Lazy.noop) // lazy - some unique elements * Lazy(mostDupes).uniq().each(Lazy.noop) // lazy - mostly duplicate elements * _.each(_.uniq(mostUnique), _.noop) // lodash - mostly unique elements * _.each(_.uniq(someUnique), _.noop) // lodash - some unique elements * _.each(_.uniq(mostDupes), _.noop) // lodash - mostly duplicate elements */ Sequence.prototype.uniq = function uniq(keyFn) { return new UniqueSequence(this, keyFn); }; Sequence.prototype.unique = function unique(keyFn) { return this.uniq(keyFn); }; /** * @constructor */ function UniqueSequence(parent, keyFn) { this.parent = parent; this.keyFn = keyFn; } UniqueSequence.prototype = new Sequence(); UniqueSequence.prototype.each = function each(fn) { var cache = new Set(), keyFn = this.keyFn, i = 0; if (keyFn) { keyFn = createCallback(keyFn); return this.parent.each(function(e) { if (cache.add(keyFn(e))) { return fn(e, i++); } }); } else { return this.parent.each(function(e) { if (cache.add(e)) { return fn(e, i++); } }); } }; /** * Creates a new sequence by combining the elements from this sequence with * corresponding elements from the specified array(s). * * @public * @param {...Array} var_args One or more arrays of elements to combine with * those of this sequence. * @returns {Sequence} The new sequence. * * @examples * Lazy([1, 2]).zip([3, 4]) // sequence: [[1, 3], [2, 4]] * * @benchmarks * var smArrL = Lazy.range(10).toArray(), * smArrR = Lazy.range(10, 20).toArray(), * lgArrL = Lazy.range(100).toArray(), * lgArrR = Lazy.range(100, 200).toArray(); * * Lazy(smArrL).zip(smArrR).each(Lazy.noop) // lazy - zipping 10-element arrays * Lazy(lgArrL).zip(lgArrR).each(Lazy.noop) // lazy - zipping 100-element arrays * _.each(_.zip(smArrL, smArrR), _.noop) // lodash - zipping 10-element arrays * _.each(_.zip(lgArrL, lgArrR), _.noop) // lodash - zipping 100-element arrays */ Sequence.prototype.zip = function zip(var_args) { if (arguments.length === 1) { return new SimpleZippedSequence(this, (/** @type {Array} */ var_args)); } else { return new ZippedSequence(this, arraySlice.call(arguments, 0)); } }; /** * @constructor */ function ZippedSequence(parent, arrays) { this.parent = parent; this.arrays = arrays; } ZippedSequence.prototype = new Sequence(); ZippedSequence.prototype.each = function each(fn) { var arrays = this.arrays, i = 0; this.parent.each(function(e) { var group = [e]; for (var j = 0; j < arrays.length; ++j) { if (arrays[j].length > i) { group.push(arrays[j][i]); } } return fn(group, i++); }); }; /** * Creates a new sequence with the same elements as this one, in a randomized * order. * * @public * @returns {Sequence} The new sequence. * * @examples * Lazy([1, 2, 3, 4, 5]).shuffle() // the values [1, 2, 3, 4, 5] in any order */ Sequence.prototype.shuffle = function shuffle() { return new ShuffledSequence(this); }; /** * @constructor */ function ShuffledSequence(parent) { this.parent = parent; } ShuffledSequence.prototype = new Sequence(); ShuffledSequence.prototype.each = function each(fn) { var shuffled = this.parent.toArray(), floor = Math.floor, random = Math.random, j = 0; for (var i = shuffled.length - 1; i > 0; --i) { swap(shuffled, i, floor(random() * i) + 1); if (fn(shuffled[i], j++) === false) { return; } } fn(shuffled[0], j); }; /** * Creates a new sequence with every element from this sequence, and with arrays * exploded so that a sequence of arrays (of arrays) becomes a flat sequence of * values. * * @public * @returns {Sequence} The new sequence. * * @examples * Lazy([1, [2, 3], [4, [5]]]).flatten() // sequence: [1, 2, 3, 4, 5] * Lazy([1, Lazy([2, 3])]).flatten() // sequence: [1, 2, 3] */ Sequence.prototype.flatten = function flatten() { return new FlattenedSequence(this); }; /** * @constructor */ function FlattenedSequence(parent) { this.parent = parent; } FlattenedSequence.prototype = new Sequence(); FlattenedSequence.prototype.each = function each(fn) { var index = 0; return this.parent.each(function recurseVisitor(e) { if (e instanceof Array) { return forEach(e, recurseVisitor); } if (e instanceof Sequence) { return e.each(recurseVisitor); } return fn(e, index++); }); }; /** * Creates a new sequence with the same elements as this one, except for all * falsy values (`false`, `0`, `""`, `null`, and `undefined`). * * @public * @returns {Sequence} The new sequence. * * @examples * Lazy(["foo", null, "bar", undefined]).compact() // sequence: ["foo", "bar"] */ Sequence.prototype.compact = function compact() { return this.filter(function(e) { return !!e; }); }; /** * Creates a new sequence with all the elements of this sequence that are not * also among the specified arguments. * * @public * @aka difference * @param {...*} var_args The values, or array(s) of values, to be excluded from the * resulting sequence. * @returns {Sequence} The new sequence. * * @examples * Lazy([1, 2, 3, 4, 5]).without(2, 3) // sequence: [1, 4, 5] * Lazy([1, 2, 3, 4, 5]).without([4, 5]) // sequence: [1, 2, 3] */ Sequence.prototype.without = function without(var_args) { return new WithoutSequence(this, arraySlice.call(arguments, 0)); }; Sequence.prototype.difference = function difference(var_args) { return this.without.apply(this, arguments); }; /** * @constructor */ function WithoutSequence(parent, values) { this.parent = parent; this.values = values; } WithoutSequence.prototype = new Sequence(); WithoutSequence.prototype.each = function each(fn) { var set = createSet(this.values), i = 0; return this.parent.each(function(e) { if (!set.contains(e)) { return fn(e, i++); } }); }; /** * Creates a new sequence with all the unique elements either in this sequence * or among the specified arguments. * * @public * @param {...*} var_args The values, or array(s) of values, to be additionally * included in the resulting sequence. * @returns {Sequence} The new sequence. * * @examples * Lazy(["foo", "bar"]).union([]) // sequence: ["foo", "bar"] * Lazy(["foo", "bar"]).union(["bar", "baz"]) // sequence: ["foo", "bar", "baz"] */ Sequence.prototype.union = function union(var_args) { return this.concat(var_args).uniq(); }; /** * Creates a new sequence with all the elements of this sequence that also * appear among the specified arguments. * * @public * @param {...*} var_args The values, or array(s) of values, in which elements * from this sequence must also be included to end up in the resulting sequence. * @returns {Sequence} The new sequence. * * @examples * Lazy(["foo", "bar"]).intersection([]) // sequence: [] * Lazy(["foo", "bar"]).intersection(["bar", "baz"]) // sequence: ["bar"] */ Sequence.prototype.intersection = function intersection(var_args) { if (arguments.length === 1 && arguments[0] instanceof Array) { return new SimpleIntersectionSequence(this, (/** @type {Array} */ var_args)); } else { return new IntersectionSequence(this, arraySlice.call(arguments, 0)); } }; /** * @constructor */ function IntersectionSequence(parent, arrays) { this.parent = parent; this.arrays = arrays; } IntersectionSequence.prototype = new Sequence(); IntersectionSequence.prototype.each = function each(fn) { var sets = Lazy(this.arrays).map(function(values) { return new UniqueMemoizer(Lazy(values).getIterator()); }); var setIterator = new UniqueMemoizer(sets.getIterator()), i = 0; return this.parent.each(function(e) { var includedInAll = true; setIterator.each(function(set) { if (!set.contains(e)) { includedInAll = false; return false; } }); if (includedInAll) { return fn(e, i++); }