source: trunk/web/addons/job_monarch/lib/extjs-30/src/widgets/grid/GridView.js @ 625

Last change on this file since 625 was 625, checked in by ramonb, 15 years ago

lib/extjs-30:

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