3 This file is part of Ext JS 4
5 Copyright (c) 2011 Sencha Inc
7 Contact: http://www.sencha.com/contact
9 GNU General Public License Usage
10 This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
16 * @author Jacky Nguyen <jacky@sencha.com>
17 * @docauthor Jacky Nguyen <jacky@sencha.com>
20 * A set of useful static methods to deal with arrays; provide missing methods for older browsers.
27 var arrayPrototype = Array.prototype,
28 slice = arrayPrototype.slice,
29 supportsSplice = function () {
38 // This detects a bug in IE8 splice method:
39 // see http://social.msdn.microsoft.com/Forums/en-US/iewebdevelopment/thread/6e946d03-e09f-4b22-a4dd-cd5e276bf05a/
45 array.splice(15, 0, "F", "F", "F", "F", "F","F","F","F","F","F","F","F","F","F","F","F","F","F","F","F","F");
47 lengthBefore = array.length; //41
48 array.splice(13, 0, "XXX"); // add one element
50 if (lengthBefore+1 != array.length) {
57 supportsForEach = 'forEach' in arrayPrototype,
58 supportsMap = 'map' in arrayPrototype,
59 supportsIndexOf = 'indexOf' in arrayPrototype,
60 supportsEvery = 'every' in arrayPrototype,
61 supportsSome = 'some' in arrayPrototype,
62 supportsFilter = 'filter' in arrayPrototype,
63 supportsSort = function() {
64 var a = [1,2,3,4,5].sort(function(){ return 0; });
65 return a[0] === 1 && a[1] === 2 && a[2] === 3 && a[3] === 4 && a[4] === 5;
67 supportsSliceOnNodeList = true,
71 // IE 6 - 8 will throw an error when using Array.prototype.slice on NodeList
72 if (typeof document !== 'undefined') {
73 slice.call(document.getElementsByTagName('body'));
76 supportsSliceOnNodeList = false;
79 function fixArrayIndex (array, index) {
80 return (index < 0) ? Math.max(0, array.length + index)
81 : Math.min(array.length, index);
85 Does the same work as splice, but with a slightly more convenient signature. The splice
86 method has bugs in IE8, so this is the implementation we use on that platform.
88 The rippling of items in the array can be tricky. Consider two use cases:
93 +---+---+---+---+---+---+---+---+
94 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
95 +---+---+---+---+---+---+---+---+
98 / / \/ \/ \ +--------------------------+
99 / / /\ /\ +--------------------------+ \
100 / / / \/ +--------------------------+ \ \
101 / / / /+--------------------------+ \ \ \
104 +---+---+---+---+---+---+ +---+---+---+---+---+---+---+---+---+
105 | 0 | 1 | 4 | 5 | 6 | 7 | | 0 | 1 | a | b | c | 4 | 5 | 6 | 7 |
106 +---+---+---+---+---+---+ +---+---+---+---+---+---+---+---+---+
110 In case A, it is obvious that copying of [4,5,6,7] must be left-to-right so
111 that we don't end up with [0,1,6,7,6,7]. In case B, we have the opposite; we
112 must go right-to-left or else we would end up with [0,1,a,b,c,4,4,4,4].
114 function replaceSim (array, index, removeCount, insert) {
115 var add = insert ? insert.length : 0,
116 length = array.length,
117 pos = fixArrayIndex(array, index);
119 // we try to use Array.push when we can for efficiency...
120 if (pos === length) {
122 array.push.apply(array, insert);
125 var remove = Math.min(removeCount, length - pos),
126 tailOldPos = pos + remove,
127 tailNewPos = tailOldPos + add - remove,
128 tailCount = length - tailOldPos,
129 lengthAfterRemove = length - remove,
132 if (tailNewPos < tailOldPos) { // case A
133 for (i = 0; i < tailCount; ++i) {
134 array[tailNewPos+i] = array[tailOldPos+i];
136 } else if (tailNewPos > tailOldPos) { // case B
137 for (i = tailCount; i--; ) {
138 array[tailNewPos+i] = array[tailOldPos+i];
140 } // else, add == remove (nothing to do)
142 if (add && pos === lengthAfterRemove) {
143 array.length = lengthAfterRemove; // truncate array
144 array.push.apply(array, insert);
146 array.length = lengthAfterRemove + add; // reserves space
147 for (i = 0; i < add; ++i) {
148 array[pos+i] = insert[i];
156 function replaceNative (array, index, removeCount, insert) {
157 if (insert && insert.length) {
158 if (index < array.length) {
159 array.splice.apply(array, [index, removeCount].concat(insert));
161 array.push.apply(array, insert);
164 array.splice(index, removeCount);
169 function eraseSim (array, index, removeCount) {
170 return replaceSim(array, index, removeCount);
173 function eraseNative (array, index, removeCount) {
174 array.splice(index, removeCount);
178 function spliceSim (array, index, removeCount) {
179 var pos = fixArrayIndex(array, index),
180 removed = array.slice(index, fixArrayIndex(array, pos+removeCount));
182 if (arguments.length < 4) {
183 replaceSim(array, pos, removeCount);
185 replaceSim(array, pos, removeCount, slice.call(arguments, 3));
191 function spliceNative (array) {
192 return array.splice.apply(array, slice.call(arguments, 1));
195 var erase = supportsSplice ? eraseNative : eraseSim,
196 replace = supportsSplice ? replaceNative : replaceSim,
197 splice = supportsSplice ? spliceNative : spliceSim;
199 // NOTE: from here on, use erase, replace or splice (not native methods)...
201 ExtArray = Ext.Array = {
203 * Iterates an array or an iterable value and invoke the given callback function for each item.
205 * var countries = ['Vietnam', 'Singapore', 'United States', 'Russia'];
207 * Ext.Array.each(countries, function(name, index, countriesItSelf) {
211 * var sum = function() {
214 * Ext.Array.each(arguments, function(value) {
221 * sum(1, 2, 3); // returns 6
223 * The iteration can be stopped by returning false in the function callback.
225 * Ext.Array.each(countries, function(name, index, countriesItSelf) {
226 * if (name === 'Singapore') {
227 * return false; // break here
231 * {@link Ext#each Ext.each} is alias for {@link Ext.Array#each Ext.Array.each}
233 * @param {Array/NodeList/Mixed} iterable The value to be iterated. If this
234 * argument is not iterable, the callback function is called once.
235 * @param {Function} fn The callback function. If it returns false, the iteration stops and this method returns
236 * the current `index`. Arguments passed to this callback function are:
238 * - `item` : Mixed - The item at the current `index` in the passed `array`
239 * - `index` : Number - The current `index` within the `array`
240 * - `allItems` : Array/NodeList/Mixed - The `array` passed as the first argument to `Ext.Array.each`
242 * @param {Object} scope (Optional) The scope (`this` reference) in which the specified function is executed.
243 * @param {Boolean} reverse (Optional) Reverse the iteration order (loop from the end to the beginning)
245 * @return {Boolean} See description for the `fn` parameter.
247 each: function(array, fn, scope, reverse) {
248 array = ExtArray.from(array);
253 if (reverse !== true) {
254 for (i = 0; i < ln; i++) {
255 if (fn.call(scope || array[i], array[i], i, array) === false) {
261 for (i = ln - 1; i > -1; i--) {
262 if (fn.call(scope || array[i], array[i], i, array) === false) {
272 * Iterates an array and invoke the given callback function for each item. Note that this will simply
273 * delegate to the native Array.prototype.forEach method if supported.
274 * It doesn't support stopping the iteration by returning false in the callback function like
275 * {@link Ext.Array#each}. However, performance could be much better in modern browsers comparing with
276 * {@link Ext.Array#each}
278 * @param {Array} array The array to iterate
279 * @param {Function} fn The function callback, to be invoked these arguments:
281 * - `item` : Mixed - The item at the current `index` in the passed `array`
282 * - `index` : Number - The current `index` within the `array`
283 * - `allItems` : Array - The `array` itself which was passed as the first argument
285 * @param {Object} scope (Optional) The execution scope (`this`) in which the specified function is executed.
287 forEach: function(array, fn, scope) {
288 if (supportsForEach) {
289 return array.forEach(fn, scope);
295 for (; i < ln; i++) {
296 fn.call(scope, array[i], i, array);
301 * Get the index of the provided `item` in the given `array`, a supplement for the
302 * missing arrayPrototype.indexOf in Internet Explorer.
304 * @param {Array} array The array to check
305 * @param {Mixed} item The item to look for
306 * @param {Number} from (Optional) The index at which to begin the search
307 * @return {Number} The index of item in the array (or -1 if it is not found)
309 indexOf: function(array, item, from) {
310 if (supportsIndexOf) {
311 return array.indexOf(item, from);
314 var i, length = array.length;
316 for (i = (from < 0) ? Math.max(0, length + from) : from || 0; i < length; i++) {
317 if (array[i] === item) {
326 * Checks whether or not the given `array` contains the specified `item`
328 * @param {Array} array The array to check
329 * @param {Mixed} item The item to look for
330 * @return {Boolean} True if the array contains the item, false otherwise
332 contains: function(array, item) {
333 if (supportsIndexOf) {
334 return array.indexOf(item) !== -1;
339 for (i = 0, ln = array.length; i < ln; i++) {
340 if (array[i] === item) {
349 * Converts any iterable (numeric indices and a length property) into a true array.
352 * var args = Ext.Array.toArray(arguments),
353 * fromSecondToLastArgs = Ext.Array.toArray(arguments, 1);
355 * alert(args.join(' '));
356 * alert(fromSecondToLastArgs.join(' '));
359 * test('just', 'testing', 'here'); // alerts 'just testing here';
360 * // alerts 'testing here';
362 * Ext.Array.toArray(document.getElementsByTagName('div')); // will convert the NodeList into an array
363 * Ext.Array.toArray('splitted'); // returns ['s', 'p', 'l', 'i', 't', 't', 'e', 'd']
364 * Ext.Array.toArray('splitted', 0, 3); // returns ['s', 'p', 'l', 'i']
366 * {@link Ext#toArray Ext.toArray} is alias for {@link Ext.Array#toArray Ext.Array.toArray}
368 * @param {Mixed} iterable the iterable object to be turned into a true Array.
369 * @param {Number} start (Optional) a zero-based index that specifies the start of extraction. Defaults to 0
370 * @param {Number} end (Optional) a zero-based index that specifies the end of extraction. Defaults to the last
371 * index of the iterable value
372 * @return {Array} array
374 toArray: function(iterable, start, end){
375 if (!iterable || !iterable.length) {
379 if (typeof iterable === 'string') {
380 iterable = iterable.split('');
383 if (supportsSliceOnNodeList) {
384 return slice.call(iterable, start || 0, end || iterable.length);
391 end = end ? ((end < 0) ? iterable.length + end : end) : iterable.length;
393 for (i = start; i < end; i++) {
394 array.push(iterable[i]);
401 * Plucks the value of a property from each item in the Array. Example:
403 * Ext.Array.pluck(Ext.query("p"), "className"); // [el1.className, el2.className, ..., elN.className]
405 * @param {Array|NodeList} array The Array of items to pluck the value from.
406 * @param {String} propertyName The property name to pluck from each element.
407 * @return {Array} The value from each item in the Array.
409 pluck: function(array, propertyName) {
413 for (i = 0, ln = array.length; i < ln; i++) {
416 ret.push(item[propertyName]);
423 * Creates a new array with the results of calling a provided function on every element in this array.
425 * @param {Array} array
426 * @param {Function} fn Callback function for each item
427 * @param {Object} scope Callback function scope
428 * @return {Array} results
430 map: function(array, fn, scope) {
432 return array.map(fn, scope);
439 for (; i < len; i++) {
440 results[i] = fn.call(scope, array[i], i, array);
447 * Executes the specified function for each array element until the function returns a falsy value.
448 * If such an item is found, the function will return false immediately.
449 * Otherwise, it will return true.
451 * @param {Array} array
452 * @param {Function} fn Callback function for each item
453 * @param {Object} scope Callback function scope
454 * @return {Boolean} True if no false value is returned by the callback function.
456 every: function(array, fn, scope) {
459 Ext.Error.raise('Ext.Array.every must have a callback function passed as second argument.');
463 return array.every(fn, scope);
469 for (; i < ln; ++i) {
470 if (!fn.call(scope, array[i], i, array)) {
479 * Executes the specified function for each array element until the function returns a truthy value.
480 * If such an item is found, the function will return true immediately. Otherwise, it will return false.
482 * @param {Array} array
483 * @param {Function} fn Callback function for each item
484 * @param {Object} scope Callback function scope
485 * @return {Boolean} True if the callback function returns a truthy value.
487 some: function(array, fn, scope) {
490 Ext.Error.raise('Ext.Array.some must have a callback function passed as second argument.');
494 return array.some(fn, scope);
500 for (; i < ln; ++i) {
501 if (fn.call(scope, array[i], i, array)) {
510 * Filter through an array and remove empty item as defined in {@link Ext#isEmpty Ext.isEmpty}
512 * See {@link Ext.Array#filter}
514 * @param {Array} array
515 * @return {Array} results
517 clean: function(array) {
523 for (; i < ln; i++) {
526 if (!Ext.isEmpty(item)) {
535 * Returns a new array with unique items
537 * @param {Array} array
538 * @return {Array} results
540 unique: function(array) {
546 for (; i < ln; i++) {
549 if (ExtArray.indexOf(clone, item) === -1) {
558 * Creates a new array with all of the elements of this array for which
559 * the provided filtering function returns true.
561 * @param {Array} array
562 * @param {Function} fn Callback function for each item
563 * @param {Object} scope Callback function scope
564 * @return {Array} results
566 filter: function(array, fn, scope) {
567 if (supportsFilter) {
568 return array.filter(fn, scope);
575 for (; i < ln; i++) {
576 if (fn.call(scope, array[i], i, array)) {
577 results.push(array[i]);
585 * Converts a value to an array if it's not already an array; returns:
587 * - An empty array if given value is `undefined` or `null`
588 * - Itself if given value is already an array
589 * - An array copy if given value is {@link Ext#isIterable iterable} (arguments, NodeList and alike)
590 * - An array with one item which is the given value, otherwise
592 * @param {Array/Mixed} value The value to convert to an array if it's not already is an array
593 * @param {Boolean} (Optional) newReference True to clone the given array and return a new reference if necessary,
595 * @return {Array} array
597 from: function(value, newReference) {
598 if (value === undefined || value === null) {
602 if (Ext.isArray(value)) {
603 return (newReference) ? slice.call(value) : value;
606 if (value && value.length !== undefined && typeof value !== 'string') {
607 return Ext.toArray(value);
614 * Removes the specified item from the array if it exists
616 * @param {Array} array The array
617 * @param {Mixed} item The item to remove
618 * @return {Array} The passed array itself
620 remove: function(array, item) {
621 var index = ExtArray.indexOf(array, item);
624 erase(array, index, 1);
631 * Push an item into the array only if the array doesn't contain it yet
633 * @param {Array} array The array
634 * @param {Mixed} item The item to include
636 include: function(array, item) {
637 if (!ExtArray.contains(array, item)) {
643 * Clone a flat array without referencing the previous one. Note that this is different
644 * from Ext.clone since it doesn't handle recursive cloning. It's simply a convenient, easy-to-remember method
645 * for Array.prototype.slice.call(array)
647 * @param {Array} array The array
648 * @return {Array} The clone array
650 clone: function(array) {
651 return slice.call(array);
655 * Merge multiple arrays into one with unique items.
657 * {@link Ext.Array#union} is alias for {@link Ext.Array#merge}
659 * @param {Array} array1
660 * @param {Array} array2
662 * @return {Array} merged
665 var args = slice.call(arguments),
669 for (i = 0, ln = args.length; i < ln; i++) {
670 array = array.concat(args[i]);
673 return ExtArray.unique(array);
677 * Merge multiple arrays into one with unique items that exist in all of the arrays.
679 * @param {Array} array1
680 * @param {Array} array2
682 * @return {Array} intersect
684 intersect: function() {
686 arrays = slice.call(arguments),
687 i, j, k, minArray, array, x, y, ln, arraysLn, arrayLn;
689 if (!arrays.length) {
693 // Find the smallest array
694 for (i = x = 0,ln = arrays.length; i < ln,array = arrays[i]; i++) {
695 if (!minArray || array.length < minArray.length) {
701 minArray = ExtArray.unique(minArray);
704 // Use the smallest unique'd array as the anchor loop. If the other array(s) do contain
705 // an item in the small array, we're likely to find it before reaching the end
706 // of the inner loop and can terminate the search early.
707 for (i = 0,ln = minArray.length; i < ln,x = minArray[i]; i++) {
710 for (j = 0,arraysLn = arrays.length; j < arraysLn,array = arrays[j]; j++) {
711 for (k = 0,arrayLn = array.length; k < arrayLn,y = array[k]; k++) {
719 if (count === arraysLn) {
728 * Perform a set difference A-B by subtracting all items in array B from array A.
730 * @param {Array} arrayA
731 * @param {Array} arrayB
732 * @return {Array} difference
734 difference: function(arrayA, arrayB) {
735 var clone = slice.call(arrayA),
739 for (i = 0,lnB = arrayB.length; i < lnB; i++) {
740 for (j = 0; j < ln; j++) {
741 if (clone[j] === arrayB[i]) {
753 * Returns a shallow copy of a part of an array. This is equivalent to the native
754 * call "Array.prototype.slice.call(array, begin, end)". This is often used when "array"
755 * is "arguments" since the arguments object does not supply a slice method but can
756 * be the context object to Array.prototype.slice.
758 * @param {Array} array The array (or arguments object).
759 * @param {Number} begin The index at which to begin. Negative values are offsets from
760 * the end of the array.
761 * @param {Number} end The index at which to end. The copied items do not include
762 * end. Negative values are offsets from the end of the array. If end is omitted,
763 * all items up to the end of the array are copied.
764 * @return {Array} The copied piece of the array.
766 slice: function(array, begin, end) {
767 return slice.call(array, begin, end);
771 * Sorts the elements of an Array.
772 * By default, this method sorts the elements alphabetically and ascending.
774 * @param {Array} array The array to sort.
775 * @param {Function} sortFn (optional) The comparison function.
776 * @return {Array} The sorted array.
778 sort: function(array, sortFn) {
781 return array.sort(sortFn);
787 var length = array.length,
792 for (; i < length; i++) {
794 for (j = i + 1; j < length; j++) {
796 comparison = sortFn(array[j], array[min]);
797 if (comparison < 0) {
800 } else if (array[j] < array[min]) {
806 array[i] = array[min];
815 * Recursively flattens into 1-d Array. Injects Arrays inline.
818 flatten: function(array) {
821 function rFlatten(a) {
824 for (i = 0, ln = a.length; i < ln; i++) {
827 if (Ext.isArray(v)) {
837 return rFlatten(array);
841 * Returns the minimum value in the Array.
843 * @param {Array|NodeList} array The Array from which to select the minimum value.
844 * @param {Function} comparisonFn (optional) a function to perform the comparision which determines minimization.
845 * If omitted the "<" operator will be used. Note: gt = 1; eq = 0; lt = -1
846 * @return {Mixed} minValue The minimum value
848 min: function(array, comparisonFn) {
852 for (i = 0, ln = array.length; i < ln; i++) {
856 if (comparisonFn(min, item) === 1) {
871 * Returns the maximum value in the Array.
873 * @param {Array|NodeList} array The Array from which to select the maximum value.
874 * @param {Function} comparisonFn (optional) a function to perform the comparision which determines maximization.
875 * If omitted the ">" operator will be used. Note: gt = 1; eq = 0; lt = -1
876 * @return {Mixed} maxValue The maximum value
878 max: function(array, comparisonFn) {
882 for (i = 0, ln = array.length; i < ln; i++) {
886 if (comparisonFn(max, item) === -1) {
901 * Calculates the mean of all items in the array.
903 * @param {Array} array The Array to calculate the mean value of.
904 * @return {Number} The mean.
906 mean: function(array) {
907 return array.length > 0 ? ExtArray.sum(array) / array.length : undefined;
911 * Calculates the sum of all items in the given array.
913 * @param {Array} array The Array to calculate the sum value of.
914 * @return {Number} The sum.
916 sum: function(array) {
920 for (i = 0,ln = array.length; i < ln; i++) {
930 _replaceSim: replaceSim, // for unit testing
931 _spliceSim: spliceSim,
935 * Removes items from an array. This is functionally equivalent to the splice method
936 * of Array, but works around bugs in IE8's splice method and does not copy the
937 * removed elements in order to return them (because very often they are ignored).
939 * @param {Array} array The Array on which to replace.
940 * @param {Number} index The index in the array at which to operate.
941 * @param {Number} removeCount The number of items to remove at index.
942 * @return {Array} The array passed.
948 * Inserts items in to an array.
950 * @param {Array} array The Array on which to replace.
951 * @param {Number} index The index in the array at which to operate.
952 * @param {Array} items The array of items to insert at index.
953 * @return {Array} The array passed.
955 insert: function (array, index, items) {
956 return replace(array, index, 0, items);
960 * Replaces items in an array. This is functionally equivalent to the splice method
961 * of Array, but works around bugs in IE8's splice method and is often more convenient
962 * to call because it accepts an array of items to insert rather than use a variadic
965 * @param {Array} array The Array on which to replace.
966 * @param {Number} index The index in the array at which to operate.
967 * @param {Number} removeCount The number of items to remove at index (can be 0).
968 * @param {Array} insert An optional array of items to insert at index.
969 * @return {Array} The array passed.
975 * Replaces items in an array. This is equivalent to the splice method of Array, but
976 * works around bugs in IE8's splice method. The signature is exactly the same as the
977 * splice method except that the array is the first argument. All arguments following
978 * removeCount are inserted in the array at index.
980 * @param {Array} array The Array on which to replace.
981 * @param {Number} index The index in the array at which to operate.
982 * @param {Number} removeCount The number of items to remove at index (can be 0).
983 * @return {Array} An array containing the removed items.
992 * @alias Ext.Array#each
994 Ext.each = ExtArray.each;
999 * @alias Ext.Array#merge
1001 ExtArray.union = ExtArray.merge;
1004 * Old alias to {@link Ext.Array#min}
1005 * @deprecated 4.0.0 Use {@link Ext.Array#min} instead
1008 * @alias Ext.Array#min
1010 Ext.min = ExtArray.min;
1013 * Old alias to {@link Ext.Array#max}
1014 * @deprecated 4.0.0 Use {@link Ext.Array#max} instead
1017 * @alias Ext.Array#max
1019 Ext.max = ExtArray.max;
1022 * Old alias to {@link Ext.Array#sum}
1023 * @deprecated 4.0.0 Use {@link Ext.Array#sum} instead
1026 * @alias Ext.Array#sum
1028 Ext.sum = ExtArray.sum;
1031 * Old alias to {@link Ext.Array#mean}
1032 * @deprecated 4.0.0 Use {@link Ext.Array#mean} instead
1035 * @alias Ext.Array#mean
1037 Ext.mean = ExtArray.mean;
1040 * Old alias to {@link Ext.Array#flatten}
1041 * @deprecated 4.0.0 Use {@link Ext.Array#flatten} instead
1044 * @alias Ext.Array#flatten
1046 Ext.flatten = ExtArray.flatten;
1049 * Old alias to {@link Ext.Array#clean}
1050 * @deprecated 4.0.0 Use {@link Ext.Array#clean} instead
1053 * @alias Ext.Array#clean
1055 Ext.clean = ExtArray.clean;
1058 * Old alias to {@link Ext.Array#unique}
1059 * @deprecated 4.0.0 Use {@link Ext.Array#unique} instead
1062 * @alias Ext.Array#unique
1064 Ext.unique = ExtArray.unique;
1067 * Old alias to {@link Ext.Array#pluck Ext.Array.pluck}
1068 * @deprecated 4.0.0 Use {@link Ext.Array#pluck Ext.Array.pluck} instead
1071 * @alias Ext.Array#pluck
1073 Ext.pluck = ExtArray.pluck;
1078 * @alias Ext.Array#toArray
1080 Ext.toArray = function() {
1081 return ExtArray.toArray.apply(ExtArray, arguments);