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