Upgrade to ExtJS 3.1.0 - Released 12/16/2009
[extjs.git] / docs / source / GridView.html
1 <html>\r
2 <head>\r
3   <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />    \r
4   <title>The source code</title>\r
5     <link href="../resources/prettify/prettify.css" type="text/css" rel="stylesheet" />\r
6     <script type="text/javascript" src="../resources/prettify/prettify.js"></script>\r
7 </head>\r
8 <body  onload="prettyPrint();">\r
9     <pre class="prettyprint lang-js"><div id="cls-Ext.grid.GridView"></div>/**
10  * @class Ext.grid.GridView
11  * @extends Ext.util.Observable
12  * <p>This class encapsulates the user interface of an {@link Ext.grid.GridPanel}.
13  * Methods of this class may be used to access user interface elements to enable
14  * special display effects. Do not change the DOM structure of the user interface.</p>
15  * <p>This class does not provide ways to manipulate the underlying data. The data
16  * model of a Grid is held in an {@link Ext.data.Store}.</p>
17  * @constructor
18  * @param {Object} config
19  */
20 Ext.grid.GridView = Ext.extend(Ext.util.Observable, {
21     <div id="method-Ext.grid.GridView-getRowClass"></div>/**
22      * Override this function to apply custom CSS classes to rows during rendering.  You can also supply custom
23      * parameters to the row template for the current row to customize how it is rendered using the <b>rowParams</b>
24      * parameter.  This function should return the CSS class name (or empty string '' for none) that will be added
25      * to the row's wrapping div.  To apply multiple class names, simply return them space-delimited within the string
26      * (e.g., 'my-class another-class'). Example usage:
27     <pre><code>
28 viewConfig: {
29     forceFit: true,
30     showPreview: true, // custom property
31     enableRowBody: true, // required to create a second, full-width row to show expanded Record data
32     getRowClass: function(record, rowIndex, rp, ds){ // rp = rowParams
33         if(this.showPreview){
34             rp.body = '&lt;p>'+record.data.excerpt+'&lt;/p>';
35             return 'x-grid3-row-expanded';
36         }
37         return 'x-grid3-row-collapsed';
38     }
39 },     
40     </code></pre>
41      * @param {Record} record The {@link Ext.data.Record} corresponding to the current row.
42      * @param {Number} index The row index.
43      * @param {Object} rowParams A config object that is passed to the row template during rendering that allows
44      * customization of various aspects of a grid row.
45      * <p>If {@link #enableRowBody} is configured <b><tt></tt>true</b>, then the following properties may be set
46      * by this function, and will be used to render a full-width expansion row below each grid row:</p>
47      * <ul>
48      * <li><code>body</code> : String <div class="sub-desc">An HTML fragment to be used as the expansion row's body content (defaults to '').</div></li>
49      * <li><code>bodyStyle</code> : String <div class="sub-desc">A CSS style specification that will be applied to the expansion row's &lt;tr> element. (defaults to '').</div></li>
50      * </ul>
51      * The following property will be passed in, and may be appended to:
52      * <ul>
53      * <li><code>tstyle</code> : String <div class="sub-desc">A CSS style specification that willl be applied to the &lt;table> element which encapsulates
54      * both the standard grid row, and any expansion row.</div></li>
55      * </ul>
56      * @param {Store} store The {@link Ext.data.Store} this grid is bound to
57      * @method getRowClass
58      * @return {String} a CSS class name to add to the row.
59      */
60     <div id="cfg-Ext.grid.GridView-enableRowBody"></div>/**
61      * @cfg {Boolean} enableRowBody True to add a second TR element per row that can be used to provide a row body
62      * that spans beneath the data row.  Use the {@link #getRowClass} method's rowParams config to customize the row body.
63      */
64     <div id="cfg-Ext.grid.GridView-emptyText"></div>/**
65      * @cfg {String} emptyText Default text (html tags are accepted) to display in the grid body when no rows
66      * are available (defaults to ''). This value will be used to update the <tt>{@link #mainBody}</tt>:
67     <pre><code>
68     this.mainBody.update('&lt;div class="x-grid-empty">' + this.emptyText + '&lt;/div>');
69     </code></pre>
70      */
71     <div id="cfg-Ext.grid.GridView-headersDisabled"></div>/**
72      * @cfg {Boolean} headersDisabled True to disable the grid column headers (defaults to <tt>false</tt>). 
73      * Use the {@link Ext.grid.ColumnModel ColumnModel} <tt>{@link Ext.grid.ColumnModel#menuDisabled menuDisabled}</tt>
74      * config to disable the <i>menu</i> for individual columns.  While this config is true the
75      * following will be disabled:<div class="mdetail-params"><ul>
76      * <li>clicking on header to sort</li>
77      * <li>the trigger to reveal the menu.</li>
78      * </ul></div>
79      */
80     <div id="prop-Ext.grid.GridView-dragZone"></div>/**
81      * <p>A customized implementation of a {@link Ext.dd.DragZone DragZone} which provides default implementations
82      * of the template methods of DragZone to enable dragging of the selected rows of a GridPanel.
83      * See {@link Ext.grid.GridDragZone} for details.</p>
84      * <p>This will <b>only</b> be present:<div class="mdetail-params"><ul>
85      * <li><i>if</i> the owning GridPanel was configured with {@link Ext.grid.GridPanel#enableDragDrop enableDragDrop}: <tt>true</tt>.</li>
86      * <li><i>after</i> the owning GridPanel has been rendered.</li>
87      * </ul></div>
88      * @property dragZone
89      * @type {Ext.grid.GridDragZone}
90      */
91     <div id="cfg-Ext.grid.GridView-deferEmptyText"></div>/**
92      * @cfg {Boolean} deferEmptyText True to defer <tt>{@link #emptyText}</tt> being applied until the store's
93      * first load (defaults to <tt>true</tt>).
94      */
95     deferEmptyText : true,
96     <div id="cfg-Ext.grid.GridView-scrollOffset"></div>/**
97      * @cfg {Number} scrollOffset The amount of space to reserve for the vertical scrollbar
98      * (defaults to <tt>undefined</tt>). If an explicit value isn't specified, this will be automatically
99      * calculated.
100      */
101     scrollOffset : undefined,
102     <div id="cfg-Ext.grid.GridView-autoFill"></div>/**
103      * @cfg {Boolean} autoFill
104      * Defaults to <tt>false</tt>.  Specify <tt>true</tt> to have the column widths re-proportioned
105      * when the grid is <b>initially rendered</b>.  The 
106      * {@link Ext.grid.Column#width initially configured width}</tt> of each column will be adjusted
107      * to fit the grid width and prevent horizontal scrolling. If columns are later resized (manually
108      * or programmatically), the other columns in the grid will <b>not</b> be resized to fit the grid width.
109      * See <tt>{@link #forceFit}</tt> also.
110      */
111     autoFill : false,
112     <div id="cfg-Ext.grid.GridView-forceFit"></div>/**
113      * @cfg {Boolean} forceFit
114      * Defaults to <tt>false</tt>.  Specify <tt>true</tt> to have the column widths re-proportioned
115      * at <b>all times</b>.  The {@link Ext.grid.Column#width initially configured width}</tt> of each
116      * column will be adjusted to fit the grid width and prevent horizontal scrolling. If columns are
117      * later resized (manually or programmatically), the other columns in the grid <b>will</b> be resized
118      * to fit the grid width. See <tt>{@link #autoFill}</tt> also.
119      */
120     forceFit : false,
121     <div id="cfg-Ext.grid.GridView-sortClasses"></div>/**
122      * @cfg {Array} sortClasses The CSS classes applied to a header when it is sorted. (defaults to <tt>['sort-asc', 'sort-desc']</tt>)
123      */
124     sortClasses : ['sort-asc', 'sort-desc'],
125     <div id="cfg-Ext.grid.GridView-sortAscText"></div>/**
126      * @cfg {String} sortAscText The text displayed in the 'Sort Ascending' menu item (defaults to <tt>'Sort Ascending'</tt>)
127      */
128     sortAscText : 'Sort Ascending',
129     <div id="cfg-Ext.grid.GridView-sortDescText"></div>/**
130      * @cfg {String} sortDescText The text displayed in the 'Sort Descending' menu item (defaults to <tt>'Sort Descending'</tt>)
131      */
132     sortDescText : 'Sort Descending',
133     <div id="cfg-Ext.grid.GridView-columnsText"></div>/**
134      * @cfg {String} columnsText The text displayed in the 'Columns' menu item (defaults to <tt>'Columns'</tt>)
135      */
136     columnsText : 'Columns',
137
138     <div id="cfg-Ext.grid.GridView-selectedRowClass"></div>/**
139      * @cfg {String} selectedRowClass The CSS class applied to a selected row (defaults to <tt>'x-grid3-row-selected'</tt>). An
140      * example overriding the default styling:
141     <pre><code>
142     .x-grid3-row-selected {background-color: yellow;}
143     </code></pre>
144      * Note that this only controls the row, and will not do anything for the text inside it.  To style inner
145      * facets (like text) use something like:
146     <pre><code>
147     .x-grid3-row-selected .x-grid3-cell-inner {
148         color: #FFCC00;
149     }
150     </code></pre>
151      * @type String
152      */
153     selectedRowClass : 'x-grid3-row-selected',
154
155     // private
156     borderWidth : 2,
157     tdClass : 'x-grid3-cell',
158     hdCls : 'x-grid3-hd',
159     markDirty : true,
160
161     <div id="cfg-Ext.grid.GridView-cellSelectorDepth"></div>/**
162      * @cfg {Number} cellSelectorDepth The number of levels to search for cells in event delegation (defaults to <tt>4</tt>)
163      */
164     cellSelectorDepth : 4,
165     <div id="cfg-Ext.grid.GridView-rowSelectorDepth"></div>/**
166      * @cfg {Number} rowSelectorDepth The number of levels to search for rows in event delegation (defaults to <tt>10</tt>)
167      */
168     rowSelectorDepth : 10,
169     
170     <div id="cfg-Ext.grid.GridView-rowBodySelectorDepth"></div>/**
171      * @cfg {Number} rowBodySelectorDepth The number of levels to search for row bodies in event delegation (defaults to <tt>10</tt>)
172      */
173     rowBodySelectorDepth : 10,
174
175     <div id="cfg-Ext.grid.GridView-cellSelector"></div>/**
176      * @cfg {String} cellSelector The selector used to find cells internally (defaults to <tt>'td.x-grid3-cell'</tt>)
177      */
178     cellSelector : 'td.x-grid3-cell',
179     <div id="cfg-Ext.grid.GridView-rowSelector"></div>/**
180      * @cfg {String} rowSelector The selector used to find rows internally (defaults to <tt>'div.x-grid3-row'</tt>)
181      */
182     rowSelector : 'div.x-grid3-row',
183     
184     <div id="cfg-Ext.grid.GridView-rowBodySelector"></div>/**
185      * @cfg {String} rowBodySelector The selector used to find row bodies internally (defaults to <tt>'div.x-grid3-row'</tt>)
186      */
187     rowBodySelector : 'div.x-grid3-row-body',
188     
189     // private
190     firstRowCls: 'x-grid3-row-first',
191     lastRowCls: 'x-grid3-row-last',
192     rowClsRe: /(?:^|\s+)x-grid3-row-(first|last|alt)(?:\s+|$)/g,
193     
194     constructor : function(config){
195         Ext.apply(this, config);
196             // These events are only used internally by the grid components
197             this.addEvents(
198                 <div id="event-Ext.grid.GridView-beforerowremoved"></div>/**
199                  * @event beforerowremoved
200                  * Internal UI Event. Fired before a row is removed.
201                  * @param {Ext.grid.GridView} view
202                  * @param {Number} rowIndex The index of the row to be removed.
203                  * @param {Ext.data.Record} record The Record to be removed
204                  */
205                 'beforerowremoved',
206                 <div id="event-Ext.grid.GridView-beforerowsinserted"></div>/**
207                  * @event beforerowsinserted
208                  * Internal UI Event. Fired before rows are inserted.
209                  * @param {Ext.grid.GridView} view
210                  * @param {Number} firstRow The index of the first row to be inserted.
211                  * @param {Number} lastRow The index of the last row to be inserted.
212                  */
213                 'beforerowsinserted',
214                 <div id="event-Ext.grid.GridView-beforerefresh"></div>/**
215                  * @event beforerefresh
216                  * Internal UI Event. Fired before the view is refreshed.
217                  * @param {Ext.grid.GridView} view
218                  */
219                 'beforerefresh',
220                 <div id="event-Ext.grid.GridView-rowremoved"></div>/**
221                  * @event rowremoved
222                  * Internal UI Event. Fired after a row is removed.
223                  * @param {Ext.grid.GridView} view
224                  * @param {Number} rowIndex The index of the row that was removed.
225                  * @param {Ext.data.Record} record The Record that was removed
226                  */
227                 'rowremoved',
228                 <div id="event-Ext.grid.GridView-rowsinserted"></div>/**
229                  * @event rowsinserted
230                  * Internal UI Event. Fired after rows are inserted.
231                  * @param {Ext.grid.GridView} view
232                  * @param {Number} firstRow The index of the first inserted.
233                  * @param {Number} lastRow The index of the last row inserted.
234                  */
235                 'rowsinserted',
236                 <div id="event-Ext.grid.GridView-rowupdated"></div>/**
237                  * @event rowupdated
238                  * Internal UI Event. Fired after a row has been updated.
239                  * @param {Ext.grid.GridView} view
240                  * @param {Number} firstRow The index of the row updated.
241                  * @param {Ext.data.record} record The Record backing the row updated.
242                  */
243                 'rowupdated',
244                 <div id="event-Ext.grid.GridView-refresh"></div>/**
245                  * @event refresh
246                  * Internal UI Event. Fired after the GridView's body has been refreshed.
247                  * @param {Ext.grid.GridView} view
248                  */
249                 'refresh'
250             );
251             Ext.grid.GridView.superclass.constructor.call(this);    
252     },
253
254     /* -------------------------------- UI Specific ----------------------------- */
255
256     // private
257     initTemplates : function(){
258         var ts = this.templates || {};
259         if(!ts.master){
260             ts.master = new Ext.Template(
261                     '<div class="x-grid3" hidefocus="true">',
262                         '<div class="x-grid3-viewport">',
263                             '<div class="x-grid3-header"><div class="x-grid3-header-inner"><div class="x-grid3-header-offset" style="{ostyle}">{header}</div></div><div class="x-clear"></div></div>',
264                             '<div class="x-grid3-scroller"><div class="x-grid3-body" style="{bstyle}">{body}</div><a href="#" class="x-grid3-focus" tabIndex="-1"></a></div>',
265                         '</div>',
266                         '<div class="x-grid3-resize-marker">&#160;</div>',
267                         '<div class="x-grid3-resize-proxy">&#160;</div>',
268                     '</div>'
269                     );
270         }
271
272         if(!ts.header){
273             ts.header = new Ext.Template(
274                     '<table border="0" cellspacing="0" cellpadding="0" style="{tstyle}">',
275                     '<thead><tr class="x-grid3-hd-row">{cells}</tr></thead>',
276                     '</table>'
277                     );
278         }
279
280         if(!ts.hcell){
281             ts.hcell = new Ext.Template(
282                     '<td class="x-grid3-hd x-grid3-cell x-grid3-td-{id} {css}" style="{style}"><div {tooltip} {attr} class="x-grid3-hd-inner x-grid3-hd-{id}" unselectable="on" style="{istyle}">', this.grid.enableHdMenu ? '<a class="x-grid3-hd-btn" href="#"></a>' : '',
283                     '{value}<img class="x-grid3-sort-icon" src="', Ext.BLANK_IMAGE_URL, '" />',
284                     '</div></td>'
285                     );
286         }
287
288         if(!ts.body){
289             ts.body = new Ext.Template('{rows}');
290         }
291
292         if(!ts.row){
293             ts.row = new Ext.Template(
294                     '<div class="x-grid3-row {alt}" style="{tstyle}"><table class="x-grid3-row-table" border="0" cellspacing="0" cellpadding="0" style="{tstyle}">',
295                     '<tbody><tr>{cells}</tr>',
296                     (this.enableRowBody ? '<tr class="x-grid3-row-body-tr" style="{bodyStyle}"><td colspan="{cols}" class="x-grid3-body-cell" tabIndex="0" hidefocus="on"><div class="x-grid3-row-body">{body}</div></td></tr>' : ''),
297                     '</tbody></table></div>'
298                     );
299         }
300
301         if(!ts.cell){
302             ts.cell = new Ext.Template(
303                     '<td class="x-grid3-col x-grid3-cell x-grid3-td-{id} {css}" style="{style}" tabIndex="0" {cellAttr}>',
304                     '<div class="x-grid3-cell-inner x-grid3-col-{id}" unselectable="on" {attr}>{value}</div>',
305                     '</td>'
306                     );
307         }
308
309         for(var k in ts){
310             var t = ts[k];
311             if(t && Ext.isFunction(t.compile) && !t.compiled){
312                 t.disableFormats = true;
313                 t.compile();
314             }
315         }
316
317         this.templates = ts;
318         this.colRe = new RegExp('x-grid3-td-([^\\s]+)', '');
319     },
320
321     // private
322     fly : function(el){
323         if(!this._flyweight){
324             this._flyweight = new Ext.Element.Flyweight(document.body);
325         }
326         this._flyweight.dom = el;
327         return this._flyweight;
328     },
329
330     // private
331     getEditorParent : function(){
332         return this.scroller.dom;
333     },
334
335     // private
336     initElements : function(){
337         var E = Ext.Element;
338
339         var el = this.grid.getGridEl().dom.firstChild;
340         var cs = el.childNodes;
341
342         this.el = new E(el);
343
344         this.mainWrap = new E(cs[0]);
345         this.mainHd = new E(this.mainWrap.dom.firstChild);
346
347         if(this.grid.hideHeaders){
348             this.mainHd.setDisplayed(false);
349         }
350
351         this.innerHd = this.mainHd.dom.firstChild;
352         this.scroller = new E(this.mainWrap.dom.childNodes[1]);
353         if(this.forceFit){
354             this.scroller.setStyle('overflow-x', 'hidden');
355         }
356         <div id="prop-Ext.grid.GridView-mainBody"></div>/**
357          * <i>Read-only</i>. The GridView's body Element which encapsulates all rows in the Grid.
358          * This {@link Ext.Element Element} is only available after the GridPanel has been rendered.
359          * @type Ext.Element
360          * @property mainBody
361          */
362         this.mainBody = new E(this.scroller.dom.firstChild);
363
364         this.focusEl = new E(this.scroller.dom.childNodes[1]);
365         this.focusEl.swallowEvent('click', true);
366
367         this.resizeMarker = new E(cs[1]);
368         this.resizeProxy = new E(cs[2]);
369     },
370
371     // private
372     getRows : function(){
373         return this.hasRows() ? this.mainBody.dom.childNodes : [];
374     },
375
376     // finder methods, used with delegation
377
378     // private
379     findCell : function(el){
380         if(!el){
381             return false;
382         }
383         return this.fly(el).findParent(this.cellSelector, this.cellSelectorDepth);
384     },
385
386     <div id="method-Ext.grid.GridView-findCellIndex"></div>/**
387      * <p>Return the index of the grid column which contains the passed HTMLElement.</p>
388      * See also {@link #findRowIndex}
389      * @param {HTMLElement} el The target element
390      * @return {Number} The column index, or <b>false</b> if the target element is not within a row of this GridView.
391      */
392     findCellIndex : function(el, requiredCls){
393         var cell = this.findCell(el);
394         if(cell && (!requiredCls || this.fly(cell).hasClass(requiredCls))){
395             return this.getCellIndex(cell);
396         }
397         return false;
398     },
399
400     // private
401     getCellIndex : function(el){
402         if(el){
403             var m = el.className.match(this.colRe);
404             if(m && m[1]){
405                 return this.cm.getIndexById(m[1]);
406             }
407         }
408         return false;
409     },
410
411     // private
412     findHeaderCell : function(el){
413         var cell = this.findCell(el);
414         return cell && this.fly(cell).hasClass(this.hdCls) ? cell : null;
415     },
416
417     // private
418     findHeaderIndex : function(el){
419         return this.findCellIndex(el, this.hdCls);
420     },
421
422     <div id="method-Ext.grid.GridView-findRow"></div>/**
423      * Return the HtmlElement representing the grid row which contains the passed element.
424      * @param {HTMLElement} el The target HTMLElement
425      * @return {HTMLElement} The row element, or null if the target element is not within a row of this GridView.
426      */
427     findRow : function(el){
428         if(!el){
429             return false;
430         }
431         return this.fly(el).findParent(this.rowSelector, this.rowSelectorDepth);
432     },
433
434     <div id="method-Ext.grid.GridView-findRowIndex"></div>/**
435      * <p>Return the index of the grid row which contains the passed HTMLElement.</p>
436      * See also {@link #findCellIndex}
437      * @param {HTMLElement} el The target HTMLElement
438      * @return {Number} The row index, or <b>false</b> if the target element is not within a row of this GridView.
439      */
440     findRowIndex : function(el){
441         var r = this.findRow(el);
442         return r ? r.rowIndex : false;
443     },
444     
445     <div id="method-Ext.grid.GridView-findRowBody"></div>/**
446      * Return the HtmlElement representing the grid row body which contains the passed element.
447      * @param {HTMLElement} el The target HTMLElement
448      * @return {HTMLElement} The row body element, or null if the target element is not within a row body of this GridView.
449      */
450     findRowBody : function(el){
451         if(!el){
452             return false;
453         }
454         return this.fly(el).findParent(this.rowBodySelector, this.rowBodySelectorDepth);
455     },
456
457     // getter methods for fetching elements dynamically in the grid
458
459     <div id="method-Ext.grid.GridView-getRow"></div>/**
460      * Return the <tt>&lt;div></tt> HtmlElement which represents a Grid row for the specified index.
461      * @param {Number} index The row index
462      * @return {HtmlElement} The div element.
463      */
464     getRow : function(row){
465         return this.getRows()[row];
466     },
467
468     <div id="method-Ext.grid.GridView-getCell"></div>/**
469      * Returns the grid's <tt>&lt;td></tt> HtmlElement at the specified coordinates.
470      * @param {Number} row The row index in which to find the cell.
471      * @param {Number} col The column index of the cell.
472      * @return {HtmlElement} The td at the specified coordinates.
473      */
474     getCell : function(row, col){
475         return this.getRow(row).getElementsByTagName('td')[col];
476     },
477
478     <div id="method-Ext.grid.GridView-getHeaderCell"></div>/**
479      * Return the <tt>&lt;td></tt> HtmlElement which represents the Grid's header cell for the specified column index.
480      * @param {Number} index The column index
481      * @return {HtmlElement} The td element.
482      */
483     getHeaderCell : function(index){
484       return this.mainHd.dom.getElementsByTagName('td')[index];
485     },
486
487     // manipulating elements
488
489     // private - use getRowClass to apply custom row classes
490     addRowClass : function(row, cls){
491         var r = this.getRow(row);
492         if(r){
493             this.fly(r).addClass(cls);
494         }
495     },
496
497     // private
498     removeRowClass : function(row, cls){
499         var r = this.getRow(row);
500         if(r){
501             this.fly(r).removeClass(cls);
502         }
503     },
504
505     // private
506     removeRow : function(row){
507         Ext.removeNode(this.getRow(row));
508         this.syncFocusEl(row);
509     },
510     
511     // private
512     removeRows : function(firstRow, lastRow){
513         var bd = this.mainBody.dom;
514         for(var rowIndex = firstRow; rowIndex <= lastRow; rowIndex++){
515             Ext.removeNode(bd.childNodes[firstRow]);
516         }
517         this.syncFocusEl(firstRow);
518     },
519
520     // scrolling stuff
521
522     // private
523     getScrollState : function(){
524         var sb = this.scroller.dom;
525         return {left: sb.scrollLeft, top: sb.scrollTop};
526     },
527
528     // private
529     restoreScroll : function(state){
530         var sb = this.scroller.dom;
531         sb.scrollLeft = state.left;
532         sb.scrollTop = state.top;
533     },
534
535     <div id="method-Ext.grid.GridView-scrollToTop"></div>/**
536      * Scrolls the grid to the top
537      */
538     scrollToTop : function(){
539         this.scroller.dom.scrollTop = 0;
540         this.scroller.dom.scrollLeft = 0;
541     },
542
543     // private
544     syncScroll : function(){
545       this.syncHeaderScroll();
546       var mb = this.scroller.dom;
547         this.grid.fireEvent('bodyscroll', mb.scrollLeft, mb.scrollTop);
548     },
549
550     // private
551     syncHeaderScroll : function(){
552         var mb = this.scroller.dom;
553         this.innerHd.scrollLeft = mb.scrollLeft;
554         this.innerHd.scrollLeft = mb.scrollLeft; // second time for IE (1/2 time first fails, other browsers ignore)
555     },
556
557     // private
558     updateSortIcon : function(col, dir){
559         var sc = this.sortClasses;
560         var hds = this.mainHd.select('td').removeClass(sc);
561         hds.item(col).addClass(sc[dir == 'DESC' ? 1 : 0]);
562     },
563
564     // private
565     updateAllColumnWidths : function(){
566         var tw = this.getTotalWidth(),
567             clen = this.cm.getColumnCount(),
568             ws = [],
569             len,
570             i;
571         for(i = 0; i < clen; i++){
572             ws[i] = this.getColumnWidth(i);
573         }
574         this.innerHd.firstChild.style.width = this.getOffsetWidth();
575         this.innerHd.firstChild.firstChild.style.width = tw;
576         this.mainBody.dom.style.width = tw;
577         for(i = 0; i < clen; i++){
578             var hd = this.getHeaderCell(i);
579             hd.style.width = ws[i];
580         }
581
582         var ns = this.getRows(), row, trow;
583         for(i = 0, len = ns.length; i < len; i++){
584             row = ns[i];
585             row.style.width = tw;
586             if(row.firstChild){
587                 row.firstChild.style.width = tw;
588                 trow = row.firstChild.rows[0];
589                 for (var j = 0; j < clen; j++) {
590                    trow.childNodes[j].style.width = ws[j];
591                 }
592             }
593         }
594
595         this.onAllColumnWidthsUpdated(ws, tw);
596     },
597
598     // private
599     updateColumnWidth : function(col, width){
600         var w = this.getColumnWidth(col);
601         var tw = this.getTotalWidth();
602         this.innerHd.firstChild.style.width = this.getOffsetWidth();
603         this.innerHd.firstChild.firstChild.style.width = tw;
604         this.mainBody.dom.style.width = tw;
605         var hd = this.getHeaderCell(col);
606         hd.style.width = w;
607
608         var ns = this.getRows(), row;
609         for(var i = 0, len = ns.length; i < len; i++){
610             row = ns[i];
611             row.style.width = tw;
612             if(row.firstChild){
613                 row.firstChild.style.width = tw;
614                 row.firstChild.rows[0].childNodes[col].style.width = w;
615             }
616         }
617
618         this.onColumnWidthUpdated(col, w, tw);
619     },
620
621     // private
622     updateColumnHidden : function(col, hidden){
623         var tw = this.getTotalWidth();
624         this.innerHd.firstChild.style.width = this.getOffsetWidth();
625         this.innerHd.firstChild.firstChild.style.width = tw;
626         this.mainBody.dom.style.width = tw;
627         var display = hidden ? 'none' : '';
628
629         var hd = this.getHeaderCell(col);
630         hd.style.display = display;
631
632         var ns = this.getRows(), row;
633         for(var i = 0, len = ns.length; i < len; i++){
634             row = ns[i];
635             row.style.width = tw;
636             if(row.firstChild){
637                 row.firstChild.style.width = tw;
638                 row.firstChild.rows[0].childNodes[col].style.display = display;
639             }
640         }
641
642         this.onColumnHiddenUpdated(col, hidden, tw);
643         delete this.lastViewWidth; // force recalc
644         this.layout();
645     },
646
647     // private
648     doRender : function(cs, rs, ds, startRow, colCount, stripe){
649         var ts = this.templates, ct = ts.cell, rt = ts.row, last = colCount-1;
650         var tstyle = 'width:'+this.getTotalWidth()+';';
651         // buffers
652         var buf = [], cb, c, p = {}, rp = {tstyle: tstyle}, r;
653         for(var j = 0, len = rs.length; j < len; j++){
654             r = rs[j]; cb = [];
655             var rowIndex = (j+startRow);
656             for(var i = 0; i < colCount; i++){
657                 c = cs[i];
658                 p.id = c.id;
659                 p.css = i === 0 ? 'x-grid3-cell-first ' : (i == last ? 'x-grid3-cell-last ' : '');
660                 p.attr = p.cellAttr = '';
661                 p.value = c.renderer.call(c.scope, r.data[c.name], p, r, rowIndex, i, ds);
662                 p.style = c.style;
663                 if(Ext.isEmpty(p.value)){
664                     p.value = '&#160;';
665                 }
666                 if(this.markDirty && r.dirty && Ext.isDefined(r.modified[c.name])){
667                     p.css += ' x-grid3-dirty-cell';
668                 }
669                 cb[cb.length] = ct.apply(p);
670             }
671             var alt = [];
672             if(stripe && ((rowIndex+1) % 2 === 0)){
673                 alt[0] = 'x-grid3-row-alt';
674             }
675             if(r.dirty){
676                 alt[1] = ' x-grid3-dirty-row';
677             }
678             rp.cols = colCount;
679             if(this.getRowClass){
680                 alt[2] = this.getRowClass(r, rowIndex, rp, ds);
681             }
682             rp.alt = alt.join(' ');
683             rp.cells = cb.join('');
684             buf[buf.length] =  rt.apply(rp);
685         }
686         return buf.join('');
687     },
688
689     // private
690     processRows : function(startRow, skipStripe){
691         if(!this.ds || this.ds.getCount() < 1){
692             return;
693         }
694         var rows = this.getRows(),
695             len = rows.length,
696             i, r;
697             
698         skipStripe = skipStripe || !this.grid.stripeRows;
699         startRow = startRow || 0;
700         for(i = 0; i<len; i++) {
701             r = rows[i];
702             if(r) {
703                 r.rowIndex = i;
704                 if(!skipStripe){
705                     r.className = r.className.replace(this.rowClsRe, ' ');
706                     if ((i + 1) % 2 === 0){
707                         r.className += ' x-grid3-row-alt';
708                     }
709                 }   
710             }          
711         }
712         // add first/last-row classes
713         if(startRow === 0){
714             Ext.fly(rows[0]).addClass(this.firstRowCls);
715         }
716         Ext.fly(rows[rows.length - 1]).addClass(this.lastRowCls);
717     },
718
719     afterRender : function(){
720         if(!this.ds || !this.cm){
721             return;
722         }
723         this.mainBody.dom.innerHTML = this.renderRows() || '&#160;';
724         this.processRows(0, true);
725
726         if(this.deferEmptyText !== true){
727             this.applyEmptyText();
728         }
729         this.grid.fireEvent('viewready', this.grid);
730     },
731
732     // private
733     renderUI : function(){
734
735         var header = this.renderHeaders();
736         var body = this.templates.body.apply({rows:'&#160;'});
737
738
739         var html = this.templates.master.apply({
740             body: body,
741             header: header,
742             ostyle: 'width:'+this.getOffsetWidth()+';',
743             bstyle: 'width:'+this.getTotalWidth()+';'
744         });
745
746         var g = this.grid;
747
748         g.getGridEl().dom.innerHTML = html;
749
750         this.initElements();
751
752         // get mousedowns early
753         Ext.fly(this.innerHd).on('click', this.handleHdDown, this);
754         this.mainHd.on({
755             scope: this,
756             mouseover: this.handleHdOver,
757             mouseout: this.handleHdOut,
758             mousemove: this.handleHdMove
759         });
760
761         this.scroller.on('scroll', this.syncScroll,  this);
762         if(g.enableColumnResize !== false){
763             this.splitZone = new Ext.grid.GridView.SplitDragZone(g, this.mainHd.dom);
764         }
765
766         if(g.enableColumnMove){
767             this.columnDrag = new Ext.grid.GridView.ColumnDragZone(g, this.innerHd);
768             this.columnDrop = new Ext.grid.HeaderDropZone(g, this.mainHd.dom);
769         }
770
771         if(g.enableHdMenu !== false){
772             this.hmenu = new Ext.menu.Menu({id: g.id + '-hctx'});
773             this.hmenu.add(
774                 {itemId:'asc', text: this.sortAscText, cls: 'xg-hmenu-sort-asc'},
775                 {itemId:'desc', text: this.sortDescText, cls: 'xg-hmenu-sort-desc'}
776             );
777             if(g.enableColumnHide !== false){
778                 this.colMenu = new Ext.menu.Menu({id:g.id + '-hcols-menu'});
779                 this.colMenu.on({
780                     scope: this,
781                     beforeshow: this.beforeColMenuShow,
782                     itemclick: this.handleHdMenuClick
783                 });
784                 this.hmenu.add('-', {
785                     itemId:'columns',
786                     hideOnClick: false,
787                     text: this.columnsText,
788                     menu: this.colMenu,
789                     iconCls: 'x-cols-icon'
790                 });
791             }
792             this.hmenu.on('itemclick', this.handleHdMenuClick, this);
793         }
794
795         if(g.trackMouseOver){
796             this.mainBody.on({
797                 scope: this,
798                 mouseover: this.onRowOver,
799                 mouseout: this.onRowOut
800             });
801         }
802
803         if(g.enableDragDrop || g.enableDrag){
804             this.dragZone = new Ext.grid.GridDragZone(g, {
805                 ddGroup : g.ddGroup || 'GridDD'
806             });
807         }
808
809         this.updateHeaderSortState();
810
811     },
812     
813     // private
814     processEvent: Ext.emptyFn,
815
816     // private
817     layout : function(){
818         if(!this.mainBody){
819             return; // not rendered
820         }
821         var g = this.grid;
822         var c = g.getGridEl();
823         var csize = c.getSize(true);
824         var vw = csize.width;
825
826         if(!g.hideHeaders && (vw < 20 || csize.height < 20)){ // display: none?
827             return;
828         }
829         
830         if(g.autoHeight){
831             this.scroller.dom.style.overflow = 'visible';
832             if(Ext.isWebKit){
833                 this.scroller.dom.style.position = 'static';
834             }
835         }else{
836             this.el.setSize(csize.width, csize.height);
837
838             var hdHeight = this.mainHd.getHeight();
839             var vh = csize.height - (hdHeight);
840
841             this.scroller.setSize(vw, vh);
842             if(this.innerHd){
843                 this.innerHd.style.width = (vw)+'px';
844             }
845         }
846         if(this.forceFit){
847             if(this.lastViewWidth != vw){
848                 this.fitColumns(false, false);
849                 this.lastViewWidth = vw;
850             }
851         }else {
852             this.autoExpand();
853             this.syncHeaderScroll();
854         }
855         this.onLayout(vw, vh);
856     },
857
858     // template functions for subclasses and plugins
859     // these functions include precalculated values
860     onLayout : function(vw, vh){
861         // do nothing
862     },
863
864     onColumnWidthUpdated : function(col, w, tw){
865         //template method
866     },
867
868     onAllColumnWidthsUpdated : function(ws, tw){
869         //template method
870     },
871
872     onColumnHiddenUpdated : function(col, hidden, tw){
873         // template method
874     },
875
876     updateColumnText : function(col, text){
877         // template method
878     },
879
880     afterMove : function(colIndex){
881         // template method
882     },
883
884     /* ----------------------------------- Core Specific -------------------------------------------*/
885     // private
886     init : function(grid){
887         this.grid = grid;
888
889         this.initTemplates();
890         this.initData(grid.store, grid.colModel);
891         this.initUI(grid);
892     },
893
894     // private
895     getColumnId : function(index){
896       return this.cm.getColumnId(index);
897     },
898     
899     // private 
900     getOffsetWidth : function() {
901         return (this.cm.getTotalWidth() + this.getScrollOffset()) + 'px';
902     },
903     
904     getScrollOffset: function(){
905         return Ext.num(this.scrollOffset, Ext.getScrollBarWidth());
906     },
907
908     // private
909     renderHeaders : function(){
910         var cm = this.cm, 
911             ts = this.templates,
912             ct = ts.hcell,
913             cb = [], 
914             p = {},
915             len = cm.getColumnCount(),
916             last = len - 1;
917             
918         for(var i = 0; i < len; i++){
919             p.id = cm.getColumnId(i);
920             p.value = cm.getColumnHeader(i) || '';
921             p.style = this.getColumnStyle(i, true);
922             p.tooltip = this.getColumnTooltip(i);
923             p.css = i === 0 ? 'x-grid3-cell-first ' : (i == last ? 'x-grid3-cell-last ' : '');
924             if(cm.config[i].align == 'right'){
925                 p.istyle = 'padding-right:16px';
926             } else {
927                 delete p.istyle;
928             }
929             cb[cb.length] = ct.apply(p);
930         }
931         return ts.header.apply({cells: cb.join(''), tstyle:'width:'+this.getTotalWidth()+';'});
932     },
933
934     // private
935     getColumnTooltip : function(i){
936         var tt = this.cm.getColumnTooltip(i);
937         if(tt){
938             if(Ext.QuickTips.isEnabled()){
939                 return 'ext:qtip="'+tt+'"';
940             }else{
941                 return 'title="'+tt+'"';
942             }
943         }
944         return '';
945     },
946
947     // private
948     beforeUpdate : function(){
949         this.grid.stopEditing(true);
950     },
951
952     // private
953     updateHeaders : function(){
954         this.innerHd.firstChild.innerHTML = this.renderHeaders();
955         this.innerHd.firstChild.style.width = this.getOffsetWidth();
956         this.innerHd.firstChild.firstChild.style.width = this.getTotalWidth();
957     },
958
959     <div id="method-Ext.grid.GridView-focusRow"></div>/**
960      * Focuses the specified row.
961      * @param {Number} row The row index
962      */
963     focusRow : function(row){
964         this.focusCell(row, 0, false);
965     },
966
967     <div id="method-Ext.grid.GridView-focusCell"></div>/**
968      * Focuses the specified cell.
969      * @param {Number} row The row index
970      * @param {Number} col The column index
971      */
972     focusCell : function(row, col, hscroll){
973         this.syncFocusEl(this.ensureVisible(row, col, hscroll));
974         if(Ext.isGecko){
975             this.focusEl.focus();
976         }else{
977             this.focusEl.focus.defer(1, this.focusEl);
978         }
979     },
980
981     resolveCell : function(row, col, hscroll){
982         if(!Ext.isNumber(row)){
983             row = row.rowIndex;
984         }
985         if(!this.ds){
986             return null;
987         }
988         if(row < 0 || row >= this.ds.getCount()){
989             return null;
990         }
991         col = (col !== undefined ? col : 0);
992
993         var rowEl = this.getRow(row),
994             cm = this.cm,
995             colCount = cm.getColumnCount(),
996             cellEl;
997         if(!(hscroll === false && col === 0)){
998             while(col < colCount && cm.isHidden(col)){
999                 col++;
1000             }
1001             cellEl = this.getCell(row, col);
1002         }
1003
1004         return {row: rowEl, cell: cellEl};
1005     },
1006
1007     getResolvedXY : function(resolved){
1008         if(!resolved){
1009             return null;
1010         }
1011         var s = this.scroller.dom, c = resolved.cell, r = resolved.row;
1012         return c ? Ext.fly(c).getXY() : [this.el.getX(), Ext.fly(r).getY()];
1013     },
1014
1015     syncFocusEl : function(row, col, hscroll){
1016         var xy = row;
1017         if(!Ext.isArray(xy)){
1018             row = Math.min(row, Math.max(0, this.getRows().length-1));
1019             xy = this.getResolvedXY(this.resolveCell(row, col, hscroll));
1020         }
1021         this.focusEl.setXY(xy||this.scroller.getXY());
1022     },
1023
1024     ensureVisible : function(row, col, hscroll){
1025         var resolved = this.resolveCell(row, col, hscroll);
1026         if(!resolved || !resolved.row){
1027             return;
1028         }
1029
1030         var rowEl = resolved.row, 
1031             cellEl = resolved.cell,
1032             c = this.scroller.dom,
1033             ctop = 0,
1034             p = rowEl, 
1035             stop = this.el.dom;
1036             
1037         while(p && p != stop){
1038             ctop += p.offsetTop;
1039             p = p.offsetParent;
1040         }
1041         
1042         ctop -= this.mainHd.dom.offsetHeight;
1043         stop = parseInt(c.scrollTop, 10);
1044         
1045         var cbot = ctop + rowEl.offsetHeight,
1046             ch = c.clientHeight,
1047             sbot = stop + ch;
1048         
1049
1050         if(ctop < stop){
1051           c.scrollTop = ctop;
1052         }else if(cbot > sbot){
1053             c.scrollTop = cbot-ch;
1054         }
1055
1056         if(hscroll !== false){
1057             var cleft = parseInt(cellEl.offsetLeft, 10);
1058             var cright = cleft + cellEl.offsetWidth;
1059
1060             var sleft = parseInt(c.scrollLeft, 10);
1061             var sright = sleft + c.clientWidth;
1062             if(cleft < sleft){
1063                 c.scrollLeft = cleft;
1064             }else if(cright > sright){
1065                 c.scrollLeft = cright-c.clientWidth;
1066             }
1067         }
1068         return this.getResolvedXY(resolved);
1069     },
1070
1071     // private
1072     insertRows : function(dm, firstRow, lastRow, isUpdate){
1073         var last = dm.getCount() - 1;
1074         if(!isUpdate && firstRow === 0 && lastRow >= last){
1075             this.fireEvent('beforerowsinserted', this, firstRow, lastRow);
1076             this.refresh();
1077             this.fireEvent('rowsinserted', this, firstRow, lastRow);
1078         }else{
1079             if(!isUpdate){
1080                 this.fireEvent('beforerowsinserted', this, firstRow, lastRow);
1081             }
1082             var html = this.renderRows(firstRow, lastRow),
1083                 before = this.getRow(firstRow);
1084             if(before){
1085                 if(firstRow === 0){
1086                     Ext.fly(this.getRow(0)).removeClass(this.firstRowCls);
1087                 }
1088                 Ext.DomHelper.insertHtml('beforeBegin', before, html);
1089             }else{
1090                 var r = this.getRow(last - 1);
1091                 if(r){
1092                     Ext.fly(r).removeClass(this.lastRowCls);
1093                 }
1094                 Ext.DomHelper.insertHtml('beforeEnd', this.mainBody.dom, html);
1095             }
1096             if(!isUpdate){
1097                 this.fireEvent('rowsinserted', this, firstRow, lastRow);
1098                 this.processRows(firstRow);
1099             }else if(firstRow === 0 || firstRow >= last){
1100                 //ensure first/last row is kept after an update.
1101                 Ext.fly(this.getRow(firstRow)).addClass(firstRow === 0 ? this.firstRowCls : this.lastRowCls);
1102             }
1103         }
1104         this.syncFocusEl(firstRow);
1105     },
1106
1107     // private
1108     deleteRows : function(dm, firstRow, lastRow){
1109         if(dm.getRowCount()<1){
1110             this.refresh();
1111         }else{
1112             this.fireEvent('beforerowsdeleted', this, firstRow, lastRow);
1113
1114             this.removeRows(firstRow, lastRow);
1115
1116             this.processRows(firstRow);
1117             this.fireEvent('rowsdeleted', this, firstRow, lastRow);
1118         }
1119     },
1120
1121     // private
1122     getColumnStyle : function(col, isHeader){
1123         var style = !isHeader ? (this.cm.config[col].css || '') : '';
1124         style += 'width:'+this.getColumnWidth(col)+';';
1125         if(this.cm.isHidden(col)){
1126             style += 'display:none;';
1127         }
1128         var align = this.cm.config[col].align;
1129         if(align){
1130             style += 'text-align:'+align+';';
1131         }
1132         return style;
1133     },
1134
1135     // private
1136     getColumnWidth : function(col){
1137         var w = this.cm.getColumnWidth(col);
1138         if(Ext.isNumber(w)){
1139             return (Ext.isBorderBox || (Ext.isWebKit && !Ext.isSafari2) ? w : (w - this.borderWidth > 0 ? w - this.borderWidth : 0)) + 'px';
1140         }
1141         return w;
1142     },
1143
1144     // private
1145     getTotalWidth : function(){
1146         return this.cm.getTotalWidth()+'px';
1147     },
1148
1149     // private
1150     fitColumns : function(preventRefresh, onlyExpand, omitColumn){
1151         var cm = this.cm, i;
1152         var tw = cm.getTotalWidth(false);
1153         var aw = this.grid.getGridEl().getWidth(true)-this.getScrollOffset();
1154
1155         if(aw < 20){ // not initialized, so don't screw up the default widths
1156             return;
1157         }
1158         var extra = aw - tw;
1159
1160         if(extra === 0){
1161             return false;
1162         }
1163
1164         var vc = cm.getColumnCount(true);
1165         var ac = vc-(Ext.isNumber(omitColumn) ? 1 : 0);
1166         if(ac === 0){
1167             ac = 1;
1168             omitColumn = undefined;
1169         }
1170         var colCount = cm.getColumnCount();
1171         var cols = [];
1172         var extraCol = 0;
1173         var width = 0;
1174         var w;
1175         for (i = 0; i < colCount; i++){
1176             if(!cm.isHidden(i) && !cm.isFixed(i) && i !== omitColumn){
1177                 w = cm.getColumnWidth(i);
1178                 cols.push(i);
1179                 extraCol = i;
1180                 cols.push(w);
1181                 width += w;
1182             }
1183         }
1184         var frac = (aw - cm.getTotalWidth())/width;
1185         while (cols.length){
1186             w = cols.pop();
1187             i = cols.pop();
1188             cm.setColumnWidth(i, Math.max(this.grid.minColumnWidth, Math.floor(w + w*frac)), true);
1189         }
1190
1191         if((tw = cm.getTotalWidth(false)) > aw){
1192             var adjustCol = ac != vc ? omitColumn : extraCol;
1193              cm.setColumnWidth(adjustCol, Math.max(1,
1194                      cm.getColumnWidth(adjustCol)- (tw-aw)), true);
1195         }
1196
1197         if(preventRefresh !== true){
1198             this.updateAllColumnWidths();
1199         }
1200
1201
1202         return true;
1203     },
1204
1205     // private
1206     autoExpand : function(preventUpdate){
1207         var g = this.grid, cm = this.cm;
1208         if(!this.userResized && g.autoExpandColumn){
1209             var tw = cm.getTotalWidth(false);
1210             var aw = this.grid.getGridEl().getWidth(true)-this.getScrollOffset();
1211             if(tw != aw){
1212                 var ci = cm.getIndexById(g.autoExpandColumn);
1213                 var currentWidth = cm.getColumnWidth(ci);
1214                 var cw = Math.min(Math.max(((aw-tw)+currentWidth), g.autoExpandMin), g.autoExpandMax);
1215                 if(cw != currentWidth){
1216                     cm.setColumnWidth(ci, cw, true);
1217                     if(preventUpdate !== true){
1218                         this.updateColumnWidth(ci, cw);
1219                     }
1220                 }
1221             }
1222         }
1223     },
1224
1225     // private
1226     getColumnData : function(){
1227         // build a map for all the columns
1228         var cs = [], cm = this.cm, colCount = cm.getColumnCount();
1229         for(var i = 0; i < colCount; i++){
1230             var name = cm.getDataIndex(i);
1231             cs[i] = {
1232                 name : (!Ext.isDefined(name) ? this.ds.fields.get(i).name : name),
1233                 renderer : cm.getRenderer(i),
1234                 scope: cm.getRendererScope(i),
1235                 id : cm.getColumnId(i),
1236                 style : this.getColumnStyle(i)
1237             };
1238         }
1239         return cs;
1240     },
1241
1242     // private
1243     renderRows : function(startRow, endRow){
1244         // pull in all the crap needed to render rows
1245         var g = this.grid, cm = g.colModel, ds = g.store, stripe = g.stripeRows;
1246         var colCount = cm.getColumnCount();
1247
1248         if(ds.getCount() < 1){
1249             return '';
1250         }
1251
1252         var cs = this.getColumnData();
1253
1254         startRow = startRow || 0;
1255         endRow = !Ext.isDefined(endRow) ? ds.getCount()-1 : endRow;
1256
1257         // records to render
1258         var rs = ds.getRange(startRow, endRow);
1259
1260         return this.doRender(cs, rs, ds, startRow, colCount, stripe);
1261     },
1262
1263     // private
1264     renderBody : function(){
1265         var markup = this.renderRows() || '&#160;';
1266         return this.templates.body.apply({rows: markup});
1267     },
1268
1269     // private
1270     refreshRow : function(record){
1271         var ds = this.ds, index;
1272         if(Ext.isNumber(record)){
1273             index = record;
1274             record = ds.getAt(index);
1275             if(!record){
1276                 return;
1277             }
1278         }else{
1279             index = ds.indexOf(record);
1280             if(index < 0){
1281                 return;
1282             }
1283         }
1284         this.insertRows(ds, index, index, true);
1285         this.getRow(index).rowIndex = index;
1286         this.onRemove(ds, record, index+1, true);
1287         this.fireEvent('rowupdated', this, index, record);
1288     },
1289
1290     <div id="method-Ext.grid.GridView-refresh"></div>/**
1291      * Refreshs the grid UI
1292      * @param {Boolean} headersToo (optional) True to also refresh the headers
1293      */
1294     refresh : function(headersToo){
1295         this.fireEvent('beforerefresh', this);
1296         this.grid.stopEditing(true);
1297
1298         var result = this.renderBody();
1299         this.mainBody.update(result).setWidth(this.getTotalWidth());
1300         if(headersToo === true){
1301             this.updateHeaders();
1302             this.updateHeaderSortState();
1303         }
1304         this.processRows(0, true);
1305         this.layout();
1306         this.applyEmptyText();
1307         this.fireEvent('refresh', this);
1308     },
1309
1310     // private
1311     applyEmptyText : function(){
1312         if(this.emptyText && !this.hasRows()){
1313             this.mainBody.update('<div class="x-grid-empty">' + this.emptyText + '</div>');
1314         }
1315     },
1316
1317     // private
1318     updateHeaderSortState : function(){
1319         var state = this.ds.getSortState();
1320         if(!state){
1321             return;
1322         }
1323         if(!this.sortState || (this.sortState.field != state.field || this.sortState.direction != state.direction)){
1324             this.grid.fireEvent('sortchange', this.grid, state);
1325         }
1326         this.sortState = state;
1327         var sortColumn = this.cm.findColumnIndex(state.field);
1328         if(sortColumn != -1){
1329             var sortDir = state.direction;
1330             this.updateSortIcon(sortColumn, sortDir);
1331         }
1332     },
1333
1334     // private
1335     clearHeaderSortState : function(){
1336         if(!this.sortState){
1337             return;
1338         }
1339         this.grid.fireEvent('sortchange', this.grid, null);
1340         this.mainHd.select('td').removeClass(this.sortClasses);
1341         delete this.sortState;
1342     },
1343
1344     // private
1345     destroy : function(){
1346         if(this.colMenu){
1347             Ext.menu.MenuMgr.unregister(this.colMenu);
1348             this.colMenu.destroy();
1349             delete this.colMenu;
1350         }
1351         if(this.hmenu){
1352             Ext.menu.MenuMgr.unregister(this.hmenu);
1353             this.hmenu.destroy();
1354             delete this.hmenu;
1355         }
1356
1357         this.initData(null, null);
1358         this.purgeListeners();
1359         Ext.fly(this.innerHd).un("click", this.handleHdDown, this);
1360
1361         if(this.grid.enableColumnMove){
1362             Ext.destroy(
1363                 this.columnDrag.el,
1364                 this.columnDrag.proxy.ghost,
1365                 this.columnDrag.proxy.el,
1366                 this.columnDrop.el,
1367                 this.columnDrop.proxyTop,
1368                 this.columnDrop.proxyBottom,
1369                 this.columnDrag.dragData.ddel,
1370                 this.columnDrag.dragData.header
1371             );
1372             if (this.columnDrag.proxy.anim) {
1373                 Ext.destroy(this.columnDrag.proxy.anim);
1374             }
1375             delete this.columnDrag.proxy.ghost;
1376             delete this.columnDrag.dragData.ddel;
1377             delete this.columnDrag.dragData.header;
1378             this.columnDrag.destroy();
1379             delete Ext.dd.DDM.locationCache[this.columnDrag.id];
1380             delete this.columnDrag._domRef;
1381
1382             delete this.columnDrop.proxyTop;
1383             delete this.columnDrop.proxyBottom;
1384             this.columnDrop.destroy();
1385             delete Ext.dd.DDM.locationCache["gridHeader" + this.grid.getGridEl().id];
1386             delete this.columnDrop._domRef;
1387             delete Ext.dd.DDM.ids[this.columnDrop.ddGroup];
1388         }
1389
1390         if (this.splitZone){ // enableColumnResize
1391             this.splitZone.destroy();
1392             delete this.splitZone._domRef;
1393             delete Ext.dd.DDM.ids["gridSplitters" + this.grid.getGridEl().id];
1394         }
1395
1396         Ext.fly(this.innerHd).removeAllListeners();
1397         Ext.removeNode(this.innerHd);
1398         delete this.innerHd;
1399
1400         Ext.destroy(
1401             this.el,
1402             this.mainWrap,
1403             this.mainHd,
1404             this.scroller,
1405             this.mainBody,
1406             this.focusEl,
1407             this.resizeMarker,
1408             this.resizeProxy,
1409             this.activeHdBtn,
1410             this.dragZone,
1411             this.splitZone,
1412             this._flyweight
1413         );
1414
1415         delete this.grid.container;
1416
1417         if(this.dragZone){
1418             this.dragZone.destroy();
1419         }
1420
1421         Ext.dd.DDM.currentTarget = null;
1422         delete Ext.dd.DDM.locationCache[this.grid.getGridEl().id];
1423
1424         Ext.EventManager.removeResizeListener(this.onWindowResize, this);
1425     },
1426
1427     // private
1428     onDenyColumnHide : function(){
1429
1430     },
1431
1432     // private
1433     render : function(){
1434         if(this.autoFill){
1435             var ct = this.grid.ownerCt;
1436             if (ct && ct.getLayout()){
1437                 ct.on('afterlayout', function(){ 
1438                     this.fitColumns(true, true);
1439                     this.updateHeaders(); 
1440                 }, this, {single: true}); 
1441             }else{ 
1442                 this.fitColumns(true, true); 
1443             }
1444         }else if(this.forceFit){
1445             this.fitColumns(true, false);
1446         }else if(this.grid.autoExpandColumn){
1447             this.autoExpand(true);
1448         }
1449
1450         this.renderUI();
1451     },
1452
1453     /* --------------------------------- Model Events and Handlers --------------------------------*/
1454     // private
1455     initData : function(ds, cm){
1456         if(this.ds){
1457             this.ds.un('load', this.onLoad, this);
1458             this.ds.un('datachanged', this.onDataChange, this);
1459             this.ds.un('add', this.onAdd, this);
1460             this.ds.un('remove', this.onRemove, this);
1461             this.ds.un('update', this.onUpdate, this);
1462             this.ds.un('clear', this.onClear, this);
1463             if(this.ds !== ds && this.ds.autoDestroy){
1464                 this.ds.destroy();
1465             }
1466         }
1467         if(ds){
1468             ds.on({
1469                 scope: this,
1470                 load: this.onLoad,
1471                 datachanged: this.onDataChange,
1472                 add: this.onAdd,
1473                 remove: this.onRemove,
1474                 update: this.onUpdate,
1475                 clear: this.onClear
1476             });
1477         }
1478         this.ds = ds;
1479
1480         if(this.cm){
1481             this.cm.un('configchange', this.onColConfigChange, this);
1482             this.cm.un('widthchange', this.onColWidthChange, this);
1483             this.cm.un('headerchange', this.onHeaderChange, this);
1484             this.cm.un('hiddenchange', this.onHiddenChange, this);
1485             this.cm.un('columnmoved', this.onColumnMove, this);
1486         }
1487         if(cm){
1488             delete this.lastViewWidth;
1489             cm.on({
1490                 scope: this,
1491                 configchange: this.onColConfigChange,
1492                 widthchange: this.onColWidthChange,
1493                 headerchange: this.onHeaderChange,
1494                 hiddenchange: this.onHiddenChange,
1495                 columnmoved: this.onColumnMove
1496             });
1497         }
1498         this.cm = cm;
1499     },
1500
1501     // private
1502     onDataChange : function(){
1503         this.refresh();
1504         this.updateHeaderSortState();
1505         this.syncFocusEl(0);
1506     },
1507
1508     // private
1509     onClear : function(){
1510         this.refresh();
1511         this.syncFocusEl(0);
1512     },
1513
1514     // private
1515     onUpdate : function(ds, record){
1516         this.refreshRow(record);
1517     },
1518
1519     // private
1520     onAdd : function(ds, records, index){
1521         
1522         this.insertRows(ds, index, index + (records.length-1));
1523     },
1524
1525     // private
1526     onRemove : function(ds, record, index, isUpdate){
1527         if(isUpdate !== true){
1528             this.fireEvent('beforerowremoved', this, index, record);
1529         }
1530         this.removeRow(index);
1531         if(isUpdate !== true){
1532             this.processRows(index);
1533             this.applyEmptyText();
1534             this.fireEvent('rowremoved', this, index, record);
1535         }
1536     },
1537
1538     // private
1539     onLoad : function(){
1540         this.scrollToTop.defer(Ext.isGecko ? 1 : 0, this);
1541     },
1542
1543     // private
1544     onColWidthChange : function(cm, col, width){
1545         this.updateColumnWidth(col, width);
1546     },
1547
1548     // private
1549     onHeaderChange : function(cm, col, text){
1550         this.updateHeaders();
1551     },
1552
1553     // private
1554     onHiddenChange : function(cm, col, hidden){
1555         this.updateColumnHidden(col, hidden);
1556     },
1557
1558     // private
1559     onColumnMove : function(cm, oldIndex, newIndex){
1560         this.indexMap = null;
1561         var s = this.getScrollState();
1562         this.refresh(true);
1563         this.restoreScroll(s);
1564         this.afterMove(newIndex);
1565         this.grid.fireEvent('columnmove', oldIndex, newIndex);
1566     },
1567
1568     // private
1569     onColConfigChange : function(){
1570         delete this.lastViewWidth;
1571         this.indexMap = null;
1572         this.refresh(true);
1573     },
1574
1575     /* -------------------- UI Events and Handlers ------------------------------ */
1576     // private
1577     initUI : function(grid){
1578         grid.on('headerclick', this.onHeaderClick, this);
1579     },
1580
1581     // private
1582     initEvents : function(){
1583     },
1584
1585     // private
1586     onHeaderClick : function(g, index){
1587         if(this.headersDisabled || !this.cm.isSortable(index)){
1588             return;
1589         }
1590         g.stopEditing(true);
1591         g.store.sort(this.cm.getDataIndex(index));
1592     },
1593
1594     // private
1595     onRowOver : function(e, t){
1596         var row;
1597         if((row = this.findRowIndex(t)) !== false){
1598             this.addRowClass(row, 'x-grid3-row-over');
1599         }
1600     },
1601
1602     // private
1603     onRowOut : function(e, t){
1604         var row;
1605         if((row = this.findRowIndex(t)) !== false && !e.within(this.getRow(row), true)){
1606             this.removeRowClass(row, 'x-grid3-row-over');
1607         }
1608     },
1609
1610     // private
1611     handleWheel : function(e){
1612         e.stopPropagation();
1613     },
1614
1615     // private
1616     onRowSelect : function(row){
1617         this.addRowClass(row, this.selectedRowClass);
1618     },
1619
1620     // private
1621     onRowDeselect : function(row){
1622         this.removeRowClass(row, this.selectedRowClass);
1623     },
1624
1625     // private
1626     onCellSelect : function(row, col){
1627         var cell = this.getCell(row, col);
1628         if(cell){
1629             this.fly(cell).addClass('x-grid3-cell-selected');
1630         }
1631     },
1632
1633     // private
1634     onCellDeselect : function(row, col){
1635         var cell = this.getCell(row, col);
1636         if(cell){
1637             this.fly(cell).removeClass('x-grid3-cell-selected');
1638         }
1639     },
1640
1641     // private
1642     onColumnSplitterMoved : function(i, w){
1643         this.userResized = true;
1644         var cm = this.grid.colModel;
1645         cm.setColumnWidth(i, w, true);
1646
1647         if(this.forceFit){
1648             this.fitColumns(true, false, i);
1649             this.updateAllColumnWidths();
1650         }else{
1651             this.updateColumnWidth(i, w);
1652             this.syncHeaderScroll();
1653         }
1654
1655         this.grid.fireEvent('columnresize', i, w);
1656     },
1657
1658     // private
1659     handleHdMenuClick : function(item){
1660         var index = this.hdCtxIndex,
1661             cm = this.cm, 
1662             ds = this.ds,
1663             id = item.getItemId();
1664         switch(id){
1665             case 'asc':
1666                 ds.sort(cm.getDataIndex(index), 'ASC');
1667                 break;
1668             case 'desc':
1669                 ds.sort(cm.getDataIndex(index), 'DESC');
1670                 break;
1671             default:
1672                 index = cm.getIndexById(id.substr(4));
1673                 if(index != -1){
1674                     if(item.checked && cm.getColumnsBy(this.isHideableColumn, this).length <= 1){
1675                         this.onDenyColumnHide();
1676                         return false;
1677                     }
1678                     cm.setHidden(index, item.checked);
1679                 }
1680         }
1681         return true;
1682     },
1683
1684     // private
1685     isHideableColumn : function(c){
1686         return !c.hidden && !c.fixed;
1687     },
1688
1689     // private
1690     beforeColMenuShow : function(){
1691         var cm = this.cm,  colCount = cm.getColumnCount();
1692         this.colMenu.removeAll();
1693         for(var i = 0; i < colCount; i++){
1694             if(cm.config[i].fixed !== true && cm.config[i].hideable !== false){
1695                 this.colMenu.add(new Ext.menu.CheckItem({
1696                     itemId: 'col-'+cm.getColumnId(i),
1697                     text: cm.getColumnHeader(i),
1698                     checked: !cm.isHidden(i),
1699                     hideOnClick:false,
1700                     disabled: cm.config[i].hideable === false
1701                 }));
1702             }
1703         }
1704     },
1705
1706     // private
1707     handleHdDown : function(e, t){
1708         if(Ext.fly(t).hasClass('x-grid3-hd-btn')){
1709             e.stopEvent();
1710             var hd = this.findHeaderCell(t);
1711             Ext.fly(hd).addClass('x-grid3-hd-menu-open');
1712             var index = this.getCellIndex(hd);
1713             this.hdCtxIndex = index;
1714             var ms = this.hmenu.items, cm = this.cm;
1715             ms.get('asc').setDisabled(!cm.isSortable(index));
1716             ms.get('desc').setDisabled(!cm.isSortable(index));
1717             this.hmenu.on('hide', function(){
1718                 Ext.fly(hd).removeClass('x-grid3-hd-menu-open');
1719             }, this, {single:true});
1720             this.hmenu.show(t, 'tl-bl?');
1721         }
1722     },
1723
1724     // private
1725     handleHdOver : function(e, t){
1726         var hd = this.findHeaderCell(t);
1727         if(hd && !this.headersDisabled){
1728             this.activeHdRef = t;
1729             this.activeHdIndex = this.getCellIndex(hd);
1730             var fly = this.fly(hd);
1731             this.activeHdRegion = fly.getRegion();
1732             if(!this.cm.isMenuDisabled(this.activeHdIndex)){
1733                 fly.addClass('x-grid3-hd-over');
1734                 this.activeHdBtn = fly.child('.x-grid3-hd-btn');
1735                 if(this.activeHdBtn){
1736                     this.activeHdBtn.dom.style.height = (hd.firstChild.offsetHeight-1)+'px';
1737                 }
1738             }
1739         }
1740     },
1741
1742     // private
1743     handleHdMove : function(e, t){
1744         var hd = this.findHeaderCell(this.activeHdRef);
1745         if(hd && !this.headersDisabled){
1746             var hw = this.splitHandleWidth || 5,
1747                 r = this.activeHdRegion,
1748                 x = e.getPageX(),
1749                 ss = hd.style,
1750                 cur = '';
1751             if(this.grid.enableColumnResize !== false){
1752                 if(x - r.left <= hw && this.cm.isResizable(this.activeHdIndex-1)){
1753                     cur = Ext.isAir ? 'move' : Ext.isWebKit ? 'e-resize' : 'col-resize'; // col-resize not always supported
1754                 }else if(r.right - x <= (!this.activeHdBtn ? hw : 2) && this.cm.isResizable(this.activeHdIndex)){
1755                     cur = Ext.isAir ? 'move' : Ext.isWebKit ? 'w-resize' : 'col-resize';
1756                 }
1757             }
1758             ss.cursor = cur;
1759         }
1760     },
1761
1762     // private
1763     handleHdOut : function(e, t){
1764         var hd = this.findHeaderCell(t);
1765         if(hd && (!Ext.isIE || !e.within(hd, true))){
1766             this.activeHdRef = null;
1767             this.fly(hd).removeClass('x-grid3-hd-over');
1768             hd.style.cursor = '';
1769         }
1770     },
1771
1772     // private
1773     hasRows : function(){
1774         var fc = this.mainBody.dom.firstChild;
1775         return fc && fc.nodeType == 1 && fc.className != 'x-grid-empty';
1776     },
1777
1778     // back compat
1779     bind : function(d, c){
1780         this.initData(d, c);
1781     }
1782 });
1783
1784
1785 // private
1786 // This is a support class used internally by the Grid components
1787 Ext.grid.GridView.SplitDragZone = function(grid, hd){
1788     this.grid = grid;
1789     this.view = grid.getView();
1790     this.marker = this.view.resizeMarker;
1791     this.proxy = this.view.resizeProxy;
1792     Ext.grid.GridView.SplitDragZone.superclass.constructor.call(this, hd,
1793         'gridSplitters' + this.grid.getGridEl().id, {
1794         dragElId : Ext.id(this.proxy.dom), resizeFrame:false
1795     });
1796     this.scroll = false;
1797     this.hw = this.view.splitHandleWidth || 5;
1798 };
1799 Ext.extend(Ext.grid.GridView.SplitDragZone, Ext.dd.DDProxy, {
1800
1801     b4StartDrag : function(x, y){
1802         this.view.headersDisabled = true;
1803         var h = this.view.mainWrap.getHeight();
1804         this.marker.setHeight(h);
1805         this.marker.show();
1806         this.marker.alignTo(this.view.getHeaderCell(this.cellIndex), 'tl-tl', [-2, 0]);
1807         this.proxy.setHeight(h);
1808         var w = this.cm.getColumnWidth(this.cellIndex);
1809         var minw = Math.max(w-this.grid.minColumnWidth, 0);
1810         this.resetConstraints();
1811         this.setXConstraint(minw, 1000);
1812         this.setYConstraint(0, 0);
1813         this.minX = x - minw;
1814         this.maxX = x + 1000;
1815         this.startPos = x;
1816         Ext.dd.DDProxy.prototype.b4StartDrag.call(this, x, y);
1817     },
1818     
1819     allowHeaderDrag : function(e){
1820         return true;
1821     },
1822
1823
1824     handleMouseDown : function(e){
1825         var t = this.view.findHeaderCell(e.getTarget());
1826         if(t && this.allowHeaderDrag(e)){
1827             var xy = this.view.fly(t).getXY(), x = xy[0], y = xy[1];
1828             var exy = e.getXY(), ex = exy[0];
1829             var w = t.offsetWidth, adjust = false;
1830             if((ex - x) <= this.hw){
1831                 adjust = -1;
1832             }else if((x+w) - ex <= this.hw){
1833                 adjust = 0;
1834             }
1835             if(adjust !== false){
1836                 this.cm = this.grid.colModel;
1837                 var ci = this.view.getCellIndex(t);
1838                 if(adjust == -1){
1839                   if (ci + adjust < 0) {
1840                     return;
1841                   }
1842                     while(this.cm.isHidden(ci+adjust)){
1843                         --adjust;
1844                         if(ci+adjust < 0){
1845                             return;
1846                         }
1847                     }
1848                 }
1849                 this.cellIndex = ci+adjust;
1850                 this.split = t.dom;
1851                 if(this.cm.isResizable(this.cellIndex) && !this.cm.isFixed(this.cellIndex)){
1852                     Ext.grid.GridView.SplitDragZone.superclass.handleMouseDown.apply(this, arguments);
1853                 }
1854             }else if(this.view.columnDrag){
1855                 this.view.columnDrag.callHandleMouseDown(e);
1856             }
1857         }
1858     },
1859
1860     endDrag : function(e){
1861         this.marker.hide();
1862         var v = this.view;
1863         var endX = Math.max(this.minX, e.getPageX());
1864         var diff = endX - this.startPos;
1865         v.onColumnSplitterMoved(this.cellIndex, this.cm.getColumnWidth(this.cellIndex)+diff);
1866         setTimeout(function(){
1867             v.headersDisabled = false;
1868         }, 50);
1869     },
1870
1871     autoOffset : function(){
1872         this.setDelta(0,0);
1873     }
1874 });
1875 </pre>    \r
1876 </body>\r
1877 </html>