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