Upgrade to ExtJS 4.0.2 - Released 06/09/2011
[extjs.git] / src / util / HashMap.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.HashMap
17  * <p>
18  * Represents a collection of a set of key and value pairs. Each key in the HashMap
19  * must be unique, the same key cannot exist twice. Access to items is provided via
20  * the key only. Sample usage:
21  * <pre><code>
22 var map = new Ext.util.HashMap();
23 map.add('key1', 1);
24 map.add('key2', 2);
25 map.add('key3', 3);
26
27 map.each(function(key, value, length){
28     console.log(key, value, length);
29 });
30  * </code></pre>
31  * </p>
32  *
33  * <p>The HashMap is an unordered class,
34  * there is no guarantee when iterating over the items that they will be in any particular
35  * order. If this is required, then use a {@link Ext.util.MixedCollection}.
36  * </p>
37  */
38 Ext.define('Ext.util.HashMap', {
39
40     /**
41      * @cfg {Function} keyFn A function that is used to retrieve a default key for a passed object.
42      * A default is provided that returns the <b>id</b> property on the object. This function is only used
43      * if the add method is called with a single argument.
44      */
45
46     mixins: {
47         observable: 'Ext.util.Observable'
48     },
49
50     /**
51      * Creates new HashMap.
52      * @param {Object} config (optional) Config object.
53      */
54     constructor: function(config) {
55         config = config || {};
56         
57         var me = this,
58             keyFn = config.keyFn;
59
60         me.addEvents(
61             /**
62              * @event add
63              * Fires when a new item is added to the hash
64              * @param {Ext.util.HashMap} this.
65              * @param {String} key The key of the added item.
66              * @param {Object} value The value of the added item.
67              */
68             'add',
69             /**
70              * @event clear
71              * Fires when the hash is cleared.
72              * @param {Ext.util.HashMap} this.
73              */
74             'clear',
75             /**
76              * @event remove
77              * Fires when an item is removed from the hash.
78              * @param {Ext.util.HashMap} this.
79              * @param {String} key The key of the removed item.
80              * @param {Object} value The value of the removed item.
81              */
82             'remove',
83             /**
84              * @event replace
85              * Fires when an item is replaced in the hash.
86              * @param {Ext.util.HashMap} this.
87              * @param {String} key The key of the replaced item.
88              * @param {Object} value The new value for the item.
89              * @param {Object} old The old value for the item.
90              */
91             'replace'
92         );
93
94         me.mixins.observable.constructor.call(me, config);
95         me.clear(true);
96         
97         if (keyFn) {
98             me.getKey = keyFn;
99         }
100     },
101
102     /**
103      * Gets the number of items in the hash.
104      * @return {Number} The number of items in the hash.
105      */
106     getCount: function() {
107         return this.length;
108     },
109
110     /**
111      * Implementation for being able to extract the key from an object if only
112      * a single argument is passed.
113      * @private
114      * @param {String} key The key
115      * @param {Object} value The value
116      * @return {Array} [key, value]
117      */
118     getData: function(key, value) {
119         // if we have no value, it means we need to get the key from the object
120         if (value === undefined) {
121             value = key;
122             key = this.getKey(value);
123         }
124
125         return [key, value];
126     },
127
128     /**
129      * Extracts the key from an object. This is a default implementation, it may be overridden
130      * @param {Object} o The object to get the key from
131      * @return {String} The key to use.
132      */
133     getKey: function(o) {
134         return o.id;
135     },
136
137     /**
138      * Adds an item to the collection. Fires the {@link #add} event when complete.
139      * @param {String} key <p>The key to associate with the item, or the new item.</p>
140      * <p>If a {@link #getKey} implementation was specified for this HashMap,
141      * or if the key of the stored items is in a property called <tt><b>id</b></tt>,
142      * the HashMap will be able to <i>derive</i> the key for the new item.
143      * In this case just pass the new item in this parameter.</p>
144      * @param {Object} o The item to add.
145      * @return {Object} The item added.
146      */
147     add: function(key, value) {
148         var me = this,
149             data;
150
151         if (arguments.length === 1) {
152             value = key;
153             key = me.getKey(value);
154         }
155
156         if (me.containsKey(key)) {
157             me.replace(key, value);
158         }
159
160         data = me.getData(key, value);
161         key = data[0];
162         value = data[1];
163         me.map[key] = value;
164         ++me.length;
165         me.fireEvent('add', me, key, value);
166         return value;
167     },
168
169     /**
170      * Replaces an item in the hash. If the key doesn't exist, the
171      * {@link #add} method will be used.
172      * @param {String} key The key of the item.
173      * @param {Object} value The new value for the item.
174      * @return {Object} The new value of the item.
175      */
176     replace: function(key, value) {
177         var me = this,
178             map = me.map,
179             old;
180
181         if (!me.containsKey(key)) {
182             me.add(key, value);
183         }
184         old = map[key];
185         map[key] = value;
186         me.fireEvent('replace', me, key, value, old);
187         return value;
188     },
189
190     /**
191      * Remove an item from the hash.
192      * @param {Object} o The value of the item to remove.
193      * @return {Boolean} True if the item was successfully removed.
194      */
195     remove: function(o) {
196         var key = this.findKey(o);
197         if (key !== undefined) {
198             return this.removeAtKey(key);
199         }
200         return false;
201     },
202
203     /**
204      * Remove an item from the hash.
205      * @param {String} key The key to remove.
206      * @return {Boolean} True if the item was successfully removed.
207      */
208     removeAtKey: function(key) {
209         var me = this,
210             value;
211
212         if (me.containsKey(key)) {
213             value = me.map[key];
214             delete me.map[key];
215             --me.length;
216             me.fireEvent('remove', me, key, value);
217             return true;
218         }
219         return false;
220     },
221
222     /**
223      * Retrieves an item with a particular key.
224      * @param {String} key The key to lookup.
225      * @return {Object} The value at that key. If it doesn't exist, <tt>undefined</tt> is returned.
226      */
227     get: function(key) {
228         return this.map[key];
229     },
230
231     /**
232      * Removes all items from the hash.
233      * @return {Ext.util.HashMap} this
234      */
235     clear: function(/* private */ initial) {
236         var me = this;
237         me.map = {};
238         me.length = 0;
239         if (initial !== true) {
240             me.fireEvent('clear', me);
241         }
242         return me;
243     },
244
245     /**
246      * Checks whether a key exists in the hash.
247      * @param {String} key The key to check for.
248      * @return {Boolean} True if they key exists in the hash.
249      */
250     containsKey: function(key) {
251         return this.map[key] !== undefined;
252     },
253
254     /**
255      * Checks whether a value exists in the hash.
256      * @param {Object} value The value to check for.
257      * @return {Boolean} True if the value exists in the dictionary.
258      */
259     contains: function(value) {
260         return this.containsKey(this.findKey(value));
261     },
262
263     /**
264      * Return all of the keys in the hash.
265      * @return {Array} An array of keys.
266      */
267     getKeys: function() {
268         return this.getArray(true);
269     },
270
271     /**
272      * Return all of the values in the hash.
273      * @return {Array} An array of values.
274      */
275     getValues: function() {
276         return this.getArray(false);
277     },
278
279     /**
280      * Gets either the keys/values in an array from the hash.
281      * @private
282      * @param {Boolean} isKey True to extract the keys, otherwise, the value
283      * @return {Array} An array of either keys/values from the hash.
284      */
285     getArray: function(isKey) {
286         var arr = [],
287             key,
288             map = this.map;
289         for (key in map) {
290             if (map.hasOwnProperty(key)) {
291                 arr.push(isKey ? key: map[key]);
292             }
293         }
294         return arr;
295     },
296
297     /**
298      * Executes the specified function once for each item in the hash.
299      * Returning false from the function will cease iteration.
300      *
301      * The paramaters passed to the function are:
302      * <div class="mdetail-params"><ul>
303      * <li><b>key</b> : String<p class="sub-desc">The key of the item</p></li>
304      * <li><b>value</b> : Number<p class="sub-desc">The value of the item</p></li>
305      * <li><b>length</b> : Number<p class="sub-desc">The total number of items in the hash</p></li>
306      * </ul></div>
307      * @param {Function} fn The function to execute.
308      * @param {Object} scope The scope to execute in. Defaults to <tt>this</tt>.
309      * @return {Ext.util.HashMap} this
310      */
311     each: function(fn, scope) {
312         // copy items so they may be removed during iteration.
313         var items = Ext.apply({}, this.map),
314             key,
315             length = this.length;
316
317         scope = scope || this;
318         for (key in items) {
319             if (items.hasOwnProperty(key)) {
320                 if (fn.call(scope, key, items[key], length) === false) {
321                     break;
322                 }
323             }
324         }
325         return this;
326     },
327
328     /**
329      * Performs a shallow copy on this hash.
330      * @return {Ext.util.HashMap} The new hash object.
331      */
332     clone: function() {
333         var hash = new this.self(),
334             map = this.map,
335             key;
336
337         hash.suspendEvents();
338         for (key in map) {
339             if (map.hasOwnProperty(key)) {
340                 hash.add(key, map[key]);
341             }
342         }
343         hash.resumeEvents();
344         return hash;
345     },
346
347     /**
348      * @private
349      * Find the key for a value.
350      * @param {Object} value The value to find.
351      * @return {Object} The value of the item. Returns <tt>undefined</tt> if not found.
352      */
353     findKey: function(value) {
354         var key,
355             map = this.map;
356
357         for (key in map) {
358             if (map.hasOwnProperty(key) && map[key] === value) {
359                 return key;
360             }
361         }
362         return undefined;
363     }
364 });
365