data-list-views-debug.js
上传用户:shuoshiled
上传日期:2018-01-28
资源大小:10124k
文件大小:44k
源码类别:

中间件编程

开发平台:

JavaScript

  1. /*!  * Ext JS Library 3.0.0  * Copyright(c) 2006-2009 Ext JS, LLC  * licensing@extjs.com  * http://www.extjs.com/license  */ /**  * @class Ext.DataView  * @extends Ext.BoxComponent  * A mechanism for displaying data using custom layout templates and formatting. DataView uses an {@link Ext.XTemplate}  * as its internal templating mechanism, and is bound to an {@link Ext.data.Store}  * so that as the data in the store changes the view is automatically updated to reflect the changes.  The view also  * provides built-in behavior for many common events that can occur for its contained items including click, doubleclick,  * mouseover, mouseout, etc. as well as a built-in selection model. <b>In order to use these features, an {@link #itemSelector}  * config must be provided for the DataView to determine what nodes it will be working with.</b>  *  * <p>The example below binds a DataView to a {@link Ext.data.Store} and renders it into an {@link Ext.Panel}.</p>  * <pre><code> var store = new Ext.data.JsonStore({     url: 'get-images.php',     root: 'images',     fields: [         'name', 'url',         {name:'size', type: 'float'},         {name:'lastmod', type:'date', dateFormat:'timestamp'}     ] }); store.load(); var tpl = new Ext.XTemplate(     '&lt;tpl for="."&gt;',         '&lt;div class="thumb-wrap" id="{name}"&gt;',         '&lt;div class="thumb"&gt;&lt;img src="{url}" title="{name}"&gt;&lt;/div&gt;',         '&lt;span class="x-editable"&gt;{shortName}&lt;/span&gt;&lt;/div&gt;',     '&lt;/tpl&gt;',     '&lt;div class="x-clear"&gt;&lt;/div&gt;' ); var panel = new Ext.Panel({     id:'images-view',     frame:true,     width:535,     autoHeight:true,     collapsible:true,     layout:'fit',     title:'Simple DataView',     items: new Ext.DataView({         store: store,         tpl: tpl,         autoHeight:true,         multiSelect: true,         overClass:'x-view-over',         itemSelector:'div.thumb-wrap',         emptyText: 'No images to display'     }) }); panel.render(document.body); </code></pre>  * @constructor  * Create a new DataView  * @param {Object} config The config object  * @xtype dataview  */ Ext.DataView = Ext.extend(Ext.BoxComponent, {     /**      * @cfg {String/Array} tpl      * The HTML fragment or an array of fragments that will make up the template used by this DataView.  This should      * be specified in the same format expected by the constructor of {@link Ext.XTemplate}.      */     /**      * @cfg {Ext.data.Store} store      * The {@link Ext.data.Store} to bind this DataView to.      */     /**      * @cfg {String} itemSelector      * <b>This is a required setting</b>. A simple CSS selector (e.g. <tt>div.some-class</tt> or       * <tt>span:first-child</tt>) that will be used to determine what nodes this DataView will be      * working with.      */     /**      * @cfg {Boolean} multiSelect      * True to allow selection of more than one item at a time, false to allow selection of only a single item      * at a time or no selection at all, depending on the value of {@link #singleSelect} (defaults to false).      */     /**      * @cfg {Boolean} singleSelect      * True to allow selection of exactly one item at a time, false to allow no selection at all (defaults to false).      * Note that if {@link #multiSelect} = true, this value will be ignored.      */     /**      * @cfg {Boolean} simpleSelect      * True to enable multiselection by clicking on multiple items without requiring the user to hold Shift or Ctrl,      * false to force the user to hold Ctrl or Shift to select more than on item (defaults to false).      */     /**      * @cfg {String} overClass      * A CSS class to apply to each item in the view on mouseover (defaults to undefined).      */     /**      * @cfg {String} loadingText      * A string to display during data load operations (defaults to undefined).  If specified, this text will be      * displayed in a loading div and the view's contents will be cleared while loading, otherwise the view's      * contents will continue to display normally until the new data is loaded and the contents are replaced.      */     /**      * @cfg {String} selectedClass      * A CSS class to apply to each selected item in the view (defaults to 'x-view-selected').      */     selectedClass : "x-view-selected",     /**      * @cfg {String} emptyText      * The text to display in the view when there is no data to display (defaults to '').      */     emptyText : "",     /**      * @cfg {Boolean} deferEmptyText True to defer emptyText being applied until the store's first load      */     deferEmptyText: true,     /**      * @cfg {Boolean} trackOver True to enable mouseenter and mouseleave events      */     trackOver: false,     //private     last: false,     // private     initComponent : function(){         Ext.DataView.superclass.initComponent.call(this);         if(Ext.isString(this.tpl) || Ext.isArray(this.tpl)){             this.tpl = new Ext.XTemplate(this.tpl);         }         this.addEvents(             /**              * @event beforeclick              * Fires before a click is processed. Returns false to cancel the default action.              * @param {Ext.DataView} this              * @param {Number} index The index of the target node              * @param {HTMLElement} node The target node              * @param {Ext.EventObject} e The raw event object              */             "beforeclick",             /**              * @event click              * Fires when a template node is clicked.              * @param {Ext.DataView} this              * @param {Number} index The index of the target node              * @param {HTMLElement} node The target node              * @param {Ext.EventObject} e The raw event object              */             "click",             /**              * @event mouseenter              * Fires when the mouse enters a template node. trackOver:true or an overCls must be set to enable this event.              * @param {Ext.DataView} this              * @param {Number} index The index of the target node              * @param {HTMLElement} node The target node              * @param {Ext.EventObject} e The raw event object              */             "mouseenter",             /**              * @event mouseleave              * Fires when the mouse leaves a template node. trackOver:true or an overCls must be set to enable this event.              * @param {Ext.DataView} this              * @param {Number} index The index of the target node              * @param {HTMLElement} node The target node              * @param {Ext.EventObject} e The raw event object              */             "mouseleave",             /**              * @event containerclick              * Fires when a click occurs and it is not on a template node.              * @param {Ext.DataView} this              * @param {Ext.EventObject} e The raw event object              */             "containerclick",             /**              * @event dblclick              * Fires when a template node is double clicked.              * @param {Ext.DataView} this              * @param {Number} index The index of the target node              * @param {HTMLElement} node The target node              * @param {Ext.EventObject} e The raw event object              */             "dblclick",             /**              * @event contextmenu              * Fires when a template node is right clicked.              * @param {Ext.DataView} this              * @param {Number} index The index of the target node              * @param {HTMLElement} node The target node              * @param {Ext.EventObject} e The raw event object              */             "contextmenu",             /**              * @event containercontextmenu              * Fires when a right click occurs that is not on a template node.              * @param {Ext.DataView} this              * @param {Ext.EventObject} e The raw event object              */             "containercontextmenu",             /**              * @event selectionchange              * Fires when the selected nodes change.              * @param {Ext.DataView} this              * @param {Array} selections Array of the selected nodes              */             "selectionchange",             /**              * @event beforeselect              * Fires before a selection is made. If any handlers return false, the selection is cancelled.              * @param {Ext.DataView} this              * @param {HTMLElement} node The node to be selected              * @param {Array} selections Array of currently selected nodes              */             "beforeselect"         );         this.store = Ext.StoreMgr.lookup(this.store);         this.all = new Ext.CompositeElementLite();         this.selected = new Ext.CompositeElementLite();     },     // private     afterRender : function(){         Ext.DataView.superclass.afterRender.call(this); this.mon(this.getTemplateTarget(), {             "click": this.onClick,             "dblclick": this.onDblClick,             "contextmenu": this.onContextMenu,             scope:this         });         if(this.overClass || this.trackOver){             this.mon(this.getTemplateTarget(), {                 "mouseover": this.onMouseOver,                 "mouseout": this.onMouseOut,                 scope:this             });         }         if(this.store){             this.bindStore(this.store, true);         }     },     /**      * Refreshes the view by reloading the data from the store and re-rendering the template.      */     refresh : function(){         this.clearSelections(false, true);         var el = this.getTemplateTarget();         el.update("");         var records = this.store.getRange();         if(records.length < 1){             if(!this.deferEmptyText || this.hasSkippedEmptyText){                 el.update(this.emptyText);             }             this.all.clear();         }else{             this.tpl.overwrite(el, this.collectData(records, 0));             this.all.fill(Ext.query(this.itemSelector, el.dom));             this.updateIndexes(0);         }         this.hasSkippedEmptyText = true;     },     getTemplateTarget: function(){         return this.el;     },     /**      * Function which can be overridden to provide custom formatting for each Record that is used by this      * DataView's {@link #tpl template} to render each node.      * @param {Array/Object} data The raw data object that was used to create the Record.      * @param {Number} recordIndex the index number of the Record being prepared for rendering.      * @param {Record} record The Record being prepared for rendering.      * @return {Array/Object} The formatted data in a format expected by the internal {@link #tpl template}'s overwrite() method.      * (either an array if your params are numeric (i.e. {0}) or an object (i.e. {foo: 'bar'}))      */     prepareData : function(data){         return data;     },     /**      * <p>Function which can be overridden which returns the data object passed to this      * DataView's {@link #tpl template} to render the whole DataView.</p>      * <p>This is usually an Array of data objects, each element of which is processed by an      * {@link Ext.XTemplate XTemplate} which uses <tt>'&lt;tpl for="."&gt;'</tt> to iterate over its supplied      * data object as an Array. However, <i>named</i> properties may be placed into the data object to      * provide non-repeating data such as headings, totals etc.</p>      * @param {Array} records An Array of {@link Ext.data.Record}s to be rendered into the DataView.      * @param {Number} startIndex the index number of the Record being prepared for rendering.      * @return {Array} An Array of data objects to be processed by a repeating XTemplate. May also      * contain <i>named</i> properties.      */     collectData : function(records, startIndex){         var r = [];         for(var i = 0, len = records.length; i < len; i++){             r[r.length] = this.prepareData(records[i].data, startIndex+i, records[i]);         }         return r;     },     // private     bufferRender : function(records){         var div = document.createElement('div');         this.tpl.overwrite(div, this.collectData(records));         return Ext.query(this.itemSelector, div);     },     // private     onUpdate : function(ds, record){         var index = this.store.indexOf(record);         var sel = this.isSelected(index);         var original = this.all.elements[index];         var node = this.bufferRender([record], index)[0];         this.all.replaceElement(index, node, true);         if(sel){             this.selected.replaceElement(original, node);             this.all.item(index).addClass(this.selectedClass);         }         this.updateIndexes(index, index);     },     // private     onAdd : function(ds, records, index){         if(this.all.getCount() === 0){             this.refresh();             return;         }         var nodes = this.bufferRender(records, index), n, a = this.all.elements;         if(index < this.all.getCount()){             n = this.all.item(index).insertSibling(nodes, 'before', true);             a.splice.apply(a, [index, 0].concat(nodes));         }else{             n = this.all.last().insertSibling(nodes, 'after', true);             a.push.apply(a, nodes);         }         this.updateIndexes(index);     },     // private     onRemove : function(ds, record, index){         this.deselect(index);         this.all.removeElement(index, true);         this.updateIndexes(index);         if (this.store.getCount() === 0){             this.refresh();         }     },     /**      * Refreshes an individual node's data from the store.      * @param {Number} index The item's data index in the store      */     refreshNode : function(index){         this.onUpdate(this.store, this.store.getAt(index));     },     // private     updateIndexes : function(startIndex, endIndex){         var ns = this.all.elements;         startIndex = startIndex || 0;         endIndex = endIndex || ((endIndex === 0) ? 0 : (ns.length - 1));         for(var i = startIndex; i <= endIndex; i++){             ns[i].viewIndex = i;         }     },          /**      * Returns the store associated with this DataView.      * @return {Ext.data.Store} The store      */     getStore : function(){         return this.store;     },     /**      * Changes the data store bound to this view and refreshes it.      * @param {Store} store The store to bind to this view      */     bindStore : function(store, initial){         if(!initial && this.store){             this.store.un("beforeload", this.onBeforeLoad, this);             this.store.un("datachanged", this.refresh, this);             this.store.un("add", this.onAdd, this);             this.store.un("remove", this.onRemove, this);             this.store.un("update", this.onUpdate, this);             this.store.un("clear", this.refresh, this);             if(store !== this.store && this.store.autoDestroy){                 this.store.destroy();             }         }         if(store){             store = Ext.StoreMgr.lookup(store);             store.on({                 scope: this,                 beforeload: this.onBeforeLoad,                 datachanged: this.refresh,                 add: this.onAdd,                 remove: this.onRemove,                 update: this.onUpdate,                 clear: this.refresh             });         }         this.store = store;         if(store){             this.refresh();         }     },     /**      * Returns the template node the passed child belongs to, or null if it doesn't belong to one.      * @param {HTMLElement} node      * @return {HTMLElement} The template node      */     findItemFromChild : function(node){         return Ext.fly(node).findParent(this.itemSelector, this.getTemplateTarget());     },     // private     onClick : function(e){         var item = e.getTarget(this.itemSelector, this.getTemplateTarget());         if(item){             var index = this.indexOf(item);             if(this.onItemClick(item, index, e) !== false){                 this.fireEvent("click", this, index, item, e);             }         }else{             if(this.fireEvent("containerclick", this, e) !== false){                 this.onContainerClick(e);             }         }     },     onContainerClick : function(e){         this.clearSelections();     },     // private     onContextMenu : function(e){         var item = e.getTarget(this.itemSelector, this.getTemplateTarget());         if(item){             this.fireEvent("contextmenu", this, this.indexOf(item), item, e);         }else{             this.fireEvent("containercontextmenu", this, e);         }     },     // private     onDblClick : function(e){         var item = e.getTarget(this.itemSelector, this.getTemplateTarget());         if(item){             this.fireEvent("dblclick", this, this.indexOf(item), item, e);         }     },     // private     onMouseOver : function(e){         var item = e.getTarget(this.itemSelector, this.getTemplateTarget());         if(item && item !== this.lastItem){             this.lastItem = item;             Ext.fly(item).addClass(this.overClass);             this.fireEvent("mouseenter", this, this.indexOf(item), item, e);         }     },     // private     onMouseOut : function(e){         if(this.lastItem){             if(!e.within(this.lastItem, true, true)){                 Ext.fly(this.lastItem).removeClass(this.overClass);                 this.fireEvent("mouseleave", this, this.indexOf(this.lastItem), this.lastItem, e);                 delete this.lastItem;             }         }     },     // private     onItemClick : function(item, index, e){         if(this.fireEvent("beforeclick", this, index, item, e) === false){             return false;         }         if(this.multiSelect){             this.doMultiSelection(item, index, e);             e.preventDefault();         }else if(this.singleSelect){             this.doSingleSelection(item, index, e);             e.preventDefault();         }         return true;     },     // private     doSingleSelection : function(item, index, e){         if(e.ctrlKey && this.isSelected(index)){             this.deselect(index);         }else{             this.select(index, false);         }     },     // private     doMultiSelection : function(item, index, e){         if(e.shiftKey && this.last !== false){             var last = this.last;             this.selectRange(last, index, e.ctrlKey);             this.last = last; // reset the last         }else{             if((e.ctrlKey||this.simpleSelect) && this.isSelected(index)){                 this.deselect(index);             }else{                 this.select(index, e.ctrlKey || e.shiftKey || this.simpleSelect);             }         }     },     /**      * Gets the number of selected nodes.      * @return {Number} The node count      */     getSelectionCount : function(){         return this.selected.getCount();     },     /**      * Gets the currently selected nodes.      * @return {Array} An array of HTMLElements      */     getSelectedNodes : function(){         return this.selected.elements;     },     /**      * Gets the indexes of the selected nodes.      * @return {Array} An array of numeric indexes      */     getSelectedIndexes : function(){         var indexes = [], s = this.selected.elements;         for(var i = 0, len = s.length; i < len; i++){             indexes.push(s[i].viewIndex);         }         return indexes;     },     /**      * Gets an array of the selected records      * @return {Array} An array of {@link Ext.data.Record} objects      */     getSelectedRecords : function(){         var r = [], s = this.selected.elements;         for(var i = 0, len = s.length; i < len; i++){             r[r.length] = this.store.getAt(s[i].viewIndex);         }         return r;     },     /**      * Gets an array of the records from an array of nodes      * @param {Array} nodes The nodes to evaluate      * @return {Array} records The {@link Ext.data.Record} objects      */     getRecords : function(nodes){         var r = [], s = nodes;         for(var i = 0, len = s.length; i < len; i++){             r[r.length] = this.store.getAt(s[i].viewIndex);         }         return r;     },     /**      * Gets a record from a node      * @param {HTMLElement} node The node to evaluate      * @return {Record} record The {@link Ext.data.Record} object      */     getRecord : function(node){         return this.store.getAt(node.viewIndex);     },     /**      * Clears all selections.      * @param {Boolean} suppressEvent (optional) True to skip firing of the selectionchange event      */     clearSelections : function(suppressEvent, skipUpdate){         if((this.multiSelect || this.singleSelect) && this.selected.getCount() > 0){             if(!skipUpdate){                 this.selected.removeClass(this.selectedClass);             }             this.selected.clear();             this.last = false;             if(!suppressEvent){                 this.fireEvent("selectionchange", this, this.selected.elements);             }         }     },     /**      * Returns true if the passed node is selected, else false.      * @param {HTMLElement/Number} node The node or node index to check      * @return {Boolean} True if selected, else false      */     isSelected : function(node){         return this.selected.contains(this.getNode(node));     },     /**      * Deselects a node.      * @param {HTMLElement/Number} node The node to deselect      */     deselect : function(node){         if(this.isSelected(node)){             node = this.getNode(node);             this.selected.removeElement(node);             if(this.last == node.viewIndex){                 this.last = false;             }             Ext.fly(node).removeClass(this.selectedClass);             this.fireEvent("selectionchange", this, this.selected.elements);         }     },     /**      * Selects a set of nodes.      * @param {Array/HTMLElement/String/Number} nodeInfo An HTMLElement template node, index of a template node,      * id of a template node or an array of any of those to select      * @param {Boolean} keepExisting (optional) true to keep existing selections      * @param {Boolean} suppressEvent (optional) true to skip firing of the selectionchange vent      */     select : function(nodeInfo, keepExisting, suppressEvent){         if(Ext.isArray(nodeInfo)){             if(!keepExisting){                 this.clearSelections(true);             }             for(var i = 0, len = nodeInfo.length; i < len; i++){                 this.select(nodeInfo[i], true, true);             }             if(!suppressEvent){                 this.fireEvent("selectionchange", this, this.selected.elements);             }         } else{             var node = this.getNode(nodeInfo);             if(!keepExisting){                 this.clearSelections(true);             }             if(node && !this.isSelected(node)){                 if(this.fireEvent("beforeselect", this, node, this.selected.elements) !== false){                     Ext.fly(node).addClass(this.selectedClass);                     this.selected.add(node);                     this.last = node.viewIndex;                     if(!suppressEvent){                         this.fireEvent("selectionchange", this, this.selected.elements);                     }                 }             }         }     },     /**      * Selects a range of nodes. All nodes between start and end are selected.      * @param {Number} start The index of the first node in the range      * @param {Number} end The index of the last node in the range      * @param {Boolean} keepExisting (optional) True to retain existing selections      */     selectRange : function(start, end, keepExisting){         if(!keepExisting){             this.clearSelections(true);         }         this.select(this.getNodes(start, end), true);     },     /**      * Gets a template node.      * @param {HTMLElement/String/Number} nodeInfo An HTMLElement template node, index of a template node or the id of a template node      * @return {HTMLElement} The node or null if it wasn't found      */     getNode : function(nodeInfo){         if(Ext.isString(nodeInfo)){             return document.getElementById(nodeInfo);         }else if(Ext.isNumber(nodeInfo)){             return this.all.elements[nodeInfo];         }         return nodeInfo;     },     /**      * Gets a range nodes.      * @param {Number} start (optional) The index of the first node in the range      * @param {Number} end (optional) The index of the last node in the range      * @return {Array} An array of nodes      */     getNodes : function(start, end){         var ns = this.all.elements;         start = start || 0;         end = !Ext.isDefined(end) ? Math.max(ns.length - 1, 0) : end;         var nodes = [], i;         if(start <= end){             for(i = start; i <= end && ns[i]; i++){                 nodes.push(ns[i]);             }         } else{             for(i = start; i >= end && ns[i]; i--){                 nodes.push(ns[i]);             }         }         return nodes;     },     /**      * Finds the index of the passed node.      * @param {HTMLElement/String/Number} nodeInfo An HTMLElement template node, index of a template node or the id of a template node      * @return {Number} The index of the node or -1      */     indexOf : function(node){         node = this.getNode(node);         if(Ext.isNumber(node.viewIndex)){             return node.viewIndex;         }         return this.all.indexOf(node);     },     // private     onBeforeLoad : function(){         if(this.loadingText){             this.clearSelections(false, true);             this.getTemplateTarget().update('<div class="loading-indicator">'+this.loadingText+'</div>');             this.all.clear();         }     },     onDestroy : function(){         Ext.DataView.superclass.onDestroy.call(this);         this.bindStore(null);     } }); /**  * Changes the data store bound to this view and refreshes it. (deprecated in favor of bindStore)  * @param {Store} store The store to bind to this view  */ Ext.DataView.prototype.setStore = Ext.DataView.prototype.bindStore; Ext.reg('dataview', Ext.DataView);/**
  2.  * @class Ext.ListView
  3.  * @extends Ext.DataView
  4.  * <p>Ext.ListView is a fast and light-weight implentation of a
  5.  * {@link Ext.grid.GridPanel Grid} like view with the following characteristics:</p>
  6.  * <div class="mdetail-params"><ul>
  7.  * <li>resizable columns</li>
  8.  * <li>selectable</li>
  9.  * <li>column widths are initially proportioned by percentage based on the container
  10.  * width and number of columns</li>
  11.  * <li>uses templates to render the data in any required format</li>
  12.  * <li>no horizontal scrolling</li>
  13.  * <li>no editing</li>
  14.  * </ul></div>
  15.  * <p>Example usage:</p>
  16.  * <pre><code>
  17. // consume JSON of this form:
  18. {
  19.    "images":[
  20.       {
  21.          "name":"dance_fever.jpg",
  22.          "size":2067,
  23.          "lastmod":1236974993000,
  24.          "url":"images/thumbs/dance_fever.jpg"
  25.       },
  26.       {
  27.          "name":"zack_sink.jpg",
  28.          "size":2303,
  29.          "lastmod":1236974993000,
  30.          "url":"images/thumbs/zack_sink.jpg"
  31.       }
  32.    ]
  33. var store = new Ext.data.JsonStore({
  34.     url: 'get-images.php',
  35.     root: 'images',
  36.     fields: [
  37.         'name', 'url',
  38.         {name:'size', type: 'float'},
  39.         {name:'lastmod', type:'date', dateFormat:'timestamp'}
  40.     ]
  41. });
  42. store.load();
  43. var listView = new Ext.ListView({
  44.     store: store,
  45.     multiSelect: true,
  46.     emptyText: 'No images to display',
  47.     reserveScrollOffset: true,
  48.     columns: [{
  49.         header: 'File',
  50.         width: .5,
  51.         dataIndex: 'name'
  52.     },{
  53.         header: 'Last Modified',
  54.         width: .35, 
  55.         dataIndex: 'lastmod',
  56.         tpl: '{lastmod:date("m-d h:i a")}'
  57.     },{
  58.         header: 'Size',
  59.         dataIndex: 'size',
  60.         tpl: '{size:fileSize}', // format using Ext.util.Format.fileSize()
  61.         align: 'right'
  62.     }]
  63. });
  64. // put it in a Panel so it looks pretty
  65. var panel = new Ext.Panel({
  66.     id:'images-view',
  67.     width:425,
  68.     height:250,
  69.     collapsible:true,
  70.     layout:'fit',
  71.     title:'Simple ListView <i>(0 items selected)</i>',
  72.     items: listView
  73. });
  74. panel.render(document.body);
  75. // little bit of feedback
  76. listView.on('selectionchange', function(view, nodes){
  77.     var l = nodes.length;
  78.     var s = l != 1 ? 's' : '';
  79.     panel.setTitle('Simple ListView <i>('+l+' item'+s+' selected)</i>');
  80. });
  81.  * </code></pre>
  82.  * @constructor
  83.  * @param {Object} config
  84.  * @xtype listview
  85.  */
  86. Ext.ListView = Ext.extend(Ext.DataView, {
  87.     /**
  88.      * Set this property to <tt>true</tt> to disable the header click handler disabling sort
  89.      * (defaults to <tt>false</tt>).
  90.      * @type Boolean
  91.      * @property disableHeaders
  92.      */
  93.     /**
  94.      * @cfg {Boolean} hideHeaders
  95.      * <tt>true</tt> to hide the {@link #internalTpl header row} (defaults to <tt>false</tt> so
  96.      * the {@link #internalTpl header row} will be shown).
  97.      */
  98.     /**
  99.      * @cfg {String} itemSelector
  100.      * Defaults to <tt>'dl'</tt> to work with the preconfigured <b><tt>{@link Ext.DataView#tpl tpl}</tt></b>.
  101.      * This setting specifies the CSS selector (e.g. <tt>div.some-class</tt> or <tt>span:first-child</tt>)
  102.      * that will be used to determine what nodes the ListView will be working with.   
  103.      */
  104.     itemSelector: 'dl',
  105.     /**
  106.      * @cfg {String} selectedClass The CSS class applied to a selected row (defaults to
  107.      * <tt>'x-list-selected'</tt>). An example overriding the default styling:
  108.     <pre><code>
  109.     .x-list-selected {background-color: yellow;}
  110.     </code></pre>
  111.      * @type String
  112.      */
  113.     selectedClass:'x-list-selected',
  114.     /**
  115.      * @cfg {String} overClass The CSS class applied when over a row (defaults to
  116.      * <tt>'x-list-over'</tt>). An example overriding the default styling:
  117.     <pre><code>
  118.     .x-list-over {background-color: orange;}
  119.     </code></pre>
  120.      * @type String
  121.      */
  122.     overClass:'x-list-over',
  123.     /**
  124.      * @cfg {Boolean} reserveScrollOffset
  125.      * By default will defer accounting for the configured <b><tt>{@link #scrollOffset}</tt></b>
  126.      * for 10 milliseconds.  Specify <tt>true</tt> to account for the configured
  127.      * <b><tt>{@link #scrollOffset}</tt></b> immediately.
  128.      */
  129.     /**
  130.      * @cfg {Number} scrollOffset The amount of space to reserve for the scrollbar (defaults to
  131.      * <tt>19</tt> pixels)
  132.      */
  133.     scrollOffset : 19,
  134.     /**
  135.      * @cfg {Boolean/Object} columnResize
  136.      * Specify <tt>true</tt> or specify a configuration object for {@link Ext.ListView.ColumnResizer}
  137.      * to enable the columns to be resizable (defaults to <tt>true</tt>).
  138.      */
  139.     columnResize: true,
  140.     /**
  141.      * @cfg {Array} columns An array of column configuration objects, for example:
  142.      * <pre><code>
  143. {
  144.     align: 'right',
  145.     dataIndex: 'size',
  146.     header: 'Size',
  147.     tpl: '{size:fileSize}',
  148.     width: .35
  149. }
  150.      * </code></pre> 
  151.      * Acceptable properties for each column configuration object are:
  152.      * <div class="mdetail-params"><ul>
  153.      * <li><b><tt>align</tt></b> : String<div class="sub-desc">Set the CSS text-align property
  154.      * of the column. Defaults to <tt>'left'</tt>.</div></li>
  155.      * <li><b><tt>dataIndex</tt></b> : String<div class="sub-desc">See {@link Ext.grid.Column}.
  156.      * {@link Ext.grid.Column#dataIndex dataIndex} for details.</div></li>
  157.      * <li><b><tt>header</tt></b> : String<div class="sub-desc">See {@link Ext.grid.Column}.
  158.      * {@link Ext.grid.Column#header header} for details.</div></li>
  159.      * <li><b><tt>tpl</tt></b> : String<div class="sub-desc">Specify a string to pass as the
  160.      * configuration string for {@link Ext.XTemplate}.  By default an {@link Ext.XTemplate}
  161.      * will be implicitly created using the <tt>dataIndex</tt>.</div></li>
  162.      * <li><b><tt>width</tt></b> : Number<div class="sub-desc">Percentage of the container width
  163.      * this column should be allocated.  Columns that have no width specified will be
  164.      * allocated with an equal percentage to fill 100% of the container width.  To easily take
  165.      * advantage of the full container width, leave the width of at least one column undefined.
  166.      * Note that if you do not want to take up the full width of the container, the width of
  167.      * every column needs to be explicitly defined.</div></li>
  168.      * </ul></div>
  169.      */
  170.     /**
  171.      * @cfg {Boolean/Object} columnSort
  172.      * Specify <tt>true</tt> or specify a configuration object for {@link Ext.ListView.Sorter}
  173.      * to enable the columns to be sortable (defaults to <tt>true</tt>).
  174.      */
  175.     columnSort: true,
  176.     /**
  177.      * @cfg {String/Array} internalTpl
  178.      * The template to be used for the header row.  See {@link #tpl} for more details.
  179.      */
  180.     initComponent : function(){
  181.         if(this.columnResize){
  182.             this.colResizer = new Ext.ListView.ColumnResizer(this.colResizer);
  183.             this.colResizer.init(this);
  184.         }
  185.         if(this.columnSort){
  186.             this.colSorter = new Ext.ListView.Sorter(this.columnSort);
  187.             this.colSorter.init(this);
  188.         }
  189.         if(!this.internalTpl){
  190.             this.internalTpl = new Ext.XTemplate(
  191.                 '<div class="x-list-header"><div class="x-list-header-inner">',
  192.                     '<tpl for="columns">',
  193.                     '<div style="width:{width}%;text-align:{align};"><em unselectable="on" id="',this.id, '-xlhd-{#}">',
  194.                         '{header}',
  195.                     '</em></div>',
  196.                     '</tpl>',
  197.                     '<div class="x-clear"></div>',
  198.                 '</div></div>',
  199.                 '<div class="x-list-body"><div class="x-list-body-inner">',
  200.                 '</div></div>'
  201.             );
  202.         }
  203.         if(!this.tpl){
  204.             this.tpl = new Ext.XTemplate(
  205.                 '<tpl for="rows">',
  206.                     '<dl>',
  207.                         '<tpl for="parent.columns">',
  208.                         '<dt style="width:{width}%;text-align:{align};"><em unselectable="on">',
  209.                             '{[values.tpl.apply(parent)]}',
  210.                         '</em></dt>',
  211.                         '</tpl>',
  212.                         '<div class="x-clear"></div>',
  213.                     '</dl>',
  214.                 '</tpl>'
  215.             );
  216.         };
  217.         var cs = this.columns, allocatedWidth = 0, colsWithWidth = 0, len = cs.length;
  218.         for(var i = 0; i < len; i++){
  219.             var c = cs[i];
  220.             if(!c.tpl){
  221.                 c.tpl = new Ext.XTemplate('{' + c.dataIndex + '}');
  222.             }else if(Ext.isString(c.tpl)){
  223.                 c.tpl = new Ext.XTemplate(c.tpl);
  224.             }
  225.             c.align = c.align || 'left';
  226.             if(Ext.isNumber(c.width)){
  227.                 c.width *= 100;
  228.                 allocatedWidth += c.width;
  229.                 colsWithWidth++;
  230.             }
  231.         }
  232.         // auto calculate missing column widths
  233.         if(colsWithWidth < len){
  234.             var remaining = len - colsWithWidth;
  235.             if(allocatedWidth < 100){
  236.                 var perCol = ((100-allocatedWidth) / remaining);
  237.                 for(var j = 0; j < len; j++){
  238.                     var c = cs[j];
  239.                     if(!Ext.isNumber(c.width)){
  240.                         c.width = perCol;
  241.                     }
  242.                 }
  243.             }
  244.         }
  245.         Ext.ListView.superclass.initComponent.call(this);
  246.     },
  247.     onRender : function(){
  248.         Ext.ListView.superclass.onRender.apply(this, arguments);
  249.         this.internalTpl.overwrite(this.el, {columns: this.columns});
  250.         
  251.         this.innerBody = Ext.get(this.el.dom.childNodes[1].firstChild);
  252.         this.innerHd = Ext.get(this.el.dom.firstChild.firstChild);
  253.         if(this.hideHeaders){
  254.             this.el.dom.firstChild.style.display = 'none';
  255.         }
  256.     },
  257.     getTemplateTarget : function(){
  258.         return this.innerBody;
  259.     },
  260.     /**
  261.      * <p>Function which can be overridden which returns the data object passed to this
  262.      * view's {@link #tpl template} to render the whole ListView. The returned object 
  263.      * shall contain the following properties:</p>
  264.      * <div class="mdetail-params"><ul>
  265.      * <li><b>columns</b> : String<div class="sub-desc">See <tt>{@link #columns}</tt></div></li>
  266.      * <li><b>rows</b> : String<div class="sub-desc">See
  267.      * <tt>{@link Ext.DataView}.{@link Ext.DataView#collectData collectData}</div></li>
  268.      * </ul></div>
  269.      * @param {Array} records An Array of {@link Ext.data.Record}s to be rendered into the DataView.
  270.      * @param {Number} startIndex the index number of the Record being prepared for rendering.
  271.      * @return {Object} A data object containing properties to be processed by a repeating
  272.      * XTemplate as described above.
  273.      */
  274.     collectData : function(){
  275.         var rs = Ext.ListView.superclass.collectData.apply(this, arguments);
  276.         return {
  277.             columns: this.columns,
  278.             rows: rs
  279.         }
  280.     },
  281.     verifyInternalSize : function(){
  282.         if(this.lastSize){
  283.             this.onResize(this.lastSize.width, this.lastSize.height);
  284.         }
  285.     },
  286.     // private
  287.     onResize : function(w, h){
  288.         var bd = this.innerBody.dom;
  289.         var hd = this.innerHd.dom
  290.         if(!bd){
  291.             return;
  292.         }
  293.         var bdp = bd.parentNode;
  294.         if(Ext.isNumber(w)){
  295.             var sw = w - this.scrollOffset;
  296.             if(this.reserveScrollOffset || ((bdp.offsetWidth - bdp.clientWidth) > 10)){
  297.                 bd.style.width = sw + 'px';
  298.                 hd.style.width = sw + 'px';
  299.             }else{
  300.                 bd.style.width = w + 'px';
  301.                 hd.style.width = w + 'px';
  302.                 setTimeout(function(){
  303.                     if((bdp.offsetWidth - bdp.clientWidth) > 10){
  304.                         bd.style.width = sw + 'px';
  305.                         hd.style.width = sw + 'px';
  306.                     }
  307.                 }, 10);
  308.             }
  309.         }
  310.         if(Ext.isNumber(h == 'number')){
  311.             bdp.style.height = (h - hd.parentNode.offsetHeight) + 'px';
  312.         }
  313.     },
  314.     updateIndexes : function(){
  315.         Ext.ListView.superclass.updateIndexes.apply(this, arguments);
  316.         this.verifyInternalSize();
  317.     },
  318.     findHeaderIndex : function(hd){
  319.         hd = hd.dom || hd;
  320.         var pn = hd.parentNode, cs = pn.parentNode.childNodes;
  321.         for(var i = 0, c; c = cs[i]; i++){
  322.             if(c == pn){
  323.                 return i;
  324.             }
  325.         }
  326.         return -1;
  327.     },
  328.     setHdWidths : function(){
  329.         var els = this.innerHd.dom.getElementsByTagName('div');
  330.         for(var i = 0, cs = this.columns, len = cs.length; i < len; i++){
  331.             els[i].style.width = cs[i].width + '%';
  332.         }
  333.     }
  334. });
  335. Ext.reg('listview', Ext.ListView);/**
  336.  * @class Ext.ListView.ColumnResizer
  337.  * @extends Ext.util.Observable
  338.  * <p>Supporting Class for Ext.ListView.</p>
  339.  * @constructor
  340.  * @param {Object} config
  341.  */
  342. Ext.ListView.ColumnResizer = Ext.extend(Ext.util.Observable, {
  343.     /**
  344.      * @cfg {Number} minPct The minimum percentage to allot for any column (defaults to <tt>.05</tt>)
  345.      */
  346.     minPct: .05,
  347.     constructor: function(config){
  348.         Ext.apply(this, config);
  349.         Ext.ListView.ColumnResizer.superclass.constructor.call(this);
  350.     },
  351.     init : function(listView){
  352.         this.view = listView;
  353.         listView.on('render', this.initEvents, this);
  354.     },
  355.     initEvents : function(view){
  356.         view.mon(view.innerHd, 'mousemove', this.handleHdMove, this);
  357.         this.tracker = new Ext.dd.DragTracker({
  358.             onBeforeStart: this.onBeforeStart.createDelegate(this),
  359.             onStart: this.onStart.createDelegate(this),
  360.             onDrag: this.onDrag.createDelegate(this),
  361.             onEnd: this.onEnd.createDelegate(this),
  362.             tolerance: 3,
  363.             autoStart: 300
  364.         });
  365.         this.tracker.initEl(view.innerHd);
  366.         view.on('beforedestroy', this.tracker.destroy, this.tracker);
  367.     },
  368.     handleHdMove : function(e, t){
  369.         var hw = 5;
  370.         var x = e.getPageX();
  371.         var hd = e.getTarget('em', 3, true);
  372.         if(hd){
  373.             var r = hd.getRegion();
  374.             var ss = hd.dom.style;
  375.             var pn = hd.dom.parentNode;
  376.             if(x - r.left <= hw && pn != pn.parentNode.firstChild){
  377.                 this.activeHd = Ext.get(pn.previousSibling.firstChild);
  378. ss.cursor = Ext.isWebKit ? 'e-resize' : 'col-resize';
  379.             } else if(r.right - x <= hw && pn != pn.parentNode.lastChild.previousSibling){
  380.                 this.activeHd = hd;
  381. ss.cursor = Ext.isWebKit ? 'w-resize' : 'col-resize';
  382.             } else{
  383.                 delete this.activeHd;
  384.                 ss.cursor = '';
  385.             }
  386.         }
  387.     },
  388.     onBeforeStart : function(e){
  389.         this.dragHd = this.activeHd;
  390.         return !!this.dragHd;
  391.     },
  392.     onStart: function(e){
  393.         this.view.disableHeaders = true;
  394.         this.proxy = this.view.el.createChild({cls:'x-list-resizer'});
  395.         this.proxy.setHeight(this.view.el.getHeight());
  396.         var x = this.tracker.getXY()[0];
  397.         var w = this.view.innerHd.getWidth();
  398.         this.hdX = this.dragHd.getX();
  399.         this.hdIndex = this.view.findHeaderIndex(this.dragHd);
  400.         this.proxy.setX(this.hdX);
  401.         this.proxy.setWidth(x-this.hdX);
  402.         this.minWidth = w*this.minPct;
  403.         this.maxWidth = w - (this.minWidth*(this.view.columns.length-1-this.hdIndex));
  404.     },
  405.     onDrag: function(e){
  406.         var cursorX = this.tracker.getXY()[0];
  407.         this.proxy.setWidth((cursorX-this.hdX).constrain(this.minWidth, this.maxWidth));
  408.     },
  409.     onEnd: function(e){
  410.         var nw = this.proxy.getWidth();
  411.         this.proxy.remove();
  412.         var index = this.hdIndex;
  413.         var vw = this.view, cs = vw.columns, len = cs.length;
  414.         var w = this.view.innerHd.getWidth(), minPct = this.minPct * 100;
  415.         var pct = Math.ceil((nw*100) / w);
  416.         var diff = cs[index].width - pct;
  417.         var each = Math.floor(diff / (len-1-index));
  418.         var mod = diff - (each * (len-1-index));
  419.         for(var i = index+1; i < len; i++){
  420.             var cw = cs[i].width + each;
  421.             var ncw = Math.max(minPct, cw);
  422.             if(cw != ncw){
  423.                 mod += cw - ncw;
  424.             }
  425.             cs[i].width = ncw;
  426.         }
  427.         cs[index].width = pct;
  428.         cs[index+1].width += mod;
  429.         delete this.dragHd;
  430.         this.view.setHdWidths();
  431.         this.view.refresh();
  432.         setTimeout(function(){
  433.             vw.disableHeaders = false;
  434.         }, 100);
  435.     }
  436. });/**
  437.  * @class Ext.ListView.Sorter
  438.  * @extends Ext.util.Observable
  439.  * <p>Supporting Class for Ext.ListView.</p>
  440.  * @constructor
  441.  * @param {Object} config
  442.  */
  443. Ext.ListView.Sorter = Ext.extend(Ext.util.Observable, {
  444.     /**
  445.      * @cfg {Array} sortClasses
  446.      * The CSS classes applied to a header when it is sorted. (defaults to <tt>["sort-asc", "sort-desc"]</tt>)
  447.      */
  448.     sortClasses : ["sort-asc", "sort-desc"],
  449.     constructor: function(config){
  450.         Ext.apply(this, config);
  451.         Ext.ListView.Sorter.superclass.constructor.call(this);
  452.     },
  453.     init : function(listView){
  454.         this.view = listView;
  455.         listView.on('render', this.initEvents, this);
  456.     },
  457.     initEvents : function(view){
  458.         view.mon(view.innerHd, 'click', this.onHdClick, this);
  459.         view.innerHd.setStyle('cursor', 'pointer');
  460.         view.mon(view.store, 'datachanged', this.updateSortState, this);
  461.         this.updateSortState.defer(10, this, [view.store]);
  462.     },
  463.     updateSortState : function(store){
  464.         var state = store.getSortState();
  465.         if(!state){
  466.             return;
  467.         }
  468.         this.sortState = state;
  469.         var cs = this.view.columns, sortColumn = -1;
  470.         for(var i = 0, len = cs.length; i < len; i++){
  471.             if(cs[i].dataIndex == state.field){
  472.                 sortColumn = i;
  473.                 break;
  474.             }
  475.         }
  476.         if(sortColumn != -1){
  477.             var sortDir = state.direction;
  478.             this.updateSortIcon(sortColumn, sortDir);
  479.         }
  480.     },
  481.     updateSortIcon : function(col, dir){
  482.         var sc = this.sortClasses;
  483.         var hds = this.view.innerHd.select('em').removeClass(sc);
  484.         hds.item(col).addClass(sc[dir == "DESC" ? 1 : 0]);
  485.     },
  486.     onHdClick : function(e){
  487.         var hd = e.getTarget('em', 3);
  488.         if(hd && !this.view.disableHeaders){
  489.             var index = this.view.findHeaderIndex(hd);
  490.             this.view.store.sort(this.view.columns[index].dataIndex);
  491.         }
  492.     }
  493. });