PageRenderTime 63ms CodeModel.GetById 17ms app.highlight 38ms RepoModel.GetById 1ms app.codeStats 1ms

/hippo/src/main/webapp/ext/src/util/MixedCollection.js

http://hdbc.googlecode.com/
JavaScript | 576 lines | 281 code | 34 blank | 261 comment | 67 complexity | 9c029a4b79f682b42ce0907fde70648b MD5 | raw file
  1/*!
  2 * Ext JS Library 3.0.0
  3 * Copyright(c) 2006-2009 Ext JS, LLC
  4 * licensing@extjs.com
  5 * http://www.extjs.com/license
  6 */
  7/**
  8 * @class Ext.util.MixedCollection
  9 * @extends Ext.util.Observable
 10 * A Collection class that maintains both numeric indexes and keys and exposes events.
 11 * @constructor
 12 * @param {Boolean} allowFunctions True if the addAll function should add function references to the
 13 * collection (defaults to false)
 14 * @param {Function} keyFn A function that can accept an item of the type(s) stored in this MixedCollection
 15 * and return the key value for that item.  This is used when available to look up the key on items that
 16 * were passed without an explicit key parameter to a MixedCollection method.  Passing this parameter is
 17 * equivalent to providing an implementation for the {@link #getKey} method.
 18 */
 19Ext.util.MixedCollection = function(allowFunctions, keyFn){
 20    this.items = [];
 21    this.map = {};
 22    this.keys = [];
 23    this.length = 0;
 24    this.addEvents(
 25        /**
 26         * @event clear
 27         * Fires when the collection is cleared.
 28         */
 29        "clear",
 30        /**
 31         * @event add
 32         * Fires when an item is added to the collection.
 33         * @param {Number} index The index at which the item was added.
 34         * @param {Object} o The item added.
 35         * @param {String} key The key associated with the added item.
 36         */
 37        "add",
 38        /**
 39         * @event replace
 40         * Fires when an item is replaced in the collection.
 41         * @param {String} key he key associated with the new added.
 42         * @param {Object} old The item being replaced.
 43         * @param {Object} new The new item.
 44         */
 45        "replace",
 46        /**
 47         * @event remove
 48         * Fires when an item is removed from the collection.
 49         * @param {Object} o The item being removed.
 50         * @param {String} key (optional) The key associated with the removed item.
 51         */
 52        "remove",
 53        "sort"
 54    );
 55    this.allowFunctions = allowFunctions === true;
 56    if(keyFn){
 57        this.getKey = keyFn;
 58    }
 59    Ext.util.MixedCollection.superclass.constructor.call(this);
 60};
 61
 62Ext.extend(Ext.util.MixedCollection, Ext.util.Observable, {
 63    allowFunctions : false,
 64
 65/**
 66 * Adds an item to the collection. Fires the {@link #add} event when complete.
 67 * @param {String} key <p>The key to associate with the item, or the new item.</p>
 68 * <p>If you supplied a {@link #getKey} implementation for this MixedCollection, or if the key
 69 * of your stored items is in a property called <tt><b>id</b></tt>, then the MixedCollection
 70 * will be able to <i>derive</i> the key for the new item. In this case just pass the new item in
 71 * this parameter.</p>
 72 * @param {Object} o The item to add.
 73 * @return {Object} The item added.
 74 */
 75    add: function(key, o){
 76        if(arguments.length == 1){
 77            o = arguments[0];
 78            key = this.getKey(o);
 79        }
 80        if(typeof key != 'undefined' && key !== null){
 81            var old = this.map[key];
 82            if(typeof old != 'undefined'){
 83                return this.replace(key, o);
 84            }
 85            this.map[key] = o;
 86        }
 87        this.length++;
 88        this.items.push(o);
 89        this.keys.push(key);
 90        this.fireEvent('add', this.length-1, o, key);
 91        return o;
 92    },
 93
 94/**
 95  * MixedCollection has a generic way to fetch keys if you implement getKey.  The default implementation
 96  * simply returns <tt style="font-weight:bold;">item.id</tt> but you can provide your own implementation
 97  * to return a different value as in the following examples:
 98<pre><code>
 99// normal way
100var mc = new Ext.util.MixedCollection();
101mc.add(someEl.dom.id, someEl);
102mc.add(otherEl.dom.id, otherEl);
103//and so on
104
105// using getKey
106var mc = new Ext.util.MixedCollection();
107mc.getKey = function(el){
108   return el.dom.id;
109};
110mc.add(someEl);
111mc.add(otherEl);
112
113// or via the constructor
114var mc = new Ext.util.MixedCollection(false, function(el){
115   return el.dom.id;
116});
117mc.add(someEl);
118mc.add(otherEl);
119</code></pre>
120 * @param {Object} item The item for which to find the key.
121 * @return {Object} The key for the passed item.
122 */
123    getKey : function(o){
124         return o.id;
125    },
126
127/**
128 * Replaces an item in the collection. Fires the {@link #replace} event when complete.
129 * @param {String} key <p>The key associated with the item to replace, or the replacement item.</p>
130 * <p>If you supplied a {@link #getKey} implementation for this MixedCollection, or if the key
131 * of your stored items is in a property called <tt><b>id</b></tt>, then the MixedCollection
132 * will be able to <i>derive</i> the key of the replacement item. If you want to replace an item
133 * with one having the same key value, then just pass the replacement item in this parameter.</p>
134 * @param o {Object} o (optional) If the first parameter passed was a key, the item to associate
135 * with that key.
136 * @return {Object}  The new item.
137 */
138    replace : function(key, o){
139        if(arguments.length == 1){
140            o = arguments[0];
141            key = this.getKey(o);
142        }
143        var old = this.map[key];
144        if(typeof key == "undefined" || key === null || typeof old == "undefined"){
145             return this.add(key, o);
146        }
147        var index = this.indexOfKey(key);
148        this.items[index] = o;
149        this.map[key] = o;
150        this.fireEvent("replace", key, old, o);
151        return o;
152    },
153
154/**
155 * Adds all elements of an Array or an Object to the collection.
156 * @param {Object/Array} objs An Object containing properties which will be added to the collection, or
157 * an Array of values, each of which are added to the collection.
158 */
159    addAll : function(objs){
160        if(arguments.length > 1 || Ext.isArray(objs)){
161            var args = arguments.length > 1 ? arguments : objs;
162            for(var i = 0, len = args.length; i < len; i++){
163                this.add(args[i]);
164            }
165        }else{
166            for(var key in objs){
167                if(this.allowFunctions || typeof objs[key] != "function"){
168                    this.add(key, objs[key]);
169                }
170            }
171        }
172    },
173
174/**
175 * Executes the specified function once for every item in the collection, passing the following arguments:
176 * <div class="mdetail-params"><ul>
177 * <li><b>item</b> : Mixed<p class="sub-desc">The collection item</p></li>
178 * <li><b>index</b> : Number<p class="sub-desc">The item's index</p></li>
179 * <li><b>length</b> : Number<p class="sub-desc">The total number of items in the collection</p></li>
180 * </ul></div>
181 * The function should return a boolean value. Returning false from the function will stop the iteration.
182 * @param {Function} fn The function to execute for each item.
183 * @param {Object} scope (optional) The scope in which to execute the function.
184 */
185    each : function(fn, scope){
186        var items = [].concat(this.items); // each safe for removal
187        for(var i = 0, len = items.length; i < len; i++){
188            if(fn.call(scope || items[i], items[i], i, len) === false){
189                break;
190            }
191        }
192    },
193
194/**
195 * Executes the specified function once for every key in the collection, passing each
196 * key, and its associated item as the first two parameters.
197 * @param {Function} fn The function to execute for each item.
198 * @param {Object} scope (optional) The scope in which to execute the function.
199 */
200    eachKey : function(fn, scope){
201        for(var i = 0, len = this.keys.length; i < len; i++){
202            fn.call(scope || window, this.keys[i], this.items[i], i, len);
203        }
204    },
205
206    /**
207     * Returns the first item in the collection which elicits a true return value from the
208     * passed selection function.
209     * @param {Function} fn The selection function to execute for each item.
210     * @param {Object} scope (optional) The scope in which to execute the function.
211     * @return {Object} The first item in the collection which returned true from the selection function.
212     */
213    find : function(fn, scope){
214        for(var i = 0, len = this.items.length; i < len; i++){
215            if(fn.call(scope || window, this.items[i], this.keys[i])){
216                return this.items[i];
217            }
218        }
219        return null;
220    },
221
222/**
223 * Inserts an item at the specified index in the collection. Fires the {@link #add} event when complete.
224 * @param {Number} index The index to insert the item at.
225 * @param {String} key The key to associate with the new item, or the item itself.
226 * @param {Object} o (optional) If the second parameter was a key, the new item.
227 * @return {Object} The item inserted.
228 */
229    insert : function(index, key, o){
230        if(arguments.length == 2){
231            o = arguments[1];
232            key = this.getKey(o);
233        }
234        if(this.containsKey(key)){
235            this.suspendEvents();
236            this.removeKey(key);
237            this.resumeEvents();
238        }
239        if(index >= this.length){
240            return this.add(key, o);
241        }
242        this.length++;
243        this.items.splice(index, 0, o);
244        if(typeof key != "undefined" && key !== null){
245            this.map[key] = o;
246        }
247        this.keys.splice(index, 0, key);
248        this.fireEvent("add", index, o, key);
249        return o;
250    },
251
252/**
253 * Remove an item from the collection.
254 * @param {Object} o The item to remove.
255 * @return {Object} The item removed or false if no item was removed.
256 */
257    remove : function(o){
258        return this.removeAt(this.indexOf(o));
259    },
260
261/**
262 * Remove an item from a specified index in the collection. Fires the {@link #remove} event when complete.
263 * @param {Number} index The index within the collection of the item to remove.
264 * @return {Object} The item removed or false if no item was removed.
265 */
266    removeAt : function(index){
267        if(index < this.length && index >= 0){
268            this.length--;
269            var o = this.items[index];
270            this.items.splice(index, 1);
271            var key = this.keys[index];
272            if(typeof key != "undefined"){
273                delete this.map[key];
274            }
275            this.keys.splice(index, 1);
276            this.fireEvent("remove", o, key);
277            return o;
278        }
279        return false;
280    },
281
282/**
283 * Removed an item associated with the passed key fom the collection.
284 * @param {String} key The key of the item to remove.
285 * @return {Object} The item removed or false if no item was removed.
286 */
287    removeKey : function(key){
288        return this.removeAt(this.indexOfKey(key));
289    },
290
291/**
292 * Returns the number of items in the collection.
293 * @return {Number} the number of items in the collection.
294 */
295    getCount : function(){
296        return this.length;
297    },
298
299/**
300 * Returns index within the collection of the passed Object.
301 * @param {Object} o The item to find the index of.
302 * @return {Number} index of the item. Returns -1 if not found.
303 */
304    indexOf : function(o){
305        return this.items.indexOf(o);
306    },
307
308/**
309 * Returns index within the collection of the passed key.
310 * @param {String} key The key to find the index of.
311 * @return {Number} index of the key.
312 */
313    indexOfKey : function(key){
314        return this.keys.indexOf(key);
315    },
316
317/**
318 * Returns the item associated with the passed key OR index. Key has priority over index.  This is the equivalent
319 * of calling {@link #key} first, then if nothing matched calling {@link #itemAt}.
320 * @param {String/Number} key The key or index of the item.
321 * @return {Object} If the item is found, returns the item.  If the item was not found, returns <tt>undefined</tt>.
322 * If an item was found, but is a Class, returns <tt>null</tt>.
323 */
324    item : function(key){
325        var mk = this.map[key],
326            item = mk !== undefined ? mk : (typeof key == 'number') ? this.items[key] : undefined;
327        return !Ext.isFunction(item) || this.allowFunctions ? item : null; // for prototype!
328    },
329
330/**
331 * Returns the item at the specified index.
332 * @param {Number} index The index of the item.
333 * @return {Object} The item at the specified index.
334 */
335    itemAt : function(index){
336        return this.items[index];
337    },
338
339/**
340 * Returns the item associated with the passed key.
341 * @param {String/Number} key The key of the item.
342 * @return {Object} The item associated with the passed key.
343 */
344    key : function(key){
345        return this.map[key];
346    },
347
348/**
349 * Returns true if the collection contains the passed Object as an item.
350 * @param {Object} o  The Object to look for in the collection.
351 * @return {Boolean} True if the collection contains the Object as an item.
352 */
353    contains : function(o){
354        return this.indexOf(o) != -1;
355    },
356
357/**
358 * Returns true if the collection contains the passed Object as a key.
359 * @param {String} key The key to look for in the collection.
360 * @return {Boolean} True if the collection contains the Object as a key.
361 */
362    containsKey : function(key){
363        return typeof this.map[key] != "undefined";
364    },
365
366/**
367 * Removes all items from the collection.  Fires the {@link #clear} event when complete.
368 */
369    clear : function(){
370        this.length = 0;
371        this.items = [];
372        this.keys = [];
373        this.map = {};
374        this.fireEvent("clear");
375    },
376
377/**
378 * Returns the first item in the collection.
379 * @return {Object} the first item in the collection..
380 */
381    first : function(){
382        return this.items[0];
383    },
384
385/**
386 * Returns the last item in the collection.
387 * @return {Object} the last item in the collection..
388 */
389    last : function(){
390        return this.items[this.length-1];
391    },
392
393    // private
394    _sort : function(property, dir, fn){
395        var i,
396            len,
397            dsc = String(dir).toUpperCase() == "DESC" ? -1 : 1,
398            c = [], k = this.keys, items = this.items;
399            
400        fn = fn || function(a, b){
401            return a-b;
402        };
403        for(i = 0, len = items.length; i < len; i++){
404            c[c.length] = {key: k[i], value: items[i], index: i};
405        }
406        c.sort(function(a, b){
407            var v = fn(a[property], b[property]) * dsc;
408            if(v === 0){
409                v = (a.index < b.index ? -1 : 1);
410            }
411            return v;
412        });
413        for(i = 0, len = c.length; i < len; i++){
414            items[i] = c[i].value;
415            k[i] = c[i].key;
416        }
417        this.fireEvent("sort", this);
418    },
419
420    /**
421     * Sorts this collection with the passed comparison function
422     * @param {String} direction (optional) "ASC" or "DESC"
423     * @param {Function} fn (optional) comparison function
424     */
425    sort : function(dir, fn){
426        this._sort("value", dir, fn);
427    },
428
429    /**
430     * Sorts this collection by keys
431     * @param {String} direction (optional) "ASC" or "DESC"
432     * @param {Function} fn (optional) a comparison function (defaults to case insensitive string)
433     */
434    keySort : function(dir, fn){
435        this._sort("key", dir, fn || function(a, b){
436            var v1 = String(a).toUpperCase(), v2 = String(b).toUpperCase();
437            return v1 > v2 ? 1 : (v1 < v2 ? -1 : 0);
438        });
439    },
440
441    /**
442     * Returns a range of items in this collection
443     * @param {Number} startIndex (optional) defaults to 0
444     * @param {Number} endIndex (optional) default to the last item
445     * @return {Array} An array of items
446     */
447    getRange : function(start, end){
448        var items = this.items;
449        if(items.length < 1){
450            return [];
451        }
452        start = start || 0;
453        end = Math.min(typeof end == "undefined" ? this.length-1 : end, this.length-1);
454        var i, r = [];
455        if(start <= end){
456            for(i = start; i <= end; i++) {
457                r[r.length] = items[i];
458            }
459        }else{
460            for(i = start; i >= end; i--) {
461                r[r.length] = items[i];
462            }
463        }
464        return r;
465    },
466
467    /**
468     * Filter the <i>objects</i> in this collection by a specific property.
469     * Returns a new collection that has been filtered.
470     * @param {String} property A property on your objects
471     * @param {String/RegExp} value Either string that the property values
472     * should start with or a RegExp to test against the property
473     * @param {Boolean} anyMatch (optional) True to match any part of the string, not just the beginning
474     * @param {Boolean} caseSensitive (optional) True for case sensitive comparison (defaults to False).
475     * @return {MixedCollection} The new filtered collection
476     */
477    filter : function(property, value, anyMatch, caseSensitive){
478        if(Ext.isEmpty(value, false)){
479            return this.clone();
480        }
481        value = this.createValueMatcher(value, anyMatch, caseSensitive);
482        return this.filterBy(function(o){
483            return o && value.test(o[property]);
484        });
485    },
486
487    /**
488     * Filter by a function. Returns a <i>new</i> collection that has been filtered.
489     * The passed function will be called with each object in the collection.
490     * If the function returns true, the value is included otherwise it is filtered.
491     * @param {Function} fn The function to be called, it will receive the args o (the object), k (the key)
492     * @param {Object} scope (optional) The scope of the function (defaults to this)
493     * @return {MixedCollection} The new filtered collection
494     */
495    filterBy : function(fn, scope){
496        var r = new Ext.util.MixedCollection();
497        r.getKey = this.getKey;
498        var k = this.keys, it = this.items;
499        for(var i = 0, len = it.length; i < len; i++){
500            if(fn.call(scope||this, it[i], k[i])){
501                r.add(k[i], it[i]);
502            }
503        }
504        return r;
505    },
506
507    /**
508     * Finds the index of the first matching object in this collection by a specific property/value.
509     * @param {String} property The name of a property on your objects.
510     * @param {String/RegExp} value A string that the property values
511     * should start with or a RegExp to test against the property.
512     * @param {Number} start (optional) The index to start searching at (defaults to 0).
513     * @param {Boolean} anyMatch (optional) True to match any part of the string, not just the beginning.
514     * @param {Boolean} caseSensitive (optional) True for case sensitive comparison.
515     * @return {Number} The matched index or -1
516     */
517    findIndex : function(property, value, start, anyMatch, caseSensitive){
518        if(Ext.isEmpty(value, false)){
519            return -1;
520        }
521        value = this.createValueMatcher(value, anyMatch, caseSensitive);
522        return this.findIndexBy(function(o){
523            return o && value.test(o[property]);
524        }, null, start);
525    },
526
527    /**
528     * Find the index of the first matching object in this collection by a function.
529     * If the function returns <i>true</i> it is considered a match.
530     * @param {Function} fn The function to be called, it will receive the args o (the object), k (the key).
531     * @param {Object} scope (optional) The scope of the function (defaults to this).
532     * @param {Number} start (optional) The index to start searching at (defaults to 0).
533     * @return {Number} The matched index or -1
534     */
535    findIndexBy : function(fn, scope, start){
536        var k = this.keys, it = this.items;
537        for(var i = (start||0), len = it.length; i < len; i++){
538            if(fn.call(scope||this, it[i], k[i])){
539                return i;
540            }
541        }
542        return -1;
543    },
544
545    // private
546    createValueMatcher : function(value, anyMatch, caseSensitive){
547        if(!value.exec){ // not a regex
548            value = String(value);
549            value = new RegExp((anyMatch === true ? '' : '^') + Ext.escapeRe(value), caseSensitive ? '' : 'i');
550        }
551        return value;
552    },
553
554    /**
555     * Creates a shallow copy of this collection
556     * @return {MixedCollection}
557     */
558    clone : function(){
559        var r = new Ext.util.MixedCollection();
560        var k = this.keys, it = this.items;
561        for(var i = 0, len = it.length; i < len; i++){
562            r.add(k[i], it[i]);
563        }
564        r.getKey = this.getKey;
565        return r;
566    }
567});
568/**
569 * This method calls {@link #item item()}.
570 * Returns the item associated with the passed key OR index. Key has priority over index.  This is the equivalent
571 * of calling {@link #key} first, then if nothing matched calling {@link #itemAt}.
572 * @param {String/Number} key The key or index of the item.
573 * @return {Object} If the item is found, returns the item.  If the item was not found, returns <tt>undefined</tt>.
574 * If an item was found, but is a Class, returns <tt>null</tt>.
575 */
576Ext.util.MixedCollection.prototype.get = Ext.util.MixedCollection.prototype.item;