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