Upgrade to ExtJS 4.0.7 - Released 10/19/2011
[extjs.git] / src / core / src / lang / Array.js
1 /*
2
3 This file is part of Ext JS 4
4
5 Copyright (c) 2011 Sencha Inc
6
7 Contact:  http://www.sencha.com/contact
8
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.
11
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
13
14 */
15 /**
16  * @class Ext.Array
17  * @singleton
18  * @author Jacky Nguyen <jacky@sencha.com>
19  * @docauthor Jacky Nguyen <jacky@sencha.com>
20  *
21  * A set of useful static methods to deal with arrays; provide missing methods for older browsers.
22  */
23 (function() {
24
25     var arrayPrototype = Array.prototype,
26         slice = arrayPrototype.slice,
27         supportsSplice = function () {
28             var array = [],
29                 lengthBefore,
30                 j = 20;
31
32             if (!array.splice) {
33                 return false;
34             }
35
36             // This detects a bug in IE8 splice method:
37             // see http://social.msdn.microsoft.com/Forums/en-US/iewebdevelopment/thread/6e946d03-e09f-4b22-a4dd-cd5e276bf05a/
38
39             while (j--) {
40                 array.push("A");
41             }
42
43             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");
44
45             lengthBefore = array.length; //41
46             array.splice(13, 0, "XXX"); // add one element
47
48             if (lengthBefore+1 != array.length) {
49                 return false;
50             }
51             // end IE8 bug
52
53             return true;
54         }(),
55         supportsForEach = 'forEach' in arrayPrototype,
56         supportsMap = 'map' in arrayPrototype,
57         supportsIndexOf = 'indexOf' in arrayPrototype,
58         supportsEvery = 'every' in arrayPrototype,
59         supportsSome = 'some' in arrayPrototype,
60         supportsFilter = 'filter' in arrayPrototype,
61         supportsSort = function() {
62             var a = [1,2,3,4,5].sort(function(){ return 0; });
63             return a[0] === 1 && a[1] === 2 && a[2] === 3 && a[3] === 4 && a[4] === 5;
64         }(),
65         supportsSliceOnNodeList = true,
66         ExtArray;
67
68     try {
69         // IE 6 - 8 will throw an error when using Array.prototype.slice on NodeList
70         if (typeof document !== 'undefined') {
71             slice.call(document.getElementsByTagName('body'));
72         }
73     } catch (e) {
74         supportsSliceOnNodeList = false;
75     }
76
77     function fixArrayIndex (array, index) {
78         return (index < 0) ? Math.max(0, array.length + index)
79                            : Math.min(array.length, index);
80     }
81
82     /*
83     Does the same work as splice, but with a slightly more convenient signature. The splice
84     method has bugs in IE8, so this is the implementation we use on that platform.
85
86     The rippling of items in the array can be tricky. Consider two use cases:
87
88                   index=2
89                   removeCount=2
90                  /=====\
91         +---+---+---+---+---+---+---+---+
92         | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
93         +---+---+---+---+---+---+---+---+
94                          /  \/  \/  \/  \
95                         /   /\  /\  /\   \
96                        /   /  \/  \/  \   +--------------------------+
97                       /   /   /\  /\   +--------------------------+   \
98                      /   /   /  \/  +--------------------------+   \   \
99                     /   /   /   /+--------------------------+   \   \   \
100                    /   /   /   /                             \   \   \   \
101                   v   v   v   v                               v   v   v   v
102         +---+---+---+---+---+---+       +---+---+---+---+---+---+---+---+---+
103         | 0 | 1 | 4 | 5 | 6 | 7 |       | 0 | 1 | a | b | c | 4 | 5 | 6 | 7 |
104         +---+---+---+---+---+---+       +---+---+---+---+---+---+---+---+---+
105         A                               B        \=========/
106                                                  insert=[a,b,c]
107
108     In case A, it is obvious that copying of [4,5,6,7] must be left-to-right so
109     that we don't end up with [0,1,6,7,6,7]. In case B, we have the opposite; we
110     must go right-to-left or else we would end up with [0,1,a,b,c,4,4,4,4].
111     */
112     function replaceSim (array, index, removeCount, insert) {
113         var add = insert ? insert.length : 0,
114             length = array.length,
115             pos = fixArrayIndex(array, index);
116
117         // we try to use Array.push when we can for efficiency...
118         if (pos === length) {
119             if (add) {
120                 array.push.apply(array, insert);
121             }
122         } else {
123             var remove = Math.min(removeCount, length - pos),
124                 tailOldPos = pos + remove,
125                 tailNewPos = tailOldPos + add - remove,
126                 tailCount = length - tailOldPos,
127                 lengthAfterRemove = length - remove,
128                 i;
129
130             if (tailNewPos < tailOldPos) { // case A
131                 for (i = 0; i < tailCount; ++i) {
132                     array[tailNewPos+i] = array[tailOldPos+i];
133                 }
134             } else if (tailNewPos > tailOldPos) { // case B
135                 for (i = tailCount; i--; ) {
136                     array[tailNewPos+i] = array[tailOldPos+i];
137                 }
138             } // else, add == remove (nothing to do)
139
140             if (add && pos === lengthAfterRemove) {
141                 array.length = lengthAfterRemove; // truncate array
142                 array.push.apply(array, insert);
143             } else {
144                 array.length = lengthAfterRemove + add; // reserves space
145                 for (i = 0; i < add; ++i) {
146                     array[pos+i] = insert[i];
147                 }
148             }
149         }
150
151         return array;
152     }
153
154     function replaceNative (array, index, removeCount, insert) {
155         if (insert && insert.length) {
156             if (index < array.length) {
157                 array.splice.apply(array, [index, removeCount].concat(insert));
158             } else {
159                 array.push.apply(array, insert);
160             }
161         } else {
162             array.splice(index, removeCount);
163         }
164         return array;
165     }
166
167     function eraseSim (array, index, removeCount) {
168         return replaceSim(array, index, removeCount);
169     }
170
171     function eraseNative (array, index, removeCount) {
172         array.splice(index, removeCount);
173         return array;
174     }
175
176     function spliceSim (array, index, removeCount) {
177         var pos = fixArrayIndex(array, index),
178             removed = array.slice(index, fixArrayIndex(array, pos+removeCount));
179
180         if (arguments.length < 4) {
181             replaceSim(array, pos, removeCount);
182         } else {
183             replaceSim(array, pos, removeCount, slice.call(arguments, 3));
184         }
185
186         return removed;
187     }
188
189     function spliceNative (array) {
190         return array.splice.apply(array, slice.call(arguments, 1));
191     }
192
193     var erase = supportsSplice ? eraseNative : eraseSim,
194         replace = supportsSplice ? replaceNative : replaceSim,
195         splice = supportsSplice ? spliceNative : spliceSim;
196
197     // NOTE: from here on, use erase, replace or splice (not native methods)...
198
199     ExtArray = Ext.Array = {
200         /**
201          * Iterates an array or an iterable value and invoke the given callback function for each item.
202          *
203          *     var countries = ['Vietnam', 'Singapore', 'United States', 'Russia'];
204          *
205          *     Ext.Array.each(countries, function(name, index, countriesItSelf) {
206          *         console.log(name);
207          *     });
208          *
209          *     var sum = function() {
210          *         var sum = 0;
211          *
212          *         Ext.Array.each(arguments, function(value) {
213          *             sum += value;
214          *         });
215          *
216          *         return sum;
217          *     };
218          *
219          *     sum(1, 2, 3); // returns 6
220          *
221          * The iteration can be stopped by returning false in the function callback.
222          *
223          *     Ext.Array.each(countries, function(name, index, countriesItSelf) {
224          *         if (name === 'Singapore') {
225          *             return false; // break here
226          *         }
227          *     });
228          *
229          * {@link Ext#each Ext.each} is alias for {@link Ext.Array#each Ext.Array.each}
230          *
231          * @param {Array/NodeList/Object} iterable The value to be iterated. If this
232          * argument is not iterable, the callback function is called once.
233          * @param {Function} fn The callback function. If it returns false, the iteration stops and this method returns
234          * the current `index`.
235          * @param {Object} fn.item The item at the current `index` in the passed `array`
236          * @param {Number} fn.index The current `index` within the `array`
237          * @param {Array} fn.allItems The `array` itself which was passed as the first argument
238          * @param {Boolean} fn.return Return false to stop iteration.
239          * @param {Object} scope (Optional) The scope (`this` reference) in which the specified function is executed.
240          * @param {Boolean} reverse (Optional) Reverse the iteration order (loop from the end to the beginning)
241          * Defaults false
242          * @return {Boolean} See description for the `fn` parameter.
243          */
244         each: function(array, fn, scope, reverse) {
245             array = ExtArray.from(array);
246
247             var i,
248                 ln = array.length;
249
250             if (reverse !== true) {
251                 for (i = 0; i < ln; i++) {
252                     if (fn.call(scope || array[i], array[i], i, array) === false) {
253                         return i;
254                     }
255                 }
256             }
257             else {
258                 for (i = ln - 1; i > -1; i--) {
259                     if (fn.call(scope || array[i], array[i], i, array) === false) {
260                         return i;
261                     }
262                 }
263             }
264
265             return true;
266         },
267
268         /**
269          * Iterates an array and invoke the given callback function for each item. Note that this will simply
270          * delegate to the native Array.prototype.forEach method if supported. It doesn't support stopping the
271          * iteration by returning false in the callback function like {@link Ext.Array#each}. However, performance
272          * could be much better in modern browsers comparing with {@link Ext.Array#each}
273          *
274          * @param {Array} array The array to iterate
275          * @param {Function} fn The callback function.
276          * @param {Object} fn.item The item at the current `index` in the passed `array`
277          * @param {Number} fn.index The current `index` within the `array`
278          * @param {Array}  fn.allItems The `array` itself which was passed as the first argument
279          * @param {Object} scope (Optional) The execution scope (`this`) in which the specified function is executed.
280          */
281         forEach: function(array, fn, scope) {
282             if (supportsForEach) {
283                 return array.forEach(fn, scope);
284             }
285
286             var i = 0,
287                 ln = array.length;
288
289             for (; i < ln; i++) {
290                 fn.call(scope, array[i], i, array);
291             }
292         },
293
294         /**
295          * Get the index of the provided `item` in the given `array`, a supplement for the
296          * missing arrayPrototype.indexOf in Internet Explorer.
297          *
298          * @param {Array} array The array to check
299          * @param {Object} item The item to look for
300          * @param {Number} from (Optional) The index at which to begin the search
301          * @return {Number} The index of item in the array (or -1 if it is not found)
302          */
303         indexOf: function(array, item, from) {
304             if (supportsIndexOf) {
305                 return array.indexOf(item, from);
306             }
307
308             var i, length = array.length;
309
310             for (i = (from < 0) ? Math.max(0, length + from) : from || 0; i < length; i++) {
311                 if (array[i] === item) {
312                     return i;
313                 }
314             }
315
316             return -1;
317         },
318
319         /**
320          * Checks whether or not the given `array` contains the specified `item`
321          *
322          * @param {Array} array The array to check
323          * @param {Object} item The item to look for
324          * @return {Boolean} True if the array contains the item, false otherwise
325          */
326         contains: function(array, item) {
327             if (supportsIndexOf) {
328                 return array.indexOf(item) !== -1;
329             }
330
331             var i, ln;
332
333             for (i = 0, ln = array.length; i < ln; i++) {
334                 if (array[i] === item) {
335                     return true;
336                 }
337             }
338
339             return false;
340         },
341
342         /**
343          * Converts any iterable (numeric indices and a length property) into a true array.
344          *
345          *     function test() {
346          *         var args = Ext.Array.toArray(arguments),
347          *             fromSecondToLastArgs = Ext.Array.toArray(arguments, 1);
348          *
349          *         alert(args.join(' '));
350          *         alert(fromSecondToLastArgs.join(' '));
351          *     }
352          *
353          *     test('just', 'testing', 'here'); // alerts 'just testing here';
354          *                                      // alerts 'testing here';
355          *
356          *     Ext.Array.toArray(document.getElementsByTagName('div')); // will convert the NodeList into an array
357          *     Ext.Array.toArray('splitted'); // returns ['s', 'p', 'l', 'i', 't', 't', 'e', 'd']
358          *     Ext.Array.toArray('splitted', 0, 3); // returns ['s', 'p', 'l', 'i']
359          *
360          * {@link Ext#toArray Ext.toArray} is alias for {@link Ext.Array#toArray Ext.Array.toArray}
361          *
362          * @param {Object} iterable the iterable object to be turned into a true Array.
363          * @param {Number} start (Optional) a zero-based index that specifies the start of extraction. Defaults to 0
364          * @param {Number} end (Optional) a zero-based index that specifies the end of extraction. Defaults to the last
365          * index of the iterable value
366          * @return {Array} array
367          */
368         toArray: function(iterable, start, end){
369             if (!iterable || !iterable.length) {
370                 return [];
371             }
372
373             if (typeof iterable === 'string') {
374                 iterable = iterable.split('');
375             }
376
377             if (supportsSliceOnNodeList) {
378                 return slice.call(iterable, start || 0, end || iterable.length);
379             }
380
381             var array = [],
382                 i;
383
384             start = start || 0;
385             end = end ? ((end < 0) ? iterable.length + end : end) : iterable.length;
386
387             for (i = start; i < end; i++) {
388                 array.push(iterable[i]);
389             }
390
391             return array;
392         },
393
394         /**
395          * Plucks the value of a property from each item in the Array. Example:
396          *
397          *     Ext.Array.pluck(Ext.query("p"), "className"); // [el1.className, el2.className, ..., elN.className]
398          *
399          * @param {Array/NodeList} array The Array of items to pluck the value from.
400          * @param {String} propertyName The property name to pluck from each element.
401          * @return {Array} The value from each item in the Array.
402          */
403         pluck: function(array, propertyName) {
404             var ret = [],
405                 i, ln, item;
406
407             for (i = 0, ln = array.length; i < ln; i++) {
408                 item = array[i];
409
410                 ret.push(item[propertyName]);
411             }
412
413             return ret;
414         },
415
416         /**
417          * Creates a new array with the results of calling a provided function on every element in this array.
418          *
419          * @param {Array} array
420          * @param {Function} fn Callback function for each item
421          * @param {Object} scope Callback function scope
422          * @return {Array} results
423          */
424         map: function(array, fn, scope) {
425             if (supportsMap) {
426                 return array.map(fn, scope);
427             }
428
429             var results = [],
430                 i = 0,
431                 len = array.length;
432
433             for (; i < len; i++) {
434                 results[i] = fn.call(scope, array[i], i, array);
435             }
436
437             return results;
438         },
439
440         /**
441          * Executes the specified function for each array element until the function returns a falsy value.
442          * If such an item is found, the function will return false immediately.
443          * Otherwise, it will return true.
444          *
445          * @param {Array} array
446          * @param {Function} fn Callback function for each item
447          * @param {Object} scope Callback function scope
448          * @return {Boolean} True if no false value is returned by the callback function.
449          */
450         every: function(array, fn, scope) {
451             //<debug>
452             if (!fn) {
453                 Ext.Error.raise('Ext.Array.every must have a callback function passed as second argument.');
454             }
455             //</debug>
456             if (supportsEvery) {
457                 return array.every(fn, scope);
458             }
459
460             var i = 0,
461                 ln = array.length;
462
463             for (; i < ln; ++i) {
464                 if (!fn.call(scope, array[i], i, array)) {
465                     return false;
466                 }
467             }
468
469             return true;
470         },
471
472         /**
473          * Executes the specified function for each array element until the function returns a truthy value.
474          * If such an item is found, the function will return true immediately. Otherwise, it will return false.
475          *
476          * @param {Array} array
477          * @param {Function} fn Callback function for each item
478          * @param {Object} scope Callback function scope
479          * @return {Boolean} True if the callback function returns a truthy value.
480          */
481         some: function(array, fn, scope) {
482             //<debug>
483             if (!fn) {
484                 Ext.Error.raise('Ext.Array.some must have a callback function passed as second argument.');
485             }
486             //</debug>
487             if (supportsSome) {
488                 return array.some(fn, scope);
489             }
490
491             var i = 0,
492                 ln = array.length;
493
494             for (; i < ln; ++i) {
495                 if (fn.call(scope, array[i], i, array)) {
496                     return true;
497                 }
498             }
499
500             return false;
501         },
502
503         /**
504          * Filter through an array and remove empty item as defined in {@link Ext#isEmpty Ext.isEmpty}
505          *
506          * See {@link Ext.Array#filter}
507          *
508          * @param {Array} array
509          * @return {Array} results
510          */
511         clean: function(array) {
512             var results = [],
513                 i = 0,
514                 ln = array.length,
515                 item;
516
517             for (; i < ln; i++) {
518                 item = array[i];
519
520                 if (!Ext.isEmpty(item)) {
521                     results.push(item);
522                 }
523             }
524
525             return results;
526         },
527
528         /**
529          * Returns a new array with unique items
530          *
531          * @param {Array} array
532          * @return {Array} results
533          */
534         unique: function(array) {
535             var clone = [],
536                 i = 0,
537                 ln = array.length,
538                 item;
539
540             for (; i < ln; i++) {
541                 item = array[i];
542
543                 if (ExtArray.indexOf(clone, item) === -1) {
544                     clone.push(item);
545                 }
546             }
547
548             return clone;
549         },
550
551         /**
552          * Creates a new array with all of the elements of this array for which
553          * the provided filtering function returns true.
554          *
555          * @param {Array} array
556          * @param {Function} fn Callback function for each item
557          * @param {Object} scope Callback function scope
558          * @return {Array} results
559          */
560         filter: function(array, fn, scope) {
561             if (supportsFilter) {
562                 return array.filter(fn, scope);
563             }
564
565             var results = [],
566                 i = 0,
567                 ln = array.length;
568
569             for (; i < ln; i++) {
570                 if (fn.call(scope, array[i], i, array)) {
571                     results.push(array[i]);
572                 }
573             }
574
575             return results;
576         },
577
578         /**
579          * Converts a value to an array if it's not already an array; returns:
580          *
581          * - An empty array if given value is `undefined` or `null`
582          * - Itself if given value is already an array
583          * - An array copy if given value is {@link Ext#isIterable iterable} (arguments, NodeList and alike)
584          * - An array with one item which is the given value, otherwise
585          *
586          * @param {Object} value The value to convert to an array if it's not already is an array
587          * @param {Boolean} newReference (Optional) True to clone the given array and return a new reference if necessary,
588          * defaults to false
589          * @return {Array} array
590          */
591         from: function(value, newReference) {
592             if (value === undefined || value === null) {
593                 return [];
594             }
595
596             if (Ext.isArray(value)) {
597                 return (newReference) ? slice.call(value) : value;
598             }
599
600             if (value && value.length !== undefined && typeof value !== 'string') {
601                 return Ext.toArray(value);
602             }
603
604             return [value];
605         },
606
607         /**
608          * Removes the specified item from the array if it exists
609          *
610          * @param {Array} array The array
611          * @param {Object} item The item to remove
612          * @return {Array} The passed array itself
613          */
614         remove: function(array, item) {
615             var index = ExtArray.indexOf(array, item);
616
617             if (index !== -1) {
618                 erase(array, index, 1);
619             }
620
621             return array;
622         },
623
624         /**
625          * Push an item into the array only if the array doesn't contain it yet
626          *
627          * @param {Array} array The array
628          * @param {Object} item The item to include
629          */
630         include: function(array, item) {
631             if (!ExtArray.contains(array, item)) {
632                 array.push(item);
633             }
634         },
635
636         /**
637          * Clone a flat array without referencing the previous one. Note that this is different
638          * from Ext.clone since it doesn't handle recursive cloning. It's simply a convenient, easy-to-remember method
639          * for Array.prototype.slice.call(array)
640          *
641          * @param {Array} array The array
642          * @return {Array} The clone array
643          */
644         clone: function(array) {
645             return slice.call(array);
646         },
647
648         /**
649          * Merge multiple arrays into one with unique items.
650          *
651          * {@link Ext.Array#union} is alias for {@link Ext.Array#merge}
652          *
653          * @param {Array} array1
654          * @param {Array} array2
655          * @param {Array} etc
656          * @return {Array} merged
657          */
658         merge: function() {
659             var args = slice.call(arguments),
660                 array = [],
661                 i, ln;
662
663             for (i = 0, ln = args.length; i < ln; i++) {
664                 array = array.concat(args[i]);
665             }
666
667             return ExtArray.unique(array);
668         },
669
670         /**
671          * Merge multiple arrays into one with unique items that exist in all of the arrays.
672          *
673          * @param {Array} array1
674          * @param {Array} array2
675          * @param {Array} etc
676          * @return {Array} intersect
677          */
678         intersect: function() {
679             var intersect = [],
680                 arrays = slice.call(arguments),
681                 i, j, k, minArray, array, x, y, ln, arraysLn, arrayLn;
682
683             if (!arrays.length) {
684                 return intersect;
685             }
686
687             // Find the smallest array
688             for (i = x = 0,ln = arrays.length; i < ln,array = arrays[i]; i++) {
689                 if (!minArray || array.length < minArray.length) {
690                     minArray = array;
691                     x = i;
692                 }
693             }
694
695             minArray = ExtArray.unique(minArray);
696             erase(arrays, x, 1);
697
698             // Use the smallest unique'd array as the anchor loop. If the other array(s) do contain
699             // an item in the small array, we're likely to find it before reaching the end
700             // of the inner loop and can terminate the search early.
701             for (i = 0,ln = minArray.length; i < ln,x = minArray[i]; i++) {
702                 var count = 0;
703
704                 for (j = 0,arraysLn = arrays.length; j < arraysLn,array = arrays[j]; j++) {
705                     for (k = 0,arrayLn = array.length; k < arrayLn,y = array[k]; k++) {
706                         if (x === y) {
707                             count++;
708                             break;
709                         }
710                     }
711                 }
712
713                 if (count === arraysLn) {
714                     intersect.push(x);
715                 }
716             }
717
718             return intersect;
719         },
720
721         /**
722          * Perform a set difference A-B by subtracting all items in array B from array A.
723          *
724          * @param {Array} arrayA
725          * @param {Array} arrayB
726          * @return {Array} difference
727          */
728         difference: function(arrayA, arrayB) {
729             var clone = slice.call(arrayA),
730                 ln = clone.length,
731                 i, j, lnB;
732
733             for (i = 0,lnB = arrayB.length; i < lnB; i++) {
734                 for (j = 0; j < ln; j++) {
735                     if (clone[j] === arrayB[i]) {
736                         erase(clone, j, 1);
737                         j--;
738                         ln--;
739                     }
740                 }
741             }
742
743             return clone;
744         },
745
746         /**
747          * Returns a shallow copy of a part of an array. This is equivalent to the native
748          * call "Array.prototype.slice.call(array, begin, end)". This is often used when "array"
749          * is "arguments" since the arguments object does not supply a slice method but can
750          * be the context object to Array.prototype.slice.
751          *
752          * @param {Array} array The array (or arguments object).
753          * @param {Number} begin The index at which to begin. Negative values are offsets from
754          * the end of the array.
755          * @param {Number} end The index at which to end. The copied items do not include
756          * end. Negative values are offsets from the end of the array. If end is omitted,
757          * all items up to the end of the array are copied.
758          * @return {Array} The copied piece of the array.
759          */
760         // Note: IE6 will return [] on slice.call(x, undefined).
761         slice: ([1,2].slice(1, undefined).length ?
762             function (array, begin, end) {
763                 return slice.call(array, begin, end);
764             } :
765             // at least IE6 uses arguments.length for variadic signature
766             function (array, begin, end) {
767                 // After tested for IE 6, the one below is of the best performance
768                 // see http://jsperf.com/slice-fix
769                 if (typeof begin === 'undefined') {
770                     return slice.call(array);
771                 }
772                 if (typeof end === 'undefined') {
773                     return slice.call(array, begin);
774                 }
775                 return slice.call(array, begin, end);
776             }
777         ),
778
779         /**
780          * Sorts the elements of an Array.
781          * By default, this method sorts the elements alphabetically and ascending.
782          *
783          * @param {Array} array The array to sort.
784          * @param {Function} sortFn (optional) The comparison function.
785          * @return {Array} The sorted array.
786          */
787         sort: function(array, sortFn) {
788             if (supportsSort) {
789                 if (sortFn) {
790                     return array.sort(sortFn);
791                 } else {
792                     return array.sort();
793                 }
794             }
795
796             var length = array.length,
797                 i = 0,
798                 comparison,
799                 j, min, tmp;
800
801             for (; i < length; i++) {
802                 min = i;
803                 for (j = i + 1; j < length; j++) {
804                     if (sortFn) {
805                         comparison = sortFn(array[j], array[min]);
806                         if (comparison < 0) {
807                             min = j;
808                         }
809                     } else if (array[j] < array[min]) {
810                         min = j;
811                     }
812                 }
813                 if (min !== i) {
814                     tmp = array[i];
815                     array[i] = array[min];
816                     array[min] = tmp;
817                 }
818             }
819
820             return array;
821         },
822
823         /**
824          * Recursively flattens into 1-d Array. Injects Arrays inline.
825          *
826          * @param {Array} array The array to flatten
827          * @return {Array} The 1-d array.
828          */
829         flatten: function(array) {
830             var worker = [];
831
832             function rFlatten(a) {
833                 var i, ln, v;
834
835                 for (i = 0, ln = a.length; i < ln; i++) {
836                     v = a[i];
837
838                     if (Ext.isArray(v)) {
839                         rFlatten(v);
840                     } else {
841                         worker.push(v);
842                     }
843                 }
844
845                 return worker;
846             }
847
848             return rFlatten(array);
849         },
850
851         /**
852          * Returns the minimum value in the Array.
853          *
854          * @param {Array/NodeList} array The Array from which to select the minimum value.
855          * @param {Function} comparisonFn (optional) a function to perform the comparision which determines minimization.
856          * If omitted the "<" operator will be used. Note: gt = 1; eq = 0; lt = -1
857          * @return {Object} minValue The minimum value
858          */
859         min: function(array, comparisonFn) {
860             var min = array[0],
861                 i, ln, item;
862
863             for (i = 0, ln = array.length; i < ln; i++) {
864                 item = array[i];
865
866                 if (comparisonFn) {
867                     if (comparisonFn(min, item) === 1) {
868                         min = item;
869                     }
870                 }
871                 else {
872                     if (item < min) {
873                         min = item;
874                     }
875                 }
876             }
877
878             return min;
879         },
880
881         /**
882          * Returns the maximum value in the Array.
883          *
884          * @param {Array/NodeList} array The Array from which to select the maximum value.
885          * @param {Function} comparisonFn (optional) a function to perform the comparision which determines maximization.
886          * If omitted the ">" operator will be used. Note: gt = 1; eq = 0; lt = -1
887          * @return {Object} maxValue The maximum value
888          */
889         max: function(array, comparisonFn) {
890             var max = array[0],
891                 i, ln, item;
892
893             for (i = 0, ln = array.length; i < ln; i++) {
894                 item = array[i];
895
896                 if (comparisonFn) {
897                     if (comparisonFn(max, item) === -1) {
898                         max = item;
899                     }
900                 }
901                 else {
902                     if (item > max) {
903                         max = item;
904                     }
905                 }
906             }
907
908             return max;
909         },
910
911         /**
912          * Calculates the mean of all items in the array.
913          *
914          * @param {Array} array The Array to calculate the mean value of.
915          * @return {Number} The mean.
916          */
917         mean: function(array) {
918             return array.length > 0 ? ExtArray.sum(array) / array.length : undefined;
919         },
920
921         /**
922          * Calculates the sum of all items in the given array.
923          *
924          * @param {Array} array The Array to calculate the sum value of.
925          * @return {Number} The sum.
926          */
927         sum: function(array) {
928             var sum = 0,
929                 i, ln, item;
930
931             for (i = 0,ln = array.length; i < ln; i++) {
932                 item = array[i];
933
934                 sum += item;
935             }
936
937             return sum;
938         },
939
940         //<debug>
941         _replaceSim: replaceSim, // for unit testing
942         _spliceSim: spliceSim,
943         //</debug>
944
945         /**
946          * Removes items from an array. This is functionally equivalent to the splice method
947          * of Array, but works around bugs in IE8's splice method and does not copy the
948          * removed elements in order to return them (because very often they are ignored).
949          *
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 {Number} removeCount The number of items to remove at index.
953          * @return {Array} The array passed.
954          * @method
955          */
956         erase: erase,
957
958         /**
959          * Inserts items in to an array.
960          *
961          * @param {Array} array The Array on which to replace.
962          * @param {Number} index The index in the array at which to operate.
963          * @param {Array} items The array of items to insert at index.
964          * @return {Array} The array passed.
965          */
966         insert: function (array, index, items) {
967             return replace(array, index, 0, items);
968         },
969
970         /**
971          * Replaces items in an array. This is functionally equivalent to the splice method
972          * of Array, but works around bugs in IE8's splice method and is often more convenient
973          * to call because it accepts an array of items to insert rather than use a variadic
974          * argument list.
975          *
976          * @param {Array} array The Array on which to replace.
977          * @param {Number} index The index in the array at which to operate.
978          * @param {Number} removeCount The number of items to remove at index (can be 0).
979          * @param {Array} insert (optional) An array of items to insert at index.
980          * @return {Array} The array passed.
981          * @method
982          */
983         replace: replace,
984
985         /**
986          * Replaces items in an array. This is equivalent to the splice method of Array, but
987          * works around bugs in IE8's splice method. The signature is exactly the same as the
988          * splice method except that the array is the first argument. All arguments following
989          * removeCount are inserted in the array at index.
990          *
991          * @param {Array} array The Array on which to replace.
992          * @param {Number} index The index in the array at which to operate.
993          * @param {Number} removeCount The number of items to remove at index (can be 0).
994          * @return {Array} An array containing the removed items.
995          * @method
996          */
997         splice: splice
998     };
999
1000     /**
1001      * @method
1002      * @member Ext
1003      * @alias Ext.Array#each
1004      */
1005     Ext.each = ExtArray.each;
1006
1007     /**
1008      * @method
1009      * @member Ext.Array
1010      * @alias Ext.Array#merge
1011      */
1012     ExtArray.union = ExtArray.merge;
1013
1014     /**
1015      * Old alias to {@link Ext.Array#min}
1016      * @deprecated 4.0.0 Use {@link Ext.Array#min} instead
1017      * @method
1018      * @member Ext
1019      * @alias Ext.Array#min
1020      */
1021     Ext.min = ExtArray.min;
1022
1023     /**
1024      * Old alias to {@link Ext.Array#max}
1025      * @deprecated 4.0.0 Use {@link Ext.Array#max} instead
1026      * @method
1027      * @member Ext
1028      * @alias Ext.Array#max
1029      */
1030     Ext.max = ExtArray.max;
1031
1032     /**
1033      * Old alias to {@link Ext.Array#sum}
1034      * @deprecated 4.0.0 Use {@link Ext.Array#sum} instead
1035      * @method
1036      * @member Ext
1037      * @alias Ext.Array#sum
1038      */
1039     Ext.sum = ExtArray.sum;
1040
1041     /**
1042      * Old alias to {@link Ext.Array#mean}
1043      * @deprecated 4.0.0 Use {@link Ext.Array#mean} instead
1044      * @method
1045      * @member Ext
1046      * @alias Ext.Array#mean
1047      */
1048     Ext.mean = ExtArray.mean;
1049
1050     /**
1051      * Old alias to {@link Ext.Array#flatten}
1052      * @deprecated 4.0.0 Use {@link Ext.Array#flatten} instead
1053      * @method
1054      * @member Ext
1055      * @alias Ext.Array#flatten
1056      */
1057     Ext.flatten = ExtArray.flatten;
1058
1059     /**
1060      * Old alias to {@link Ext.Array#clean}
1061      * @deprecated 4.0.0 Use {@link Ext.Array#clean} instead
1062      * @method
1063      * @member Ext
1064      * @alias Ext.Array#clean
1065      */
1066     Ext.clean = ExtArray.clean;
1067
1068     /**
1069      * Old alias to {@link Ext.Array#unique}
1070      * @deprecated 4.0.0 Use {@link Ext.Array#unique} instead
1071      * @method
1072      * @member Ext
1073      * @alias Ext.Array#unique
1074      */
1075     Ext.unique = ExtArray.unique;
1076
1077     /**
1078      * Old alias to {@link Ext.Array#pluck Ext.Array.pluck}
1079      * @deprecated 4.0.0 Use {@link Ext.Array#pluck Ext.Array.pluck} instead
1080      * @method
1081      * @member Ext
1082      * @alias Ext.Array#pluck
1083      */
1084     Ext.pluck = ExtArray.pluck;
1085
1086     /**
1087      * @method
1088      * @member Ext
1089      * @alias Ext.Array#toArray
1090      */
1091     Ext.toArray = function() {
1092         return ExtArray.toArray.apply(ExtArray, arguments);
1093     };
1094 })();
1095