/** * @author Jacky Nguyen <jacky@sencha.com> * @docauthor Jacky Nguyen <jacky@sencha.com> * @class Ext.Array * * A set of useful static methods to deal with arrays; provide missing methods for older browsers. * @singleton * @markdown */ (function() { var arrayPrototype = Array.prototype, slice = arrayPrototype.slice, supportsForEach = 'forEach' in arrayPrototype, supportsMap = 'map' in arrayPrototype, supportsIndexOf = 'indexOf' in arrayPrototype, supportsEvery = 'every' in arrayPrototype, supportsSome = 'some' in arrayPrototype, supportsFilter = 'filter' in arrayPrototype, supportsSort = function() { var a = [1,2,3,4,5].sort(function(){ return 0; }); return a[0] === 1 && a[1] === 2 && a[2] === 3 && a[3] === 4 && a[4] === 5; }(), supportsSliceOnNodeList = true, ExtArray; try { // IE 6 - 8 will throw an error when using Array.prototype.slice on NodeList if (typeof document !== 'undefined') { slice.call(document.getElementsByTagName('body')); } } catch (e) { supportsSliceOnNodeList = false; } ExtArray = Ext.Array = { /** * Iterates an array or an iterable value and invoke the given callback function for each item. var countries = ['Vietnam', 'Singapore', 'United States', 'Russia']; Ext.Array.each(countries, function(name, index, countriesItSelf) { console.log(name); }); var sum = function() { var sum = 0; Ext.Array.each(arguments, function(value) { sum += value; }); return sum; }; sum(1, 2, 3); // returns 6 * The iteration can be stopped by returning false in the function callback. Ext.Array.each(countries, function(name, index, countriesItSelf) { if (name === 'Singapore') { return false; // break here } }); * @param {Array/NodeList/Mixed} iterable The value to be iterated. If this * argument is not iterable, the callback function is called once. * @param {Function} fn The callback function. If it returns false, the iteration stops and this method returns * the current `index`. Arguments passed to this callback function are: - `item`: {Mixed} The item at the current `index` in the passed `array` - `index`: {Number} The current `index` within the `array` - `allItems`: {Array/NodeList/Mixed} The `array` passed as the first argument to `Ext.Array.each` * @param {Object} scope (Optional) The scope (`this` reference) in which the specified function is executed. * @param {Boolean} reverse (Optional) Reverse the iteration order (loop from the end to the beginning) * Defaults false * @return {Boolean} See description for the `fn` parameter. * @markdown */ each: function(array, fn, scope, reverse) { array = ExtArray.from(array); var i, ln = array.length; if (reverse !== true) { for (i = 0; i < ln; i++) { if (fn.call(scope || array[i], array[i], i, array) === false) { return i; } } } else { for (i = ln - 1; i > -1; i--) { if (fn.call(scope || array[i], array[i], i, array) === false) { return i; } } } return true; }, /** * Iterates an array and invoke the given callback function for each item. Note that this will simply * delegate to the native Array.prototype.forEach method if supported. * It doesn't support stopping the iteration by returning false in the callback function like * {@link Ext.Array#each}. However, performance could be much better in modern browsers comparing with * {@link Ext.Array#each} * * @param {Array} array The array to iterate * @param {Function} fn The function callback, to be invoked these arguments: * - `item`: {Mixed} The item at the current `index` in the passed `array` - `index`: {Number} The current `index` within the `array` - `allItems`: {Array} The `array` itself which was passed as the first argument * @param {Object} scope (Optional) The execution scope (`this`) in which the specified function is executed. * @markdown */ forEach: function(array, fn, scope) { if (supportsForEach) { return array.forEach(fn, scope); } var i = 0, ln = array.length; for (; i < ln; i++) { fn.call(scope, array[i], i, array); } }, /** * Get the index of the provided `item` in the given `array`, a supplement for the * missing arrayPrototype.indexOf in Internet Explorer. * * @param {Array} array The array to check * @param {Mixed} item The item to look for * @param {Number} from (Optional) The index at which to begin the search * @return {Number} The index of item in the array (or -1 if it is not found) * @markdown */ indexOf: function(array, item, from) { if (supportsIndexOf) { return array.indexOf(item, from); } var i, length = array.length; for (i = (from < 0) ? Math.max(0, length + from) : from || 0; i < length; i++) { if (array[i] === item) { return i; } } return -1; }, /** * Checks whether or not the given `array` contains the specified `item` * * @param {Array} array The array to check * @param {Mixed} item The item to look for * @return {Boolean} True if the array contains the item, false otherwise * @markdown */ contains: function(array, item) { if (supportsIndexOf) { return array.indexOf(item) !== -1; } var i, ln; for (i = 0, ln = array.length; i < ln; i++) { if (array[i] === item) { return true; } } return false; }, /** * Converts any iterable (numeric indices and a length property) into a true array. function test() { var args = Ext.Array.toArray(arguments), fromSecondToLastArgs = Ext.Array.toArray(arguments, 1); alert(args.join(' ')); alert(fromSecondToLastArgs.join(' ')); } test('just', 'testing', 'here'); // alerts 'just testing here'; // alerts 'testing here'; Ext.Array.toArray(document.getElementsByTagName('div')); // will convert the NodeList into an array Ext.Array.toArray('splitted'); // returns ['s', 'p', 'l', 'i', 't', 't', 'e', 'd'] Ext.Array.toArray('splitted', 0, 3); // returns ['s', 'p', 'l', 'i'] * @param {Mixed} iterable the iterable object to be turned into a true Array. * @param {Number} start (Optional) a zero-based index that specifies the start of extraction. Defaults to 0 * @param {Number} end (Optional) a zero-based index that specifies the end of extraction. Defaults to the last * index of the iterable value * @return {Array} array * @markdown */ toArray: function(iterable, start, end){ if (!iterable || !iterable.length) { return []; } if (typeof iterable === 'string') { iterable = iterable.split(''); } if (supportsSliceOnNodeList) { return slice.call(iterable, start || 0, end || iterable.length); } var array = [], i; start = start || 0; end = end ? ((end < 0) ? iterable.length + end : end) : iterable.length; for (i = start; i < end; i++) { array.push(iterable[i]); } return array; }, /** * Plucks the value of a property from each item in the Array. Example: * Ext.Array.pluck(Ext.query("p"), "className"); // [el1.className, el2.className, ..., elN.className] * @param {Array|NodeList} array The Array of items to pluck the value from. * @param {String} propertyName The property name to pluck from each element. * @return {Array} The value from each item in the Array. */ pluck: function(array, propertyName) { var ret = [], i, ln, item; for (i = 0, ln = array.length; i < ln; i++) { item = array[i]; ret.push(item[propertyName]); } return ret; }, /** * Creates a new array with the results of calling a provided function on every element in this array. * @param {Array} array * @param {Function} fn Callback function for each item * @param {Object} scope Callback function scope * @return {Array} results */ map: function(array, fn, scope) { if (supportsMap) { return array.map(fn, scope); } var results = [], i = 0, len = array.length; for (; i < len; i++) { results[i] = fn.call(scope, array[i], i, array); } return results; }, /** * Executes the specified function for each array element until the function returns a falsy value. * If such an item is found, the function will return false immediately. * Otherwise, it will return true. * * @param {Array} array * @param {Function} fn Callback function for each item * @param {Object} scope Callback function scope * @return {Boolean} True if no false value is returned by the callback function. */ every: function(array, fn, scope) { //<debug> if (!fn) { Ext.Error.raise('Ext.Array.every must have a callback function passed as second argument.'); } //</debug> if (supportsEvery) { return array.every(fn, scope); } var i = 0, ln = array.length; for (; i < ln; ++i) { if (!fn.call(scope, array[i], i, array)) { return false; } } return true; }, /** * Executes the specified function for each array element until the function returns a truthy value. * If such an item is found, the function will return true immediately. Otherwise, it will return false. * * @param {Array} array * @param {Function} fn Callback function for each item * @param {Object} scope Callback function scope * @return {Boolean} True if the callback function returns a truthy value. */ some: function(array, fn, scope) { //<debug> if (!fn) { Ext.Error.raise('Ext.Array.some must have a callback function passed as second argument.'); } //</debug> if (supportsSome) { return array.some(fn, scope); } var i = 0, ln = array.length; for (; i < ln; ++i) { if (fn.call(scope, array[i], i, array)) { return true; } } return false; }, /** * Filter through an array and remove empty item as defined in {@link Ext#isEmpty Ext.isEmpty} * * @see Ext.Array.filter * @param {Array} array * @return {Array} results */ clean: function(array) { var results = [], i = 0, ln = array.length, item; for (; i < ln; i++) { item = array[i]; if (!Ext.isEmpty(item)) { results.push(item); } } return results; }, /** * Returns a new array with unique items * * @param {Array} array * @return {Array} results */ unique: function(array) { var clone = [], i = 0, ln = array.length, item; for (; i < ln; i++) { item = array[i]; if (ExtArray.indexOf(clone, item) === -1) { clone.push(item); } } return clone; }, /** * Creates a new array with all of the elements of this array for which * the provided filtering function returns true. * @param {Array} array * @param {Function} fn Callback function for each item * @param {Object} scope Callback function scope * @return {Array} results */ filter: function(array, fn, scope) { if (supportsFilter) { return array.filter(fn, scope); } var results = [], i = 0, ln = array.length; for (; i < ln; i++) { if (fn.call(scope, array[i], i, array)) { results.push(array[i]); } } return results; }, /** * Converts a value to an array if it's not already an array; returns: * * - An empty array if given value is `undefined` or `null` * - Itself if given value is already an array * - An array copy if given value is {@link Ext#isIterable iterable} (arguments, NodeList and alike) * - An array with one item which is the given value, otherwise * * @param {Array/Mixed} value The value to convert to an array if it's not already is an array * @param {Boolean} (Optional) newReference True to clone the given array and return a new reference if necessary, * defaults to false * @return {Array} array * @markdown */ from: function(value, newReference) { if (value === undefined || value === null) { return []; } if (Ext.isArray(value)) { return (newReference) ? slice.call(value) : value; } if (value && value.length !== undefined && typeof value !== 'string') { return Ext.toArray(value); } return [value]; }, /** * Removes the specified item from the array if it exists * * @param {Array} array The array * @param {Mixed} item The item to remove * @return {Array} The passed array itself */ remove: function(array, item) { var index = ExtArray.indexOf(array, item); if (index !== -1) { array.splice(index, 1); } return array; }, /** * Push an item into the array only if the array doesn't contain it yet * * @param {Array} array The array * @param {Mixed} item The item to include * @return {Array} The passed array itself */ include: function(array, item) { if (!ExtArray.contains(array, item)) { array.push(item); } }, /** * Clone a flat array without referencing the previous one. Note that this is different * from Ext.clone since it doesn't handle recursive cloning. It's simply a convenient, easy-to-remember method * for Array.prototype.slice.call(array) * * @param {Array} array The array * @return {Array} The clone array */ clone: function(array) { return slice.call(array); }, /** * Merge multiple arrays into one with unique items. Alias to {@link Ext.Array#union}. * * @param {Array} array,... * @return {Array} merged */ merge: function() { var args = slice.call(arguments), array = [], i, ln; for (i = 0, ln = args.length; i < ln; i++) { array = array.concat(args[i]); } return ExtArray.unique(array); }, /** * Merge multiple arrays into one with unique items that exist in all of the arrays. * * @param {Array} array,... * @return {Array} intersect */ intersect: function() { var intersect = [], arrays = slice.call(arguments), i, j, k, minArray, array, x, y, ln, arraysLn, arrayLn; if (!arrays.length) { return intersect; } // Find the smallest array for (i = x = 0,ln = arrays.length; i < ln,array = arrays[i]; i++) { if (!minArray || array.length < minArray.length) { minArray = array; x = i; } } minArray = Ext.Array.unique(minArray); arrays.splice(x, 1); // Use the smallest unique'd array as the anchor loop. If the other array(s) do contain // an item in the small array, we're likely to find it before reaching the end // of the inner loop and can terminate the search early. for (i = 0,ln = minArray.length; i < ln,x = minArray[i]; i++) { var count = 0; for (j = 0,arraysLn = arrays.length; j < arraysLn,array = arrays[j]; j++) { for (k = 0,arrayLn = array.length; k < arrayLn,y = array[k]; k++) { if (x === y) { count++; break; } } } if (count === arraysLn) { intersect.push(x); } } return intersect; }, /** * Perform a set difference A-B by subtracting all items in array B from array A. * * @param {Array} array A * @param {Array} array B * @return {Array} difference */ difference: function(arrayA, arrayB) { var clone = slice.call(arrayA), ln = clone.length, i, j, lnB; for (i = 0,lnB = arrayB.length; i < lnB; i++) { for (j = 0; j < ln; j++) { if (clone[j] === arrayB[i]) { clone.splice(j, 1); j--; ln--; } } } return clone; }, /** * Sorts the elements of an Array. * By default, this method sorts the elements alphabetically and ascending. * * @param {Array} array The array to sort. * @param {Function} sortFn (optional) The comparison function. * @return {Array} The sorted array. */ sort: function(array, sortFn) { if (supportsSort) { if (sortFn) { return array.sort(sortFn); } else { return array.sort(); } } var length = array.length, i = 0, comparison, j, min, tmp; for (; i < length; i++) { min = i; for (j = i + 1; j < length; j++) { if (sortFn) { comparison = sortFn(array[j], array[min]); if (comparison < 0) { min = j; } } else if (array[j] < array[min]) { min = j; } } if (min !== i) { tmp = array[i]; array[i] = array[min]; array[min] = tmp; } } return array; }, /** * Recursively flattens into 1-d Array. Injects Arrays inline. * @param {Array} array The array to flatten * @return {Array} The new, flattened array. */ flatten: function(array) { var worker = []; function rFlatten(a) { var i, ln, v; for (i = 0, ln = a.length; i < ln; i++) { v = a[i]; if (Ext.isArray(v)) { rFlatten(v); } else { worker.push(v); } } return worker; } return rFlatten(array); }, /** * Returns the minimum value in the Array. * @param {Array|NodeList} array The Array from which to select the minimum value. * @param {Function} comparisonFn (optional) a function to perform the comparision which determines minimization. * If omitted the "<" operator will be used. Note: gt = 1; eq = 0; lt = -1 * @return {Mixed} minValue The minimum value */ min: function(array, comparisonFn) { var min = array[0], i, ln, item; for (i = 0, ln = array.length; i < ln; i++) { item = array[i]; if (comparisonFn) { if (comparisonFn(min, item) === 1) { min = item; } } else { if (item < min) { min = item; } } } return min; }, /** * Returns the maximum value in the Array * @param {Array|NodeList} array The Array from which to select the maximum value. * @param {Function} comparisonFn (optional) a function to perform the comparision which determines maximization. * If omitted the ">" operator will be used. Note: gt = 1; eq = 0; lt = -1 * @return {Mixed} maxValue The maximum value */ max: function(array, comparisonFn) { var max = array[0], i, ln, item; for (i = 0, ln = array.length; i < ln; i++) { item = array[i]; if (comparisonFn) { if (comparisonFn(max, item) === -1) { max = item; } } else { if (item > max) { max = item; } } } return max; }, /** * Calculates the mean of all items in the array * @param {Array} array The Array to calculate the mean value of. * @return {Number} The mean. */ mean: function(array) { return array.length > 0 ? ExtArray.sum(array) / array.length : undefined; }, /** * Calculates the sum of all items in the given array * @param {Array} array The Array to calculate the sum value of. * @return {Number} The sum. */ sum: function(array) { var sum = 0, i, ln, item; for (i = 0,ln = array.length; i < ln; i++) { item = array[i]; sum += item; } return sum; } }; /** * Convenient alias to {@link Ext.Array#each} * @member Ext * @method each */ Ext.each = Ext.Array.each; /** * Alias to {@link Ext.Array#merge}. * @member Ext.Array * @method union */ Ext.Array.union = Ext.Array.merge; /** * Old alias to {@link Ext.Array#min} * @deprecated 4.0.0 Use {@link Ext.Array#min} instead * @member Ext * @method min */ Ext.min = Ext.Array.min; /** * Old alias to {@link Ext.Array#max} * @deprecated 4.0.0 Use {@link Ext.Array#max} instead * @member Ext * @method max */ Ext.max = Ext.Array.max; /** * Old alias to {@link Ext.Array#sum} * @deprecated 4.0.0 Use {@link Ext.Array#sum} instead * @member Ext * @method sum */ Ext.sum = Ext.Array.sum; /** * Old alias to {@link Ext.Array#mean} * @deprecated 4.0.0 Use {@link Ext.Array#mean} instead * @member Ext * @method mean */ Ext.mean = Ext.Array.mean; /** * Old alias to {@link Ext.Array#flatten} * @deprecated 4.0.0 Use {@link Ext.Array#flatten} instead * @member Ext * @method flatten */ Ext.flatten = Ext.Array.flatten; /** * Old alias to {@link Ext.Array#clean Ext.Array.clean} * @deprecated 4.0.0 Use {@link Ext.Array.clean} instead * @member Ext * @method clean */ Ext.clean = Ext.Array.clean; /** * Old alias to {@link Ext.Array#unique Ext.Array.unique} * @deprecated 4.0.0 Use {@link Ext.Array.unique} instead * @member Ext * @method unique */ Ext.unique = Ext.Array.unique; /** * Old alias to {@link Ext.Array#pluck Ext.Array.pluck} * @deprecated 4.0.0 Use {@link Ext.Array#pluck Ext.Array.pluck} instead * @member Ext * @method pluck */ Ext.pluck = Ext.Array.pluck; /** * Convenient alias to {@link Ext.Array#toArray Ext.Array.toArray} * @param {Iterable} the iterable object to be turned into a true Array. * @member Ext * @method toArray * @return {Array} array */ Ext.toArray = function() { return ExtArray.toArray.apply(ExtArray, arguments); } })();