X-Git-Url: http://git.ithinksw.org/extjs.git/blobdiff_plain/0494b8d9b9bb03ab6c22b34dae81261e3cd7e3e6..7a654f8d43fdb43d78b63d90528bed6e86b608cc:/src/core/src/lang/Array.js?ds=sidebyside diff --git a/src/core/src/lang/Array.js b/src/core/src/lang/Array.js new file mode 100644 index 00000000..1b6d49d3 --- /dev/null +++ b/src/core/src/lang/Array.js @@ -0,0 +1,827 @@ +/** + * @author Jacky Nguyen + * @docauthor Jacky Nguyen + * @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) { + // + if (!fn) { + Ext.Error.raise('Ext.Array.every must have a callback function passed as second argument.'); + } + // + 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) { + // + if (!fn) { + Ext.Error.raise('Ext.Array.some must have a callback function passed as second argument.'); + } + // + 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); + } +})();