MixedCollection.js
上传用户:shuoshiled
上传日期:2018-01-28
资源大小:10124k
文件大小:21k
源码类别:

中间件编程

开发平台:

JavaScript

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