Forked mumble-django project from https://bitbucket.org/Svedrin/mumble-django
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1487 lines
51 KiB

  1. /*!
  2. * Ext JS Library 3.2.0
  3. * Copyright(c) 2006-2010 Ext JS, Inc.
  4. * licensing@extjs.com
  5. * http://www.extjs.com/license
  6. */
  7. /**
  8. * @class Ext.DataView
  9. * @extends Ext.BoxComponent
  10. * A mechanism for displaying data using custom layout templates and formatting. DataView uses an {@link Ext.XTemplate}
  11. * as its internal templating mechanism, and is bound to an {@link Ext.data.Store}
  12. * so that as the data in the store changes the view is automatically updated to reflect the changes. The view also
  13. * provides built-in behavior for many common events that can occur for its contained items including click, doubleclick,
  14. * mouseover, mouseout, etc. as well as a built-in selection model. <b>In order to use these features, an {@link #itemSelector}
  15. * config must be provided for the DataView to determine what nodes it will be working with.</b>
  16. *
  17. * <p>The example below binds a DataView to a {@link Ext.data.Store} and renders it into an {@link Ext.Panel}.</p>
  18. * <pre><code>
  19. var store = new Ext.data.JsonStore({
  20. url: 'get-images.php',
  21. root: 'images',
  22. fields: [
  23. 'name', 'url',
  24. {name:'size', type: 'float'},
  25. {name:'lastmod', type:'date', dateFormat:'timestamp'}
  26. ]
  27. });
  28. store.load();
  29. var tpl = new Ext.XTemplate(
  30. '&lt;tpl for="."&gt;',
  31. '&lt;div class="thumb-wrap" id="{name}"&gt;',
  32. '&lt;div class="thumb"&gt;&lt;img src="{url}" title="{name}"&gt;&lt;/div&gt;',
  33. '&lt;span class="x-editable"&gt;{shortName}&lt;/span&gt;&lt;/div&gt;',
  34. '&lt;/tpl&gt;',
  35. '&lt;div class="x-clear"&gt;&lt;/div&gt;'
  36. );
  37. var panel = new Ext.Panel({
  38. id:'images-view',
  39. frame:true,
  40. width:535,
  41. autoHeight:true,
  42. collapsible:true,
  43. layout:'fit',
  44. title:'Simple DataView',
  45. items: new Ext.DataView({
  46. store: store,
  47. tpl: tpl,
  48. autoHeight:true,
  49. multiSelect: true,
  50. overClass:'x-view-over',
  51. itemSelector:'div.thumb-wrap',
  52. emptyText: 'No images to display'
  53. })
  54. });
  55. panel.render(document.body);
  56. </code></pre>
  57. * @constructor
  58. * Create a new DataView
  59. * @param {Object} config The config object
  60. * @xtype dataview
  61. */
  62. Ext.DataView = Ext.extend(Ext.BoxComponent, {
  63. /**
  64. * @cfg {String/Array} tpl
  65. * The HTML fragment or an array of fragments that will make up the template used by this DataView. This should
  66. * be specified in the same format expected by the constructor of {@link Ext.XTemplate}.
  67. */
  68. /**
  69. * @cfg {Ext.data.Store} store
  70. * The {@link Ext.data.Store} to bind this DataView to.
  71. */
  72. /**
  73. * @cfg {String} itemSelector
  74. * <b>This is a required setting</b>. A simple CSS selector (e.g. <tt>div.some-class</tt> or
  75. * <tt>span:first-child</tt>) that will be used to determine what nodes this DataView will be
  76. * working with.
  77. */
  78. /**
  79. * @cfg {Boolean} multiSelect
  80. * True to allow selection of more than one item at a time, false to allow selection of only a single item
  81. * at a time or no selection at all, depending on the value of {@link #singleSelect} (defaults to false).
  82. */
  83. /**
  84. * @cfg {Boolean} singleSelect
  85. * True to allow selection of exactly one item at a time, false to allow no selection at all (defaults to false).
  86. * Note that if {@link #multiSelect} = true, this value will be ignored.
  87. */
  88. /**
  89. * @cfg {Boolean} simpleSelect
  90. * True to enable multiselection by clicking on multiple items without requiring the user to hold Shift or Ctrl,
  91. * false to force the user to hold Ctrl or Shift to select more than on item (defaults to false).
  92. */
  93. /**
  94. * @cfg {String} overClass
  95. * A CSS class to apply to each item in the view on mouseover (defaults to undefined).
  96. */
  97. /**
  98. * @cfg {String} loadingText
  99. * A string to display during data load operations (defaults to undefined). If specified, this text will be
  100. * displayed in a loading div and the view's contents will be cleared while loading, otherwise the view's
  101. * contents will continue to display normally until the new data is loaded and the contents are replaced.
  102. */
  103. /**
  104. * @cfg {String} selectedClass
  105. * A CSS class to apply to each selected item in the view (defaults to 'x-view-selected').
  106. */
  107. selectedClass : "x-view-selected",
  108. /**
  109. * @cfg {String} emptyText
  110. * The text to display in the view when there is no data to display (defaults to '').
  111. */
  112. emptyText : "",
  113. /**
  114. * @cfg {Boolean} deferEmptyText True to defer emptyText being applied until the store's first load
  115. */
  116. deferEmptyText: true,
  117. /**
  118. * @cfg {Boolean} trackOver True to enable mouseenter and mouseleave events
  119. */
  120. trackOver: false,
  121. /**
  122. * @cfg {Boolean} blockRefresh Set this to true to ignore datachanged events on the bound store. This is useful if
  123. * you wish to provide custom transition animations via a plugin (defaults to false)
  124. */
  125. blockRefresh: false,
  126. //private
  127. last: false,
  128. // private
  129. initComponent : function(){
  130. Ext.DataView.superclass.initComponent.call(this);
  131. if(Ext.isString(this.tpl) || Ext.isArray(this.tpl)){
  132. this.tpl = new Ext.XTemplate(this.tpl);
  133. }
  134. this.addEvents(
  135. /**
  136. * @event beforeclick
  137. * Fires before a click is processed. Returns false to cancel the default action.
  138. * @param {Ext.DataView} this
  139. * @param {Number} index The index of the target node
  140. * @param {HTMLElement} node The target node
  141. * @param {Ext.EventObject} e The raw event object
  142. */
  143. "beforeclick",
  144. /**
  145. * @event click
  146. * Fires when a template node is clicked.
  147. * @param {Ext.DataView} this
  148. * @param {Number} index The index of the target node
  149. * @param {HTMLElement} node The target node
  150. * @param {Ext.EventObject} e The raw event object
  151. */
  152. "click",
  153. /**
  154. * @event mouseenter
  155. * Fires when the mouse enters a template node. trackOver:true or an overClass must be set to enable this event.
  156. * @param {Ext.DataView} this
  157. * @param {Number} index The index of the target node
  158. * @param {HTMLElement} node The target node
  159. * @param {Ext.EventObject} e The raw event object
  160. */
  161. "mouseenter",
  162. /**
  163. * @event mouseleave
  164. * Fires when the mouse leaves a template node. trackOver:true or an overClass must be set to enable this event.
  165. * @param {Ext.DataView} this
  166. * @param {Number} index The index of the target node
  167. * @param {HTMLElement} node The target node
  168. * @param {Ext.EventObject} e The raw event object
  169. */
  170. "mouseleave",
  171. /**
  172. * @event containerclick
  173. * Fires when a click occurs and it is not on a template node.
  174. * @param {Ext.DataView} this
  175. * @param {Ext.EventObject} e The raw event object
  176. */
  177. "containerclick",
  178. /**
  179. * @event dblclick
  180. * Fires when a template node is double clicked.
  181. * @param {Ext.DataView} this
  182. * @param {Number} index The index of the target node
  183. * @param {HTMLElement} node The target node
  184. * @param {Ext.EventObject} e The raw event object
  185. */
  186. "dblclick",
  187. /**
  188. * @event contextmenu
  189. * Fires when a template node is right clicked.
  190. * @param {Ext.DataView} this
  191. * @param {Number} index The index of the target node
  192. * @param {HTMLElement} node The target node
  193. * @param {Ext.EventObject} e The raw event object
  194. */
  195. "contextmenu",
  196. /**
  197. * @event containercontextmenu
  198. * Fires when a right click occurs that is not on a template node.
  199. * @param {Ext.DataView} this
  200. * @param {Ext.EventObject} e The raw event object
  201. */
  202. "containercontextmenu",
  203. /**
  204. * @event selectionchange
  205. * Fires when the selected nodes change.
  206. * @param {Ext.DataView} this
  207. * @param {Array} selections Array of the selected nodes
  208. */
  209. "selectionchange",
  210. /**
  211. * @event beforeselect
  212. * Fires before a selection is made. If any handlers return false, the selection is cancelled.
  213. * @param {Ext.DataView} this
  214. * @param {HTMLElement} node The node to be selected
  215. * @param {Array} selections Array of currently selected nodes
  216. */
  217. "beforeselect"
  218. );
  219. this.store = Ext.StoreMgr.lookup(this.store);
  220. this.all = new Ext.CompositeElementLite();
  221. this.selected = new Ext.CompositeElementLite();
  222. },
  223. // private
  224. afterRender : function(){
  225. Ext.DataView.superclass.afterRender.call(this);
  226. this.mon(this.getTemplateTarget(), {
  227. "click": this.onClick,
  228. "dblclick": this.onDblClick,
  229. "contextmenu": this.onContextMenu,
  230. scope:this
  231. });
  232. if(this.overClass || this.trackOver){
  233. this.mon(this.getTemplateTarget(), {
  234. "mouseover": this.onMouseOver,
  235. "mouseout": this.onMouseOut,
  236. scope:this
  237. });
  238. }
  239. if(this.store){
  240. this.bindStore(this.store, true);
  241. }
  242. },
  243. /**
  244. * Refreshes the view by reloading the data from the store and re-rendering the template.
  245. */
  246. refresh : function() {
  247. this.clearSelections(false, true);
  248. var el = this.getTemplateTarget();
  249. el.update("");
  250. var records = this.store.getRange();
  251. if(records.length < 1){
  252. if(!this.deferEmptyText || this.hasSkippedEmptyText){
  253. el.update(this.emptyText);
  254. }
  255. this.all.clear();
  256. }else{
  257. this.tpl.overwrite(el, this.collectData(records, 0));
  258. this.all.fill(Ext.query(this.itemSelector, el.dom));
  259. this.updateIndexes(0);
  260. }
  261. this.hasSkippedEmptyText = true;
  262. },
  263. getTemplateTarget: function(){
  264. return this.el;
  265. },
  266. /**
  267. * Function which can be overridden to provide custom formatting for each Record that is used by this
  268. * DataView's {@link #tpl template} to render each node.
  269. * @param {Array/Object} data The raw data object that was used to create the Record.
  270. * @param {Number} recordIndex the index number of the Record being prepared for rendering.
  271. * @param {Record} record The Record being prepared for rendering.
  272. * @return {Array/Object} The formatted data in a format expected by the internal {@link #tpl template}'s overwrite() method.
  273. * (either an array if your params are numeric (i.e. {0}) or an object (i.e. {foo: 'bar'}))
  274. */
  275. prepareData : function(data){
  276. return data;
  277. },
  278. /**
  279. * <p>Function which can be overridden which returns the data object passed to this
  280. * DataView's {@link #tpl template} to render the whole DataView.</p>
  281. * <p>This is usually an Array of data objects, each element of which is processed by an
  282. * {@link Ext.XTemplate XTemplate} which uses <tt>'&lt;tpl for="."&gt;'</tt> to iterate over its supplied
  283. * data object as an Array. However, <i>named</i> properties may be placed into the data object to
  284. * provide non-repeating data such as headings, totals etc.</p>
  285. * @param {Array} records An Array of {@link Ext.data.Record}s to be rendered into the DataView.
  286. * @param {Number} startIndex the index number of the Record being prepared for rendering.
  287. * @return {Array} An Array of data objects to be processed by a repeating XTemplate. May also
  288. * contain <i>named</i> properties.
  289. */
  290. collectData : function(records, startIndex){
  291. var r = [];
  292. for(var i = 0, len = records.length; i < len; i++){
  293. r[r.length] = this.prepareData(records[i].data, startIndex+i, records[i]);
  294. }
  295. return r;
  296. },
  297. // private
  298. bufferRender : function(records){
  299. var div = document.createElement('div');
  300. this.tpl.overwrite(div, this.collectData(records));
  301. return Ext.query(this.itemSelector, div);
  302. },
  303. // private
  304. onUpdate : function(ds, record){
  305. var index = this.store.indexOf(record);
  306. if(index > -1){
  307. var sel = this.isSelected(index);
  308. var original = this.all.elements[index];
  309. var node = this.bufferRender([record], index)[0];
  310. this.all.replaceElement(index, node, true);
  311. if(sel){
  312. this.selected.replaceElement(original, node);
  313. this.all.item(index).addClass(this.selectedClass);
  314. }
  315. this.updateIndexes(index, index);
  316. }
  317. },
  318. // private
  319. onAdd : function(ds, records, index){
  320. if(this.all.getCount() === 0){
  321. this.refresh();
  322. return;
  323. }
  324. var nodes = this.bufferRender(records, index), n, a = this.all.elements;
  325. if(index < this.all.getCount()){
  326. n = this.all.item(index).insertSibling(nodes, 'before', true);
  327. a.splice.apply(a, [index, 0].concat(nodes));
  328. }else{
  329. n = this.all.last().insertSibling(nodes, 'after', true);
  330. a.push.apply(a, nodes);
  331. }
  332. this.updateIndexes(index);
  333. },
  334. // private
  335. onRemove : function(ds, record, index){
  336. this.deselect(index);
  337. this.all.removeElement(index, true);
  338. this.updateIndexes(index);
  339. if (this.store.getCount() === 0){
  340. this.refresh();
  341. }
  342. },
  343. /**
  344. * Refreshes an individual node's data from the store.
  345. * @param {Number} index The item's data index in the store
  346. */
  347. refreshNode : function(index){
  348. this.onUpdate(this.store, this.store.getAt(index));
  349. },
  350. // private
  351. updateIndexes : function(startIndex, endIndex){
  352. var ns = this.all.elements;
  353. startIndex = startIndex || 0;
  354. endIndex = endIndex || ((endIndex === 0) ? 0 : (ns.length - 1));
  355. for(var i = startIndex; i <= endIndex; i++){
  356. ns[i].viewIndex = i;
  357. }
  358. },
  359. /**
  360. * Returns the store associated with this DataView.
  361. * @return {Ext.data.Store} The store
  362. */
  363. getStore : function(){
  364. return this.store;
  365. },
  366. /**
  367. * Changes the data store bound to this view and refreshes it.
  368. * @param {Store} store The store to bind to this view
  369. */
  370. bindStore : function(store, initial){
  371. if(!initial && this.store){
  372. if(store !== this.store && this.store.autoDestroy){
  373. this.store.destroy();
  374. }else{
  375. this.store.un("beforeload", this.onBeforeLoad, this);
  376. this.store.un("datachanged", this.onDataChanged, this);
  377. this.store.un("add", this.onAdd, this);
  378. this.store.un("remove", this.onRemove, this);
  379. this.store.un("update", this.onUpdate, this);
  380. this.store.un("clear", this.refresh, this);
  381. }
  382. if(!store){
  383. this.store = null;
  384. }
  385. }
  386. if(store){
  387. store = Ext.StoreMgr.lookup(store);
  388. store.on({
  389. scope: this,
  390. beforeload: this.onBeforeLoad,
  391. datachanged: this.onDataChanged,
  392. add: this.onAdd,
  393. remove: this.onRemove,
  394. update: this.onUpdate,
  395. clear: this.refresh
  396. });
  397. }
  398. this.store = store;
  399. if(store){
  400. this.refresh();
  401. }
  402. },
  403. /**
  404. * @private
  405. * Calls this.refresh if this.blockRefresh is not true
  406. */
  407. onDataChanged: function() {
  408. if (this.blockRefresh !== true) {
  409. this.refresh.apply(this, arguments);
  410. }
  411. },
  412. /**
  413. * Returns the template node the passed child belongs to, or null if it doesn't belong to one.
  414. * @param {HTMLElement} node
  415. * @return {HTMLElement} The template node
  416. */
  417. findItemFromChild : function(node){
  418. return Ext.fly(node).findParent(this.itemSelector, this.getTemplateTarget());
  419. },
  420. // private
  421. onClick : function(e){
  422. var item = e.getTarget(this.itemSelector, this.getTemplateTarget());
  423. if(item){
  424. var index = this.indexOf(item);
  425. if(this.onItemClick(item, index, e) !== false){
  426. this.fireEvent("click", this, index, item, e);
  427. }
  428. }else{
  429. if(this.fireEvent("containerclick", this, e) !== false){
  430. this.onContainerClick(e);
  431. }
  432. }
  433. },
  434. onContainerClick : function(e){
  435. this.clearSelections();
  436. },
  437. // private
  438. onContextMenu : function(e){
  439. var item = e.getTarget(this.itemSelector, this.getTemplateTarget());
  440. if(item){
  441. this.fireEvent("contextmenu", this, this.indexOf(item), item, e);
  442. }else{
  443. this.fireEvent("containercontextmenu", this, e);
  444. }
  445. },
  446. // private
  447. onDblClick : function(e){
  448. var item = e.getTarget(this.itemSelector, this.getTemplateTarget());
  449. if(item){
  450. this.fireEvent("dblclick", this, this.indexOf(item), item, e);
  451. }
  452. },
  453. // private
  454. onMouseOver : function(e){
  455. var item = e.getTarget(this.itemSelector, this.getTemplateTarget());
  456. if(item && item !== this.lastItem){
  457. this.lastItem = item;
  458. Ext.fly(item).addClass(this.overClass);
  459. this.fireEvent("mouseenter", this, this.indexOf(item), item, e);
  460. }
  461. },
  462. // private
  463. onMouseOut : function(e){
  464. if(this.lastItem){
  465. if(!e.within(this.lastItem, true, true)){
  466. Ext.fly(this.lastItem).removeClass(this.overClass);
  467. this.fireEvent("mouseleave", this, this.indexOf(this.lastItem), this.lastItem, e);
  468. delete this.lastItem;
  469. }
  470. }
  471. },
  472. // private
  473. onItemClick : function(item, index, e){
  474. if(this.fireEvent("beforeclick", this, index, item, e) === false){
  475. return false;
  476. }
  477. if(this.multiSelect){
  478. this.doMultiSelection(item, index, e);
  479. e.preventDefault();
  480. }else if(this.singleSelect){
  481. this.doSingleSelection(item, index, e);
  482. e.preventDefault();
  483. }
  484. return true;
  485. },
  486. // private
  487. doSingleSelection : function(item, index, e){
  488. if(e.ctrlKey && this.isSelected(index)){
  489. this.deselect(index);
  490. }else{
  491. this.select(index, false);
  492. }
  493. },
  494. // private
  495. doMultiSelection : function(item, index, e){
  496. if(e.shiftKey && this.last !== false){
  497. var last = this.last;
  498. this.selectRange(last, index, e.ctrlKey);
  499. this.last = last; // reset the last
  500. }else{
  501. if((e.ctrlKey||this.simpleSelect) && this.isSelected(index)){
  502. this.deselect(index);
  503. }else{
  504. this.select(index, e.ctrlKey || e.shiftKey || this.simpleSelect);
  505. }
  506. }
  507. },
  508. /**
  509. * Gets the number of selected nodes.
  510. * @return {Number} The node count
  511. */
  512. getSelectionCount : function(){
  513. return this.selected.getCount();
  514. },
  515. /**
  516. * Gets the currently selected nodes.
  517. * @return {Array} An array of HTMLElements
  518. */
  519. getSelectedNodes : function(){
  520. return this.selected.elements;
  521. },
  522. /**
  523. * Gets the indexes of the selected nodes.
  524. * @return {Array} An array of numeric indexes
  525. */
  526. getSelectedIndexes : function(){
  527. var indexes = [], s = this.selected.elements;
  528. for(var i = 0, len = s.length; i < len; i++){
  529. indexes.push(s[i].viewIndex);
  530. }
  531. return indexes;
  532. },
  533. /**
  534. * Gets an array of the selected records
  535. * @return {Array} An array of {@link Ext.data.Record} objects
  536. */
  537. getSelectedRecords : function(){
  538. var r = [], s = this.selected.elements;
  539. for(var i = 0, len = s.length; i < len; i++){
  540. r[r.length] = this.store.getAt(s[i].viewIndex);
  541. }
  542. return r;
  543. },
  544. /**
  545. * Gets an array of the records from an array of nodes
  546. * @param {Array} nodes The nodes to evaluate
  547. * @return {Array} records The {@link Ext.data.Record} objects
  548. */
  549. getRecords : function(nodes){
  550. var r = [], s = nodes;
  551. for(var i = 0, len = s.length; i < len; i++){
  552. r[r.length] = this.store.getAt(s[i].viewIndex);
  553. }
  554. return r;
  555. },
  556. /**
  557. * Gets a record from a node
  558. * @param {HTMLElement} node The node to evaluate
  559. * @return {Record} record The {@link Ext.data.Record} object
  560. */
  561. getRecord : function(node){
  562. return this.store.getAt(node.viewIndex);
  563. },
  564. /**
  565. * Clears all selections.
  566. * @param {Boolean} suppressEvent (optional) True to skip firing of the selectionchange event
  567. */
  568. clearSelections : function(suppressEvent, skipUpdate){
  569. if((this.multiSelect || this.singleSelect) && this.selected.getCount() > 0){
  570. if(!skipUpdate){
  571. this.selected.removeClass(this.selectedClass);
  572. }
  573. this.selected.clear();
  574. this.last = false;
  575. if(!suppressEvent){
  576. this.fireEvent("selectionchange", this, this.selected.elements);
  577. }
  578. }
  579. },
  580. /**
  581. * Returns true if the passed node is selected, else false.
  582. * @param {HTMLElement/Number/Ext.data.Record} node The node, node index or record to check
  583. * @return {Boolean} True if selected, else false
  584. */
  585. isSelected : function(node){
  586. return this.selected.contains(this.getNode(node));
  587. },
  588. /**
  589. * Deselects a node.
  590. * @param {HTMLElement/Number/Record} node The node, node index or record to deselect
  591. */
  592. deselect : function(node){
  593. if(this.isSelected(node)){
  594. node = this.getNode(node);
  595. this.selected.removeElement(node);
  596. if(this.last == node.viewIndex){
  597. this.last = false;
  598. }
  599. Ext.fly(node).removeClass(this.selectedClass);
  600. this.fireEvent("selectionchange", this, this.selected.elements);
  601. }
  602. },
  603. /**
  604. * Selects a set of nodes.
  605. * @param {Array/HTMLElement/String/Number/Ext.data.Record} nodeInfo An HTMLElement template node, index of a template node,
  606. * id of a template node, record associated with a node or an array of any of those to select
  607. * @param {Boolean} keepExisting (optional) true to keep existing selections
  608. * @param {Boolean} suppressEvent (optional) true to skip firing of the selectionchange vent
  609. */
  610. select : function(nodeInfo, keepExisting, suppressEvent){
  611. if(Ext.isArray(nodeInfo)){
  612. if(!keepExisting){
  613. this.clearSelections(true);
  614. }
  615. for(var i = 0, len = nodeInfo.length; i < len; i++){
  616. this.select(nodeInfo[i], true, true);
  617. }
  618. if(!suppressEvent){
  619. this.fireEvent("selectionchange", this, this.selected.elements);
  620. }
  621. } else{
  622. var node = this.getNode(nodeInfo);
  623. if(!keepExisting){
  624. this.clearSelections(true);
  625. }
  626. if(node && !this.isSelected(node)){
  627. if(this.fireEvent("beforeselect", this, node, this.selected.elements) !== false){
  628. Ext.fly(node).addClass(this.selectedClass);
  629. this.selected.add(node);
  630. this.last = node.viewIndex;
  631. if(!suppressEvent){
  632. this.fireEvent("selectionchange", this, this.selected.elements);
  633. }
  634. }
  635. }
  636. }
  637. },
  638. /**
  639. * Selects a range of nodes. All nodes between start and end are selected.
  640. * @param {Number} start The index of the first node in the range
  641. * @param {Number} end The index of the last node in the range
  642. * @param {Boolean} keepExisting (optional) True to retain existing selections
  643. */
  644. selectRange : function(start, end, keepExisting){
  645. if(!keepExisting){
  646. this.clearSelections(true);
  647. }
  648. this.select(this.getNodes(start, end), true);
  649. },
  650. /**
  651. * Gets a template node.
  652. * @param {HTMLElement/String/Number/Ext.data.Record} nodeInfo An HTMLElement template node, index of a template node,
  653. * the id of a template node or the record associated with the node.
  654. * @return {HTMLElement} The node or null if it wasn't found
  655. */
  656. getNode : function(nodeInfo){
  657. if(Ext.isString(nodeInfo)){
  658. return document.getElementById(nodeInfo);
  659. }else if(Ext.isNumber(nodeInfo)){
  660. return this.all.elements[nodeInfo];
  661. }else if(nodeInfo instanceof Ext.data.Record){
  662. var idx = this.store.indexOf(nodeInfo);
  663. return this.all.elements[idx];
  664. }
  665. return nodeInfo;
  666. },
  667. /**
  668. * Gets a range nodes.
  669. * @param {Number} start (optional) The index of the first node in the range
  670. * @param {Number} end (optional) The index of the last node in the range
  671. * @return {Array} An array of nodes
  672. */
  673. getNodes : function(start, end){
  674. var ns = this.all.elements;
  675. start = start || 0;
  676. end = !Ext.isDefined(end) ? Math.max(ns.length - 1, 0) : end;
  677. var nodes = [], i;
  678. if(start <= end){
  679. for(i = start; i <= end && ns[i]; i++){
  680. nodes.push(ns[i]);
  681. }
  682. } else{
  683. for(i = start; i >= end && ns[i]; i--){
  684. nodes.push(ns[i]);
  685. }
  686. }
  687. return nodes;
  688. },
  689. /**
  690. * Finds the index of the passed node.
  691. * @param {HTMLElement/String/Number/Record} nodeInfo An HTMLElement template node, index of a template node, the id of a template node
  692. * or a record associated with a node.
  693. * @return {Number} The index of the node or -1
  694. */
  695. indexOf : function(node){
  696. node = this.getNode(node);
  697. if(Ext.isNumber(node.viewIndex)){
  698. return node.viewIndex;
  699. }
  700. return this.all.indexOf(node);
  701. },
  702. // private
  703. onBeforeLoad : function(){
  704. if(this.loadingText){
  705. this.clearSelections(false, true);
  706. this.getTemplateTarget().update('<div class="loading-indicator">'+this.loadingText+'</div>');
  707. this.all.clear();
  708. }
  709. },
  710. onDestroy : function(){
  711. this.all.clear();
  712. this.selected.clear();
  713. Ext.DataView.superclass.onDestroy.call(this);
  714. this.bindStore(null);
  715. }
  716. });
  717. /**
  718. * Changes the data store bound to this view and refreshes it. (deprecated in favor of bindStore)
  719. * @param {Store} store The store to bind to this view
  720. */
  721. Ext.DataView.prototype.setStore = Ext.DataView.prototype.bindStore;
  722. Ext.reg('dataview', Ext.DataView);
  723. /**
  724. * @class Ext.list.ListView
  725. * @extends Ext.DataView
  726. * <p>Ext.list.ListView is a fast and light-weight implentation of a
  727. * {@link Ext.grid.GridPanel Grid} like view with the following characteristics:</p>
  728. * <div class="mdetail-params"><ul>
  729. * <li>resizable columns</li>
  730. * <li>selectable</li>
  731. * <li>column widths are initially proportioned by percentage based on the container
  732. * width and number of columns</li>
  733. * <li>uses templates to render the data in any required format</li>
  734. * <li>no horizontal scrolling</li>
  735. * <li>no editing</li>
  736. * </ul></div>
  737. * <p>Example usage:</p>
  738. * <pre><code>
  739. // consume JSON of this form:
  740. {
  741. "images":[
  742. {
  743. "name":"dance_fever.jpg",
  744. "size":2067,
  745. "lastmod":1236974993000,
  746. "url":"images\/thumbs\/dance_fever.jpg"
  747. },
  748. {
  749. "name":"zack_sink.jpg",
  750. "size":2303,
  751. "lastmod":1236974993000,
  752. "url":"images\/thumbs\/zack_sink.jpg"
  753. }
  754. ]
  755. }
  756. var store = new Ext.data.JsonStore({
  757. url: 'get-images.php',
  758. root: 'images',
  759. fields: [
  760. 'name', 'url',
  761. {name:'size', type: 'float'},
  762. {name:'lastmod', type:'date', dateFormat:'timestamp'}
  763. ]
  764. });
  765. store.load();
  766. var listView = new Ext.list.ListView({
  767. store: store,
  768. multiSelect: true,
  769. emptyText: 'No images to display',
  770. reserveScrollOffset: true,
  771. columns: [{
  772. header: 'File',
  773. width: .5,
  774. dataIndex: 'name'
  775. },{
  776. header: 'Last Modified',
  777. width: .35,
  778. dataIndex: 'lastmod',
  779. tpl: '{lastmod:date("m-d h:i a")}'
  780. },{
  781. header: 'Size',
  782. dataIndex: 'size',
  783. tpl: '{size:fileSize}', // format using Ext.util.Format.fileSize()
  784. align: 'right'
  785. }]
  786. });
  787. // put it in a Panel so it looks pretty
  788. var panel = new Ext.Panel({
  789. id:'images-view',
  790. width:425,
  791. height:250,
  792. collapsible:true,
  793. layout:'fit',
  794. title:'Simple ListView <i>(0 items selected)</i>',
  795. items: listView
  796. });
  797. panel.render(document.body);
  798. // little bit of feedback
  799. listView.on('selectionchange', function(view, nodes){
  800. var l = nodes.length;
  801. var s = l != 1 ? 's' : '';
  802. panel.setTitle('Simple ListView <i>('+l+' item'+s+' selected)</i>');
  803. });
  804. * </code></pre>
  805. * @constructor
  806. * @param {Object} config
  807. * @xtype listview
  808. */
  809. Ext.list.ListView = Ext.extend(Ext.DataView, {
  810. /**
  811. * Set this property to <tt>true</tt> to disable the header click handler disabling sort
  812. * (defaults to <tt>false</tt>).
  813. * @type Boolean
  814. * @property disableHeaders
  815. */
  816. /**
  817. * @cfg {Boolean} hideHeaders
  818. * <tt>true</tt> to hide the {@link #internalTpl header row} (defaults to <tt>false</tt> so
  819. * the {@link #internalTpl header row} will be shown).
  820. */
  821. /**
  822. * @cfg {String} itemSelector
  823. * Defaults to <tt>'dl'</tt> to work with the preconfigured <b><tt>{@link Ext.DataView#tpl tpl}</tt></b>.
  824. * This setting specifies the CSS selector (e.g. <tt>div.some-class</tt> or <tt>span:first-child</tt>)
  825. * that will be used to determine what nodes the ListView will be working with.
  826. */
  827. itemSelector: 'dl',
  828. /**
  829. * @cfg {String} selectedClass The CSS class applied to a selected row (defaults to
  830. * <tt>'x-list-selected'</tt>). An example overriding the default styling:
  831. <pre><code>
  832. .x-list-selected {background-color: yellow;}
  833. </code></pre>
  834. * @type String
  835. */
  836. selectedClass:'x-list-selected',
  837. /**
  838. * @cfg {String} overClass The CSS class applied when over a row (defaults to
  839. * <tt>'x-list-over'</tt>). An example overriding the default styling:
  840. <pre><code>
  841. .x-list-over {background-color: orange;}
  842. </code></pre>
  843. * @type String
  844. */
  845. overClass:'x-list-over',
  846. /**
  847. * @cfg {Boolean} reserveScrollOffset
  848. * By default will defer accounting for the configured <b><tt>{@link #scrollOffset}</tt></b>
  849. * for 10 milliseconds. Specify <tt>true</tt> to account for the configured
  850. * <b><tt>{@link #scrollOffset}</tt></b> immediately.
  851. */
  852. /**
  853. * @cfg {Number} scrollOffset The amount of space to reserve for the scrollbar (defaults to
  854. * <tt>undefined</tt>). If an explicit value isn't specified, this will be automatically
  855. * calculated.
  856. */
  857. scrollOffset : undefined,
  858. /**
  859. * @cfg {Boolean/Object} columnResize
  860. * Specify <tt>true</tt> or specify a configuration object for {@link Ext.list.ListView.ColumnResizer}
  861. * to enable the columns to be resizable (defaults to <tt>true</tt>).
  862. */
  863. columnResize: true,
  864. /**
  865. * @cfg {Array} columns An array of column configuration objects, for example:
  866. * <pre><code>
  867. {
  868. align: 'right',
  869. dataIndex: 'size',
  870. header: 'Size',
  871. tpl: '{size:fileSize}',
  872. width: .35
  873. }
  874. * </code></pre>
  875. * Acceptable properties for each column configuration object are:
  876. * <div class="mdetail-params"><ul>
  877. * <li><b><tt>align</tt></b> : String<div class="sub-desc">Set the CSS text-align property
  878. * of the column. Defaults to <tt>'left'</tt>.</div></li>
  879. * <li><b><tt>dataIndex</tt></b> : String<div class="sub-desc">See {@link Ext.grid.Column}.
  880. * {@link Ext.grid.Column#dataIndex dataIndex} for details.</div></li>
  881. * <li><b><tt>header</tt></b> : String<div class="sub-desc">See {@link Ext.grid.Column}.
  882. * {@link Ext.grid.Column#header header} for details.</div></li>
  883. * <li><b><tt>tpl</tt></b> : String<div class="sub-desc">Specify a string to pass as the
  884. * configuration string for {@link Ext.XTemplate}. By default an {@link Ext.XTemplate}
  885. * will be implicitly created using the <tt>dataIndex</tt>.</div></li>
  886. * <li><b><tt>width</tt></b> : Number<div class="sub-desc">Percentage of the container width
  887. * this column should be allocated. Columns that have no width specified will be
  888. * allocated with an equal percentage to fill 100% of the container width. To easily take
  889. * advantage of the full container width, leave the width of at least one column undefined.
  890. * Note that if you do not want to take up the full width of the container, the width of
  891. * every column needs to be explicitly defined.</div></li>
  892. * </ul></div>
  893. */
  894. /**
  895. * @cfg {Boolean/Object} columnSort
  896. * Specify <tt>true</tt> or specify a configuration object for {@link Ext.list.ListView.Sorter}
  897. * to enable the columns to be sortable (defaults to <tt>true</tt>).
  898. */
  899. columnSort: true,
  900. /**
  901. * @cfg {String/Array} internalTpl
  902. * The template to be used for the header row. See {@link #tpl} for more details.
  903. */
  904. /*
  905. * IE has issues when setting percentage based widths to 100%. Default to 99.
  906. */
  907. maxWidth: Ext.isIE ? 99 : 100,
  908. initComponent : function(){
  909. if(this.columnResize){
  910. this.colResizer = new Ext.list.ColumnResizer(this.colResizer);
  911. this.colResizer.init(this);
  912. }
  913. if(this.columnSort){
  914. this.colSorter = new Ext.list.Sorter(this.columnSort);
  915. this.colSorter.init(this);
  916. }
  917. if(!this.internalTpl){
  918. this.internalTpl = new Ext.XTemplate(
  919. '<div class="x-list-header"><div class="x-list-header-inner">',
  920. '<tpl for="columns">',
  921. '<div style="width:{[values.width*100]}%;text-align:{align};"><em unselectable="on" id="',this.id, '-xlhd-{#}">',
  922. '{header}',
  923. '</em></div>',
  924. '</tpl>',
  925. '<div class="x-clear"></div>',
  926. '</div></div>',
  927. '<div class="x-list-body"><div class="x-list-body-inner">',
  928. '</div></div>'
  929. );
  930. }
  931. if(!this.tpl){
  932. this.tpl = new Ext.XTemplate(
  933. '<tpl for="rows">',
  934. '<dl>',
  935. '<tpl for="parent.columns">',
  936. '<dt style="width:{[values.width*100]}%;text-align:{align};">',
  937. '<em unselectable="on"<tpl if="cls"> class="{cls}</tpl>">',
  938. '{[values.tpl.apply(parent)]}',
  939. '</em></dt>',
  940. '</tpl>',
  941. '<div class="x-clear"></div>',
  942. '</dl>',
  943. '</tpl>'
  944. );
  945. };
  946. var cs = this.columns,
  947. allocatedWidth = 0,
  948. colsWithWidth = 0,
  949. len = cs.length,
  950. columns = [];
  951. for(var i = 0; i < len; i++){
  952. var c = cs[i];
  953. if(!c.isColumn) {
  954. c.xtype = c.xtype ? (/^lv/.test(c.xtype) ? c.xtype : 'lv' + c.xtype) : 'lvcolumn';
  955. c = Ext.create(c);
  956. }
  957. if(c.width) {
  958. allocatedWidth += c.width*100;
  959. colsWithWidth++;
  960. }
  961. columns.push(c);
  962. }
  963. cs = this.columns = columns;
  964. // auto calculate missing column widths
  965. if(colsWithWidth < len){
  966. var remaining = len - colsWithWidth;
  967. if(allocatedWidth < this.maxWidth){
  968. var perCol = ((this.maxWidth-allocatedWidth) / remaining)/100;
  969. for(var j = 0; j < len; j++){
  970. var c = cs[j];
  971. if(!c.width){
  972. c.width = perCol;
  973. }
  974. }
  975. }
  976. }
  977. Ext.list.ListView.superclass.initComponent.call(this);
  978. },
  979. onRender : function(){
  980. this.autoEl = {
  981. cls: 'x-list-wrap'
  982. };
  983. Ext.list.ListView.superclass.onRender.apply(this, arguments);
  984. this.internalTpl.overwrite(this.el, {columns: this.columns});
  985. this.innerBody = Ext.get(this.el.dom.childNodes[1].firstChild);
  986. this.innerHd = Ext.get(this.el.dom.firstChild.firstChild);
  987. if(this.hideHeaders){
  988. this.el.dom.firstChild.style.display = 'none';
  989. }
  990. },
  991. getTemplateTarget : function(){
  992. return this.innerBody;
  993. },
  994. /**
  995. * <p>Function which can be overridden which returns the data object passed to this
  996. * view's {@link #tpl template} to render the whole ListView. The returned object
  997. * shall contain the following properties:</p>
  998. * <div class="mdetail-params"><ul>
  999. * <li><b>columns</b> : String<div class="sub-desc">See <tt>{@link #columns}</tt></div></li>
  1000. * <li><b>rows</b> : String<div class="sub-desc">See
  1001. * <tt>{@link Ext.DataView}.{@link Ext.DataView#collectData collectData}</div></li>
  1002. * </ul></div>
  1003. * @param {Array} records An Array of {@link Ext.data.Record}s to be rendered into the DataView.
  1004. * @param {Number} startIndex the index number of the Record being prepared for rendering.
  1005. * @return {Object} A data object containing properties to be processed by a repeating
  1006. * XTemplate as described above.
  1007. */
  1008. collectData : function(){
  1009. var rs = Ext.list.ListView.superclass.collectData.apply(this, arguments);
  1010. return {
  1011. columns: this.columns,
  1012. rows: rs
  1013. }
  1014. },
  1015. verifyInternalSize : function(){
  1016. if(this.lastSize){
  1017. this.onResize(this.lastSize.width, this.lastSize.height);
  1018. }
  1019. },
  1020. // private
  1021. onResize : function(w, h){
  1022. var bd = this.innerBody.dom;
  1023. var hd = this.innerHd.dom;
  1024. if(!bd){
  1025. return;
  1026. }
  1027. var bdp = bd.parentNode;
  1028. if(Ext.isNumber(w)){
  1029. var sw = w - Ext.num(this.scrollOffset, Ext.getScrollBarWidth());
  1030. if(this.reserveScrollOffset || ((bdp.offsetWidth - bdp.clientWidth) > 10)){
  1031. bd.style.width = sw + 'px';
  1032. hd.style.width = sw + 'px';
  1033. }else{
  1034. bd.style.width = w + 'px';
  1035. hd.style.width = w + 'px';
  1036. setTimeout(function(){
  1037. if((bdp.offsetWidth - bdp.clientWidth) > 10){
  1038. bd.style.width = sw + 'px';
  1039. hd.style.width = sw + 'px';
  1040. }
  1041. }, 10);
  1042. }
  1043. }
  1044. if(Ext.isNumber(h)){
  1045. bdp.style.height = (h - hd.parentNode.offsetHeight) + 'px';
  1046. }
  1047. },
  1048. updateIndexes : function(){
  1049. Ext.list.ListView.superclass.updateIndexes.apply(this, arguments);
  1050. this.verifyInternalSize();
  1051. },
  1052. findHeaderIndex : function(hd){
  1053. hd = hd.dom || hd;
  1054. var pn = hd.parentNode, cs = pn.parentNode.childNodes;
  1055. for(var i = 0, c; c = cs[i]; i++){
  1056. if(c == pn){
  1057. return i;
  1058. }
  1059. }
  1060. return -1;
  1061. },
  1062. setHdWidths : function(){
  1063. var els = this.innerHd.dom.getElementsByTagName('div');
  1064. for(var i = 0, cs = this.columns, len = cs.length; i < len; i++){
  1065. els[i].style.width = (cs[i].width*100) + '%';
  1066. }
  1067. }
  1068. });
  1069. Ext.reg('listview', Ext.list.ListView);
  1070. // Backwards compatibility alias
  1071. Ext.ListView = Ext.list.ListView;/**
  1072. * @class Ext.list.Column
  1073. * <p>This class encapsulates column configuration data to be used in the initialization of a
  1074. * {@link Ext.list.ListView ListView}.</p>
  1075. * <p>While subclasses are provided to render data in different ways, this class renders a passed
  1076. * data field unchanged and is usually used for textual columns.</p>
  1077. */
  1078. Ext.list.Column = Ext.extend(Object, {
  1079. /**
  1080. * @private
  1081. * @cfg {Boolean} isColumn
  1082. * Used by ListView constructor method to avoid reprocessing a Column
  1083. * if <code>isColumn</code> is not set ListView will recreate a new Ext.list.Column
  1084. * Defaults to true.
  1085. */
  1086. isColumn: true,
  1087. /**
  1088. * @cfg {String} align
  1089. * Set the CSS text-align property of the column. Defaults to <tt>'left'</tt>.
  1090. */
  1091. align: 'left',
  1092. /**
  1093. * @cfg {String} header Optional. The header text to be used as innerHTML
  1094. * (html tags are accepted) to display in the ListView. <b>Note</b>: to
  1095. * have a clickable header with no text displayed use <tt>'&#160;'</tt>.
  1096. */
  1097. header: '',
  1098. /**
  1099. * @cfg {Number} width Optional. Percentage of the container width
  1100. * this column should be allocated. Columns that have no width specified will be
  1101. * allocated with an equal percentage to fill 100% of the container width. To easily take
  1102. * advantage of the full container width, leave the width of at least one column undefined.
  1103. * Note that if you do not want to take up the full width of the container, the width of
  1104. * every column needs to be explicitly defined.
  1105. */
  1106. width: null,
  1107. /**
  1108. * @cfg {String} cls Optional. This option can be used to add a CSS class to the cell of each
  1109. * row for this column.
  1110. */
  1111. cls: '',
  1112. /**
  1113. * @cfg {String} tpl Optional. Specify a string to pass as the
  1114. * configuration string for {@link Ext.XTemplate}. By default an {@link Ext.XTemplate}
  1115. * will be implicitly created using the <tt>dataIndex</tt>.
  1116. */
  1117. /**
  1118. * @cfg {String} dataIndex <p><b>Required</b>. The name of the field in the
  1119. * ListViews's {@link Ext.data.Store}'s {@link Ext.data.Record} definition from
  1120. * which to draw the column's value.</p>
  1121. */
  1122. constructor : function(c){
  1123. if(!c.tpl){
  1124. c.tpl = new Ext.XTemplate('{' + c.dataIndex + '}');
  1125. }
  1126. else if(Ext.isString(c.tpl)){
  1127. c.tpl = new Ext.XTemplate(c.tpl);
  1128. }
  1129. Ext.apply(this, c);
  1130. }
  1131. });
  1132. Ext.reg('lvcolumn', Ext.list.Column);
  1133. /**
  1134. * @class Ext.list.NumberColumn
  1135. * @extends Ext.list.Column
  1136. * <p>A Column definition class which renders a numeric data field according to a {@link #format} string. See the
  1137. * {@link Ext.list.Column#xtype xtype} config option of {@link Ext.list.Column} for more details.</p>
  1138. */
  1139. Ext.list.NumberColumn = Ext.extend(Ext.list.Column, {
  1140. /**
  1141. * @cfg {String} format
  1142. * A formatting string as used by {@link Ext.util.Format#number} to format a numeric value for this Column
  1143. * (defaults to <tt>'0,000.00'</tt>).
  1144. */
  1145. format: '0,000.00',
  1146. constructor : function(c) {
  1147. c.tpl = c.tpl || new Ext.XTemplate('{' + c.dataIndex + ':number("' + (c.format || this.format) + '")}');
  1148. Ext.list.NumberColumn.superclass.constructor.call(this, c);
  1149. }
  1150. });
  1151. Ext.reg('lvnumbercolumn', Ext.list.NumberColumn);
  1152. /**
  1153. * @class Ext.list.DateColumn
  1154. * @extends Ext.list.Column
  1155. * <p>A Column definition class which renders a passed date according to the default locale, or a configured
  1156. * {@link #format}. See the {@link Ext.list.Column#xtype xtype} config option of {@link Ext.list.Column}
  1157. * for more details.</p>
  1158. */
  1159. Ext.list.DateColumn = Ext.extend(Ext.list.Column, {
  1160. format: 'm/d/Y',
  1161. constructor : function(c) {
  1162. c.tpl = c.tpl || new Ext.XTemplate('{' + c.dataIndex + ':date("' + (c.format || this.format) + '")}');
  1163. Ext.list.DateColumn.superclass.constructor.call(this, c);
  1164. }
  1165. });
  1166. Ext.reg('lvdatecolumn', Ext.list.DateColumn);
  1167. /**
  1168. * @class Ext.list.BooleanColumn
  1169. * @extends Ext.list.Column
  1170. * <p>A Column definition class which renders boolean data fields. See the {@link Ext.list.Column#xtype xtype}
  1171. * config option of {@link Ext.list.Column} for more details.</p>
  1172. */
  1173. Ext.list.BooleanColumn = Ext.extend(Ext.list.Column, {
  1174. /**
  1175. * @cfg {String} trueText
  1176. * The string returned by the renderer when the column value is not falsey (defaults to <tt>'true'</tt>).
  1177. */
  1178. trueText: 'true',
  1179. /**
  1180. * @cfg {String} falseText
  1181. * The string returned by the renderer when the column value is falsey (but not undefined) (defaults to
  1182. * <tt>'false'</tt>).
  1183. */
  1184. falseText: 'false',
  1185. /**
  1186. * @cfg {String} undefinedText
  1187. * The string returned by the renderer when the column value is undefined (defaults to <tt>'&#160;'</tt>).
  1188. */
  1189. undefinedText: '&#160;',
  1190. constructor : function(c) {
  1191. c.tpl = c.tpl || new Ext.XTemplate('{' + c.dataIndex + ':this.format}');
  1192. var t = this.trueText, f = this.falseText, u = this.undefinedText;
  1193. c.tpl.format = function(v){
  1194. if(v === undefined){
  1195. return u;
  1196. }
  1197. if(!v || v === 'false'){
  1198. return f;
  1199. }
  1200. return t;
  1201. };
  1202. Ext.list.DateColumn.superclass.constructor.call(this, c);
  1203. }
  1204. });
  1205. Ext.reg('lvbooleancolumn', Ext.list.BooleanColumn);/**
  1206. * @class Ext.list.ColumnResizer
  1207. * @extends Ext.util.Observable
  1208. * <p>Supporting Class for Ext.list.ListView</p>
  1209. * @constructor
  1210. * @param {Object} config
  1211. */
  1212. Ext.list.ColumnResizer = Ext.extend(Ext.util.Observable, {
  1213. /**
  1214. * @cfg {Number} minPct The minimum percentage to allot for any column (defaults to <tt>.05</tt>)
  1215. */
  1216. minPct: .05,
  1217. constructor: function(config){
  1218. Ext.apply(this, config);
  1219. Ext.list.ColumnResizer.superclass.constructor.call(this);
  1220. },
  1221. init : function(listView){
  1222. this.view = listView;
  1223. listView.on('render', this.initEvents, this);
  1224. },
  1225. initEvents : function(view){
  1226. view.mon(view.innerHd, 'mousemove', this.handleHdMove, this);
  1227. this.tracker = new Ext.dd.DragTracker({
  1228. onBeforeStart: this.onBeforeStart.createDelegate(this),
  1229. onStart: this.onStart.createDelegate(this),
  1230. onDrag: this.onDrag.createDelegate(this),
  1231. onEnd: this.onEnd.createDelegate(this),
  1232. tolerance: 3,
  1233. autoStart: 300
  1234. });
  1235. this.tracker.initEl(view.innerHd);
  1236. view.on('beforedestroy', this.tracker.destroy, this.tracker);
  1237. },
  1238. handleHdMove : function(e, t){
  1239. var hw = 5,
  1240. x = e.getPageX(),
  1241. hd = e.getTarget('em', 3, true);
  1242. if(hd){
  1243. var r = hd.getRegion(),
  1244. ss = hd.dom.style,
  1245. pn = hd.dom.parentNode;
  1246. if(x - r.left <= hw && pn != pn.parentNode.firstChild){
  1247. this.activeHd = Ext.get(pn.previousSibling.firstChild);
  1248. ss.cursor = Ext.isWebKit ? 'e-resize' : 'col-resize';
  1249. } else if(r.right - x <= hw && pn != pn.parentNode.lastChild.previousSibling){
  1250. this.activeHd = hd;
  1251. ss.cursor = Ext.isWebKit ? 'w-resize' : 'col-resize';
  1252. } else{
  1253. delete this.activeHd;
  1254. ss.cursor = '';
  1255. }
  1256. }
  1257. },
  1258. onBeforeStart : function(e){
  1259. this.dragHd = this.activeHd;
  1260. return !!this.dragHd;
  1261. },
  1262. onStart: function(e){
  1263. this.view.disableHeaders = true;
  1264. this.proxy = this.view.el.createChild({cls:'x-list-resizer'});
  1265. this.proxy.setHeight(this.view.el.getHeight());
  1266. var x = this.tracker.getXY()[0],
  1267. w = this.view.innerHd.getWidth();
  1268. this.hdX = this.dragHd.getX();
  1269. this.hdIndex = this.view.findHeaderIndex(this.dragHd);
  1270. this.proxy.setX(this.hdX);
  1271. this.proxy.setWidth(x-this.hdX);
  1272. this.minWidth = w*this.minPct;
  1273. this.maxWidth = w - (this.minWidth*(this.view.columns.length-1-this.hdIndex));
  1274. },
  1275. onDrag: function(e){
  1276. var cursorX = this.tracker.getXY()[0];
  1277. this.proxy.setWidth((cursorX-this.hdX).constrain(this.minWidth, this.maxWidth));
  1278. },
  1279. onEnd: function(e){
  1280. /* calculate desired width by measuring proxy and then remove it */
  1281. var nw = this.proxy.getWidth();
  1282. this.proxy.remove();
  1283. var index = this.hdIndex,
  1284. vw = this.view,
  1285. cs = vw.columns,
  1286. len = cs.length,
  1287. w = this.view.innerHd.getWidth(),
  1288. minPct = this.minPct * 100,
  1289. pct = Math.ceil((nw * vw.maxWidth) / w),
  1290. diff = (cs[index].width * 100) - pct,
  1291. eachItem = Math.floor(diff / (len-1-index)),
  1292. mod = diff - (eachItem * (len-1-index));
  1293. for(var i = index+1; i < len; i++){
  1294. var cw = (cs[i].width * 100) + eachItem,
  1295. ncw = Math.max(minPct, cw);
  1296. if(cw != ncw){
  1297. mod += cw - ncw;
  1298. }
  1299. cs[i].width = ncw / 100;
  1300. }
  1301. cs[index].width = pct / 100;
  1302. cs[index+1].width += (mod / 100);
  1303. delete this.dragHd;
  1304. vw.setHdWidths();
  1305. vw.refresh();
  1306. setTimeout(function(){
  1307. vw.disableHeaders = false;
  1308. }, 100);
  1309. }
  1310. });
  1311. // Backwards compatibility alias
  1312. Ext.ListView.ColumnResizer = Ext.list.ColumnResizer;/**
  1313. * @class Ext.list.Sorter
  1314. * @extends Ext.util.Observable
  1315. * <p>Supporting Class for Ext.list.ListView</p>
  1316. * @constructor
  1317. * @param {Object} config
  1318. */
  1319. Ext.list.Sorter = Ext.extend(Ext.util.Observable, {
  1320. /**
  1321. * @cfg {Array} sortClasses
  1322. * The CSS classes applied to a header when it is sorted. (defaults to <tt>["sort-asc", "sort-desc"]</tt>)
  1323. */
  1324. sortClasses : ["sort-asc", "sort-desc"],
  1325. constructor: function(config){
  1326. Ext.apply(this, config);
  1327. Ext.list.Sorter.superclass.constructor.call(this);
  1328. },
  1329. init : function(listView){
  1330. this.view = listView;
  1331. listView.on('render', this.initEvents, this);
  1332. },
  1333. initEvents : function(view){
  1334. view.mon(view.innerHd, 'click', this.onHdClick, this);
  1335. view.innerHd.setStyle('cursor', 'pointer');
  1336. view.mon(view.store, 'datachanged', this.updateSortState, this);
  1337. this.updateSortState.defer(10, this, [view.store]);
  1338. },
  1339. updateSortState : function(store){
  1340. var state = store.getSortState();
  1341. if(!state){
  1342. return;
  1343. }
  1344. this.sortState = state;
  1345. var cs = this.view.columns, sortColumn = -1;
  1346. for(var i = 0, len = cs.length; i < len; i++){
  1347. if(cs[i].dataIndex == state.field){
  1348. sortColumn = i;
  1349. break;
  1350. }
  1351. }
  1352. if(sortColumn != -1){
  1353. var sortDir = state.direction;
  1354. this.updateSortIcon(sortColumn, sortDir);
  1355. }
  1356. },
  1357. updateSortIcon : function(col, dir){
  1358. var sc = this.sortClasses;
  1359. var hds = this.view.innerHd.select('em').removeClass(sc);
  1360. hds.item(col).addClass(sc[dir == "DESC" ? 1 : 0]);
  1361. },
  1362. onHdClick : function(e){
  1363. var hd = e.getTarget('em', 3);
  1364. if(hd && !this.view.disableHeaders){
  1365. var index = this.view.findHeaderIndex(hd);
  1366. this.view.store.sort(this.view.columns[index].dataIndex);
  1367. }
  1368. }
  1369. });
  1370. // Backwards compatibility alias
  1371. Ext.ListView.Sorter = Ext.list.Sorter;