1 /**
  2  * @fileOverview Extensions/helper methods for Arrays and Array-like objects
  3  * Most methods are JavaScript implementations of the extra native Array methods introduced in JavaScript 1.6
  4  * @see <a href="https://developer.mozilla.org/en/New_in_JavaScript_1.6">New in JavaScript 1.6</a>
  5  */
  6 
  7 if (!Array.prototype.forEach) {
  8 	/**
  9 	 * Executes a provided function once per array element.
 10 	 * @param {Function} callback function to execute for each element
 11 	 * @param {Object} [context] to use as this when executing callback
 12 	 * @author <a href="http://developer.mozilla.org">Mozilla</a>
 13 	 * @see <a href="http://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Array/forEach">http://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Array/forEach</a>
 14 	 * @example
 15 	 * ['moo', 'cow', 'woof', 'dog'].forEach(alert);
 16 	 */
 17 	Array.prototype.forEach = function forEach(callback, context) {
 18 		var len = this.length;
 19 	
 20 		if (typeof callback !== "function") {
 21 			throw new TypeError();
 22 		}
 23 	
 24 		for (var i = 0; i < len; i++) {
 25 			if (i in this) {
 26 				callback.call(context, this[i], i, this);
 27 			}
 28 		}
 29 	};
 30 }
 31 if (!Array.forEach) {
 32 	/**
 33 	 * Executes a provided function once per array-like element.
 34 	 * @param {Array-like} array Array-like object to iterate over
 35 	 * @param {Function} callback function to execute for each element
 36 	 * @param {Object} [context] to use as this when executing callback
 37 	 * @author <a href="http://dean.edwards.name/">Dean Edwards</a>
 38 	 * @see <a href="http://dean.edwards.name/weblog/2006/07/enum/">http://dean.edwards.name/weblog/2006/07/enum/</a>
 39 	 * @see Array#forEach
 40 	 * @example
 41 	 * Array.forEach(document.getElementsByTagName('div'),alert);
 42 	 */
 43 	Array.forEach = function forEachHelper(array, callback, context) {
 44 		Array.prototype.forEach.call(array, callback, context);
 45 	};
 46 }
 47 
 48 if (!Array.prototype.indexOf) {
 49 	/**
 50 	 * Returns the first index at which a given element can be found in the array, or -1 if it is not present
 51 	 * @param searchElement Element to locate in the array
 52 	 * @param {Number} [from=0] the index at which to begin the search. If negative, it is taken as the offset from the end of the array
 53 	 * @returns {Number} the index the element was found at, or -1 if not found
 54 	 * @author <a href="http://developer.mozilla.org">Mozilla</a>
 55 	 * @see <a href="http://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Array/indexOf">http://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Array/indexOf</a>
 56 	 */
 57 	Array.prototype.indexOf = function indexOf(searchElement, from) {
 58 		var len = this.length;
 59 		
 60 		from = Number(from) || 0;
 61 		from = (from < 0) ? Math.ceil(from) : Math.floor(from);
 62 		if (from < 0) {
 63 			from += len;
 64 		}
 65 		
 66 		for (; from < len; from++) {
 67 			if (from in this && this[from] === searchElement) {
 68 				return from;
 69 			}
 70 		}
 71 		return -1;
 72 	};
 73 }
 74 
 75 if (!Array.prototype.lastIndexOf) {
 76 	/**
 77 	 * Returns the last index at which a given element can be found in the array, or -1 if it is not present. The array is searched backwards, starting at from
 78 	 * @param searchElement value to find the last index of
 79 	 * @param {Number} [from=index of the last entry] the index to start searching from
 80 	 * @returns {Number} the index the value was found at, or -1 if not found
 81 	 * @author <a href="http://developer.mozilla.org">Mozilla</a>
 82 	 * @see <a href="http://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Array/lastIndexOf">http://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Array/lastIndexOf</a>
 83 	 */
 84 	Array.prototype.lastIndexOf = function lastIndexOf(searchElement, from) {
 85 		var len = this.length;
 86 		
 87 		from = Number(from);
 88 		if (isNaN(from)) {
 89 			from = len - 1;
 90 		}
 91 		else {
 92 			from = (from < 0) ? Math.ceil(from) : Math.floor(from);
 93 			if (from < 0) {
 94 				from += len;
 95 			}
 96 			else if (from >= len) {
 97 				from = len - 1;
 98 			}
 99 		}
100 		
101 		for (; from > -1; from--) {
102 			if (from in this && this[from] === searchElement) {
103 				return from;
104 			}
105 		}
106 		return -1;
107 	};
108 }
109 
110 if (!Array.prototype.filter) {
111 	/**
112 	 * Creates a new array with all elements that pass the test implemented by the provided function.
113 	 * @param {Function} callback function to test each element of the array
114 	 * @param {Object} [context] to use as this when executing callback
115 	 * @returns {Array} a new array with all elements that pass the test implemented by the provided function
116 	 * @author <a href="http://developer.mozilla.org">Mozilla</a>
117 	 * @see <a href="http://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Array/filter">http://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Array/filter</a>
118 	 * @example
119 	 * function isBigEnough(element, index, array) {
120 	 * 	return (element >= 10);
121 	 * }
122 	 * var filtered = [12, 5, 8, 130, 44].filter(isBigEnough);
123 	 */
124 	Array.prototype.filter = function filter(callback, context) {
125 		var len = this.length;
126 		if (typeof callback !== "function") {
127 			throw new TypeError();
128 		}
129 		
130 		var res = [];
131 		for (var i = 0; i < len; i++) {
132 			if (i in this) {
133 				var val = this[i]; // in case fun mutates this
134 				if (callback.call(context, val, i, this)) {
135 					res.push(val);
136 				}
137 			}
138 		}
139 		return res;
140 	};
141 }
142 
143 if (!Array.prototype.every) {
144 	/**
145 	 * Tests whether all elements in the array pass the test implemented by the provided function
146 	 * @param {Function} callback function to test for each element
147 	 * @param {Object} [context] to use as this when executing callback
148 	 * @returns {Boolean} true if all elements pass the test implented by callback, false if not
149 	 * @author <a href="http://developer.mozilla.org">Mozilla</a>
150 	 * @see <a href="http://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Array/every">http://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Array/every</a>
151 	 * @example
152 	 * function isBigEnough(element, index, array) {
153 	 * 	return (element >= 10);
154 	 * }
155 	 * var passed = [12, 5, 8, 130, 44].every(isBigEnough); //false
156 	 * passed = [12, 54, 18, 130, 44].every(isBigEnough); //true
157 	 */
158 	Array.prototype.every = function every(callback, context) {
159 		var len = this.length;
160 		if (typeof callback !== "function") {
161 			throw new TypeError();
162 		}
163 	
164 		for (var i = 0; i < len; i++) {
165 			if (i in this && !callback.call(context, this[i], i, this)) {
166 				return false;
167 			}
168 		}
169 		return true;
170 	};
171 }
172 
173 if (!Array.prototype.map) {
174 	/**
175 	 * Creates a new array with the results of calling a provided function on every element in this array
176 	 * @param {Function} callback function that produces an element of the new Array from an element of the current one
177 	 * @param {Object} [context] to use as this when executing callback
178 	 * @returns {Array} new array with the results of calling a provided function on every element in this array
179 	 * @author <a href="http://developer.mozilla.org">Mozilla</a>
180 	 * @see <a href="http://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Array/map">http://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Array/map</a>
181 	 * @example
182 	 * function makePseudoPlural(single) {
183 	 *     return single.replace(/o/g, "e");
184 	 * }
185 	 * var singles = ["foot", "goose", "moose"];
186 	 * var plurals = singles.map(makePseudoPlural); //["feet", "geese", "meese"]
187 	 */
188 	Array.prototype.map = function map(callback, context) {
189 		var len = this.length;
190 		if (typeof callback !== "function") {
191 			throw new TypeError();
192 		}
193 	
194 		var res = [];
195 		for (var i = 0; i < len; i++) {
196 			if (i in this) {
197 				res[i] = callback.call(context, this[i], i, this);
198 			}
199 		}
200 		return res;
201 	};
202 }	
203 
204 if (!Array.prototype.some) {
205 	/**
206 	 * Tests whether some element in the array passes the test implemented by the provided function
207 	 * @param {Function} callback fucntion to test for each element
208 	 * @param {Object} [context] to use as this when executing callback
209 	 * @returns {Boolean} true if some element passes the test implented by callback, false if not
210 	 * @author <a href="http://developer.mozilla.org">Mozilla</a>
211 	 * @see <a href="http://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Array/some">http://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Array/some</a>
212 	 * @example
213 	 * function isBigEnough(element, index, array) {
214 	 * 	return (element >= 10);
215 	 * }
216 	 * var passed = [2, 5, 8, 1, 4].every(isBigEnough); //false
217 	 * passed = [12, 5, 8, 1, 4].every(isBigEnough); //true
218 	 */
219 	Array.prototype.some = function some(callback, context) {
220 		var len = this.length;
221 		if (typeof callback !== "function") {
222 			throw new TypeError();
223 		}
224 
225 		for (var i = 0; i < len; i++) {
226 			if (i in this && callback.call(context, this[i], i, this)) {
227 				return true;
228 			}
229 		}
230 	
231 		return false;
232 	};
233 }
234 
235 if (!Array.prototype.reduce) {	
236 	/**
237 	 * Apply a function simultaneously against two values of the array (from left-to-right) as to reduce it to a single value.
238 	 * @param {Function} callback function to execute on each value in the array
239 	 * @param [initial] object to use as the first argument to the first call of the callback
240 	 * @returns the value calculated by executing callback on each element of the array
241 	 * @author <a href="http://developer.mozilla.org">Mozilla</a>
242 	 * @see <a href="http://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/reduce">http://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/reduce</a>
243 	 * @see Array.prototype#reduceRight
244 	 * @example
245 	 * var total = [0, 1, 2, 3].reduce(function(a, b){ return a + b; }); //6
246 	 * var flattened = [[0,1], [2,3], [4,5]].reduce(function(a,b) {
247 	 *     return a.concat(b);
248 	 * }, []); //[0, 1, 2, 3, 4, 5]
249 	 */
250 	Array.prototype.reduce = function reduce(callback, initial) {
251 		var len = this.length;
252 		if (typeof callback !== "function") {
253 			throw new TypeError();
254 		}
255 	
256 		// no value to return if no initial value and an empty array
257 		if (len === 0 && arguments.length === 1) {
258 			throw new TypeError();
259 		}
260 	
261 		var i = 0;
262 		if (arguments.length >= 2) {
263 			var rv = initial;
264 		}
265 		else {
266 			do {
267 				if (i in this) {
268 					rv = this[i++];
269 					break;
270 				}
271 	
272 				// if array contains no values, no initial value to return
273 				if (++i >= len) {
274 					throw new TypeError();
275 				}
276 			}
277 			while (true);
278 		}
279 	
280 		for (; i < len; i++) {
281 			if (i in this) {
282 				rv = callback.call(null, rv, this[i], i, this);
283 			}
284 		}
285 	
286 		return rv;
287 	};
288 }
289 
290 if (!Array.prototype.reduceRight) {
291 	/**
292 	 * Apply a function simultaneously against two values of the array (from right-to-left) as to reduce it to a single value.
293 	 * @param {Function} callback function to execute on each value in the array
294 	 * @param [initial] object to use as the first argument to the first call of the callback
295 	 * @returns the value calculated by executing callback on each element of the array
296 	 * @author <a href="http://developer.mozilla.org">Mozilla</a>
297 	 * @see <a href="http://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/reduce">http://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/reduce</a>
298 	 * @see Array#reduce
299 	 * @example
300 	 * var total = [0, 1, 2, 3].reduceRight(function(a, b) { return a + b; }); //6
301 	 * var flattened = [[0,1], [2,3], [4,5]].reduceRight(function(a,b) {
302 	 *     return a.concat(b);
303 	 * }, []); //flattened is [4, 5, 2, 3, 0, 1]
304 	 */
305 	Array.prototype.reduceRight = function reduceRight(callback, initial) {
306 		var len = this.length;
307 		if (typeof callback !== "function") {
308 			throw new TypeError();
309 		}
310 	
311 		// no value to return if no initial value, empty array
312 		if (len === 0 && arguments.length === 1) {
313 			throw new TypeError();
314 		}
315 	
316 		var i = len - 1;
317 		if (arguments.length >= 2) {
318 			var rv = initial;
319 		}
320 		else {
321 			do {
322 				if (i in this) {
323 					rv = this[i--];
324 					break;
325 				}
326 	
327 			// if array contains no values, no initial value to return
328 				if (--i < 0) {
329 					throw new TypeError();
330 				}
331 			}
332 			while (true);
333 		}
334 	
335 		for (; i >= 0; i--) {
336 			if (i in this) {
337 				rv = callback.call(null, rv, this[i], i, this);
338 			}
339 		}
340 		return rv;
341 	};
342 }
343 
344 if (!Array.prototype.inArray) {
345 	/**
346 	 * Convenience method to check if a value is in an array
347 	 * @param value value to check for in array
348 	 * @returns {Boolean} true if value is found, false if not
349 	 * @requires Array#indexOf
350 	 * @example
351 	 * var test = [0, '1', 2].inArray(1); //false
352 	 * test = [0, '1', 2].inArray('1'); //true
353 	 */
354 	Array.prototype.inArray = function inArray(value) {
355 		return (this.indexOf(value) >= 0);
356 	};
357 }
358 
359 if (!Array.prototype.collect) {
360 	/**
361 	 * Special-case implementation of {@link Array#map} to collect the same property from every element of an Array
362 	 * @param {String} property the name of the property to collect from each Array element
363 	 * @returns {Array} a new Array with the specified property as the element values
364 	 * @requires Array#map
365 	 */
366 	Array.prototype.collect = function collect(property) {
367 		return this.map(function(entry) {
368 			return entry[property];
369 		});
370 	};
371 }
372 
373 if (!Array.prototype.unique) {		
374 	/**
375 	 * Special-case implementation of {@link Array#filter} to remove duplicate entries from an Array
376 	 * @returns {Array} a new Array with the duplicate elements removed
377 	 * @requires Array#filter
378 	 */
379 	Array.prototype.unique = function unique() {
380 		var arr = this.filter(function(entry, index) {
381 			return this.indexOf(entry, index+1) === -1;
382 		}, this);
383 		
384 		return arr;
385 	};
386 }
387