Upgrade to ExtJS 4.0.7 - Released 10/19/2011
[extjs.git] / src / util / MixedCollection.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.util.MixedCollection
17  * <p>
18  * Represents a collection of a set of key and value pairs. Each key in the MixedCollection
19  * must be unique, the same key cannot exist twice. This collection is ordered, items in the
20  * collection can be accessed by index  or via the key. Newly added items are added to
21  * the end of the collection. This class is similar to {@link Ext.util.HashMap} however it
22  * is heavier and provides more functionality. Sample usage:
23  * <pre><code>
24 var coll = new Ext.util.MixedCollection();
25 coll.add('key1', 'val1');
26 coll.add('key2', 'val2');
27 coll.add('key3', 'val3');
28
29 console.log(coll.get('key1')); // prints 'val1'
30 console.log(coll.indexOfKey('key3')); // prints 2
31  * </code></pre>
32  *
33  * <p>
34  * The MixedCollection also has support for sorting and filtering of the values in the collection.
35  * <pre><code>
36 var coll = new Ext.util.MixedCollection();
37 coll.add('key1', 100);
38 coll.add('key2', -100);
39 coll.add('key3', 17);
40 coll.add('key4', 0);
41 var biggerThanZero = coll.filterBy(function(value){
42     return value > 0;
43 });
44 console.log(biggerThanZero.getCount()); // prints 2
45  * </code></pre>
46  * </p>
47  */
48 Ext.define('Ext.util.MixedCollection', {
49     extend: 'Ext.util.AbstractMixedCollection',
50     mixins: {
51         sortable: 'Ext.util.Sortable'
52     },
53
54     /**
55      * Creates new MixedCollection.
56      * @param {Boolean} allowFunctions Specify <tt>true</tt> if the {@link #addAll}
57      * function should add function references to the collection. Defaults to
58      * <tt>false</tt>.
59      * @param {Function} keyFn A function that can accept an item of the type(s) stored in this MixedCollection
60      * and return the key value for that item.  This is used when available to look up the key on items that
61      * were passed without an explicit key parameter to a MixedCollection method.  Passing this parameter is
62      * equivalent to providing an implementation for the {@link #getKey} method.
63      */
64     constructor: function() {
65         var me = this;
66         me.callParent(arguments);
67         me.addEvents('sort');
68         me.mixins.sortable.initSortable.call(me);
69     },
70
71     doSort: function(sorterFn) {
72         this.sortBy(sorterFn);
73     },
74
75     /**
76      * @private
77      * Performs the actual sorting based on a direction and a sorting function. Internally,
78      * this creates a temporary array of all items in the MixedCollection, sorts it and then writes
79      * the sorted array data back into this.items and this.keys
80      * @param {String} property Property to sort by ('key', 'value', or 'index')
81      * @param {String} dir (optional) Direction to sort 'ASC' or 'DESC'. Defaults to 'ASC'.
82      * @param {Function} fn (optional) Comparison function that defines the sort order.
83      * Defaults to sorting by numeric value.
84      */
85     _sort : function(property, dir, fn){
86         var me = this,
87             i, len,
88             dsc   = String(dir).toUpperCase() == 'DESC' ? -1 : 1,
89
90             //this is a temporary array used to apply the sorting function
91             c     = [],
92             keys  = me.keys,
93             items = me.items;
94
95         //default to a simple sorter function if one is not provided
96         fn = fn || function(a, b) {
97             return a - b;
98         };
99
100         //copy all the items into a temporary array, which we will sort
101         for(i = 0, len = items.length; i < len; i++){
102             c[c.length] = {
103                 key  : keys[i],
104                 value: items[i],
105                 index: i
106             };
107         }
108
109         //sort the temporary array
110         Ext.Array.sort(c, function(a, b){
111             var v = fn(a[property], b[property]) * dsc;
112             if(v === 0){
113                 v = (a.index < b.index ? -1 : 1);
114             }
115             return v;
116         });
117
118         //copy the temporary array back into the main this.items and this.keys objects
119         for(i = 0, len = c.length; i < len; i++){
120             items[i] = c[i].value;
121             keys[i]  = c[i].key;
122         }
123
124         me.fireEvent('sort', me);
125     },
126
127     /**
128      * Sorts the collection by a single sorter function
129      * @param {Function} sorterFn The function to sort by
130      */
131     sortBy: function(sorterFn) {
132         var me     = this,
133             items  = me.items,
134             keys   = me.keys,
135             length = items.length,
136             temp   = [],
137             i;
138
139         //first we create a copy of the items array so that we can sort it
140         for (i = 0; i < length; i++) {
141             temp[i] = {
142                 key  : keys[i],
143                 value: items[i],
144                 index: i
145             };
146         }
147
148         Ext.Array.sort(temp, function(a, b) {
149             var v = sorterFn(a.value, b.value);
150             if (v === 0) {
151                 v = (a.index < b.index ? -1 : 1);
152             }
153
154             return v;
155         });
156
157         //copy the temporary array back into the main this.items and this.keys objects
158         for (i = 0; i < length; i++) {
159             items[i] = temp[i].value;
160             keys[i]  = temp[i].key;
161         }
162         
163         me.fireEvent('sort', me, items, keys);
164     },
165
166     /**
167      * Reorders each of the items based on a mapping from old index to new index. Internally this
168      * just translates into a sort. The 'sort' event is fired whenever reordering has occured.
169      * @param {Object} mapping Mapping from old item index to new item index
170      */
171     reorder: function(mapping) {
172         var me = this,
173             items = me.items,
174             index = 0,
175             length = items.length,
176             order = [],
177             remaining = [],
178             oldIndex;
179
180         me.suspendEvents();
181
182         //object of {oldPosition: newPosition} reversed to {newPosition: oldPosition}
183         for (oldIndex in mapping) {
184             order[mapping[oldIndex]] = items[oldIndex];
185         }
186
187         for (index = 0; index < length; index++) {
188             if (mapping[index] == undefined) {
189                 remaining.push(items[index]);
190             }
191         }
192
193         for (index = 0; index < length; index++) {
194             if (order[index] == undefined) {
195                 order[index] = remaining.shift();
196             }
197         }
198
199         me.clear();
200         me.addAll(order);
201
202         me.resumeEvents();
203         me.fireEvent('sort', me);
204     },
205
206     /**
207      * Sorts this collection by <b>key</b>s.
208      * @param {String} direction (optional) 'ASC' or 'DESC'. Defaults to 'ASC'.
209      * @param {Function} fn (optional) Comparison function that defines the sort order.
210      * Defaults to sorting by case insensitive string.
211      */
212     sortByKey : function(dir, fn){
213         this._sort('key', dir, fn || function(a, b){
214             var v1 = String(a).toUpperCase(), v2 = String(b).toUpperCase();
215             return v1 > v2 ? 1 : (v1 < v2 ? -1 : 0);
216         });
217     }
218 });
219