Upgrade to ExtJS 3.3.1 - Released 11/30/2010
[extjs.git] / docs / source / GridView.html
1 <html>
2 <head>
3   <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />    
4   <title>The source code</title>
5     <link href="../resources/prettify/prettify.css" type="text/css" rel="stylesheet" />
6     <script type="text/javascript" src="../resources/prettify/prettify.js"></script>
7 </head>
8 <body  onload="prettyPrint();">
9     <pre class="prettyprint lang-js">/*!
10  * Ext JS Library 3.3.1
11  * Copyright(c) 2006-2010 Sencha Inc.
12  * licensing@sencha.com
13  * http://www.sencha.com/license
14  */
15 <div id="cls-Ext.grid.GridView"></div>/**
16  * @class Ext.grid.GridView
17  * @extends Ext.util.Observable
18  * <p>This class encapsulates the user interface of an {@link Ext.grid.GridPanel}.
19  * Methods of this class may be used to access user interface elements to enable
20  * special display effects. Do not change the DOM structure of the user interface.</p>
21  * <p>This class does not provide ways to manipulate the underlying data. The data
22  * model of a Grid is held in an {@link Ext.data.Store}.</p>
23  * @constructor
24  * @param {Object} config
25  */
26 Ext.grid.GridView = Ext.extend(Ext.util.Observable, {
27     <div id="method-Ext.grid.GridView-getRowClass"></div>/**
28      * Override this function to apply custom CSS classes to rows during rendering.  You can also supply custom
29      * parameters to the row template for the current row to customize how it is rendered using the <b>rowParams</b>
30      * parameter.  This function should return the CSS class name (or empty string '' for none) that will be added
31      * to the row's wrapping div.  To apply multiple class names, simply return them space-delimited within the string
32      * (e.g., 'my-class another-class'). Example usage:
33     <pre><code>
34 viewConfig: {
35     forceFit: true,
36     showPreview: true, // custom property
37     enableRowBody: true, // required to create a second, full-width row to show expanded Record data
38     getRowClass: function(record, rowIndex, rp, ds){ // rp = rowParams
39         if(this.showPreview){
40             rp.body = '&lt;p>'+record.data.excerpt+'&lt;/p>';
41             return 'x-grid3-row-expanded';
42         }
43         return 'x-grid3-row-collapsed';
44     }
45 },
46     </code></pre>
47      * @param {Record} record The {@link Ext.data.Record} corresponding to the current row.
48      * @param {Number} index The row index.
49      * @param {Object} rowParams A config object that is passed to the row template during rendering that allows
50      * customization of various aspects of a grid row.
51      * <p>If {@link #enableRowBody} is configured <b><tt></tt>true</b>, then the following properties may be set
52      * by this function, and will be used to render a full-width expansion row below each grid row:</p>
53      * <ul>
54      * <li><code>body</code> : String <div class="sub-desc">An HTML fragment to be used as the expansion row's body content (defaults to '').</div></li>
55      * <li><code>bodyStyle</code> : String <div class="sub-desc">A CSS style specification that will be applied to the expansion row's &lt;tr> element. (defaults to '').</div></li>
56      * </ul>
57      * The following property will be passed in, and may be appended to:
58      * <ul>
59      * <li><code>tstyle</code> : String <div class="sub-desc">A CSS style specification that willl be applied to the &lt;table> element which encapsulates
60      * both the standard grid row, and any expansion row.</div></li>
61      * </ul>
62      * @param {Store} store The {@link Ext.data.Store} this grid is bound to
63      * @method getRowClass
64      * @return {String} a CSS class name to add to the row.
65      */
66
67     <div id="cfg-Ext.grid.GridView-enableRowBody"></div>/**
68      * @cfg {Boolean} enableRowBody True to add a second TR element per row that can be used to provide a row body
69      * that spans beneath the data row.  Use the {@link #getRowClass} method's rowParams config to customize the row body.
70      */
71
72     <div id="cfg-Ext.grid.GridView-emptyText"></div>/**
73      * @cfg {String} emptyText Default text (html tags are accepted) to display in the grid body when no rows
74      * are available (defaults to ''). This value will be used to update the <tt>{@link #mainBody}</tt>:
75     <pre><code>
76     this.mainBody.update('&lt;div class="x-grid-empty">' + this.emptyText + '&lt;/div>');
77     </code></pre>
78      */
79
80     <div id="cfg-Ext.grid.GridView-headersDisabled"></div>/**
81      * @cfg {Boolean} headersDisabled True to disable the grid column headers (defaults to <tt>false</tt>).
82      * Use the {@link Ext.grid.ColumnModel ColumnModel} <tt>{@link Ext.grid.ColumnModel#menuDisabled menuDisabled}</tt>
83      * config to disable the <i>menu</i> for individual columns.  While this config is true the
84      * following will be disabled:<div class="mdetail-params"><ul>
85      * <li>clicking on header to sort</li>
86      * <li>the trigger to reveal the menu.</li>
87      * </ul></div>
88      */
89
90     <div id="prop-Ext.grid.GridView-dragZone"></div>/**
91      * <p>A customized implementation of a {@link Ext.dd.DragZone DragZone} which provides default implementations
92      * of the template methods of DragZone to enable dragging of the selected rows of a GridPanel.
93      * See {@link Ext.grid.GridDragZone} for details.</p>
94      * <p>This will <b>only</b> be present:<div class="mdetail-params"><ul>
95      * <li><i>if</i> the owning GridPanel was configured with {@link Ext.grid.GridPanel#enableDragDrop enableDragDrop}: <tt>true</tt>.</li>
96      * <li><i>after</i> the owning GridPanel has been rendered.</li>
97      * </ul></div>
98      * @property dragZone
99      * @type {Ext.grid.GridDragZone}
100      */
101
102     <div id="cfg-Ext.grid.GridView-deferEmptyText"></div>/**
103      * @cfg {Boolean} deferEmptyText True to defer <tt>{@link #emptyText}</tt> being applied until the store's
104      * first load (defaults to <tt>true</tt>).
105      */
106     deferEmptyText : true,
107
108     <div id="cfg-Ext.grid.GridView-scrollOffset"></div>/**
109      * @cfg {Number} scrollOffset The amount of space to reserve for the vertical scrollbar
110      * (defaults to <tt>undefined</tt>). If an explicit value isn't specified, this will be automatically
111      * calculated.
112      */
113     scrollOffset : undefined,
114
115     <div id="cfg-Ext.grid.GridView-autoFill"></div>/**
116      * @cfg {Boolean} autoFill
117      * Defaults to <tt>false</tt>.  Specify <tt>true</tt> to have the column widths re-proportioned
118      * when the grid is <b>initially rendered</b>.  The
119      * {@link Ext.grid.Column#width initially configured width}</tt> of each column will be adjusted
120      * to fit the grid width and prevent horizontal scrolling. If columns are later resized (manually
121      * or programmatically), the other columns in the grid will <b>not</b> be resized to fit the grid width.
122      * See <tt>{@link #forceFit}</tt> also.
123      */
124     autoFill : false,
125
126     <div id="cfg-Ext.grid.GridView-forceFit"></div>/**
127      * @cfg {Boolean} forceFit
128      * <p>Defaults to <tt>false</tt>.  Specify <tt>true</tt> to have the column widths re-proportioned
129      * at <b>all times</b>.</p>
130      * <p>The {@link Ext.grid.Column#width initially configured width}</tt> of each
131      * column will be adjusted to fit the grid width and prevent horizontal scrolling. If columns are
132      * later resized (manually or programmatically), the other columns in the grid <b>will</b> be resized
133      * to fit the grid width.</p>
134      * <p>Columns which are configured with <code>fixed: true</code> are omitted from being resized.</p>
135      * <p>See <tt>{@link #autoFill}</tt>.</p>
136      */
137     forceFit : false,
138
139     <div id="cfg-Ext.grid.GridView-sortClasses"></div>/**
140      * @cfg {Array} sortClasses The CSS classes applied to a header when it is sorted. (defaults to <tt>['sort-asc', 'sort-desc']</tt>)
141      */
142     sortClasses : ['sort-asc', 'sort-desc'],
143
144     <div id="cfg-Ext.grid.GridView-sortAscText"></div>/**
145      * @cfg {String} sortAscText The text displayed in the 'Sort Ascending' menu item (defaults to <tt>'Sort Ascending'</tt>)
146      */
147     sortAscText : 'Sort Ascending',
148
149     <div id="cfg-Ext.grid.GridView-sortDescText"></div>/**
150      * @cfg {String} sortDescText The text displayed in the 'Sort Descending' menu item (defaults to <tt>'Sort Descending'</tt>)
151      */
152     sortDescText : 'Sort Descending',
153
154     <div id="cfg-Ext.grid.GridView-columnsText"></div>/**
155      * @cfg {String} columnsText The text displayed in the 'Columns' menu item (defaults to <tt>'Columns'</tt>)
156      */
157     columnsText : 'Columns',
158
159     <div id="cfg-Ext.grid.GridView-selectedRowClass"></div>/**
160      * @cfg {String} selectedRowClass The CSS class applied to a selected row (defaults to <tt>'x-grid3-row-selected'</tt>). An
161      * example overriding the default styling:
162     <pre><code>
163     .x-grid3-row-selected {background-color: yellow;}
164     </code></pre>
165      * Note that this only controls the row, and will not do anything for the text inside it.  To style inner
166      * facets (like text) use something like:
167     <pre><code>
168     .x-grid3-row-selected .x-grid3-cell-inner {
169         color: #FFCC00;
170     }
171     </code></pre>
172      * @type String
173      */
174     selectedRowClass : 'x-grid3-row-selected',
175
176     // private
177     borderWidth : 2,
178     tdClass : 'x-grid3-cell',
179     hdCls : 'x-grid3-hd',
180     
181     
182     <div id="cfg-Ext.grid.GridView-markDirty"></div>/**
183      * @cfg {Boolean} markDirty True to show the dirty cell indicator when a cell has been modified. Defaults to <tt>true</tt>.
184      */
185     markDirty : true,
186
187     <div id="cfg-Ext.grid.GridView-cellSelectorDepth"></div>/**
188      * @cfg {Number} cellSelectorDepth The number of levels to search for cells in event delegation (defaults to <tt>4</tt>)
189      */
190     cellSelectorDepth : 4,
191     
192     <div id="cfg-Ext.grid.GridView-rowSelectorDepth"></div>/**
193      * @cfg {Number} rowSelectorDepth The number of levels to search for rows in event delegation (defaults to <tt>10</tt>)
194      */
195     rowSelectorDepth : 10,
196
197     <div id="cfg-Ext.grid.GridView-rowBodySelectorDepth"></div>/**
198      * @cfg {Number} rowBodySelectorDepth The number of levels to search for row bodies in event delegation (defaults to <tt>10</tt>)
199      */
200     rowBodySelectorDepth : 10,
201
202     <div id="cfg-Ext.grid.GridView-cellSelector"></div>/**
203      * @cfg {String} cellSelector The selector used to find cells internally (defaults to <tt>'td.x-grid3-cell'</tt>)
204      */
205     cellSelector : 'td.x-grid3-cell',
206     
207     <div id="cfg-Ext.grid.GridView-rowSelector"></div>/**
208      * @cfg {String} rowSelector The selector used to find rows internally (defaults to <tt>'div.x-grid3-row'</tt>)
209      */
210     rowSelector : 'div.x-grid3-row',
211
212     <div id="cfg-Ext.grid.GridView-rowBodySelector"></div>/**
213      * @cfg {String} rowBodySelector The selector used to find row bodies internally (defaults to <tt>'div.x-grid3-row'</tt>)
214      */
215     rowBodySelector : 'div.x-grid3-row-body',
216
217     // private
218     firstRowCls: 'x-grid3-row-first',
219     lastRowCls: 'x-grid3-row-last',
220     rowClsRe: /(?:^|\s+)x-grid3-row-(first|last|alt)(?:\s+|$)/g,
221     
222     <div id="cfg-Ext.grid.GridView-headerMenuOpenCls"></div>/**
223      * @cfg {String} headerMenuOpenCls The CSS class to add to the header cell when its menu is visible. Defaults to 'x-grid3-hd-menu-open'
224      */
225     headerMenuOpenCls: 'x-grid3-hd-menu-open',
226     
227     <div id="cfg-Ext.grid.GridView-rowOverCls"></div>/**
228      * @cfg {String} rowOverCls The CSS class added to each row when it is hovered over. Defaults to 'x-grid3-row-over'
229      */
230     rowOverCls: 'x-grid3-row-over',
231
232     constructor : function(config) {
233         Ext.apply(this, config);
234         
235         // These events are only used internally by the grid components
236         this.addEvents(
237             <div id="event-Ext.grid.GridView-beforerowremoved"></div>/**
238              * @event beforerowremoved
239              * Internal UI Event. Fired before a row is removed.
240              * @param {Ext.grid.GridView} view
241              * @param {Number} rowIndex The index of the row to be removed.
242              * @param {Ext.data.Record} record The Record to be removed
243              */
244             'beforerowremoved',
245             
246             <div id="event-Ext.grid.GridView-beforerowsinserted"></div>/**
247              * @event beforerowsinserted
248              * Internal UI Event. Fired before rows are inserted.
249              * @param {Ext.grid.GridView} view
250              * @param {Number} firstRow The index of the first row to be inserted.
251              * @param {Number} lastRow The index of the last row to be inserted.
252              */
253             'beforerowsinserted',
254             
255             <div id="event-Ext.grid.GridView-beforerefresh"></div>/**
256              * @event beforerefresh
257              * Internal UI Event. Fired before the view is refreshed.
258              * @param {Ext.grid.GridView} view
259              */
260             'beforerefresh',
261             
262             <div id="event-Ext.grid.GridView-rowremoved"></div>/**
263              * @event rowremoved
264              * Internal UI Event. Fired after a row is removed.
265              * @param {Ext.grid.GridView} view
266              * @param {Number} rowIndex The index of the row that was removed.
267              * @param {Ext.data.Record} record The Record that was removed
268              */
269             'rowremoved',
270             
271             <div id="event-Ext.grid.GridView-rowsinserted"></div>/**
272              * @event rowsinserted
273              * Internal UI Event. Fired after rows are inserted.
274              * @param {Ext.grid.GridView} view
275              * @param {Number} firstRow The index of the first inserted.
276              * @param {Number} lastRow The index of the last row inserted.
277              */
278             'rowsinserted',
279             
280             <div id="event-Ext.grid.GridView-rowupdated"></div>/**
281              * @event rowupdated
282              * Internal UI Event. Fired after a row has been updated.
283              * @param {Ext.grid.GridView} view
284              * @param {Number} firstRow The index of the row updated.
285              * @param {Ext.data.record} record The Record backing the row updated.
286              */
287             'rowupdated',
288             
289             <div id="event-Ext.grid.GridView-refresh"></div>/**
290              * @event refresh
291              * Internal UI Event. Fired after the GridView's body has been refreshed.
292              * @param {Ext.grid.GridView} view
293              */
294             'refresh'
295         );
296         
297         Ext.grid.GridView.superclass.constructor.call(this);
298     },
299
300     /* -------------------------------- UI Specific ----------------------------- */
301     
302     <div id="prop-Ext.grid.GridView-Ext.Template"></div>/**
303      * The master template to use when rendering the GridView. Has a default template
304      * @property Ext.Template
305      * @type masterTpl
306      */
307     masterTpl: new Ext.Template(
308         '<div class="x-grid3" hidefocus="true">',
309             '<div class="x-grid3-viewport">',
310                 '<div class="x-grid3-header">',
311                     '<div class="x-grid3-header-inner">',
312                         '<div class="x-grid3-header-offset" style="{ostyle}">{header}</div>',
313                     '</div>',
314                     '<div class="x-clear"></div>',
315                 '</div>',
316                 '<div class="x-grid3-scroller">',
317                     '<div class="x-grid3-body" style="{bstyle}">{body}</div>',
318                     '<a href="#" class="x-grid3-focus" tabIndex="-1"></a>',
319                 '</div>',
320             '</div>',
321             '<div class="x-grid3-resize-marker">&#160;</div>',
322             '<div class="x-grid3-resize-proxy">&#160;</div>',
323         '</div>'
324     ),
325     
326     <div id="prop-Ext.grid.GridView-headerTpl"></div>/**
327      * The template to use when rendering headers. Has a default template
328      * @property headerTpl
329      * @type Ext.Template
330      */
331     headerTpl: new Ext.Template(
332         '<table border="0" cellspacing="0" cellpadding="0" style="{tstyle}">',
333             '<thead>',
334                 '<tr class="x-grid3-hd-row">{cells}</tr>',
335             '</thead>',
336         '</table>'
337     ),
338     
339     <div id="prop-Ext.grid.GridView-bodyTpl"></div>/**
340      * The template to use when rendering the body. Has a default template
341      * @property bodyTpl
342      * @type Ext.Template
343      */
344     bodyTpl: new Ext.Template('{rows}'),
345     
346     <div id="prop-Ext.grid.GridView-cellTpl"></div>/**
347      * The template to use to render each cell. Has a default template
348      * @property cellTpl
349      * @type Ext.Template
350      */
351     cellTpl: new Ext.Template(
352         '<td class="x-grid3-col x-grid3-cell x-grid3-td-{id} {css}" style="{style}" tabIndex="0" {cellAttr}>',
353             '<div class="x-grid3-cell-inner x-grid3-col-{id}" unselectable="on" {attr}>{value}</div>',
354         '</td>'
355     ),
356     
357     /**
358      * @private
359      * Provides default templates if they are not given for this particular instance. Most of the templates are defined on
360      * the prototype, the ones defined inside this function are done so because they are based on Grid or GridView configuration
361      */
362     initTemplates : function() {
363         var templates = this.templates || {},
364             template, name,
365             
366             headerCellTpl = new Ext.Template(
367                 '<td class="x-grid3-hd x-grid3-cell x-grid3-td-{id} {css}" style="{style}">',
368                     '<div {tooltip} {attr} class="x-grid3-hd-inner x-grid3-hd-{id}" unselectable="on" style="{istyle}">', 
369                         this.grid.enableHdMenu ? '<a class="x-grid3-hd-btn" href="#"></a>' : '',
370                         '{value}',
371                         '<img alt="" class="x-grid3-sort-icon" src="', Ext.BLANK_IMAGE_URL, '" />',
372                     '</div>',
373                 '</td>'
374             ),
375         
376             rowBodyText = [
377                 '<tr class="x-grid3-row-body-tr" style="{bodyStyle}">',
378                     '<td colspan="{cols}" class="x-grid3-body-cell" tabIndex="0" hidefocus="on">',
379                         '<div class="x-grid3-row-body">{body}</div>',
380                     '</td>',
381                 '</tr>'
382             ].join(""),
383         
384             innerText = [
385                 '<table class="x-grid3-row-table" border="0" cellspacing="0" cellpadding="0" style="{tstyle}">',
386                      '<tbody>',
387                         '<tr>{cells}</tr>',
388                         this.enableRowBody ? rowBodyText : '',
389                      '</tbody>',
390                 '</table>'
391             ].join("");
392         
393         Ext.applyIf(templates, {
394             hcell   : headerCellTpl,
395             cell    : this.cellTpl,
396             body    : this.bodyTpl,
397             header  : this.headerTpl,
398             master  : this.masterTpl,
399             row     : new Ext.Template('<div class="x-grid3-row {alt}" style="{tstyle}">' + innerText + '</div>'),
400             rowInner: new Ext.Template(innerText)
401         });
402
403         for (name in templates) {
404             template = templates[name];
405             
406             if (template && Ext.isFunction(template.compile) && !template.compiled) {
407                 template.disableFormats = true;
408                 template.compile();
409             }
410         }
411
412         this.templates = templates;
413         this.colRe = new RegExp('x-grid3-td-([^\\s]+)', '');
414     },
415
416     /**
417      * @private
418      * Each GridView has its own private flyweight, accessed through this method
419      */
420     fly : function(el) {
421         if (!this._flyweight) {
422             this._flyweight = new Ext.Element.Flyweight(document.body);
423         }
424         this._flyweight.dom = el;
425         return this._flyweight;
426     },
427
428     // private
429     getEditorParent : function() {
430         return this.scroller.dom;
431     },
432
433     /**
434      * @private
435      * Finds and stores references to important elements
436      */
437     initElements : function() {
438         var Element  = Ext.Element,
439             el       = Ext.get(this.grid.getGridEl().dom.firstChild),
440             mainWrap = new Element(el.child('div.x-grid3-viewport')),
441             mainHd   = new Element(mainWrap.child('div.x-grid3-header')),
442             scroller = new Element(mainWrap.child('div.x-grid3-scroller'));
443         
444         if (this.grid.hideHeaders) {
445             mainHd.setDisplayed(false);
446         }
447         
448         if (this.forceFit) {
449             scroller.setStyle('overflow-x', 'hidden');
450         }
451         
452         <div id="prop-Ext.grid.GridView-mainBody"></div>/**
453          * <i>Read-only</i>. The GridView's body Element which encapsulates all rows in the Grid.
454          * This {@link Ext.Element Element} is only available after the GridPanel has been rendered.
455          * @type Ext.Element
456          * @property mainBody
457          */
458         
459         Ext.apply(this, {
460             el      : el,
461             mainWrap: mainWrap,
462             scroller: scroller,
463             mainHd  : mainHd,
464             innerHd : mainHd.child('div.x-grid3-header-inner').dom,
465             mainBody: new Element(Element.fly(scroller).child('div.x-grid3-body')),
466             focusEl : new Element(Element.fly(scroller).child('a')),
467             
468             resizeMarker: new Element(el.child('div.x-grid3-resize-marker')),
469             resizeProxy : new Element(el.child('div.x-grid3-resize-proxy'))
470         });
471         
472         this.focusEl.swallowEvent('click', true);
473     },
474
475     // private
476     getRows : function() {
477         return this.hasRows() ? this.mainBody.dom.childNodes : [];
478     },
479
480     // finder methods, used with delegation
481
482     // private
483     findCell : function(el) {
484         if (!el) {
485             return false;
486         }
487         return this.fly(el).findParent(this.cellSelector, this.cellSelectorDepth);
488     },
489
490     <div id="method-Ext.grid.GridView-findCellIndex"></div>/**
491      * <p>Return the index of the grid column which contains the passed HTMLElement.</p>
492      * See also {@link #findRowIndex}
493      * @param {HTMLElement} el The target element
494      * @return {Number} The column index, or <b>false</b> if the target element is not within a row of this GridView.
495      */
496     findCellIndex : function(el, requiredCls) {
497         var cell = this.findCell(el),
498             hasCls;
499         
500         if (cell) {
501             hasCls = this.fly(cell).hasClass(requiredCls);
502             if (!requiredCls || hasCls) {
503                 return this.getCellIndex(cell);
504             }
505         }
506         return false;
507     },
508
509     // private
510     getCellIndex : function(el) {
511         if (el) {
512             var match = el.className.match(this.colRe);
513             
514             if (match && match[1]) {
515                 return this.cm.getIndexById(match[1]);
516             }
517         }
518         return false;
519     },
520
521     // private
522     findHeaderCell : function(el) {
523         var cell = this.findCell(el);
524         return cell && this.fly(cell).hasClass(this.hdCls) ? cell : null;
525     },
526
527     // private
528     findHeaderIndex : function(el){
529         return this.findCellIndex(el, this.hdCls);
530     },
531
532     <div id="method-Ext.grid.GridView-findRow"></div>/**
533      * Return the HtmlElement representing the grid row which contains the passed element.
534      * @param {HTMLElement} el The target HTMLElement
535      * @return {HTMLElement} The row element, or null if the target element is not within a row of this GridView.
536      */
537     findRow : function(el) {
538         if (!el) {
539             return false;
540         }
541         return this.fly(el).findParent(this.rowSelector, this.rowSelectorDepth);
542     },
543
544     <div id="method-Ext.grid.GridView-findRowIndex"></div>/**
545      * Return the index of the grid row which contains the passed HTMLElement.
546      * See also {@link #findCellIndex}
547      * @param {HTMLElement} el The target HTMLElement
548      * @return {Number} The row index, or <b>false</b> if the target element is not within a row of this GridView.
549      */
550     findRowIndex : function(el) {
551         var row = this.findRow(el);
552         return row ? row.rowIndex : false;
553     },
554
555     <div id="method-Ext.grid.GridView-findRowBody"></div>/**
556      * Return the HtmlElement representing the grid row body which contains the passed element.
557      * @param {HTMLElement} el The target HTMLElement
558      * @return {HTMLElement} The row body element, or null if the target element is not within a row body of this GridView.
559      */
560     findRowBody : function(el) {
561         if (!el) {
562             return false;
563         }
564         
565         return this.fly(el).findParent(this.rowBodySelector, this.rowBodySelectorDepth);
566     },
567
568     // getter methods for fetching elements dynamically in the grid
569
570     <div id="method-Ext.grid.GridView-getRow"></div>/**
571      * Return the <tt>&lt;div></tt> HtmlElement which represents a Grid row for the specified index.
572      * @param {Number} index The row index
573      * @return {HtmlElement} The div element.
574      */
575     getRow : function(row) {
576         return this.getRows()[row];
577     },
578
579     <div id="method-Ext.grid.GridView-getCell"></div>/**
580      * Returns the grid's <tt>&lt;td></tt> HtmlElement at the specified coordinates.
581      * @param {Number} row The row index in which to find the cell.
582      * @param {Number} col The column index of the cell.
583      * @return {HtmlElement} The td at the specified coordinates.
584      */
585     getCell : function(row, col) {
586         return Ext.fly(this.getRow(row)).query(this.cellSelector)[col]; 
587     },
588
589     <div id="method-Ext.grid.GridView-getHeaderCell"></div>/**
590      * Return the <tt>&lt;td></tt> HtmlElement which represents the Grid's header cell for the specified column index.
591      * @param {Number} index The column index
592      * @return {HtmlElement} The td element.
593      */
594     getHeaderCell : function(index) {
595         return this.mainHd.dom.getElementsByTagName('td')[index];
596     },
597
598     // manipulating elements
599
600     // private - use getRowClass to apply custom row classes
601     addRowClass : function(rowId, cls) {
602         var row = this.getRow(rowId);
603         if (row) {
604             this.fly(row).addClass(cls);
605         }
606     },
607
608     // private
609     removeRowClass : function(row, cls) {
610         var r = this.getRow(row);
611         if(r){
612             this.fly(r).removeClass(cls);
613         }
614     },
615
616     // private
617     removeRow : function(row) {
618         Ext.removeNode(this.getRow(row));
619         this.syncFocusEl(row);
620     },
621
622     // private
623     removeRows : function(firstRow, lastRow) {
624         var bd = this.mainBody.dom,
625             rowIndex;
626             
627         for (rowIndex = firstRow; rowIndex <= lastRow; rowIndex++){
628             Ext.removeNode(bd.childNodes[firstRow]);
629         }
630         
631         this.syncFocusEl(firstRow);
632     },
633
634     /* ----------------------------------- Scrolling functions -------------------------------------------*/
635     
636     // private
637     getScrollState : function() {
638         var sb = this.scroller.dom;
639         
640         return {
641             left: sb.scrollLeft, 
642             top : sb.scrollTop
643         };
644     },
645
646     // private
647     restoreScroll : function(state) {
648         var sb = this.scroller.dom;
649         sb.scrollLeft = state.left;
650         sb.scrollTop  = state.top;
651     },
652
653     <div id="method-Ext.grid.GridView-scrollToTop"></div>/**
654      * Scrolls the grid to the top
655      */
656     scrollToTop : function() {
657         var dom = this.scroller.dom;
658         
659         dom.scrollTop  = 0;
660         dom.scrollLeft = 0;
661     },
662
663     // private
664     syncScroll : function() {
665         this.syncHeaderScroll();
666         var mb = this.scroller.dom;
667         this.grid.fireEvent('bodyscroll', mb.scrollLeft, mb.scrollTop);
668     },
669
670     // private
671     syncHeaderScroll : function() {
672         var innerHd    = this.innerHd,
673             scrollLeft = this.scroller.dom.scrollLeft;
674         
675         innerHd.scrollLeft = scrollLeft;
676         innerHd.scrollLeft = scrollLeft; // second time for IE (1/2 time first fails, other browsers ignore)
677     },
678     
679     /**
680      * @private
681      * Ensures the given column has the given icon class
682      */
683     updateSortIcon : function(col, dir) {
684         var sortClasses = this.sortClasses,
685             sortClass   = sortClasses[dir == "DESC" ? 1 : 0],
686             headers     = this.mainHd.select('td').removeClass(sortClasses);
687         
688         headers.item(col).addClass(sortClass);
689     },
690
691     /**
692      * @private
693      * Updates the size of every column and cell in the grid
694      */
695     updateAllColumnWidths : function() {
696         var totalWidth = this.getTotalWidth(),
697             colCount   = this.cm.getColumnCount(),
698             rows       = this.getRows(),
699             rowCount   = rows.length,
700             widths     = [],
701             row, rowFirstChild, trow, i, j;
702         
703         for (i = 0; i < colCount; i++) {
704             widths[i] = this.getColumnWidth(i);
705             this.getHeaderCell(i).style.width = widths[i];
706         }
707         
708         this.updateHeaderWidth();
709         
710         for (i = 0; i < rowCount; i++) {
711             row = rows[i];
712             row.style.width = totalWidth;
713             rowFirstChild = row.firstChild;
714             
715             if (rowFirstChild) {
716                 rowFirstChild.style.width = totalWidth;
717                 trow = rowFirstChild.rows[0];
718                 
719                 for (j = 0; j < colCount; j++) {
720                     trow.childNodes[j].style.width = widths[j];
721                 }
722             }
723         }
724         
725         this.onAllColumnWidthsUpdated(widths, totalWidth);
726     },
727
728     /**
729      * @private
730      * Called after a column's width has been updated, this resizes all of the cells for that column in each row
731      * @param {Number} column The column index
732      */
733     updateColumnWidth : function(column, width) {
734         var columnWidth = this.getColumnWidth(column),
735             totalWidth  = this.getTotalWidth(),
736             headerCell  = this.getHeaderCell(column),
737             nodes       = this.getRows(),
738             nodeCount   = nodes.length,
739             row, i, firstChild;
740         
741         this.updateHeaderWidth();
742         headerCell.style.width = columnWidth;
743         
744         for (i = 0; i < nodeCount; i++) {
745             row = nodes[i];
746             firstChild = row.firstChild;
747             
748             row.style.width = totalWidth;
749             if (firstChild) {
750                 firstChild.style.width = totalWidth;
751                 firstChild.rows[0].childNodes[column].style.width = columnWidth;
752             }
753         }
754         
755         this.onColumnWidthUpdated(column, columnWidth, totalWidth);
756     },
757     
758     /**
759      * @private
760      * Sets the hidden status of a given column.
761      * @param {Number} col The column index
762      * @param {Boolean} hidden True to make the column hidden
763      */
764     updateColumnHidden : function(col, hidden) {
765         var totalWidth = this.getTotalWidth(),
766             display    = hidden ? 'none' : '',
767             headerCell = this.getHeaderCell(col),
768             nodes      = this.getRows(),
769             nodeCount  = nodes.length,
770             row, rowFirstChild, i;
771         
772         this.updateHeaderWidth();
773         headerCell.style.display = display;
774         
775         for (i = 0; i < nodeCount; i++) {
776             row = nodes[i];
777             row.style.width = totalWidth;
778             rowFirstChild = row.firstChild;
779             
780             if (rowFirstChild) {
781                 rowFirstChild.style.width = totalWidth;
782                 rowFirstChild.rows[0].childNodes[col].style.display = display;
783             }
784         }
785         
786         this.onColumnHiddenUpdated(col, hidden, totalWidth);
787         delete this.lastViewWidth; //recalc
788         this.layout();
789     },
790
791     /**
792      * @private
793      * Renders all of the rows to a string buffer and returns the string. This is called internally
794      * by renderRows and performs the actual string building for the rows - it does not inject HTML into the DOM.
795      * @param {Array} columns The column data acquired from getColumnData.
796      * @param {Array} records The array of records to render
797      * @param {Ext.data.Store} store The store to render the rows from
798      * @param {Number} startRow The index of the first row being rendered. Sometimes we only render a subset of
799      * the rows so this is used to maintain logic for striping etc
800      * @param {Number} colCount The total number of columns in the column model
801      * @param {Boolean} stripe True to stripe the rows
802      * @return {String} A string containing the HTML for the rendered rows
803      */
804     doRender : function(columns, records, store, startRow, colCount, stripe) {
805         var templates = this.templates,
806             cellTemplate = templates.cell,
807             rowTemplate = templates.row,
808             last = colCount - 1,
809             tstyle = 'width:' + this.getTotalWidth() + ';',
810             // buffers
811             rowBuffer = [],
812             colBuffer = [],
813             rowParams = {tstyle: tstyle},
814             meta = {},
815             len  = records.length,
816             alt,
817             column,
818             record, i, j, rowIndex;
819
820         //build up each row's HTML
821         for (j = 0; j < len; j++) {
822             record    = records[j];
823             colBuffer = [];
824
825             rowIndex = j + startRow;
826
827             //build up each column's HTML
828             for (i = 0; i < colCount; i++) {
829                 column = columns[i];
830                 
831                 meta.id    = column.id;
832                 meta.css   = i === 0 ? 'x-grid3-cell-first ' : (i == last ? 'x-grid3-cell-last ' : '');
833                 meta.attr  = meta.cellAttr = '';
834                 meta.style = column.style;
835                 meta.value = column.renderer.call(column.scope, record.data[column.name], meta, record, rowIndex, i, store);
836
837                 if (Ext.isEmpty(meta.value)) {
838                     meta.value = '&#160;';
839                 }
840
841                 if (this.markDirty && record.dirty && typeof record.modified[column.name] != 'undefined') {
842                     meta.css += ' x-grid3-dirty-cell';
843                 }
844
845                 colBuffer[colBuffer.length] = cellTemplate.apply(meta);
846             }
847
848             alt = [];
849             //set up row striping and row dirtiness CSS classes
850             if (stripe && ((rowIndex + 1) % 2 === 0)) {
851                 alt[0] = 'x-grid3-row-alt';
852             }
853
854             if (record.dirty) {
855                 alt[1] = ' x-grid3-dirty-row';
856             }
857
858             rowParams.cols = colCount;
859
860             if (this.getRowClass) {
861                 alt[2] = this.getRowClass(record, rowIndex, rowParams, store);
862             }
863
864             rowParams.alt   = alt.join(' ');
865             rowParams.cells = colBuffer.join('');
866
867             rowBuffer[rowBuffer.length] = rowTemplate.apply(rowParams);
868         }
869
870         return rowBuffer.join('');
871     },
872
873     /**
874      * @private
875      * Adds CSS classes and rowIndex to each row
876      * @param {Number} startRow The row to start from (defaults to 0)
877      */
878     processRows : function(startRow, skipStripe) {
879         if (!this.ds || this.ds.getCount() < 1) {
880             return;
881         }
882
883         var rows   = this.getRows(),
884             length = rows.length,
885             row, i;
886
887         skipStripe = skipStripe || !this.grid.stripeRows;
888         startRow   = startRow   || 0;
889
890         for (i = 0; i < length; i++) {
891             row = rows[i];
892             if (row) {
893                 row.rowIndex = i;
894                 if (!skipStripe) {
895                     row.className = row.className.replace(this.rowClsRe, ' ');
896                     if ((i + 1) % 2 === 0){
897                         row.className += ' x-grid3-row-alt';
898                     }
899                 }
900             }
901         }
902
903         // add first/last-row classes
904         if (startRow === 0) {
905             Ext.fly(rows[0]).addClass(this.firstRowCls);
906         }
907
908         Ext.fly(rows[length - 1]).addClass(this.lastRowCls);
909     },
910     
911     /**
912      * @private
913      */
914     afterRender : function() {
915         if (!this.ds || !this.cm) {
916             return;
917         }
918         
919         this.mainBody.dom.innerHTML = this.renderBody() || '&#160;';
920         this.processRows(0, true);
921
922         if (this.deferEmptyText !== true) {
923             this.applyEmptyText();
924         }
925         
926         this.grid.fireEvent('viewready', this.grid);
927     },
928     
929     /**
930      * @private
931      * This is always intended to be called after renderUI. Sets up listeners on the UI elements
932      * and sets up options like column menus, moving and resizing.
933      */
934     afterRenderUI: function() {
935         var grid = this.grid;
936         
937         this.initElements();
938
939         // get mousedowns early
940         Ext.fly(this.innerHd).on('click', this.handleHdDown, this);
941
942         this.mainHd.on({
943             scope    : this,
944             mouseover: this.handleHdOver,
945             mouseout : this.handleHdOut,
946             mousemove: this.handleHdMove
947         });
948
949         this.scroller.on('scroll', this.syncScroll,  this);
950         
951         if (grid.enableColumnResize !== false) {
952             this.splitZone = new Ext.grid.GridView.SplitDragZone(grid, this.mainHd.dom);
953         }
954
955         if (grid.enableColumnMove) {
956             this.columnDrag = new Ext.grid.GridView.ColumnDragZone(grid, this.innerHd);
957             this.columnDrop = new Ext.grid.HeaderDropZone(grid, this.mainHd.dom);
958         }
959
960         if (grid.enableHdMenu !== false) {
961             this.hmenu = new Ext.menu.Menu({id: grid.id + '-hctx'});
962             this.hmenu.add(
963                 {itemId:'asc',  text: this.sortAscText,  cls: 'xg-hmenu-sort-asc'},
964                 {itemId:'desc', text: this.sortDescText, cls: 'xg-hmenu-sort-desc'}
965             );
966
967             if (grid.enableColumnHide !== false) {
968                 this.colMenu = new Ext.menu.Menu({id:grid.id + '-hcols-menu'});
969                 this.colMenu.on({
970                     scope     : this,
971                     beforeshow: this.beforeColMenuShow,
972                     itemclick : this.handleHdMenuClick
973                 });
974                 this.hmenu.add('-', {
975                     itemId:'columns',
976                     hideOnClick: false,
977                     text: this.columnsText,
978                     menu: this.colMenu,
979                     iconCls: 'x-cols-icon'
980                 });
981             }
982
983             this.hmenu.on('itemclick', this.handleHdMenuClick, this);
984         }
985
986         if (grid.trackMouseOver) {
987             this.mainBody.on({
988                 scope    : this,
989                 mouseover: this.onRowOver,
990                 mouseout : this.onRowOut
991             });
992         }
993
994         if (grid.enableDragDrop || grid.enableDrag) {
995             this.dragZone = new Ext.grid.GridDragZone(grid, {
996                 ddGroup : grid.ddGroup || 'GridDD'
997             });
998         }
999
1000         this.updateHeaderSortState();
1001     },
1002
1003     /**
1004      * @private
1005      * Renders each of the UI elements in turn. This is called internally, once, by this.render. It does not
1006      * render rows from the store, just the surrounding UI elements.
1007      */
1008     renderUI : function() {
1009         var templates = this.templates;
1010
1011         return templates.master.apply({
1012             body  : templates.body.apply({rows:'&#160;'}),
1013             header: this.renderHeaders(),
1014             ostyle: 'width:' + this.getOffsetWidth() + ';',
1015             bstyle: 'width:' + this.getTotalWidth()  + ';'
1016         });
1017     },
1018
1019     // private
1020     processEvent : function(name, e) {
1021         var target = e.getTarget(),
1022             grid   = this.grid,
1023             header = this.findHeaderIndex(target),
1024             row, cell, col, body;
1025
1026         grid.fireEvent(name, e);
1027
1028         if (header !== false) {
1029             grid.fireEvent('header' + name, grid, header, e);
1030         } else {
1031             row = this.findRowIndex(target);
1032
1033 //          Grid's value-added events must bubble correctly to allow cancelling via returning false: cell->column->row
1034 //          We must allow a return of false at any of these levels to cancel the event processing.
1035 //          Particularly allowing rowmousedown to be cancellable by prior handlers which need to prevent selection.
1036             if (row !== false) {
1037                 cell = this.findCellIndex(target);
1038                 if (cell !== false) {
1039                     col = grid.colModel.getColumnAt(cell);
1040                     if (grid.fireEvent('cell' + name, grid, row, cell, e) !== false) {
1041                         if (!col || (col.processEvent && (col.processEvent(name, e, grid, row, cell) !== false))) {
1042                             grid.fireEvent('row' + name, grid, row, e);
1043                         }
1044                     }
1045                 } else {
1046                     if (grid.fireEvent('row' + name, grid, row, e) !== false) {
1047                         (body = this.findRowBody(target)) && grid.fireEvent('rowbody' + name, grid, row, e);
1048                     }
1049                 }
1050             } else {
1051                 grid.fireEvent('container' + name, grid, e);
1052             }
1053         }
1054     },
1055
1056     /**
1057      * @private
1058      * Sizes the grid's header and body elements
1059      */
1060     layout : function(initial) {
1061         if (!this.mainBody) {
1062             return; // not rendered
1063         }
1064
1065         var grid       = this.grid,
1066             gridEl     = grid.getGridEl(),
1067             gridSize   = gridEl.getSize(true),
1068             gridWidth  = gridSize.width,
1069             gridHeight = gridSize.height,
1070             scroller   = this.scroller,
1071             scrollStyle, headerHeight, scrollHeight;
1072         
1073         if (gridWidth < 20 || gridHeight < 20) {
1074             return;
1075         }
1076         
1077         if (grid.autoHeight) {
1078             scrollStyle = scroller.dom.style;
1079             scrollStyle.overflow = 'visible';
1080             
1081             if (Ext.isWebKit) {
1082                 scrollStyle.position = 'static';
1083             }
1084         } else {
1085             this.el.setSize(gridWidth, gridHeight);
1086             
1087             headerHeight = this.mainHd.getHeight();
1088             scrollHeight = gridHeight - headerHeight;
1089             
1090             scroller.setSize(gridWidth, scrollHeight);
1091             
1092             if (this.innerHd) {
1093                 this.innerHd.style.width = (gridWidth) + "px";
1094             }
1095         }
1096         
1097         if (this.forceFit || (initial === true && this.autoFill)) {
1098             if (this.lastViewWidth != gridWidth) {
1099                 this.fitColumns(false, false);
1100                 this.lastViewWidth = gridWidth;
1101             }
1102         } else {
1103             this.autoExpand();
1104             this.syncHeaderScroll();
1105         }
1106         
1107         this.onLayout(gridWidth, scrollHeight);
1108     },
1109
1110     // template functions for subclasses and plugins
1111     // these functions include precalculated values
1112     onLayout : function(vw, vh) {
1113         // do nothing
1114     },
1115
1116     onColumnWidthUpdated : function(col, w, tw) {
1117         //template method
1118     },
1119
1120     onAllColumnWidthsUpdated : function(ws, tw) {
1121         //template method
1122     },
1123
1124     onColumnHiddenUpdated : function(col, hidden, tw) {
1125         // template method
1126     },
1127
1128     updateColumnText : function(col, text) {
1129         // template method
1130     },
1131
1132     afterMove : function(colIndex) {
1133         // template method
1134     },
1135
1136     /* ----------------------------------- Core Specific -------------------------------------------*/
1137     // private
1138     init : function(grid) {
1139         this.grid = grid;
1140
1141         this.initTemplates();
1142         this.initData(grid.store, grid.colModel);
1143         this.initUI(grid);
1144     },
1145
1146     // private
1147     getColumnId : function(index){
1148         return this.cm.getColumnId(index);
1149     },
1150
1151     // private
1152     getOffsetWidth : function() {
1153         return (this.cm.getTotalWidth() + this.getScrollOffset()) + 'px';
1154     },
1155
1156     // private
1157     getScrollOffset: function() {
1158         return Ext.num(this.scrollOffset, Ext.getScrollBarWidth());
1159     },
1160
1161     /**
1162      * @private
1163      * Renders the header row using the 'header' template. Does not inject the HTML into the DOM, just
1164      * returns a string.
1165      * @return {String} Rendered header row
1166      */
1167     renderHeaders : function() {
1168         var colModel   = this.cm,
1169             templates  = this.templates,
1170             headerTpl  = templates.hcell,
1171             properties = {},
1172             colCount   = colModel.getColumnCount(),
1173             last       = colCount - 1,
1174             cells      = [],
1175             i, cssCls;
1176         
1177         for (i = 0; i < colCount; i++) {
1178             if (i == 0) {
1179                 cssCls = 'x-grid3-cell-first ';
1180             } else {
1181                 cssCls = i == last ? 'x-grid3-cell-last ' : '';
1182             }
1183             
1184             properties = {
1185                 id     : colModel.getColumnId(i),
1186                 value  : colModel.getColumnHeader(i) || '',
1187                 style  : this.getColumnStyle(i, true),
1188                 css    : cssCls,
1189                 tooltip: this.getColumnTooltip(i)
1190             };
1191             
1192             if (colModel.config[i].align == 'right') {
1193                 properties.istyle = 'padding-right: 16px;';
1194             } else {
1195                 delete properties.istyle;
1196             }
1197             
1198             cells[i] = headerTpl.apply(properties);
1199         }
1200         
1201         return templates.header.apply({
1202             cells : cells.join(""),
1203             tstyle: String.format("width: {0};", this.getTotalWidth())
1204         });
1205     },
1206
1207     /**
1208      * @private
1209      */
1210     getColumnTooltip : function(i) {
1211         var tooltip = this.cm.getColumnTooltip(i);
1212         if (tooltip) {
1213             if (Ext.QuickTips.isEnabled()) {
1214                 return 'ext:qtip="' + tooltip + '"';
1215             } else {
1216                 return 'title="' + tooltip + '"';
1217             }
1218         }
1219         
1220         return '';
1221     },
1222
1223     // private
1224     beforeUpdate : function() {
1225         this.grid.stopEditing(true);
1226     },
1227
1228     /**
1229      * @private
1230      * Re-renders the headers and ensures they are sized correctly
1231      */
1232     updateHeaders : function() {
1233         this.innerHd.firstChild.innerHTML = this.renderHeaders();
1234         
1235         this.updateHeaderWidth(false);
1236     },
1237     
1238     /**
1239      * @private
1240      * Ensures that the header is sized to the total width available to it
1241      * @param {Boolean} updateMain True to update the mainBody's width also (defaults to true)
1242      */
1243     updateHeaderWidth: function(updateMain) {
1244         var innerHdChild = this.innerHd.firstChild,
1245             totalWidth   = this.getTotalWidth();
1246         
1247         innerHdChild.style.width = this.getOffsetWidth();
1248         innerHdChild.firstChild.style.width = totalWidth;
1249         
1250         if (updateMain !== false) {
1251             this.mainBody.dom.style.width = totalWidth;
1252         }
1253     },
1254
1255     <div id="method-Ext.grid.GridView-focusRow"></div>/**
1256      * Focuses the specified row.
1257      * @param {Number} row The row index
1258      */
1259     focusRow : function(row) {
1260         this.focusCell(row, 0, false);
1261     },
1262
1263     <div id="method-Ext.grid.GridView-focusCell"></div>/**
1264      * Focuses the specified cell.
1265      * @param {Number} row The row index
1266      * @param {Number} col The column index
1267      */
1268     focusCell : function(row, col, hscroll) {
1269         this.syncFocusEl(this.ensureVisible(row, col, hscroll));
1270         
1271         var focusEl = this.focusEl;
1272         
1273         if (Ext.isGecko) {
1274             focusEl.focus();
1275         } else {
1276             focusEl.focus.defer(1, focusEl);
1277         }
1278     },
1279
1280     /**
1281      * @private
1282      * Finds the Elements corresponding to the given row and column indexes
1283      */
1284     resolveCell : function(row, col, hscroll) {
1285         if (!Ext.isNumber(row)) {
1286             row = row.rowIndex;
1287         }
1288         
1289         if (!this.ds) {
1290             return null;
1291         }
1292         
1293         if (row < 0 || row >= this.ds.getCount()) {
1294             return null;
1295         }
1296         col = (col !== undefined ? col : 0);
1297
1298         var rowEl    = this.getRow(row),
1299             colModel = this.cm,
1300             colCount = colModel.getColumnCount(),
1301             cellEl;
1302             
1303         if (!(hscroll === false && col === 0)) {
1304             while (col < colCount && colModel.isHidden(col)) {
1305                 col++;
1306             }
1307             
1308             cellEl = this.getCell(row, col);
1309         }
1310
1311         return {row: rowEl, cell: cellEl};
1312     },
1313
1314     /**
1315      * @private
1316      * Returns the XY co-ordinates of a given row/cell resolution (see {@link #resolveCell})
1317      * @return {Array} X and Y coords
1318      */
1319     getResolvedXY : function(resolved) {
1320         if (!resolved) {
1321             return null;
1322         }
1323         
1324         var cell = resolved.cell,
1325             row  = resolved.row;
1326         
1327         if (cell) {
1328             return Ext.fly(cell).getXY();
1329         } else {
1330             return [this.el.getX(), Ext.fly(row).getY()];
1331         }
1332     },
1333
1334     /**
1335      * @private
1336      * Moves the focus element to the x and y co-ordinates of the given row and column
1337      */
1338     syncFocusEl : function(row, col, hscroll) {
1339         var xy = row;
1340         
1341         if (!Ext.isArray(xy)) {
1342             row = Math.min(row, Math.max(0, this.getRows().length-1));
1343             
1344             if (isNaN(row)) {
1345                 return;
1346             }
1347             
1348             xy = this.getResolvedXY(this.resolveCell(row, col, hscroll));
1349         }
1350         
1351         this.focusEl.setXY(xy || this.scroller.getXY());
1352     },
1353
1354     /**
1355      * @private
1356      */
1357     ensureVisible : function(row, col, hscroll) {
1358         var resolved = this.resolveCell(row, col, hscroll);
1359         
1360         if (!resolved || !resolved.row) {
1361             return null;
1362         }
1363
1364         var rowEl  = resolved.row,
1365             cellEl = resolved.cell,
1366             c = this.scroller.dom,
1367             p = rowEl,
1368             ctop = 0,
1369             stop = this.el.dom;
1370
1371         while (p && p != stop) {
1372             ctop += p.offsetTop;
1373             p = p.offsetParent;
1374         }
1375
1376         ctop -= this.mainHd.dom.offsetHeight;
1377         stop = parseInt(c.scrollTop, 10);
1378
1379         var cbot = ctop + rowEl.offsetHeight,
1380             ch = c.clientHeight,
1381             sbot = stop + ch;
1382
1383
1384         if (ctop < stop) {
1385           c.scrollTop = ctop;
1386         } else if(cbot > sbot) {
1387             c.scrollTop = cbot-ch;
1388         }
1389
1390         if (hscroll !== false) {
1391             var cleft  = parseInt(cellEl.offsetLeft, 10),
1392                 cright = cleft + cellEl.offsetWidth,
1393                 sleft  = parseInt(c.scrollLeft, 10),
1394                 sright = sleft + c.clientWidth;
1395                 
1396             if (cleft < sleft) {
1397                 c.scrollLeft = cleft;
1398             } else if(cright > sright) {
1399                 c.scrollLeft = cright-c.clientWidth;
1400             }
1401         }
1402         
1403         return this.getResolvedXY(resolved);
1404     },
1405
1406     // private
1407     insertRows : function(dm, firstRow, lastRow, isUpdate) {
1408         var last = dm.getCount() - 1;
1409         if( !isUpdate && firstRow === 0 && lastRow >= last) {
1410             this.fireEvent('beforerowsinserted', this, firstRow, lastRow);
1411                 this.refresh();
1412             this.fireEvent('rowsinserted', this, firstRow, lastRow);
1413         } else {
1414             if (!isUpdate) {
1415                 this.fireEvent('beforerowsinserted', this, firstRow, lastRow);
1416             }
1417             var html = this.renderRows(firstRow, lastRow),
1418                 before = this.getRow(firstRow);
1419             if (before) {
1420                 if(firstRow === 0){
1421                     Ext.fly(this.getRow(0)).removeClass(this.firstRowCls);
1422                 }
1423                 Ext.DomHelper.insertHtml('beforeBegin', before, html);
1424             } else {
1425                 var r = this.getRow(last - 1);
1426                 if(r){
1427                     Ext.fly(r).removeClass(this.lastRowCls);
1428                 }
1429                 Ext.DomHelper.insertHtml('beforeEnd', this.mainBody.dom, html);
1430             }
1431             if (!isUpdate) {
1432                 this.processRows(firstRow);
1433                 this.fireEvent('rowsinserted', this, firstRow, lastRow);
1434             } else if (firstRow === 0 || firstRow >= last) {
1435                 //ensure first/last row is kept after an update.
1436                 Ext.fly(this.getRow(firstRow)).addClass(firstRow === 0 ? this.firstRowCls : this.lastRowCls);
1437             }
1438         }
1439         this.syncFocusEl(firstRow);
1440     },
1441
1442     /**
1443      * @private
1444      * DEPRECATED - this doesn't appear to be called anywhere in the library, remove in 4.0. 
1445      */
1446     deleteRows : function(dm, firstRow, lastRow) {
1447         if (dm.getRowCount() < 1) {
1448             this.refresh();
1449         } else {
1450             this.fireEvent('beforerowsdeleted', this, firstRow, lastRow);
1451
1452             this.removeRows(firstRow, lastRow);
1453
1454             this.processRows(firstRow);
1455             this.fireEvent('rowsdeleted', this, firstRow, lastRow);
1456         }
1457     },
1458
1459     /**
1460      * @private
1461      * Builds a CSS string for the given column index
1462      * @param {Number} colIndex The column index
1463      * @param {Boolean} isHeader True if getting the style for the column's header
1464      * @return {String} The CSS string
1465      */
1466     getColumnStyle : function(colIndex, isHeader) {
1467         var colModel  = this.cm,
1468             colConfig = colModel.config,
1469             style     = isHeader ? '' : colConfig[colIndex].css || '',
1470             align     = colConfig[colIndex].align;
1471         
1472         style += String.format("width: {0};", this.getColumnWidth(colIndex));
1473         
1474         if (colModel.isHidden(colIndex)) {
1475             style += 'display: none; ';
1476         }
1477         
1478         if (align) {
1479             style += String.format("text-align: {0};", align);
1480         }
1481         
1482         return style;
1483     },
1484
1485     /**
1486      * @private
1487      * Returns the width of a given column minus its border width
1488      * @return {Number} The column index
1489      * @return {String|Number} The width in pixels
1490      */
1491     getColumnWidth : function(column) {
1492         var columnWidth = this.cm.getColumnWidth(column),
1493             borderWidth = this.borderWidth;
1494         
1495         if (Ext.isNumber(columnWidth)) {
1496             if (Ext.isBorderBox || (Ext.isWebKit && !Ext.isSafari2)) {
1497                 return columnWidth + "px";
1498             } else {
1499                 return Math.max(columnWidth - borderWidth, 0) + "px";
1500             }
1501         } else {
1502             return columnWidth;
1503         }
1504     },
1505
1506     /**
1507      * @private
1508      * Returns the total width of all visible columns
1509      * @return {String} 
1510      */
1511     getTotalWidth : function() {
1512         return this.cm.getTotalWidth() + 'px';
1513     },
1514
1515     /**
1516      * @private
1517      * Resizes each column to fit the available grid width.
1518      * TODO: The second argument isn't even used, remove it in 4.0
1519      * @param {Boolean} preventRefresh True to prevent resizing of each row to the new column sizes (defaults to false)
1520      * @param {null} onlyExpand NOT USED, will be removed in 4.0
1521      * @param {Number} omitColumn The index of a column to leave at its current width. Defaults to undefined
1522      * @return {Boolean} True if the operation succeeded, false if not or undefined if the grid view is not yet initialized
1523      */
1524     fitColumns : function(preventRefresh, onlyExpand, omitColumn) {
1525         var grid          = this.grid,
1526             colModel      = this.cm,
1527             totalColWidth = colModel.getTotalWidth(false),
1528             gridWidth     = this.getGridInnerWidth(),
1529             extraWidth    = gridWidth - totalColWidth,
1530             columns       = [],
1531             extraCol      = 0,
1532             width         = 0,
1533             colWidth, fraction, i;
1534         
1535         // not initialized, so don't screw up the default widths
1536         if (gridWidth < 20 || extraWidth === 0) {
1537             return false;
1538         }
1539         
1540         var visibleColCount = colModel.getColumnCount(true),
1541             totalColCount   = colModel.getColumnCount(false),
1542             adjCount        = visibleColCount - (Ext.isNumber(omitColumn) ? 1 : 0);
1543         
1544         if (adjCount === 0) {
1545             adjCount = 1;
1546             omitColumn = undefined;
1547         }
1548         
1549         //FIXME: the algorithm used here is odd and potentially confusing. Includes this for loop and the while after it.
1550         for (i = 0; i < totalColCount; i++) {
1551             if (!colModel.isFixed(i) && i !== omitColumn) {
1552                 colWidth = colModel.getColumnWidth(i);
1553                 columns.push(i, colWidth);
1554                 
1555                 if (!colModel.isHidden(i)) {
1556                     extraCol = i;
1557                     width += colWidth;
1558                 }
1559             }
1560         }
1561         
1562         fraction = (gridWidth - colModel.getTotalWidth()) / width;
1563         
1564         while (columns.length) {
1565             colWidth = columns.pop();
1566             i        = columns.pop();
1567             
1568             colModel.setColumnWidth(i, Math.max(grid.minColumnWidth, Math.floor(colWidth + colWidth * fraction)), true);
1569         }
1570         
1571         //this has been changed above so remeasure now
1572         totalColWidth = colModel.getTotalWidth(false);
1573         
1574         if (totalColWidth > gridWidth) {
1575             var adjustCol = (adjCount == visibleColCount) ? extraCol : omitColumn,
1576                 newWidth  = Math.max(1, colModel.getColumnWidth(adjustCol) - (totalColWidth - gridWidth));
1577             
1578             colModel.setColumnWidth(adjustCol, newWidth, true);
1579         }
1580         
1581         if (preventRefresh !== true) {
1582             this.updateAllColumnWidths();
1583         }
1584         
1585         return true;
1586     },
1587
1588     /**
1589      * @private
1590      * Resizes the configured autoExpandColumn to take the available width after the other columns have 
1591      * been accounted for
1592      * @param {Boolean} preventUpdate True to prevent the resizing of all rows (defaults to false)
1593      */
1594     autoExpand : function(preventUpdate) {
1595         var grid             = this.grid,
1596             colModel         = this.cm,
1597             gridWidth        = this.getGridInnerWidth(),
1598             totalColumnWidth = colModel.getTotalWidth(false),
1599             autoExpandColumn = grid.autoExpandColumn;
1600         
1601         if (!this.userResized && autoExpandColumn) {
1602             if (gridWidth != totalColumnWidth) {
1603                 //if we are not already using all available width, resize the autoExpandColumn
1604                 var colIndex     = colModel.getIndexById(autoExpandColumn),
1605                     currentWidth = colModel.getColumnWidth(colIndex),
1606                     desiredWidth = gridWidth - totalColumnWidth + currentWidth,
1607                     newWidth     = Math.min(Math.max(desiredWidth, grid.autoExpandMin), grid.autoExpandMax);
1608                 
1609                 if (currentWidth != newWidth) {
1610                     colModel.setColumnWidth(colIndex, newWidth, true);
1611                     
1612                     if (preventUpdate !== true) {
1613                         this.updateColumnWidth(colIndex, newWidth);
1614                     }
1615                 }
1616             }
1617         }
1618     },
1619     
1620     <div id="method-Ext.grid.GridView-getGridInnerWidth"></div>/**
1621      * Returns the total internal width available to the grid, taking the scrollbar into account
1622      * @return {Number} The total width
1623      */
1624     getGridInnerWidth: function() {
1625         return this.grid.getGridEl().getWidth(true) - this.getScrollOffset();
1626     },
1627
1628     /**
1629      * @private
1630      * Returns an array of column configurations - one for each column
1631      * @return {Array} Array of column config objects. This includes the column name, renderer, id style and renderer
1632      */
1633     getColumnData : function() {
1634         var columns  = [],
1635             colModel = this.cm,
1636             colCount = colModel.getColumnCount(),
1637             fields   = this.ds.fields,
1638             i, name;
1639         
1640         for (i = 0; i < colCount; i++) {
1641             name = colModel.getDataIndex(i);
1642             
1643             columns[i] = {
1644                 name    : Ext.isDefined(name) ? name : (fields.get(i) ? fields.get(i).name : undefined),
1645                 renderer: colModel.getRenderer(i),
1646                 scope   : colModel.getRendererScope(i),
1647                 id      : colModel.getColumnId(i),
1648                 style   : this.getColumnStyle(i)
1649             };
1650         }
1651         
1652         return columns;
1653     },
1654
1655     /**
1656      * @private
1657      * Renders rows between start and end indexes
1658      * @param {Number} startRow Index of the first row to render
1659      * @param {Number} endRow Index of the last row to render
1660      */
1661     renderRows : function(startRow, endRow) {
1662         var grid     = this.grid,
1663             store    = grid.store,
1664             stripe   = grid.stripeRows,
1665             colModel = grid.colModel,
1666             colCount = colModel.getColumnCount(),
1667             rowCount = store.getCount(),
1668             records;
1669         
1670         if (rowCount < 1) {
1671             return '';
1672         }
1673         
1674         startRow = startRow || 0;
1675         endRow   = Ext.isDefined(endRow) ? endRow : rowCount - 1;
1676         records  = store.getRange(startRow, endRow);
1677         
1678         return this.doRender(this.getColumnData(), records, store, startRow, colCount, stripe);
1679     },
1680
1681     // private
1682     renderBody : function(){
1683         var markup = this.renderRows() || '&#160;';
1684         return this.templates.body.apply({rows: markup});
1685     },
1686
1687     /**
1688      * @private
1689      * Refreshes a row by re-rendering it. Fires the rowupdated event when done
1690      */
1691     refreshRow: function(record) {
1692         var store     = this.ds,
1693             colCount  = this.cm.getColumnCount(),
1694             columns   = this.getColumnData(),
1695             last      = colCount - 1,
1696             cls       = ['x-grid3-row'],
1697             rowParams = {
1698                 tstyle: String.format("width: {0};", this.getTotalWidth())
1699             },
1700             colBuffer = [],
1701             cellTpl   = this.templates.cell,
1702             rowIndex, row, column, meta, css, i;
1703         
1704         if (Ext.isNumber(record)) {
1705             rowIndex = record;
1706             record   = store.getAt(rowIndex);
1707         } else {
1708             rowIndex = store.indexOf(record);
1709         }
1710         
1711         //the record could not be found
1712         if (!record || rowIndex < 0) {
1713             return;
1714         }
1715         
1716         //builds each column in this row
1717         for (i = 0; i < colCount; i++) {
1718             column = columns[i];
1719             
1720             if (i == 0) {
1721                 css = 'x-grid3-cell-first';
1722             } else {
1723                 css = (i == last) ? 'x-grid3-cell-last ' : '';
1724             }
1725             
1726             meta = {
1727                 id      : column.id,
1728                 style   : column.style,
1729                 css     : css,
1730                 attr    : "",
1731                 cellAttr: ""
1732             };
1733             // Need to set this after, because we pass meta to the renderer
1734             meta.value = column.renderer.call(column.scope, record.data[column.name], meta, record, rowIndex, i, store);
1735             
1736             if (Ext.isEmpty(meta.value)) {
1737                 meta.value = '&#160;';
1738             }
1739             
1740             if (this.markDirty && record.dirty && typeof record.modified[column.name] != 'undefined') {
1741                 meta.css += ' x-grid3-dirty-cell';
1742             }
1743             
1744             colBuffer[i] = cellTpl.apply(meta);
1745         }
1746         
1747         row = this.getRow(rowIndex);
1748         row.className = '';
1749         
1750         if (this.grid.stripeRows && ((rowIndex + 1) % 2 === 0)) {
1751             cls.push('x-grid3-row-alt');
1752         }
1753         
1754         if (this.getRowClass) {
1755             rowParams.cols = colCount;
1756             cls.push(this.getRowClass(record, rowIndex, rowParams, store));
1757         }
1758         
1759         this.fly(row).addClass(cls).setStyle(rowParams.tstyle);
1760         rowParams.cells = colBuffer.join("");
1761         row.innerHTML = this.templates.rowInner.apply(rowParams);
1762         
1763         this.fireEvent('rowupdated', this, rowIndex, record);
1764     },
1765
1766     <div id="method-Ext.grid.GridView-refresh"></div>/**
1767      * Refreshs the grid UI
1768      * @param {Boolean} headersToo (optional) True to also refresh the headers
1769      */
1770     refresh : function(headersToo) {
1771         this.fireEvent('beforerefresh', this);
1772         this.grid.stopEditing(true);
1773
1774         var result = this.renderBody();
1775         this.mainBody.update(result).setWidth(this.getTotalWidth());
1776         if (headersToo === true) {
1777             this.updateHeaders();
1778             this.updateHeaderSortState();
1779         }
1780         this.processRows(0, true);
1781         this.layout();
1782         this.applyEmptyText();
1783         this.fireEvent('refresh', this);
1784     },
1785
1786     /**
1787      * @private
1788      * Displays the configured emptyText if there are currently no rows to display
1789      */
1790     applyEmptyText : function() {
1791         if (this.emptyText && !this.hasRows()) {
1792             this.mainBody.update('<div class="x-grid-empty">' + this.emptyText + '</div>');
1793         }
1794     },
1795
1796     /**
1797      * @private
1798      * Adds sorting classes to the column headers based on the bound store's sortInfo. Fires the 'sortchange' event
1799      * if the sorting has changed since this function was last run.
1800      */
1801     updateHeaderSortState : function() {
1802         var state = this.ds.getSortState();
1803         if (!state) {
1804             return;
1805         }
1806
1807         if (!this.sortState || (this.sortState.field != state.field || this.sortState.direction != state.direction)) {
1808             this.grid.fireEvent('sortchange', this.grid, state);
1809         }
1810
1811         this.sortState = state;
1812
1813         var sortColumn = this.cm.findColumnIndex(state.field);
1814         if (sortColumn != -1) {
1815             var sortDir = state.direction;
1816             this.updateSortIcon(sortColumn, sortDir);
1817         }
1818     },
1819
1820     /**
1821      * @private
1822      * Removes any sorting indicator classes from the column headers
1823      */
1824     clearHeaderSortState : function() {
1825         if (!this.sortState) {
1826             return;
1827         }
1828         this.grid.fireEvent('sortchange', this.grid, null);
1829         this.mainHd.select('td').removeClass(this.sortClasses);
1830         delete this.sortState;
1831     },
1832
1833     /**
1834      * @private
1835      * Destroys all objects associated with the GridView
1836      */
1837     destroy : function() {
1838         var me              = this,
1839             grid            = me.grid,
1840             gridEl          = grid.getGridEl(),
1841             dragZone        = me.dragZone,
1842             splitZone       = me.splitZone,
1843             columnDrag      = me.columnDrag,
1844             columnDrop      = me.columnDrop,
1845             scrollToTopTask = me.scrollToTopTask,
1846             columnDragData,
1847             columnDragProxy;
1848         
1849         if (scrollToTopTask && scrollToTopTask.cancel) {
1850             scrollToTopTask.cancel();
1851         }
1852         
1853         Ext.destroyMembers(me, 'colMenu', 'hmenu');
1854
1855         me.initData(null, null);
1856         me.purgeListeners();
1857         
1858         Ext.fly(me.innerHd).un("click", me.handleHdDown, me);
1859
1860         if (grid.enableColumnMove) {
1861             columnDragData = columnDrag.dragData;
1862             columnDragProxy = columnDrag.proxy;
1863             Ext.destroy(
1864                 columnDrag.el,
1865                 columnDragProxy.ghost,
1866                 columnDragProxy.el,
1867                 columnDrop.el,
1868                 columnDrop.proxyTop,
1869                 columnDrop.proxyBottom,
1870                 columnDragData.ddel,
1871                 columnDragData.header
1872             );
1873             
1874             if (columnDragProxy.anim) {
1875                 Ext.destroy(columnDragProxy.anim);
1876             }
1877             
1878             delete columnDragProxy.ghost;
1879             delete columnDragData.ddel;
1880             delete columnDragData.header;
1881             columnDrag.destroy();
1882             
1883             delete Ext.dd.DDM.locationCache[columnDrag.id];
1884             delete columnDrag._domRef;
1885
1886             delete columnDrop.proxyTop;
1887             delete columnDrop.proxyBottom;
1888             columnDrop.destroy();
1889             delete Ext.dd.DDM.locationCache["gridHeader" + gridEl.id];
1890             delete columnDrop._domRef;
1891             delete Ext.dd.DDM.ids[columnDrop.ddGroup];
1892         }
1893
1894         if (splitZone) { // enableColumnResize
1895             splitZone.destroy();
1896             delete splitZone._domRef;
1897             delete Ext.dd.DDM.ids["gridSplitters" + gridEl.id];
1898         }
1899
1900         Ext.fly(me.innerHd).removeAllListeners();
1901         Ext.removeNode(me.innerHd);
1902         delete me.innerHd;
1903
1904         Ext.destroy(
1905             me.el,
1906             me.mainWrap,
1907             me.mainHd,
1908             me.scroller,
1909             me.mainBody,
1910             me.focusEl,
1911             me.resizeMarker,
1912             me.resizeProxy,
1913             me.activeHdBtn,
1914             me._flyweight,
1915             dragZone,
1916             splitZone
1917         );
1918
1919         delete grid.container;
1920
1921         if (dragZone) {
1922             dragZone.destroy();
1923         }
1924
1925         Ext.dd.DDM.currentTarget = null;
1926         delete Ext.dd.DDM.locationCache[gridEl.id];
1927
1928         Ext.EventManager.removeResizeListener(me.onWindowResize, me);
1929     },
1930
1931     // private
1932     onDenyColumnHide : function() {
1933
1934     },
1935
1936     // private
1937     render : function() {
1938         if (this.autoFill) {
1939             var ct = this.grid.ownerCt;
1940             
1941             if (ct && ct.getLayout()) {
1942                 ct.on('afterlayout', function() {
1943                     this.fitColumns(true, true);
1944                     this.updateHeaders();
1945                     this.updateHeaderSortState();
1946                 }, this, {single: true});
1947             }
1948         } else if (this.forceFit) {
1949             this.fitColumns(true, false);
1950         } else if (this.grid.autoExpandColumn) {
1951             this.autoExpand(true);
1952         }
1953         
1954         this.grid.getGridEl().dom.innerHTML = this.renderUI();
1955         
1956         this.afterRenderUI();
1957     },
1958
1959     /* --------------------------------- Model Events and Handlers --------------------------------*/
1960     
1961     /**
1962      * @private
1963      * Binds a new Store and ColumnModel to this GridView. Removes any listeners from the old objects (if present)
1964      * and adds listeners to the new ones
1965      * @param {Ext.data.Store} newStore The new Store instance
1966      * @param {Ext.grid.ColumnModel} newColModel The new ColumnModel instance
1967      */
1968     initData : function(newStore, newColModel) {
1969         var me = this;
1970         
1971         if (me.ds) {
1972             var oldStore = me.ds;
1973             
1974             oldStore.un('add', me.onAdd, me);
1975             oldStore.un('load', me.onLoad, me);
1976             oldStore.un('clear', me.onClear, me);
1977             oldStore.un('remove', me.onRemove, me);
1978             oldStore.un('update', me.onUpdate, me);
1979             oldStore.un('datachanged', me.onDataChange, me);
1980             
1981             if (oldStore !== newStore && oldStore.autoDestroy) {
1982                 oldStore.destroy();
1983             }
1984         }
1985         
1986         if (newStore) {
1987             newStore.on({
1988                 scope      : me,
1989                 load       : me.onLoad,
1990                 add        : me.onAdd,
1991                 remove     : me.onRemove,
1992                 update     : me.onUpdate,
1993                 clear      : me.onClear,
1994                 datachanged: me.onDataChange
1995             });
1996         }
1997         
1998         if (me.cm) {
1999             var oldColModel = me.cm;
2000             
2001             oldColModel.un('configchange', me.onColConfigChange, me);
2002             oldColModel.un('widthchange',  me.onColWidthChange, me);
2003             oldColModel.un('headerchange', me.onHeaderChange, me);
2004             oldColModel.un('hiddenchange', me.onHiddenChange, me);
2005             oldColModel.un('columnmoved',  me.onColumnMove, me);
2006         }
2007         
2008         if (newColModel) {
2009             delete me.lastViewWidth;
2010             
2011             newColModel.on({
2012                 scope       : me,
2013                 configchange: me.onColConfigChange,
2014                 widthchange : me.onColWidthChange,
2015                 headerchange: me.onHeaderChange,
2016                 hiddenchange: me.onHiddenChange,
2017                 columnmoved : me.onColumnMove
2018             });
2019         }
2020         
2021         me.ds = newStore;
2022         me.cm = newColModel;
2023     },
2024
2025     // private
2026     onDataChange : function(){
2027         this.refresh(true);
2028         this.updateHeaderSortState();
2029         this.syncFocusEl(0);
2030     },
2031
2032     // private
2033     onClear : function() {
2034         this.refresh();
2035         this.syncFocusEl(0);
2036     },
2037
2038     // private
2039     onUpdate : function(store, record) {
2040         this.refreshRow(record);
2041     },
2042
2043     // private
2044     onAdd : function(store, records, index) {
2045         this.insertRows(store, index, index + (records.length-1));
2046     },
2047
2048     // private
2049     onRemove : function(store, record, index, isUpdate) {
2050         if (isUpdate !== true) {
2051             this.fireEvent('beforerowremoved', this, index, record);
2052         }
2053         
2054         this.removeRow(index);
2055         
2056         if (isUpdate !== true) {
2057             this.processRows(index);
2058             this.applyEmptyText();
2059             this.fireEvent('rowremoved', this, index, record);
2060         }
2061     },
2062
2063     /**
2064      * @private
2065      * Called when a store is loaded, scrolls to the top row
2066      */
2067     onLoad : function() {
2068         if (Ext.isGecko) {
2069             if (!this.scrollToTopTask) {
2070                 this.scrollToTopTask = new Ext.util.DelayedTask(this.scrollToTop, this);
2071             }
2072             this.scrollToTopTask.delay(1);
2073         } else {
2074             this.scrollToTop();
2075         }
2076     },
2077
2078     // private
2079     onColWidthChange : function(cm, col, width) {
2080         this.updateColumnWidth(col, width);
2081     },
2082
2083     // private
2084     onHeaderChange : function(cm, col, text) {
2085         this.updateHeaders();
2086     },
2087
2088     // private
2089     onHiddenChange : function(cm, col, hidden) {
2090         this.updateColumnHidden(col, hidden);
2091     },
2092
2093     // private
2094     onColumnMove : function(cm, oldIndex, newIndex) {
2095         this.indexMap = null;
2096         this.refresh(true);
2097         this.restoreScroll(this.getScrollState());
2098         
2099         this.afterMove(newIndex);
2100         this.grid.fireEvent('columnmove', oldIndex, newIndex);
2101     },
2102
2103     // private
2104     onColConfigChange : function() {
2105         delete this.lastViewWidth;
2106         this.indexMap = null;
2107         this.refresh(true);
2108     },
2109
2110     /* -------------------- UI Events and Handlers ------------------------------ */
2111     // private
2112     initUI : function(grid) {
2113         grid.on('headerclick', this.onHeaderClick, this);
2114     },
2115
2116     // private
2117     initEvents : Ext.emptyFn,
2118
2119     // private
2120     onHeaderClick : function(g, index) {
2121         if (this.headersDisabled || !this.cm.isSortable(index)) {
2122             return;
2123         }
2124         g.stopEditing(true);
2125         g.store.sort(this.cm.getDataIndex(index));
2126     },
2127
2128     /**
2129      * @private
2130      * Adds the hover class to a row when hovered over
2131      */
2132     onRowOver : function(e, target) {
2133         var row = this.findRowIndex(target);
2134         
2135         if (row !== false) {
2136             this.addRowClass(row, this.rowOverCls);
2137         }
2138     },
2139
2140     /**
2141      * @private
2142      * Removes the hover class from a row on mouseout
2143      */
2144     onRowOut : function(e, target) {
2145         var row = this.findRowIndex(target);
2146         
2147         if (row !== false && !e.within(this.getRow(row), true)) {
2148             this.removeRowClass(row, this.rowOverCls);
2149         }
2150     },
2151
2152     // private
2153     onRowSelect : function(row) {
2154         this.addRowClass(row, this.selectedRowClass);
2155     },
2156
2157     // private
2158     onRowDeselect : function(row) {
2159         this.removeRowClass(row, this.selectedRowClass);
2160     },
2161
2162     // private
2163     onCellSelect : function(row, col) {
2164         var cell = this.getCell(row, col);
2165         if (cell) {
2166             this.fly(cell).addClass('x-grid3-cell-selected');
2167         }
2168     },
2169
2170     // private
2171     onCellDeselect : function(row, col) {
2172         var cell = this.getCell(row, col);
2173         if (cell) {
2174             this.fly(cell).removeClass('x-grid3-cell-selected');
2175         }
2176     },
2177
2178     // private
2179     handleWheel : function(e) {
2180         e.stopPropagation();
2181     },
2182
2183     /**
2184      * @private
2185      * Called by the SplitDragZone when a drag has been completed. Resizes the columns
2186      */
2187     onColumnSplitterMoved : function(cellIndex, width) {
2188         this.userResized = true;
2189         this.grid.colModel.setColumnWidth(cellIndex, width, true);
2190
2191         if (this.forceFit) {
2192             this.fitColumns(true, false, cellIndex);
2193             this.updateAllColumnWidths();
2194         } else {
2195             this.updateColumnWidth(cellIndex, width);
2196             this.syncHeaderScroll();
2197         }
2198
2199         this.grid.fireEvent('columnresize', cellIndex, width);
2200     },
2201
2202     /**
2203      * @private
2204      * Click handler for the shared column dropdown menu, called on beforeshow. Builds the menu
2205      * which displays the list of columns for the user to show or hide.
2206      */
2207     beforeColMenuShow : function() {
2208         var colModel = this.cm,
2209             colCount = colModel.getColumnCount(),
2210             colMenu  = this.colMenu,
2211             i;
2212
2213         colMenu.removeAll();
2214
2215         for (i = 0; i < colCount; i++) {
2216             if (colModel.config[i].hideable !== false) {
2217                 colMenu.add(new Ext.menu.CheckItem({
2218                     text       : colModel.getColumnHeader(i),
2219                     itemId     : 'col-' + colModel.getColumnId(i),
2220                     checked    : !colModel.isHidden(i),
2221                     disabled   : colModel.config[i].hideable === false,
2222                     hideOnClick: false
2223                 }));
2224             }
2225         }
2226     },
2227     
2228     /**
2229      * @private
2230      * Attached as the 'itemclick' handler to the header menu and the column show/hide submenu (if available).
2231      * Performs sorting if the sorter buttons were clicked, otherwise hides/shows the column that was clicked.
2232      */
2233     handleHdMenuClick : function(item) {
2234         var store     = this.ds,
2235             dataIndex = this.cm.getDataIndex(this.hdCtxIndex);
2236
2237         switch (item.getItemId()) {
2238             case 'asc':
2239                 store.sort(dataIndex, 'ASC');
2240                 break;
2241             case 'desc':
2242                 store.sort(dataIndex, 'DESC');
2243                 break;
2244             default:
2245                 this.handleHdMenuClickDefault(item);
2246         }
2247         return true;
2248     },
2249     
2250     <div id="method-Ext.grid.GridView-handleHdMenuClickDefault"></div>/**
2251      * Called by handleHdMenuClick if any button except a sort ASC/DESC button was clicked. The default implementation provides
2252      * the column hide/show functionality based on the check state of the menu item. A different implementation can be provided
2253      * if needed.
2254      * @param {Ext.menu.BaseItem} item The menu item that was clicked
2255      */
2256     handleHdMenuClickDefault: function(item) {
2257         var colModel = this.cm,
2258             itemId   = item.getItemId(),
2259             index    = colModel.getIndexById(itemId.substr(4));
2260
2261         if (index != -1) {
2262             if (item.checked && colModel.getColumnsBy(this.isHideableColumn, this).length <= 1) {
2263                 this.onDenyColumnHide();
2264                 return;
2265             }
2266             colModel.setHidden(index, item.checked);
2267         }
2268     },
2269
2270     /**
2271      * @private
2272      * Called when a header cell is clicked - shows the menu if the click happened over a trigger button
2273      */
2274     handleHdDown : function(e, target) {
2275         if (Ext.fly(target).hasClass('x-grid3-hd-btn')) {
2276             e.stopEvent();
2277             
2278             var colModel  = this.cm,
2279                 header    = this.findHeaderCell(target),
2280                 index     = this.getCellIndex(header),
2281                 sortable  = colModel.isSortable(index),
2282                 menu      = this.hmenu,
2283                 menuItems = menu.items,
2284                 menuCls   = this.headerMenuOpenCls;
2285             
2286             this.hdCtxIndex = index;
2287             
2288             Ext.fly(header).addClass(menuCls);
2289             menuItems.get('asc').setDisabled(!sortable);
2290             menuItems.get('desc').setDisabled(!sortable);
2291             
2292             menu.on('hide', function() {
2293                 Ext.fly(header).removeClass(menuCls);
2294             }, this, {single:true});
2295             
2296             menu.show(target, 'tl-bl?');
2297         }
2298     },
2299
2300     /**
2301      * @private
2302      * Attached to the headers' mousemove event. This figures out the CSS cursor to use based on where the mouse is currently
2303      * pointed. If the mouse is currently hovered over the extreme left or extreme right of any header cell and the cell next 
2304      * to it is resizable it is given the resize cursor, otherwise the cursor is set to an empty string.
2305      */
2306     handleHdMove : function(e) {
2307         var header = this.findHeaderCell(this.activeHdRef);
2308         
2309         if (header && !this.headersDisabled) {
2310             var handleWidth  = this.splitHandleWidth || 5,
2311                 activeRegion = this.activeHdRegion,
2312                 headerStyle  = header.style,
2313                 colModel     = this.cm,
2314                 cursor       = '',
2315                 pageX        = e.getPageX();
2316                 
2317             if (this.grid.enableColumnResize !== false) {
2318                 var activeHeaderIndex = this.activeHdIndex,
2319                     previousVisible   = this.getPreviousVisible(activeHeaderIndex),
2320                     currentResizable  = colModel.isResizable(activeHeaderIndex),
2321                     previousResizable = previousVisible && colModel.isResizable(previousVisible),
2322                     inLeftResizer     = pageX - activeRegion.left <= handleWidth,
2323                     inRightResizer    = activeRegion.right - pageX <= (!this.activeHdBtn ? handleWidth : 2);
2324                 
2325                 if (inLeftResizer && previousResizable) {
2326                     cursor = Ext.isAir ? 'move' : Ext.isWebKit ? 'e-resize' : 'col-resize'; // col-resize not always supported
2327                 } else if (inRightResizer && currentResizable) {
2328                     cursor = Ext.isAir ? 'move' : Ext.isWebKit ? 'w-resize' : 'col-resize';
2329                 }
2330             }
2331             
2332             headerStyle.cursor = cursor;
2333         }
2334     },
2335     
2336     /**
2337      * @private
2338      * Returns the index of the nearest currently visible header to the left of the given index.
2339      * @param {Number} index The header index
2340      * @return {Number/undefined} The index of the nearest visible header
2341      */
2342     getPreviousVisible: function(index) {
2343         while (index > 0) {
2344             if (!this.cm.isHidden(index - 1)) {
2345                 return index;
2346             }
2347             index--;
2348         }
2349         return undefined;
2350     },
2351
2352     /**
2353      * @private
2354      * Tied to the header element's mouseover event - adds the over class to the header cell if the menu is not disabled
2355      * for that cell
2356      */
2357     handleHdOver : function(e, target) {
2358         var header = this.findHeaderCell(target);
2359         
2360         if (header && !this.headersDisabled) {
2361             var fly = this.fly(header);
2362             
2363             this.activeHdRef = target;
2364             this.activeHdIndex = this.getCellIndex(header);
2365             this.activeHdRegion = fly.getRegion();
2366             
2367             if (!this.isMenuDisabled(this.activeHdIndex, fly)) {
2368                 fly.addClass('x-grid3-hd-over');
2369                 this.activeHdBtn = fly.child('.x-grid3-hd-btn');
2370                 
2371                 if (this.activeHdBtn) {
2372                     this.activeHdBtn.dom.style.height = (header.firstChild.offsetHeight - 1) + 'px';
2373                 }
2374             }
2375         }
2376     },
2377
2378     /**
2379      * @private
2380      * Tied to the header element's mouseout event. Removes the hover class from the header cell
2381      */
2382     handleHdOut : function(e, target) {
2383         var header = this.findHeaderCell(target);
2384         
2385         if (header && (!Ext.isIE || !e.within(header, true))) {
2386             this.activeHdRef = null;
2387             this.fly(header).removeClass('x-grid3-hd-over');
2388             header.style.cursor = '';
2389         }
2390     },
2391     
2392     /**
2393      * @private
2394      * Used by {@link #handleHdOver} to determine whether or not to show the header menu class on cell hover
2395      * @param {Number} cellIndex The header cell index
2396      * @param {Ext.Element} el The cell element currently being hovered over
2397      */
2398     isMenuDisabled: function(cellIndex, el) {
2399         return this.cm.isMenuDisabled(cellIndex);
2400     },
2401
2402     /**
2403      * @private
2404      * Returns true if there are any rows rendered into the GridView
2405      * @return {Boolean} True if any rows have been rendered
2406      */
2407     hasRows : function() {
2408         var fc = this.mainBody.dom.firstChild;
2409         return fc && fc.nodeType == 1 && fc.className != 'x-grid-empty';
2410     },
2411     
2412     /**
2413      * @private
2414      */
2415     isHideableColumn : function(c) {
2416         return !c.hidden;
2417     },
2418
2419     /**
2420      * @private
2421      * DEPRECATED - will be removed in Ext JS 5.0
2422      */
2423     bind : function(d, c) {
2424         this.initData(d, c);
2425     }
2426 });
2427
2428
2429 // private
2430 // This is a support class used internally by the Grid components
2431 Ext.grid.GridView.SplitDragZone = Ext.extend(Ext.dd.DDProxy, {
2432
2433     constructor: function(grid, hd){
2434         this.grid = grid;
2435         this.view = grid.getView();
2436         this.marker = this.view.resizeMarker;
2437         this.proxy = this.view.resizeProxy;
2438         Ext.grid.GridView.SplitDragZone.superclass.constructor.call(this, hd,
2439             'gridSplitters' + this.grid.getGridEl().id, {
2440             dragElId : Ext.id(this.proxy.dom), resizeFrame:false
2441         });
2442         this.scroll = false;
2443         this.hw = this.view.splitHandleWidth || 5;
2444     },
2445
2446     b4StartDrag : function(x, y){
2447         this.dragHeadersDisabled = this.view.headersDisabled;
2448         this.view.headersDisabled = true;
2449         var h = this.view.mainWrap.getHeight();
2450         this.marker.setHeight(h);
2451         this.marker.show();
2452         this.marker.alignTo(this.view.getHeaderCell(this.cellIndex), 'tl-tl', [-2, 0]);
2453         this.proxy.setHeight(h);
2454         var w = this.cm.getColumnWidth(this.cellIndex),
2455             minw = Math.max(w-this.grid.minColumnWidth, 0);
2456         this.resetConstraints();
2457         this.setXConstraint(minw, 1000);
2458         this.setYConstraint(0, 0);
2459         this.minX = x - minw;
2460         this.maxX = x + 1000;
2461         this.startPos = x;
2462         Ext.dd.DDProxy.prototype.b4StartDrag.call(this, x, y);
2463     },
2464
2465     allowHeaderDrag : function(e){
2466         return true;
2467     },
2468
2469     handleMouseDown : function(e){
2470         var t = this.view.findHeaderCell(e.getTarget());
2471         if(t && this.allowHeaderDrag(e)){
2472             var xy = this.view.fly(t).getXY(), 
2473                 x = xy[0],
2474                 exy = e.getXY(), 
2475                 ex = exy[0],
2476                 w = t.offsetWidth, 
2477                 adjust = false;
2478                 
2479             if((ex - x) <= this.hw){
2480                 adjust = -1;
2481             }else if((x+w) - ex <= this.hw){
2482                 adjust = 0;
2483             }
2484             if(adjust !== false){
2485                 this.cm = this.grid.colModel;
2486                 var ci = this.view.getCellIndex(t);
2487                 if(adjust == -1){
2488                   if (ci + adjust < 0) {
2489                     return;
2490                   }
2491                     while(this.cm.isHidden(ci+adjust)){
2492                         --adjust;
2493                         if(ci+adjust < 0){
2494                             return;
2495                         }
2496                     }
2497                 }
2498                 this.cellIndex = ci+adjust;
2499                 this.split = t.dom;
2500                 if(this.cm.isResizable(this.cellIndex) && !this.cm.isFixed(this.cellIndex)){
2501                     Ext.grid.GridView.SplitDragZone.superclass.handleMouseDown.apply(this, arguments);
2502                 }
2503             }else if(this.view.columnDrag){
2504                 this.view.columnDrag.callHandleMouseDown(e);
2505             }
2506         }
2507     },
2508
2509     endDrag : function(e){
2510         this.marker.hide();
2511         var v = this.view,
2512             endX = Math.max(this.minX, e.getPageX()),
2513             diff = endX - this.startPos,
2514             disabled = this.dragHeadersDisabled;
2515             
2516         v.onColumnSplitterMoved(this.cellIndex, this.cm.getColumnWidth(this.cellIndex)+diff);
2517         setTimeout(function(){
2518             v.headersDisabled = disabled;
2519         }, 50);
2520     },
2521
2522     autoOffset : function(){
2523         this.setDelta(0,0);
2524     }
2525 });
2526 </pre>    
2527 </body>
2528 </html>