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