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