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