Bienvenue sur PostGIS.fr

Bienvenue sur PostGIS.fr , le site de la communauté des utilisateurs francophones de PostGIS.

PostGIS ajoute le support d'objets géographique à la base de données PostgreSQL. En effet, PostGIS "spatialise" le serverur PostgreSQL, ce qui permet de l'utiliser comme une base de données SIG.

Maintenu à jour, en fonction de nos disponibilités et des diverses sorties des outils que nous testons, nous vous proposons l'ensemble de nos travaux publiés en langue française.

source: trunk/workshop-routing-foss4g/web/ext/src/widgets/grid/GridView.js @ 77

Revision 76, 82.6 KB checked in by djay, 13 years ago (diff)

Ajout du répertoire web

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