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