Upgrade to ExtJS 3.3.1 - Released 11/30/2010
[extjs.git] / pkgs / pkg-grid-foundation-debug.js
1 /*!
2  * Ext JS Library 3.3.1
3  * Copyright(c) 2006-2010 Sencha Inc.
4  * licensing@sencha.com
5  * http://www.sencha.com/license
6  */
7 /**
8  * @class Ext.grid.GridPanel
9  * @extends Ext.Panel
10  * <p>This class represents the primary interface of a component based grid control to represent data
11  * in a tabular format of rows and columns. The GridPanel is composed of the following:</p>
12  * <div class="mdetail-params"><ul>
13  * <li><b>{@link Ext.data.Store Store}</b> : The Model holding the data records (rows)
14  * <div class="sub-desc"></div></li>
15  * <li><b>{@link Ext.grid.ColumnModel Column model}</b> : Column makeup
16  * <div class="sub-desc"></div></li>
17  * <li><b>{@link Ext.grid.GridView View}</b> : Encapsulates the user interface
18  * <div class="sub-desc"></div></li>
19  * <li><b>{@link Ext.grid.AbstractSelectionModel selection model}</b> : Selection behavior
20  * <div class="sub-desc"></div></li>
21  * </ul></div>
22  * <p>Example usage:</p>
23  * <pre><code>
24 var grid = new Ext.grid.GridPanel({
25     {@link #store}: new {@link Ext.data.Store}({
26         {@link Ext.data.Store#autoDestroy autoDestroy}: true,
27         {@link Ext.data.Store#reader reader}: reader,
28         {@link Ext.data.Store#data data}: xg.dummyData
29     }),
30     {@link #colModel}: new {@link Ext.grid.ColumnModel}({
31         {@link Ext.grid.ColumnModel#defaults defaults}: {
32             width: 120,
33             sortable: true
34         },
35         {@link Ext.grid.ColumnModel#columns columns}: [
36             {id: 'company', header: 'Company', width: 200, sortable: true, dataIndex: 'company'},
37             {header: 'Price', renderer: Ext.util.Format.usMoney, dataIndex: 'price'},
38             {header: 'Change', dataIndex: 'change'},
39             {header: '% Change', dataIndex: 'pctChange'},
40             // instead of specifying renderer: Ext.util.Format.dateRenderer('m/d/Y') use xtype
41             {
42                 header: 'Last Updated', width: 135, dataIndex: 'lastChange',
43                 xtype: 'datecolumn', format: 'M d, Y'
44             }
45         ],
46     }),
47     {@link #viewConfig}: {
48         {@link Ext.grid.GridView#forceFit forceFit}: true,
49
50 //      Return CSS class to apply to rows depending upon data values
51         {@link Ext.grid.GridView#getRowClass getRowClass}: function(record, index) {
52             var c = record.{@link Ext.data.Record#get get}('change');
53             if (c < 0) {
54                 return 'price-fall';
55             } else if (c > 0) {
56                 return 'price-rise';
57             }
58         }
59     },
60     {@link #sm}: new Ext.grid.RowSelectionModel({singleSelect:true}),
61     width: 600,
62     height: 300,
63     frame: true,
64     title: 'Framed with Row Selection and Horizontal Scrolling',
65     iconCls: 'icon-grid'
66 });
67  * </code></pre>
68  * <p><b><u>Notes:</u></b></p>
69  * <div class="mdetail-params"><ul>
70  * <li>Although this class inherits many configuration options from base classes, some of them
71  * (such as autoScroll, autoWidth, layout, items, etc) are not used by this class, and will
72  * have no effect.</li>
73  * <li>A grid <b>requires</b> a width in which to scroll its columns, and a height in which to
74  * scroll its rows. These dimensions can either be set explicitly through the
75  * <tt>{@link Ext.BoxComponent#height height}</tt> and <tt>{@link Ext.BoxComponent#width width}</tt>
76  * configuration options or implicitly set by using the grid as a child item of a
77  * {@link Ext.Container Container} which will have a {@link Ext.Container#layout layout manager}
78  * provide the sizing of its child items (for example the Container of the Grid may specify
79  * <tt>{@link Ext.Container#layout layout}:'fit'</tt>).</li>
80  * <li>To access the data in a Grid, it is necessary to use the data model encapsulated
81  * by the {@link #store Store}. See the {@link #cellclick} event for more details.</li>
82  * </ul></div>
83  * @constructor
84  * @param {Object} config The config object
85  * @xtype grid
86  */
87 Ext.grid.GridPanel = Ext.extend(Ext.Panel, {
88     /**
89      * @cfg {String} autoExpandColumn
90      * <p>The <tt>{@link Ext.grid.Column#id id}</tt> of a {@link Ext.grid.Column column} in
91      * this grid that should expand to fill unused space. This value specified here can not
92      * be <tt>0</tt>.</p>
93      * <br><p><b>Note</b>: If the Grid's {@link Ext.grid.GridView view} is configured with
94      * <tt>{@link Ext.grid.GridView#forceFit forceFit}=true</tt> the <tt>autoExpandColumn</tt>
95      * is ignored. See {@link Ext.grid.Column}.<tt>{@link Ext.grid.Column#width width}</tt>
96      * for additional details.</p>
97      * <p>See <tt>{@link #autoExpandMax}</tt> and <tt>{@link #autoExpandMin}</tt> also.</p>
98      */
99     autoExpandColumn : false,
100     
101     /**
102      * @cfg {Number} autoExpandMax The maximum width the <tt>{@link #autoExpandColumn}</tt>
103      * can have (if enabled). Defaults to <tt>1000</tt>.
104      */
105     autoExpandMax : 1000,
106     
107     /**
108      * @cfg {Number} autoExpandMin The minimum width the <tt>{@link #autoExpandColumn}</tt>
109      * can have (if enabled). Defaults to <tt>50</tt>.
110      */
111     autoExpandMin : 50,
112     
113     /**
114      * @cfg {Boolean} columnLines <tt>true</tt> to add css for column separation lines.
115      * Default is <tt>false</tt>.
116      */
117     columnLines : false,
118     
119     /**
120      * @cfg {Object} cm Shorthand for <tt>{@link #colModel}</tt>.
121      */
122     /**
123      * @cfg {Object} colModel The {@link Ext.grid.ColumnModel} to use when rendering the grid (required).
124      */
125     /**
126      * @cfg {Array} columns An array of {@link Ext.grid.Column columns} to auto create a
127      * {@link Ext.grid.ColumnModel}.  The ColumnModel may be explicitly created via the
128      * <tt>{@link #colModel}</tt> configuration property.
129      */
130     /**
131      * @cfg {String} ddGroup The DD group this GridPanel belongs to. Defaults to <tt>'GridDD'</tt> if not specified.
132      */
133     /**
134      * @cfg {String} ddText
135      * Configures the text in the drag proxy.  Defaults to:
136      * <pre><code>
137      * ddText : '{0} selected row{1}'
138      * </code></pre>
139      * <tt>{0}</tt> is replaced with the number of selected rows.
140      */
141     ddText : '{0} selected row{1}',
142     
143     /**
144      * @cfg {Boolean} deferRowRender <P>Defaults to <tt>true</tt> to enable deferred row rendering.</p>
145      * <p>This allows the GridPanel to be initially rendered empty, with the expensive update of the row
146      * structure deferred so that layouts with GridPanels appear more quickly.</p>
147      */
148     deferRowRender : true,
149     
150     /**
151      * @cfg {Boolean} disableSelection <p><tt>true</tt> to disable selections in the grid. Defaults to <tt>false</tt>.</p>
152      * <p>Ignored if a {@link #selModel SelectionModel} is specified.</p>
153      */
154     /**
155      * @cfg {Boolean} enableColumnResize <tt>false</tt> to turn off column resizing for the whole grid. Defaults to <tt>true</tt>.
156      */
157     /**
158      * @cfg {Boolean} enableColumnHide
159      * Defaults to <tt>true</tt> to enable {@link Ext.grid.Column#hidden hiding of columns}
160      * with the {@link #enableHdMenu header menu}.
161      */
162     enableColumnHide : true,
163     
164     /**
165      * @cfg {Boolean} enableColumnMove Defaults to <tt>true</tt> to enable drag and drop reorder of columns. <tt>false</tt>
166      * to turn off column reordering via drag drop.
167      */
168     enableColumnMove : true,
169     
170     /**
171      * @cfg {Boolean} enableDragDrop <p>Enables dragging of the selected rows of the GridPanel. Defaults to <tt>false</tt>.</p>
172      * <p>Setting this to <b><tt>true</tt></b> causes this GridPanel's {@link #getView GridView} to
173      * create an instance of {@link Ext.grid.GridDragZone}. <b>Note</b>: this is available only <b>after</b>
174      * the Grid has been rendered as the GridView's <tt>{@link Ext.grid.GridView#dragZone dragZone}</tt>
175      * property.</p>
176      * <p>A cooperating {@link Ext.dd.DropZone DropZone} must be created who's implementations of
177      * {@link Ext.dd.DropZone#onNodeEnter onNodeEnter}, {@link Ext.dd.DropZone#onNodeOver onNodeOver},
178      * {@link Ext.dd.DropZone#onNodeOut onNodeOut} and {@link Ext.dd.DropZone#onNodeDrop onNodeDrop} are able
179      * to process the {@link Ext.grid.GridDragZone#getDragData data} which is provided.</p>
180      */
181     enableDragDrop : false,
182     
183     /**
184      * @cfg {Boolean} enableHdMenu Defaults to <tt>true</tt> to enable the drop down button for menu in the headers.
185      */
186     enableHdMenu : true,
187     
188     /**
189      * @cfg {Boolean} hideHeaders True to hide the grid's header. Defaults to <code>false</code>.
190      */
191     /**
192      * @cfg {Object} loadMask An {@link Ext.LoadMask} config or true to mask the grid while
193      * loading. Defaults to <code>false</code>.
194      */
195     loadMask : false,
196     
197     /**
198      * @cfg {Number} maxHeight Sets the maximum height of the grid - ignored if <tt>autoHeight</tt> is not on.
199      */
200     /**
201      * @cfg {Number} minColumnWidth The minimum width a column can be resized to. Defaults to <tt>25</tt>.
202      */
203     minColumnWidth : 25,
204     
205     /**
206      * @cfg {Object} sm Shorthand for <tt>{@link #selModel}</tt>.
207      */
208     /**
209      * @cfg {Object} selModel Any subclass of {@link Ext.grid.AbstractSelectionModel} that will provide
210      * the selection model for the grid (defaults to {@link Ext.grid.RowSelectionModel} if not specified).
211      */
212     /**
213      * @cfg {Ext.data.Store} store The {@link Ext.data.Store} the grid should use as its data source (required).
214      */
215     /**
216      * @cfg {Boolean} stripeRows <tt>true</tt> to stripe the rows. Default is <tt>false</tt>.
217      * <p>This causes the CSS class <tt><b>x-grid3-row-alt</b></tt> to be added to alternate rows of
218      * the grid. A default CSS rule is provided which sets a background colour, but you can override this
219      * with a rule which either overrides the <b>background-color</b> style using the '!important'
220      * modifier, or which uses a CSS selector of higher specificity.</p>
221      */
222     stripeRows : false,
223     
224     /**
225      * @cfg {Boolean} trackMouseOver True to highlight rows when the mouse is over. Default is <tt>true</tt>
226      * for GridPanel, but <tt>false</tt> for EditorGridPanel.
227      */
228     trackMouseOver : true,
229     
230     /**
231      * @cfg {Array} stateEvents
232      * An array of events that, when fired, should trigger this component to save its state.
233      * Defaults to:<pre><code>
234      * stateEvents: ['columnmove', 'columnresize', 'sortchange', 'groupchange']
235      * </code></pre>
236      * <p>These can be any types of events supported by this component, including browser or
237      * custom events (e.g., <tt>['click', 'customerchange']</tt>).</p>
238      * <p>See {@link Ext.Component#stateful} for an explanation of saving and restoring
239      * Component state.</p>
240      */
241     stateEvents : ['columnmove', 'columnresize', 'sortchange', 'groupchange'],
242     
243     /**
244      * @cfg {Object} view The {@link Ext.grid.GridView} used by the grid. This can be set
245      * before a call to {@link Ext.Component#render render()}.
246      */
247     view : null,
248
249     /**
250      * @cfg {Array} bubbleEvents
251      * <p>An array of events that, when fired, should be bubbled to any parent container.
252      * See {@link Ext.util.Observable#enableBubble}.
253      * Defaults to <tt>[]</tt>.
254      */
255     bubbleEvents: [],
256
257     /**
258      * @cfg {Object} viewConfig A config object that will be applied to the grid's UI view.  Any of
259      * the config options available for {@link Ext.grid.GridView} can be specified here. This option
260      * is ignored if <tt>{@link #view}</tt> is specified.
261      */
262
263     // private
264     rendered : false,
265     
266     // private
267     viewReady : false,
268
269     // private
270     initComponent : function() {
271         Ext.grid.GridPanel.superclass.initComponent.call(this);
272
273         if (this.columnLines) {
274             this.cls = (this.cls || '') + ' x-grid-with-col-lines';
275         }
276         // override any provided value since it isn't valid
277         // and is causing too many bug reports ;)
278         this.autoScroll = false;
279         this.autoWidth = false;
280
281         if(Ext.isArray(this.columns)){
282             this.colModel = new Ext.grid.ColumnModel(this.columns);
283             delete this.columns;
284         }
285
286         // check and correct shorthanded configs
287         if(this.ds){
288             this.store = this.ds;
289             delete this.ds;
290         }
291         if(this.cm){
292             this.colModel = this.cm;
293             delete this.cm;
294         }
295         if(this.sm){
296             this.selModel = this.sm;
297             delete this.sm;
298         }
299         this.store = Ext.StoreMgr.lookup(this.store);
300
301         this.addEvents(
302             // raw events
303             /**
304              * @event click
305              * The raw click event for the entire grid.
306              * @param {Ext.EventObject} e
307              */
308             'click',
309             /**
310              * @event dblclick
311              * The raw dblclick event for the entire grid.
312              * @param {Ext.EventObject} e
313              */
314             'dblclick',
315             /**
316              * @event contextmenu
317              * The raw contextmenu event for the entire grid.
318              * @param {Ext.EventObject} e
319              */
320             'contextmenu',
321             /**
322              * @event mousedown
323              * The raw mousedown event for the entire grid.
324              * @param {Ext.EventObject} e
325              */
326             'mousedown',
327             /**
328              * @event mouseup
329              * The raw mouseup event for the entire grid.
330              * @param {Ext.EventObject} e
331              */
332             'mouseup',
333             /**
334              * @event mouseover
335              * The raw mouseover event for the entire grid.
336              * @param {Ext.EventObject} e
337              */
338             'mouseover',
339             /**
340              * @event mouseout
341              * The raw mouseout event for the entire grid.
342              * @param {Ext.EventObject} e
343              */
344             'mouseout',
345             /**
346              * @event keypress
347              * The raw keypress event for the entire grid.
348              * @param {Ext.EventObject} e
349              */
350             'keypress',
351             /**
352              * @event keydown
353              * The raw keydown event for the entire grid.
354              * @param {Ext.EventObject} e
355              */
356             'keydown',
357
358             // custom events
359             /**
360              * @event cellmousedown
361              * Fires before a cell is clicked
362              * @param {Grid} this
363              * @param {Number} rowIndex
364              * @param {Number} columnIndex
365              * @param {Ext.EventObject} e
366              */
367             'cellmousedown',
368             /**
369              * @event rowmousedown
370              * Fires before a row is clicked
371              * @param {Grid} this
372              * @param {Number} rowIndex
373              * @param {Ext.EventObject} e
374              */
375             'rowmousedown',
376             /**
377              * @event headermousedown
378              * Fires before a header is clicked
379              * @param {Grid} this
380              * @param {Number} columnIndex
381              * @param {Ext.EventObject} e
382              */
383             'headermousedown',
384
385             /**
386              * @event groupmousedown
387              * Fires before a group header is clicked. <b>Only applies for grids with a {@link Ext.grid.GroupingView GroupingView}</b>.
388              * @param {Grid} this
389              * @param {String} groupField
390              * @param {String} groupValue
391              * @param {Ext.EventObject} e
392              */
393             'groupmousedown',
394
395             /**
396              * @event rowbodymousedown
397              * Fires before the row body is clicked. <b>Only applies for grids with {@link Ext.grid.GridView#enableRowBody enableRowBody} configured.</b>
398              * @param {Grid} this
399              * @param {Number} rowIndex
400              * @param {Ext.EventObject} e
401              */
402             'rowbodymousedown',
403
404             /**
405              * @event containermousedown
406              * Fires before the container is clicked. The container consists of any part of the grid body that is not covered by a row.
407              * @param {Grid} this
408              * @param {Ext.EventObject} e
409              */
410             'containermousedown',
411
412             /**
413              * @event cellclick
414              * Fires when a cell is clicked.
415              * The data for the cell is drawn from the {@link Ext.data.Record Record}
416              * for this row. To access the data in the listener function use the
417              * following technique:
418              * <pre><code>
419 function(grid, rowIndex, columnIndex, e) {
420     var record = grid.getStore().getAt(rowIndex);  // Get the Record
421     var fieldName = grid.getColumnModel().getDataIndex(columnIndex); // Get field name
422     var data = record.get(fieldName);
423 }
424 </code></pre>
425              * @param {Grid} this
426              * @param {Number} rowIndex
427              * @param {Number} columnIndex
428              * @param {Ext.EventObject} e
429              */
430             'cellclick',
431             /**
432              * @event celldblclick
433              * Fires when a cell is double clicked
434              * @param {Grid} this
435              * @param {Number} rowIndex
436              * @param {Number} columnIndex
437              * @param {Ext.EventObject} e
438              */
439             'celldblclick',
440             /**
441              * @event rowclick
442              * Fires when a row is clicked
443              * @param {Grid} this
444              * @param {Number} rowIndex
445              * @param {Ext.EventObject} e
446              */
447             'rowclick',
448             /**
449              * @event rowdblclick
450              * Fires when a row is double clicked
451              * @param {Grid} this
452              * @param {Number} rowIndex
453              * @param {Ext.EventObject} e
454              */
455             'rowdblclick',
456             /**
457              * @event headerclick
458              * Fires when a header is clicked
459              * @param {Grid} this
460              * @param {Number} columnIndex
461              * @param {Ext.EventObject} e
462              */
463             'headerclick',
464             /**
465              * @event headerdblclick
466              * Fires when a header cell is double clicked
467              * @param {Grid} this
468              * @param {Number} columnIndex
469              * @param {Ext.EventObject} e
470              */
471             'headerdblclick',
472             /**
473              * @event groupclick
474              * Fires when group header is clicked. <b>Only applies for grids with a {@link Ext.grid.GroupingView GroupingView}</b>.
475              * @param {Grid} this
476              * @param {String} groupField
477              * @param {String} groupValue
478              * @param {Ext.EventObject} e
479              */
480             'groupclick',
481             /**
482              * @event groupdblclick
483              * Fires when group header is double clicked. <b>Only applies for grids with a {@link Ext.grid.GroupingView GroupingView}</b>.
484              * @param {Grid} this
485              * @param {String} groupField
486              * @param {String} groupValue
487              * @param {Ext.EventObject} e
488              */
489             'groupdblclick',
490             /**
491              * @event containerclick
492              * Fires when the container is clicked. The container consists of any part of the grid body that is not covered by a row.
493              * @param {Grid} this
494              * @param {Ext.EventObject} e
495              */
496             'containerclick',
497             /**
498              * @event containerdblclick
499              * Fires when the container is double clicked. The container consists of any part of the grid body that is not covered by a row.
500              * @param {Grid} this
501              * @param {Ext.EventObject} e
502              */
503             'containerdblclick',
504
505             /**
506              * @event rowbodyclick
507              * Fires when the row body is clicked. <b>Only applies for grids with {@link Ext.grid.GridView#enableRowBody enableRowBody} configured.</b>
508              * @param {Grid} this
509              * @param {Number} rowIndex
510              * @param {Ext.EventObject} e
511              */
512             'rowbodyclick',
513             /**
514              * @event rowbodydblclick
515              * Fires when the row body is double clicked. <b>Only applies for grids with {@link Ext.grid.GridView#enableRowBody enableRowBody} configured.</b>
516              * @param {Grid} this
517              * @param {Number} rowIndex
518              * @param {Ext.EventObject} e
519              */
520             'rowbodydblclick',
521
522             /**
523              * @event rowcontextmenu
524              * Fires when a row is right clicked
525              * @param {Grid} this
526              * @param {Number} rowIndex
527              * @param {Ext.EventObject} e
528              */
529             'rowcontextmenu',
530             /**
531              * @event cellcontextmenu
532              * Fires when a cell is right clicked
533              * @param {Grid} this
534              * @param {Number} rowIndex
535              * @param {Number} cellIndex
536              * @param {Ext.EventObject} e
537              */
538             'cellcontextmenu',
539             /**
540              * @event headercontextmenu
541              * Fires when a header is right clicked
542              * @param {Grid} this
543              * @param {Number} columnIndex
544              * @param {Ext.EventObject} e
545              */
546             'headercontextmenu',
547             /**
548              * @event groupcontextmenu
549              * Fires when group header is right clicked. <b>Only applies for grids with a {@link Ext.grid.GroupingView GroupingView}</b>.
550              * @param {Grid} this
551              * @param {String} groupField
552              * @param {String} groupValue
553              * @param {Ext.EventObject} e
554              */
555             'groupcontextmenu',
556             /**
557              * @event containercontextmenu
558              * Fires when the container is right clicked. The container consists of any part of the grid body that is not covered by a row.
559              * @param {Grid} this
560              * @param {Ext.EventObject} e
561              */
562             'containercontextmenu',
563             /**
564              * @event rowbodycontextmenu
565              * Fires when the row body is right clicked. <b>Only applies for grids with {@link Ext.grid.GridView#enableRowBody enableRowBody} configured.</b>
566              * @param {Grid} this
567              * @param {Number} rowIndex
568              * @param {Ext.EventObject} e
569              */
570             'rowbodycontextmenu',
571             /**
572              * @event bodyscroll
573              * Fires when the body element is scrolled
574              * @param {Number} scrollLeft
575              * @param {Number} scrollTop
576              */
577             'bodyscroll',
578             /**
579              * @event columnresize
580              * Fires when the user resizes a column
581              * @param {Number} columnIndex
582              * @param {Number} newSize
583              */
584             'columnresize',
585             /**
586              * @event columnmove
587              * Fires when the user moves a column
588              * @param {Number} oldIndex
589              * @param {Number} newIndex
590              */
591             'columnmove',
592             /**
593              * @event sortchange
594              * Fires when the grid's store sort changes
595              * @param {Grid} this
596              * @param {Object} sortInfo An object with the keys field and direction
597              */
598             'sortchange',
599             /**
600              * @event groupchange
601              * Fires when the grid's grouping changes (only applies for grids with a {@link Ext.grid.GroupingView GroupingView})
602              * @param {Grid} this
603              * @param {String} groupField A string with the grouping field, null if the store is not grouped.
604              */
605             'groupchange',
606             /**
607              * @event reconfigure
608              * Fires when the grid is reconfigured with a new store and/or column model.
609              * @param {Grid} this
610              * @param {Ext.data.Store} store The new store
611              * @param {Ext.grid.ColumnModel} colModel The new column model
612              */
613             'reconfigure',
614             /**
615              * @event viewready
616              * Fires when the grid view is available (use this for selecting a default row).
617              * @param {Grid} this
618              */
619             'viewready'
620         );
621     },
622
623     // private
624     onRender : function(ct, position){
625         Ext.grid.GridPanel.superclass.onRender.apply(this, arguments);
626
627         var c = this.getGridEl();
628
629         this.el.addClass('x-grid-panel');
630
631         this.mon(c, {
632             scope: this,
633             mousedown: this.onMouseDown,
634             click: this.onClick,
635             dblclick: this.onDblClick,
636             contextmenu: this.onContextMenu
637         });
638
639         this.relayEvents(c, ['mousedown','mouseup','mouseover','mouseout','keypress', 'keydown']);
640
641         var view = this.getView();
642         view.init(this);
643         view.render();
644         this.getSelectionModel().init(this);
645     },
646
647     // private
648     initEvents : function(){
649         Ext.grid.GridPanel.superclass.initEvents.call(this);
650
651         if(this.loadMask){
652             this.loadMask = new Ext.LoadMask(this.bwrap,
653                     Ext.apply({store:this.store}, this.loadMask));
654         }
655     },
656
657     initStateEvents : function(){
658         Ext.grid.GridPanel.superclass.initStateEvents.call(this);
659         this.mon(this.colModel, 'hiddenchange', this.saveState, this, {delay: 100});
660     },
661
662     applyState : function(state){
663         var cm = this.colModel,
664             cs = state.columns,
665             store = this.store,
666             s,
667             c,
668             colIndex;
669
670         if(cs){
671             for(var i = 0, len = cs.length; i < len; i++){
672                 s = cs[i];
673                 c = cm.getColumnById(s.id);
674                 if(c){
675                     colIndex = cm.getIndexById(s.id);
676                     cm.setState(colIndex, {
677                         hidden: s.hidden,
678                         width: s.width,
679                         sortable: s.sortable
680                     });
681                     if(colIndex != i){
682                         cm.moveColumn(colIndex, i);
683                     }
684                 }
685             }
686         }
687         if(store){
688             s = state.sort;
689             if(s){
690                 store[store.remoteSort ? 'setDefaultSort' : 'sort'](s.field, s.direction);
691             }
692             s = state.group;
693             if(store.groupBy){
694                 if(s){
695                     store.groupBy(s);
696                 }else{
697                     store.clearGrouping();
698                 }
699             }
700
701         }
702         var o = Ext.apply({}, state);
703         delete o.columns;
704         delete o.sort;
705         Ext.grid.GridPanel.superclass.applyState.call(this, o);
706     },
707
708     getState : function(){
709         var o = {columns: []},
710             store = this.store,
711             ss,
712             gs;
713
714         for(var i = 0, c; (c = this.colModel.config[i]); i++){
715             o.columns[i] = {
716                 id: c.id,
717                 width: c.width
718             };
719             if(c.hidden){
720                 o.columns[i].hidden = true;
721             }
722             if(c.sortable){
723                 o.columns[i].sortable = true;
724             }
725         }
726         if(store){
727             ss = store.getSortState();
728             if(ss){
729                 o.sort = ss;
730             }
731             if(store.getGroupState){
732                 gs = store.getGroupState();
733                 if(gs){
734                     o.group = gs;
735                 }
736             }
737         }
738         return o;
739     },
740
741     // private
742     afterRender : function(){
743         Ext.grid.GridPanel.superclass.afterRender.call(this);
744         var v = this.view;
745         this.on('bodyresize', v.layout, v);
746         v.layout(true);
747         if(this.deferRowRender){
748             if (!this.deferRowRenderTask){
749                 this.deferRowRenderTask = new Ext.util.DelayedTask(v.afterRender, this.view);
750             }
751             this.deferRowRenderTask.delay(10);
752         }else{
753             v.afterRender();
754         }
755         this.viewReady = true;
756     },
757
758     /**
759      * <p>Reconfigures the grid to use a different Store and Column Model
760      * and fires the 'reconfigure' event. The View will be bound to the new
761      * objects and refreshed.</p>
762      * <p>Be aware that upon reconfiguring a GridPanel, certain existing settings <i>may</i> become
763      * invalidated. For example the configured {@link #autoExpandColumn} may no longer exist in the
764      * new ColumnModel. Also, an existing {@link Ext.PagingToolbar PagingToolbar} will still be bound
765      * to the old Store, and will need rebinding. Any {@link #plugins} might also need reconfiguring
766      * with the new data.</p>
767      * @param {Ext.data.Store} store The new {@link Ext.data.Store} object
768      * @param {Ext.grid.ColumnModel} colModel The new {@link Ext.grid.ColumnModel} object
769      */
770     reconfigure : function(store, colModel){
771         var rendered = this.rendered;
772         if(rendered){
773             if(this.loadMask){
774                 this.loadMask.destroy();
775                 this.loadMask = new Ext.LoadMask(this.bwrap,
776                         Ext.apply({}, {store:store}, this.initialConfig.loadMask));
777             }
778         }
779         if(this.view){
780             this.view.initData(store, colModel);
781         }
782         this.store = store;
783         this.colModel = colModel;
784         if(rendered){
785             this.view.refresh(true);
786         }
787         this.fireEvent('reconfigure', this, store, colModel);
788     },
789
790     // private
791     onDestroy : function(){
792         if (this.deferRowRenderTask && this.deferRowRenderTask.cancel){
793             this.deferRowRenderTask.cancel();
794         }
795         if(this.rendered){
796             Ext.destroy(this.view, this.loadMask);
797         }else if(this.store && this.store.autoDestroy){
798             this.store.destroy();
799         }
800         Ext.destroy(this.colModel, this.selModel);
801         this.store = this.selModel = this.colModel = this.view = this.loadMask = null;
802         Ext.grid.GridPanel.superclass.onDestroy.call(this);
803     },
804
805     // private
806     processEvent : function(name, e){
807         this.view.processEvent(name, e);
808     },
809
810     // private
811     onClick : function(e){
812         this.processEvent('click', e);
813     },
814
815     // private
816     onMouseDown : function(e){
817         this.processEvent('mousedown', e);
818     },
819
820     // private
821     onContextMenu : function(e, t){
822         this.processEvent('contextmenu', e);
823     },
824
825     // private
826     onDblClick : function(e){
827         this.processEvent('dblclick', e);
828     },
829
830     // private
831     walkCells : function(row, col, step, fn, scope){
832         var cm    = this.colModel,
833             clen  = cm.getColumnCount(),
834             ds    = this.store,
835             rlen  = ds.getCount(),
836             first = true;
837
838         if(step < 0){
839             if(col < 0){
840                 row--;
841                 first = false;
842             }
843             while(row >= 0){
844                 if(!first){
845                     col = clen-1;
846                 }
847                 first = false;
848                 while(col >= 0){
849                     if(fn.call(scope || this, row, col, cm) === true){
850                         return [row, col];
851                     }
852                     col--;
853                 }
854                 row--;
855             }
856         } else {
857             if(col >= clen){
858                 row++;
859                 first = false;
860             }
861             while(row < rlen){
862                 if(!first){
863                     col = 0;
864                 }
865                 first = false;
866                 while(col < clen){
867                     if(fn.call(scope || this, row, col, cm) === true){
868                         return [row, col];
869                     }
870                     col++;
871                 }
872                 row++;
873             }
874         }
875         return null;
876     },
877
878     /**
879      * Returns the grid's underlying element.
880      * @return {Element} The element
881      */
882     getGridEl : function(){
883         return this.body;
884     },
885
886     // private for compatibility, overridden by editor grid
887     stopEditing : Ext.emptyFn,
888
889     /**
890      * Returns the grid's selection model configured by the <code>{@link #selModel}</code>
891      * configuration option. If no selection model was configured, this will create
892      * and return a {@link Ext.grid.RowSelectionModel RowSelectionModel}.
893      * @return {SelectionModel}
894      */
895     getSelectionModel : function(){
896         if(!this.selModel){
897             this.selModel = new Ext.grid.RowSelectionModel(
898                     this.disableSelection ? {selectRow: Ext.emptyFn} : null);
899         }
900         return this.selModel;
901     },
902
903     /**
904      * Returns the grid's data store.
905      * @return {Ext.data.Store} The store
906      */
907     getStore : function(){
908         return this.store;
909     },
910
911     /**
912      * Returns the grid's ColumnModel.
913      * @return {Ext.grid.ColumnModel} The column model
914      */
915     getColumnModel : function(){
916         return this.colModel;
917     },
918
919     /**
920      * Returns the grid's GridView object.
921      * @return {Ext.grid.GridView} The grid view
922      */
923     getView : function() {
924         if (!this.view) {
925             this.view = new Ext.grid.GridView(this.viewConfig);
926         }
927         
928         return this.view;
929     },
930     /**
931      * Called to get grid's drag proxy text, by default returns this.ddText.
932      * @return {String} The text
933      */
934     getDragDropText : function(){
935         var count = this.selModel.getCount();
936         return String.format(this.ddText, count, count == 1 ? '' : 's');
937     }
938
939     /**
940      * @cfg {String/Number} activeItem
941      * @hide
942      */
943     /**
944      * @cfg {Boolean} autoDestroy
945      * @hide
946      */
947     /**
948      * @cfg {Object/String/Function} autoLoad
949      * @hide
950      */
951     /**
952      * @cfg {Boolean} autoWidth
953      * @hide
954      */
955     /**
956      * @cfg {Boolean/Number} bufferResize
957      * @hide
958      */
959     /**
960      * @cfg {String} defaultType
961      * @hide
962      */
963     /**
964      * @cfg {Object} defaults
965      * @hide
966      */
967     /**
968      * @cfg {Boolean} hideBorders
969      * @hide
970      */
971     /**
972      * @cfg {Mixed} items
973      * @hide
974      */
975     /**
976      * @cfg {String} layout
977      * @hide
978      */
979     /**
980      * @cfg {Object} layoutConfig
981      * @hide
982      */
983     /**
984      * @cfg {Boolean} monitorResize
985      * @hide
986      */
987     /**
988      * @property items
989      * @hide
990      */
991     /**
992      * @method add
993      * @hide
994      */
995     /**
996      * @method cascade
997      * @hide
998      */
999     /**
1000      * @method doLayout
1001      * @hide
1002      */
1003     /**
1004      * @method find
1005      * @hide
1006      */
1007     /**
1008      * @method findBy
1009      * @hide
1010      */
1011     /**
1012      * @method findById
1013      * @hide
1014      */
1015     /**
1016      * @method findByType
1017      * @hide
1018      */
1019     /**
1020      * @method getComponent
1021      * @hide
1022      */
1023     /**
1024      * @method getLayout
1025      * @hide
1026      */
1027     /**
1028      * @method getUpdater
1029      * @hide
1030      */
1031     /**
1032      * @method insert
1033      * @hide
1034      */
1035     /**
1036      * @method load
1037      * @hide
1038      */
1039     /**
1040      * @method remove
1041      * @hide
1042      */
1043     /**
1044      * @event add
1045      * @hide
1046      */
1047     /**
1048      * @event afterlayout
1049      * @hide
1050      */
1051     /**
1052      * @event beforeadd
1053      * @hide
1054      */
1055     /**
1056      * @event beforeremove
1057      * @hide
1058      */
1059     /**
1060      * @event remove
1061      * @hide
1062      */
1063
1064
1065
1066     /**
1067      * @cfg {String} allowDomMove  @hide
1068      */
1069     /**
1070      * @cfg {String} autoEl @hide
1071      */
1072     /**
1073      * @cfg {String} applyTo  @hide
1074      */
1075     /**
1076      * @cfg {String} autoScroll  @hide
1077      */
1078     /**
1079      * @cfg {String} bodyBorder  @hide
1080      */
1081     /**
1082      * @cfg {String} bodyStyle  @hide
1083      */
1084     /**
1085      * @cfg {String} contentEl  @hide
1086      */
1087     /**
1088      * @cfg {String} disabledClass  @hide
1089      */
1090     /**
1091      * @cfg {String} elements  @hide
1092      */
1093     /**
1094      * @cfg {String} html  @hide
1095      */
1096     /**
1097      * @cfg {Boolean} preventBodyReset
1098      * @hide
1099      */
1100     /**
1101      * @property disabled
1102      * @hide
1103      */
1104     /**
1105      * @method applyToMarkup
1106      * @hide
1107      */
1108     /**
1109      * @method enable
1110      * @hide
1111      */
1112     /**
1113      * @method disable
1114      * @hide
1115      */
1116     /**
1117      * @method setDisabled
1118      * @hide
1119      */
1120 });
1121 Ext.reg('grid', Ext.grid.GridPanel);/**
1122  * @class Ext.grid.PivotGrid
1123  * @extends Ext.grid.GridPanel
1124  * <p>The PivotGrid component enables rapid summarization of large data sets. It provides a way to reduce a large set of
1125  * data down into a format where trends and insights become more apparent. A classic example is in sales data; a company
1126  * will often have a record of all sales it makes for a given period - this will often encompass thousands of rows of
1127  * data. The PivotGrid allows you to see how well each salesperson performed, which cities generate the most revenue, 
1128  * how products perform between cities and so on.</p>
1129  * <p>A PivotGrid is composed of two axes (left and top), one {@link #measure} and one {@link #aggregator aggregation}
1130  * function. Each axis can contain one or more {@link #dimension}, which are ordered into a hierarchy. Dimensions on the 
1131  * left axis can also specify a width. Each dimension in each axis can specify its sort ordering, defaulting to "ASC", 
1132  * and must specify one of the fields in the {@link Ext.data.Record Record} used by the PivotGrid's 
1133  * {@link Ext.data.Store Store}.</p>
1134 <pre><code>
1135 // This is the record representing a single sale
1136 var SaleRecord = Ext.data.Record.create([
1137     {name: 'person',   type: 'string'},
1138     {name: 'product',  type: 'string'},
1139     {name: 'city',     type: 'string'},
1140     {name: 'state',    type: 'string'},
1141     {name: 'year',     type: 'int'},
1142     {name: 'value',    type: 'int'}
1143 ]);
1144
1145 // A simple store that loads SaleRecord data from a url
1146 var myStore = new Ext.data.Store({
1147     url: 'data.json',
1148     autoLoad: true,
1149     reader: new Ext.data.JsonReader({
1150         root: 'rows',
1151         idProperty: 'id'
1152     }, SaleRecord)
1153 });
1154
1155 // Create the PivotGrid itself, referencing the store
1156 var pivot = new Ext.grid.PivotGrid({
1157     store     : myStore,
1158     aggregator: 'sum',
1159     measure   : 'value',
1160
1161     leftAxis: [
1162         {
1163             width: 60,
1164             dataIndex: 'product'
1165         },
1166         {
1167             width: 120,
1168             dataIndex: 'person',
1169             direction: 'DESC'
1170         }
1171     ],
1172
1173     topAxis: [
1174         {
1175             dataIndex: 'year'
1176         }
1177     ]
1178 });
1179 </code></pre>
1180  * <p>The specified {@link #measure} is the field from SaleRecord that is extracted from each combination
1181  * of product and person (on the left axis) and year on the top axis. There may be several SaleRecords in the 
1182  * data set that share this combination, so an array of measure fields is produced. This array is then 
1183  * aggregated using the {@link #aggregator} function.</p>
1184  * <p>The default aggregator function is sum, which simply adds up all of the extracted measure values. Other
1185  * built-in aggregator functions are count, avg, min and max. In addition, you can specify your own function.
1186  * In this example we show the code used to sum the measures, but you can return any value you like. See
1187  * {@link #aggregator} for more details.</p>
1188 <pre><code>
1189 new Ext.grid.PivotGrid({
1190     aggregator: function(records, measure) {
1191         var length = records.length,
1192             total  = 0,
1193             i;
1194
1195         for (i = 0; i < length; i++) {
1196             total += records[i].get(measure);
1197         }
1198
1199         return total;
1200     },
1201     
1202     renderer: function(value) {
1203         return Math.round(value);
1204     },
1205     
1206     //your normal config here
1207 });
1208 </code></pre>
1209  * <p><u>Renderers</u></p>
1210  * <p>PivotGrid optionally accepts a {@link #renderer} function which can modify the data in each cell before it
1211  * is rendered. The renderer is passed the value that would usually be placed in the cell and is expected to return
1212  * the new value. For example let's imagine we had height data expressed as a decimal - here's how we might use a
1213  * renderer to display the data in feet and inches notation:</p>
1214 <pre><code>
1215 new Ext.grid.PivotGrid({
1216     //in each case the value is a decimal number of feet
1217     renderer  : function(value) {
1218         var feet   = Math.floor(value),
1219             inches = Math.round((value - feet) * 12);
1220
1221         return String.format("{0}' {1}\"", feet, inches);
1222     },
1223     //normal config here
1224 });
1225 </code></pre>
1226  * <p><u>Reconfiguring</u></p>
1227  * <p>All aspects PivotGrid's configuration can be updated at runtime. It is easy to change the {@link #setMeasure measure}, 
1228  * {@link #setAggregator aggregation function}, {@link #setLeftAxis left} and {@link #setTopAxis top} axes and refresh the grid.</p>
1229  * <p>In this case we reconfigure the PivotGrid to have city and year as the top axis dimensions, rendering the average sale
1230  * value into the cells:</p>
1231 <pre><code>
1232 //the left axis can also be changed
1233 pivot.topAxis.setDimensions([
1234     {dataIndex: 'city', direction: 'DESC'},
1235     {dataIndex: 'year', direction: 'ASC'}
1236 ]);
1237
1238 pivot.setMeasure('value');
1239 pivot.setAggregator('avg');
1240
1241 pivot.view.refresh(true);
1242 </code></pre>
1243  * <p>See the {@link Ext.grid.PivotAxis PivotAxis} documentation for further detail on reconfiguring axes.</p>
1244  */
1245 Ext.grid.PivotGrid = Ext.extend(Ext.grid.GridPanel, {
1246     
1247     /**
1248      * @cfg {String|Function} aggregator The aggregation function to use to combine the measures extracted
1249      * for each dimension combination. Can be any of the built-in aggregators (sum, count, avg, min, max).
1250      * Can also be a function which accepts two arguments (an array of Records to aggregate, and the measure 
1251      * to aggregate them on) and should return a String.
1252      */
1253     aggregator: 'sum',
1254     
1255     /**
1256      * @cfg {Function} renderer Optional renderer to pass values through before they are rendered to the dom. This
1257      * gives an opportunity to modify cell contents after the value has been computed.
1258      */
1259     renderer: undefined,
1260     
1261     /**
1262      * @cfg {String} measure The field to extract from each Record when pivoting around the two axes. See the class
1263      * introduction docs for usage
1264      */
1265     
1266     /**
1267      * @cfg {Array|Ext.grid.PivotAxis} leftAxis Either and array of {@link #dimension} to use on the left axis, or
1268      * a {@link Ext.grid.PivotAxis} instance. If an array is passed, it is turned into a PivotAxis internally.
1269      */
1270     
1271     /**
1272      * @cfg {Array|Ext.grid.PivotAxis} topAxis Either and array of {@link #dimension} to use on the top axis, or
1273      * a {@link Ext.grid.PivotAxis} instance. If an array is passed, it is turned into a PivotAxis internally.
1274      */
1275     
1276     //inherit docs
1277     initComponent: function() {
1278         Ext.grid.PivotGrid.superclass.initComponent.apply(this, arguments);
1279         
1280         this.initAxes();
1281         
1282         //no resizing of columns is allowed yet in PivotGrid
1283         this.enableColumnResize = false;
1284         
1285         this.viewConfig = Ext.apply(this.viewConfig || {}, {
1286             forceFit: true
1287         });
1288         
1289         //TODO: dummy col model that is never used - GridView is too tightly integrated with ColumnModel
1290         //in 3.x to remove this altogether.
1291         this.colModel = new Ext.grid.ColumnModel({});
1292     },
1293     
1294     /**
1295      * Returns the function currently used to aggregate the records in each Pivot cell
1296      * @return {Function} The current aggregator function
1297      */
1298     getAggregator: function() {
1299         if (typeof this.aggregator == 'string') {
1300             return Ext.grid.PivotAggregatorMgr.types[this.aggregator];
1301         } else {
1302             return this.aggregator;
1303         }
1304     },
1305     
1306     /**
1307      * Sets the function to use when aggregating data for each cell.
1308      * @param {String|Function} aggregator The new aggregator function or named function string
1309      */
1310     setAggregator: function(aggregator) {
1311         this.aggregator = aggregator;
1312     },
1313     
1314     /**
1315      * Sets the field name to use as the Measure in this Pivot Grid
1316      * @param {String} measure The field to make the measure
1317      */
1318     setMeasure: function(measure) {
1319         this.measure = measure;
1320     },
1321     
1322     /**
1323      * Sets the left axis of this pivot grid. Optionally refreshes the grid afterwards.
1324      * @param {Ext.grid.PivotAxis} axis The pivot axis
1325      * @param {Boolean} refresh True to immediately refresh the grid and its axes (defaults to false)
1326      */
1327     setLeftAxis: function(axis, refresh) {
1328         /**
1329          * The configured {@link Ext.grid.PivotAxis} used as the left Axis for this Pivot Grid
1330          * @property leftAxis
1331          * @type Ext.grid.PivotAxis
1332          */
1333         this.leftAxis = axis;
1334         
1335         if (refresh) {
1336             this.view.refresh();
1337         }
1338     },
1339     
1340     /**
1341      * Sets the top axis of this pivot grid. Optionally refreshes the grid afterwards.
1342      * @param {Ext.grid.PivotAxis} axis The pivot axis
1343      * @param {Boolean} refresh True to immediately refresh the grid and its axes (defaults to false)
1344      */
1345     setTopAxis: function(axis, refresh) {
1346         /**
1347          * The configured {@link Ext.grid.PivotAxis} used as the top Axis for this Pivot Grid
1348          * @property topAxis
1349          * @type Ext.grid.PivotAxis
1350          */
1351         this.topAxis = axis;
1352         
1353         if (refresh) {
1354             this.view.refresh();
1355         }
1356     },
1357     
1358     /**
1359      * @private
1360      * Creates the top and left axes. Should usually only need to be called once from initComponent
1361      */
1362     initAxes: function() {
1363         var PivotAxis = Ext.grid.PivotAxis;
1364         
1365         if (!(this.leftAxis instanceof PivotAxis)) {
1366             this.setLeftAxis(new PivotAxis({
1367                 orientation: 'vertical',
1368                 dimensions : this.leftAxis || [],
1369                 store      : this.store
1370             }));
1371         };
1372         
1373         if (!(this.topAxis instanceof PivotAxis)) {
1374             this.setTopAxis(new PivotAxis({
1375                 orientation: 'horizontal',
1376                 dimensions : this.topAxis || [],
1377                 store      : this.store
1378             }));
1379         };
1380     },
1381     
1382     /**
1383      * @private
1384      * @return {Array} 2-dimensional array of cell data
1385      */
1386     extractData: function() {
1387         var records  = this.store.data.items,
1388             recCount = records.length,
1389             cells    = [],
1390             record, i, j, k;
1391         
1392         if (recCount == 0) {
1393             return [];
1394         }
1395         
1396         var leftTuples = this.leftAxis.getTuples(),
1397             leftCount  = leftTuples.length,
1398             topTuples  = this.topAxis.getTuples(),
1399             topCount   = topTuples.length,
1400             aggregator = this.getAggregator();
1401         
1402         for (i = 0; i < recCount; i++) {
1403             record = records[i];
1404             
1405             for (j = 0; j < leftCount; j++) {
1406                 cells[j] = cells[j] || [];
1407                 
1408                 if (leftTuples[j].matcher(record) === true) {
1409                     for (k = 0; k < topCount; k++) {
1410                         cells[j][k] = cells[j][k] || [];
1411                         
1412                         if (topTuples[k].matcher(record)) {
1413                             cells[j][k].push(record);
1414                         }
1415                     }
1416                 }
1417             }
1418         }
1419         
1420         var rowCount = cells.length,
1421             colCount, row;
1422         
1423         for (i = 0; i < rowCount; i++) {
1424             row = cells[i];
1425             colCount = row.length;
1426             
1427             for (j = 0; j < colCount; j++) {
1428                 cells[i][j] = aggregator(cells[i][j], this.measure);
1429             }
1430         }
1431         
1432         return cells;
1433     },
1434     
1435     /**
1436      * Returns the grid's GridView object.
1437      * @return {Ext.grid.PivotGridView} The grid view
1438      */
1439     getView: function() {
1440         if (!this.view) {
1441             this.view = new Ext.grid.PivotGridView(this.viewConfig);
1442         }
1443         
1444         return this.view;
1445     }
1446 });
1447
1448 Ext.reg('pivotgrid', Ext.grid.PivotGrid);
1449
1450
1451 Ext.grid.PivotAggregatorMgr = new Ext.AbstractManager();
1452
1453 Ext.grid.PivotAggregatorMgr.registerType('sum', function(records, measure) {
1454     var length = records.length,
1455         total  = 0,
1456         i;
1457     
1458     for (i = 0; i < length; i++) {
1459         total += records[i].get(measure);
1460     }
1461     
1462     return total;
1463 });
1464
1465 Ext.grid.PivotAggregatorMgr.registerType('avg', function(records, measure) {
1466     var length = records.length,
1467         total  = 0,
1468         i;
1469     
1470     for (i = 0; i < length; i++) {
1471         total += records[i].get(measure);
1472     }
1473     
1474     return (total / length) || 'n/a';
1475 });
1476
1477 Ext.grid.PivotAggregatorMgr.registerType('min', function(records, measure) {
1478     var data   = [],
1479         length = records.length,
1480         i;
1481     
1482     for (i = 0; i < length; i++) {
1483         data.push(records[i].get(measure));
1484     }
1485     
1486     return Math.min.apply(this, data) || 'n/a';
1487 });
1488
1489 Ext.grid.PivotAggregatorMgr.registerType('max', function(records, measure) {
1490     var data   = [],
1491         length = records.length,
1492         i;
1493     
1494     for (i = 0; i < length; i++) {
1495         data.push(records[i].get(measure));
1496     }
1497     
1498     return Math.max.apply(this, data) || 'n/a';
1499 });
1500
1501 Ext.grid.PivotAggregatorMgr.registerType('count', function(records, measure) {
1502     return records.length;
1503 });/**
1504  * @class Ext.grid.GridView
1505  * @extends Ext.util.Observable
1506  * <p>This class encapsulates the user interface of an {@link Ext.grid.GridPanel}.
1507  * Methods of this class may be used to access user interface elements to enable
1508  * special display effects. Do not change the DOM structure of the user interface.</p>
1509  * <p>This class does not provide ways to manipulate the underlying data. The data
1510  * model of a Grid is held in an {@link Ext.data.Store}.</p>
1511  * @constructor
1512  * @param {Object} config
1513  */
1514 Ext.grid.GridView = Ext.extend(Ext.util.Observable, {
1515     /**
1516      * Override this function to apply custom CSS classes to rows during rendering.  You can also supply custom
1517      * parameters to the row template for the current row to customize how it is rendered using the <b>rowParams</b>
1518      * parameter.  This function should return the CSS class name (or empty string '' for none) that will be added
1519      * to the row's wrapping div.  To apply multiple class names, simply return them space-delimited within the string
1520      * (e.g., 'my-class another-class'). Example usage:
1521     <pre><code>
1522 viewConfig: {
1523     forceFit: true,
1524     showPreview: true, // custom property
1525     enableRowBody: true, // required to create a second, full-width row to show expanded Record data
1526     getRowClass: function(record, rowIndex, rp, ds){ // rp = rowParams
1527         if(this.showPreview){
1528             rp.body = '&lt;p>'+record.data.excerpt+'&lt;/p>';
1529             return 'x-grid3-row-expanded';
1530         }
1531         return 'x-grid3-row-collapsed';
1532     }
1533 },
1534     </code></pre>
1535      * @param {Record} record The {@link Ext.data.Record} corresponding to the current row.
1536      * @param {Number} index The row index.
1537      * @param {Object} rowParams A config object that is passed to the row template during rendering that allows
1538      * customization of various aspects of a grid row.
1539      * <p>If {@link #enableRowBody} is configured <b><tt></tt>true</b>, then the following properties may be set
1540      * by this function, and will be used to render a full-width expansion row below each grid row:</p>
1541      * <ul>
1542      * <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>
1543      * <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>
1544      * </ul>
1545      * The following property will be passed in, and may be appended to:
1546      * <ul>
1547      * <li><code>tstyle</code> : String <div class="sub-desc">A CSS style specification that willl be applied to the &lt;table> element which encapsulates
1548      * both the standard grid row, and any expansion row.</div></li>
1549      * </ul>
1550      * @param {Store} store The {@link Ext.data.Store} this grid is bound to
1551      * @method getRowClass
1552      * @return {String} a CSS class name to add to the row.
1553      */
1554
1555     /**
1556      * @cfg {Boolean} enableRowBody True to add a second TR element per row that can be used to provide a row body
1557      * that spans beneath the data row.  Use the {@link #getRowClass} method's rowParams config to customize the row body.
1558      */
1559
1560     /**
1561      * @cfg {String} emptyText Default text (html tags are accepted) to display in the grid body when no rows
1562      * are available (defaults to ''). This value will be used to update the <tt>{@link #mainBody}</tt>:
1563     <pre><code>
1564     this.mainBody.update('&lt;div class="x-grid-empty">' + this.emptyText + '&lt;/div>');
1565     </code></pre>
1566      */
1567
1568     /**
1569      * @cfg {Boolean} headersDisabled True to disable the grid column headers (defaults to <tt>false</tt>).
1570      * Use the {@link Ext.grid.ColumnModel ColumnModel} <tt>{@link Ext.grid.ColumnModel#menuDisabled menuDisabled}</tt>
1571      * config to disable the <i>menu</i> for individual columns.  While this config is true the
1572      * following will be disabled:<div class="mdetail-params"><ul>
1573      * <li>clicking on header to sort</li>
1574      * <li>the trigger to reveal the menu.</li>
1575      * </ul></div>
1576      */
1577
1578     /**
1579      * <p>A customized implementation of a {@link Ext.dd.DragZone DragZone} which provides default implementations
1580      * of the template methods of DragZone to enable dragging of the selected rows of a GridPanel.
1581      * See {@link Ext.grid.GridDragZone} for details.</p>
1582      * <p>This will <b>only</b> be present:<div class="mdetail-params"><ul>
1583      * <li><i>if</i> the owning GridPanel was configured with {@link Ext.grid.GridPanel#enableDragDrop enableDragDrop}: <tt>true</tt>.</li>
1584      * <li><i>after</i> the owning GridPanel has been rendered.</li>
1585      * </ul></div>
1586      * @property dragZone
1587      * @type {Ext.grid.GridDragZone}
1588      */
1589
1590     /**
1591      * @cfg {Boolean} deferEmptyText True to defer <tt>{@link #emptyText}</tt> being applied until the store's
1592      * first load (defaults to <tt>true</tt>).
1593      */
1594     deferEmptyText : true,
1595
1596     /**
1597      * @cfg {Number} scrollOffset The amount of space to reserve for the vertical scrollbar
1598      * (defaults to <tt>undefined</tt>). If an explicit value isn't specified, this will be automatically
1599      * calculated.
1600      */
1601     scrollOffset : undefined,
1602
1603     /**
1604      * @cfg {Boolean} autoFill
1605      * Defaults to <tt>false</tt>.  Specify <tt>true</tt> to have the column widths re-proportioned
1606      * when the grid is <b>initially rendered</b>.  The
1607      * {@link Ext.grid.Column#width initially configured width}</tt> of each column will be adjusted
1608      * to fit the grid width and prevent horizontal scrolling. If columns are later resized (manually
1609      * or programmatically), the other columns in the grid will <b>not</b> be resized to fit the grid width.
1610      * See <tt>{@link #forceFit}</tt> also.
1611      */
1612     autoFill : false,
1613
1614     /**
1615      * @cfg {Boolean} forceFit
1616      * <p>Defaults to <tt>false</tt>.  Specify <tt>true</tt> to have the column widths re-proportioned
1617      * at <b>all times</b>.</p>
1618      * <p>The {@link Ext.grid.Column#width initially configured width}</tt> of each
1619      * column will be adjusted to fit the grid width and prevent horizontal scrolling. If columns are
1620      * later resized (manually or programmatically), the other columns in the grid <b>will</b> be resized
1621      * to fit the grid width.</p>
1622      * <p>Columns which are configured with <code>fixed: true</code> are omitted from being resized.</p>
1623      * <p>See <tt>{@link #autoFill}</tt>.</p>
1624      */
1625     forceFit : false,
1626
1627     /**
1628      * @cfg {Array} sortClasses The CSS classes applied to a header when it is sorted. (defaults to <tt>['sort-asc', 'sort-desc']</tt>)
1629      */
1630     sortClasses : ['sort-asc', 'sort-desc'],
1631
1632     /**
1633      * @cfg {String} sortAscText The text displayed in the 'Sort Ascending' menu item (defaults to <tt>'Sort Ascending'</tt>)
1634      */
1635     sortAscText : 'Sort Ascending',
1636
1637     /**
1638      * @cfg {String} sortDescText The text displayed in the 'Sort Descending' menu item (defaults to <tt>'Sort Descending'</tt>)
1639      */
1640     sortDescText : 'Sort Descending',
1641
1642     /**
1643      * @cfg {String} columnsText The text displayed in the 'Columns' menu item (defaults to <tt>'Columns'</tt>)
1644      */
1645     columnsText : 'Columns',
1646
1647     /**
1648      * @cfg {String} selectedRowClass The CSS class applied to a selected row (defaults to <tt>'x-grid3-row-selected'</tt>). An
1649      * example overriding the default styling:
1650     <pre><code>
1651     .x-grid3-row-selected {background-color: yellow;}
1652     </code></pre>
1653      * Note that this only controls the row, and will not do anything for the text inside it.  To style inner
1654      * facets (like text) use something like:
1655     <pre><code>
1656     .x-grid3-row-selected .x-grid3-cell-inner {
1657         color: #FFCC00;
1658     }
1659     </code></pre>
1660      * @type String
1661      */
1662     selectedRowClass : 'x-grid3-row-selected',
1663
1664     // private
1665     borderWidth : 2,
1666     tdClass : 'x-grid3-cell',
1667     hdCls : 'x-grid3-hd',
1668     
1669     
1670     /**
1671      * @cfg {Boolean} markDirty True to show the dirty cell indicator when a cell has been modified. Defaults to <tt>true</tt>.
1672      */
1673     markDirty : true,
1674
1675     /**
1676      * @cfg {Number} cellSelectorDepth The number of levels to search for cells in event delegation (defaults to <tt>4</tt>)
1677      */
1678     cellSelectorDepth : 4,
1679     
1680     /**
1681      * @cfg {Number} rowSelectorDepth The number of levels to search for rows in event delegation (defaults to <tt>10</tt>)
1682      */
1683     rowSelectorDepth : 10,
1684
1685     /**
1686      * @cfg {Number} rowBodySelectorDepth The number of levels to search for row bodies in event delegation (defaults to <tt>10</tt>)
1687      */
1688     rowBodySelectorDepth : 10,
1689
1690     /**
1691      * @cfg {String} cellSelector The selector used to find cells internally (defaults to <tt>'td.x-grid3-cell'</tt>)
1692      */
1693     cellSelector : 'td.x-grid3-cell',
1694     
1695     /**
1696      * @cfg {String} rowSelector The selector used to find rows internally (defaults to <tt>'div.x-grid3-row'</tt>)
1697      */
1698     rowSelector : 'div.x-grid3-row',
1699
1700     /**
1701      * @cfg {String} rowBodySelector The selector used to find row bodies internally (defaults to <tt>'div.x-grid3-row'</tt>)
1702      */
1703     rowBodySelector : 'div.x-grid3-row-body',
1704
1705     // private
1706     firstRowCls: 'x-grid3-row-first',
1707     lastRowCls: 'x-grid3-row-last',
1708     rowClsRe: /(?:^|\s+)x-grid3-row-(first|last|alt)(?:\s+|$)/g,
1709     
1710     /**
1711      * @cfg {String} headerMenuOpenCls The CSS class to add to the header cell when its menu is visible. Defaults to 'x-grid3-hd-menu-open'
1712      */
1713     headerMenuOpenCls: 'x-grid3-hd-menu-open',
1714     
1715     /**
1716      * @cfg {String} rowOverCls The CSS class added to each row when it is hovered over. Defaults to 'x-grid3-row-over'
1717      */
1718     rowOverCls: 'x-grid3-row-over',
1719
1720     constructor : function(config) {
1721         Ext.apply(this, config);
1722         
1723         // These events are only used internally by the grid components
1724         this.addEvents(
1725             /**
1726              * @event beforerowremoved
1727              * Internal UI Event. Fired before a row is removed.
1728              * @param {Ext.grid.GridView} view
1729              * @param {Number} rowIndex The index of the row to be removed.
1730              * @param {Ext.data.Record} record The Record to be removed
1731              */
1732             'beforerowremoved',
1733             
1734             /**
1735              * @event beforerowsinserted
1736              * Internal UI Event. Fired before rows are inserted.
1737              * @param {Ext.grid.GridView} view
1738              * @param {Number} firstRow The index of the first row to be inserted.
1739              * @param {Number} lastRow The index of the last row to be inserted.
1740              */
1741             'beforerowsinserted',
1742             
1743             /**
1744              * @event beforerefresh
1745              * Internal UI Event. Fired before the view is refreshed.
1746              * @param {Ext.grid.GridView} view
1747              */
1748             'beforerefresh',
1749             
1750             /**
1751              * @event rowremoved
1752              * Internal UI Event. Fired after a row is removed.
1753              * @param {Ext.grid.GridView} view
1754              * @param {Number} rowIndex The index of the row that was removed.
1755              * @param {Ext.data.Record} record The Record that was removed
1756              */
1757             'rowremoved',
1758             
1759             /**
1760              * @event rowsinserted
1761              * Internal UI Event. Fired after rows are inserted.
1762              * @param {Ext.grid.GridView} view
1763              * @param {Number} firstRow The index of the first inserted.
1764              * @param {Number} lastRow The index of the last row inserted.
1765              */
1766             'rowsinserted',
1767             
1768             /**
1769              * @event rowupdated
1770              * Internal UI Event. Fired after a row has been updated.
1771              * @param {Ext.grid.GridView} view
1772              * @param {Number} firstRow The index of the row updated.
1773              * @param {Ext.data.record} record The Record backing the row updated.
1774              */
1775             'rowupdated',
1776             
1777             /**
1778              * @event refresh
1779              * Internal UI Event. Fired after the GridView's body has been refreshed.
1780              * @param {Ext.grid.GridView} view
1781              */
1782             'refresh'
1783         );
1784         
1785         Ext.grid.GridView.superclass.constructor.call(this);
1786     },
1787
1788     /* -------------------------------- UI Specific ----------------------------- */
1789     
1790     /**
1791      * The master template to use when rendering the GridView. Has a default template
1792      * @property Ext.Template
1793      * @type masterTpl
1794      */
1795     masterTpl: new Ext.Template(
1796         '<div class="x-grid3" hidefocus="true">',
1797             '<div class="x-grid3-viewport">',
1798                 '<div class="x-grid3-header">',
1799                     '<div class="x-grid3-header-inner">',
1800                         '<div class="x-grid3-header-offset" style="{ostyle}">{header}</div>',
1801                     '</div>',
1802                     '<div class="x-clear"></div>',
1803                 '</div>',
1804                 '<div class="x-grid3-scroller">',
1805                     '<div class="x-grid3-body" style="{bstyle}">{body}</div>',
1806                     '<a href="#" class="x-grid3-focus" tabIndex="-1"></a>',
1807                 '</div>',
1808             '</div>',
1809             '<div class="x-grid3-resize-marker">&#160;</div>',
1810             '<div class="x-grid3-resize-proxy">&#160;</div>',
1811         '</div>'
1812     ),
1813     
1814     /**
1815      * The template to use when rendering headers. Has a default template
1816      * @property headerTpl
1817      * @type Ext.Template
1818      */
1819     headerTpl: new Ext.Template(
1820         '<table border="0" cellspacing="0" cellpadding="0" style="{tstyle}">',
1821             '<thead>',
1822                 '<tr class="x-grid3-hd-row">{cells}</tr>',
1823             '</thead>',
1824         '</table>'
1825     ),
1826     
1827     /**
1828      * The template to use when rendering the body. Has a default template
1829      * @property bodyTpl
1830      * @type Ext.Template
1831      */
1832     bodyTpl: new Ext.Template('{rows}'),
1833     
1834     /**
1835      * The template to use to render each cell. Has a default template
1836      * @property cellTpl
1837      * @type Ext.Template
1838      */
1839     cellTpl: new Ext.Template(
1840         '<td class="x-grid3-col x-grid3-cell x-grid3-td-{id} {css}" style="{style}" tabIndex="0" {cellAttr}>',
1841             '<div class="x-grid3-cell-inner x-grid3-col-{id}" unselectable="on" {attr}>{value}</div>',
1842         '</td>'
1843     ),
1844     
1845     /**
1846      * @private
1847      * Provides default templates if they are not given for this particular instance. Most of the templates are defined on
1848      * the prototype, the ones defined inside this function are done so because they are based on Grid or GridView configuration
1849      */
1850     initTemplates : function() {
1851         var templates = this.templates || {},
1852             template, name,
1853             
1854             headerCellTpl = new Ext.Template(
1855                 '<td class="x-grid3-hd x-grid3-cell x-grid3-td-{id} {css}" style="{style}">',
1856                     '<div {tooltip} {attr} class="x-grid3-hd-inner x-grid3-hd-{id}" unselectable="on" style="{istyle}">', 
1857                         this.grid.enableHdMenu ? '<a class="x-grid3-hd-btn" href="#"></a>' : '',
1858                         '{value}',
1859                         '<img alt="" class="x-grid3-sort-icon" src="', Ext.BLANK_IMAGE_URL, '" />',
1860                     '</div>',
1861                 '</td>'
1862             ),
1863         
1864             rowBodyText = [
1865                 '<tr class="x-grid3-row-body-tr" style="{bodyStyle}">',
1866                     '<td colspan="{cols}" class="x-grid3-body-cell" tabIndex="0" hidefocus="on">',
1867                         '<div class="x-grid3-row-body">{body}</div>',
1868                     '</td>',
1869                 '</tr>'
1870             ].join(""),
1871         
1872             innerText = [
1873                 '<table class="x-grid3-row-table" border="0" cellspacing="0" cellpadding="0" style="{tstyle}">',
1874                      '<tbody>',
1875                         '<tr>{cells}</tr>',
1876                         this.enableRowBody ? rowBodyText : '',
1877                      '</tbody>',
1878                 '</table>'
1879             ].join("");
1880         
1881         Ext.applyIf(templates, {
1882             hcell   : headerCellTpl,
1883             cell    : this.cellTpl,
1884             body    : this.bodyTpl,
1885             header  : this.headerTpl,
1886             master  : this.masterTpl,
1887             row     : new Ext.Template('<div class="x-grid3-row {alt}" style="{tstyle}">' + innerText + '</div>'),
1888             rowInner: new Ext.Template(innerText)
1889         });
1890
1891         for (name in templates) {
1892             template = templates[name];
1893             
1894             if (template && Ext.isFunction(template.compile) && !template.compiled) {
1895                 template.disableFormats = true;
1896                 template.compile();
1897             }
1898         }
1899
1900         this.templates = templates;
1901         this.colRe = new RegExp('x-grid3-td-([^\\s]+)', '');
1902     },
1903
1904     /**
1905      * @private
1906      * Each GridView has its own private flyweight, accessed through this method
1907      */
1908     fly : function(el) {
1909         if (!this._flyweight) {
1910             this._flyweight = new Ext.Element.Flyweight(document.body);
1911         }
1912         this._flyweight.dom = el;
1913         return this._flyweight;
1914     },
1915
1916     // private
1917     getEditorParent : function() {
1918         return this.scroller.dom;
1919     },
1920
1921     /**
1922      * @private
1923      * Finds and stores references to important elements
1924      */
1925     initElements : function() {
1926         var Element  = Ext.Element,
1927             el       = Ext.get(this.grid.getGridEl().dom.firstChild),
1928             mainWrap = new Element(el.child('div.x-grid3-viewport')),
1929             mainHd   = new Element(mainWrap.child('div.x-grid3-header')),
1930             scroller = new Element(mainWrap.child('div.x-grid3-scroller'));
1931         
1932         if (this.grid.hideHeaders) {
1933             mainHd.setDisplayed(false);
1934         }
1935         
1936         if (this.forceFit) {
1937             scroller.setStyle('overflow-x', 'hidden');
1938         }
1939         
1940         /**
1941          * <i>Read-only</i>. The GridView's body Element which encapsulates all rows in the Grid.
1942          * This {@link Ext.Element Element} is only available after the GridPanel has been rendered.
1943          * @type Ext.Element
1944          * @property mainBody
1945          */
1946         
1947         Ext.apply(this, {
1948             el      : el,
1949             mainWrap: mainWrap,
1950             scroller: scroller,
1951             mainHd  : mainHd,
1952             innerHd : mainHd.child('div.x-grid3-header-inner').dom,
1953             mainBody: new Element(Element.fly(scroller).child('div.x-grid3-body')),
1954             focusEl : new Element(Element.fly(scroller).child('a')),
1955             
1956             resizeMarker: new Element(el.child('div.x-grid3-resize-marker')),
1957             resizeProxy : new Element(el.child('div.x-grid3-resize-proxy'))
1958         });
1959         
1960         this.focusEl.swallowEvent('click', true);
1961     },
1962
1963     // private
1964     getRows : function() {
1965         return this.hasRows() ? this.mainBody.dom.childNodes : [];
1966     },
1967
1968     // finder methods, used with delegation
1969
1970     // private
1971     findCell : function(el) {
1972         if (!el) {
1973             return false;
1974         }
1975         return this.fly(el).findParent(this.cellSelector, this.cellSelectorDepth);
1976     },
1977
1978     /**
1979      * <p>Return the index of the grid column which contains the passed HTMLElement.</p>
1980      * See also {@link #findRowIndex}
1981      * @param {HTMLElement} el The target element
1982      * @return {Number} The column index, or <b>false</b> if the target element is not within a row of this GridView.
1983      */
1984     findCellIndex : function(el, requiredCls) {
1985         var cell = this.findCell(el),
1986             hasCls;
1987         
1988         if (cell) {
1989             hasCls = this.fly(cell).hasClass(requiredCls);
1990             if (!requiredCls || hasCls) {
1991                 return this.getCellIndex(cell);
1992             }
1993         }
1994         return false;
1995     },
1996
1997     // private
1998     getCellIndex : function(el) {
1999         if (el) {
2000             var match = el.className.match(this.colRe);
2001             
2002             if (match && match[1]) {
2003                 return this.cm.getIndexById(match[1]);
2004             }
2005         }
2006         return false;
2007     },
2008
2009     // private
2010     findHeaderCell : function(el) {
2011         var cell = this.findCell(el);
2012         return cell && this.fly(cell).hasClass(this.hdCls) ? cell : null;
2013     },
2014
2015     // private
2016     findHeaderIndex : function(el){
2017         return this.findCellIndex(el, this.hdCls);
2018     },
2019
2020     /**
2021      * Return the HtmlElement representing the grid row which contains the passed element.
2022      * @param {HTMLElement} el The target HTMLElement
2023      * @return {HTMLElement} The row element, or null if the target element is not within a row of this GridView.
2024      */
2025     findRow : function(el) {
2026         if (!el) {
2027             return false;
2028         }
2029         return this.fly(el).findParent(this.rowSelector, this.rowSelectorDepth);
2030     },
2031
2032     /**
2033      * Return the index of the grid row which contains the passed HTMLElement.
2034      * See also {@link #findCellIndex}
2035      * @param {HTMLElement} el The target HTMLElement
2036      * @return {Number} The row index, or <b>false</b> if the target element is not within a row of this GridView.
2037      */
2038     findRowIndex : function(el) {
2039         var row = this.findRow(el);
2040         return row ? row.rowIndex : false;
2041     },
2042
2043     /**
2044      * Return the HtmlElement representing the grid row body which contains the passed element.
2045      * @param {HTMLElement} el The target HTMLElement
2046      * @return {HTMLElement} The row body element, or null if the target element is not within a row body of this GridView.
2047      */
2048     findRowBody : function(el) {
2049         if (!el) {
2050             return false;
2051         }
2052         
2053         return this.fly(el).findParent(this.rowBodySelector, this.rowBodySelectorDepth);
2054     },
2055
2056     // getter methods for fetching elements dynamically in the grid
2057
2058     /**
2059      * Return the <tt>&lt;div></tt> HtmlElement which represents a Grid row for the specified index.
2060      * @param {Number} index The row index
2061      * @return {HtmlElement} The div element.
2062      */
2063     getRow : function(row) {
2064         return this.getRows()[row];
2065     },
2066
2067     /**
2068      * Returns the grid's <tt>&lt;td></tt> HtmlElement at the specified coordinates.
2069      * @param {Number} row The row index in which to find the cell.
2070      * @param {Number} col The column index of the cell.
2071      * @return {HtmlElement} The td at the specified coordinates.
2072      */
2073     getCell : function(row, col) {
2074         return Ext.fly(this.getRow(row)).query(this.cellSelector)[col]; 
2075     },
2076
2077     /**
2078      * Return the <tt>&lt;td></tt> HtmlElement which represents the Grid's header cell for the specified column index.
2079      * @param {Number} index The column index
2080      * @return {HtmlElement} The td element.
2081      */
2082     getHeaderCell : function(index) {
2083         return this.mainHd.dom.getElementsByTagName('td')[index];
2084     },
2085
2086     // manipulating elements
2087
2088     // private - use getRowClass to apply custom row classes
2089     addRowClass : function(rowId, cls) {
2090         var row = this.getRow(rowId);
2091         if (row) {
2092             this.fly(row).addClass(cls);
2093         }
2094     },
2095
2096     // private
2097     removeRowClass : function(row, cls) {
2098         var r = this.getRow(row);
2099         if(r){
2100             this.fly(r).removeClass(cls);
2101         }
2102     },
2103
2104     // private
2105     removeRow : function(row) {
2106         Ext.removeNode(this.getRow(row));
2107         this.syncFocusEl(row);
2108     },
2109
2110     // private
2111     removeRows : function(firstRow, lastRow) {
2112         var bd = this.mainBody.dom,
2113             rowIndex;
2114             
2115         for (rowIndex = firstRow; rowIndex <= lastRow; rowIndex++){
2116             Ext.removeNode(bd.childNodes[firstRow]);
2117         }
2118         
2119         this.syncFocusEl(firstRow);
2120     },
2121
2122     /* ----------------------------------- Scrolling functions -------------------------------------------*/
2123     
2124     // private
2125     getScrollState : function() {
2126         var sb = this.scroller.dom;
2127         
2128         return {
2129             left: sb.scrollLeft, 
2130             top : sb.scrollTop
2131         };
2132     },
2133
2134     // private
2135     restoreScroll : function(state) {
2136         var sb = this.scroller.dom;
2137         sb.scrollLeft = state.left;
2138         sb.scrollTop  = state.top;
2139     },
2140
2141     /**
2142      * Scrolls the grid to the top
2143      */
2144     scrollToTop : function() {
2145         var dom = this.scroller.dom;
2146         
2147         dom.scrollTop  = 0;
2148         dom.scrollLeft = 0;
2149     },
2150
2151     // private
2152     syncScroll : function() {
2153         this.syncHeaderScroll();
2154         var mb = this.scroller.dom;
2155         this.grid.fireEvent('bodyscroll', mb.scrollLeft, mb.scrollTop);
2156     },
2157
2158     // private
2159     syncHeaderScroll : function() {
2160         var innerHd    = this.innerHd,
2161             scrollLeft = this.scroller.dom.scrollLeft;
2162         
2163         innerHd.scrollLeft = scrollLeft;
2164         innerHd.scrollLeft = scrollLeft; // second time for IE (1/2 time first fails, other browsers ignore)
2165     },
2166     
2167     /**
2168      * @private
2169      * Ensures the given column has the given icon class
2170      */
2171     updateSortIcon : function(col, dir) {
2172         var sortClasses = this.sortClasses,
2173             sortClass   = sortClasses[dir == "DESC" ? 1 : 0],
2174             headers     = this.mainHd.select('td').removeClass(sortClasses);
2175         
2176         headers.item(col).addClass(sortClass);
2177     },
2178
2179     /**
2180      * @private
2181      * Updates the size of every column and cell in the grid
2182      */
2183     updateAllColumnWidths : function() {
2184         var totalWidth = this.getTotalWidth(),
2185             colCount   = this.cm.getColumnCount(),
2186             rows       = this.getRows(),
2187             rowCount   = rows.length,
2188             widths     = [],
2189             row, rowFirstChild, trow, i, j;
2190         
2191         for (i = 0; i < colCount; i++) {
2192             widths[i] = this.getColumnWidth(i);
2193             this.getHeaderCell(i).style.width = widths[i];
2194         }
2195         
2196         this.updateHeaderWidth();
2197         
2198         for (i = 0; i < rowCount; i++) {
2199             row = rows[i];
2200             row.style.width = totalWidth;
2201             rowFirstChild = row.firstChild;
2202             
2203             if (rowFirstChild) {
2204                 rowFirstChild.style.width = totalWidth;
2205                 trow = rowFirstChild.rows[0];
2206                 
2207                 for (j = 0; j < colCount; j++) {
2208                     trow.childNodes[j].style.width = widths[j];
2209                 }
2210             }
2211         }
2212         
2213         this.onAllColumnWidthsUpdated(widths, totalWidth);
2214     },
2215
2216     /**
2217      * @private
2218      * Called after a column's width has been updated, this resizes all of the cells for that column in each row
2219      * @param {Number} column The column index
2220      */
2221     updateColumnWidth : function(column, width) {
2222         var columnWidth = this.getColumnWidth(column),
2223             totalWidth  = this.getTotalWidth(),
2224             headerCell  = this.getHeaderCell(column),
2225             nodes       = this.getRows(),
2226             nodeCount   = nodes.length,
2227             row, i, firstChild;
2228         
2229         this.updateHeaderWidth();
2230         headerCell.style.width = columnWidth;
2231         
2232         for (i = 0; i < nodeCount; i++) {
2233             row = nodes[i];
2234             firstChild = row.firstChild;
2235             
2236             row.style.width = totalWidth;
2237             if (firstChild) {
2238                 firstChild.style.width = totalWidth;
2239                 firstChild.rows[0].childNodes[column].style.width = columnWidth;
2240             }
2241         }
2242         
2243         this.onColumnWidthUpdated(column, columnWidth, totalWidth);
2244     },
2245     
2246     /**
2247      * @private
2248      * Sets the hidden status of a given column.
2249      * @param {Number} col The column index
2250      * @param {Boolean} hidden True to make the column hidden
2251      */
2252     updateColumnHidden : function(col, hidden) {
2253         var totalWidth = this.getTotalWidth(),
2254             display    = hidden ? 'none' : '',
2255             headerCell = this.getHeaderCell(col),
2256             nodes      = this.getRows(),
2257             nodeCount  = nodes.length,
2258             row, rowFirstChild, i;
2259         
2260         this.updateHeaderWidth();
2261         headerCell.style.display = display;
2262         
2263         for (i = 0; i < nodeCount; i++) {
2264             row = nodes[i];
2265             row.style.width = totalWidth;
2266             rowFirstChild = row.firstChild;
2267             
2268             if (rowFirstChild) {
2269                 rowFirstChild.style.width = totalWidth;
2270                 rowFirstChild.rows[0].childNodes[col].style.display = display;
2271             }
2272         }
2273         
2274         this.onColumnHiddenUpdated(col, hidden, totalWidth);
2275         delete this.lastViewWidth; //recalc
2276         this.layout();
2277     },
2278
2279     /**
2280      * @private
2281      * Renders all of the rows to a string buffer and returns the string. This is called internally
2282      * by renderRows and performs the actual string building for the rows - it does not inject HTML into the DOM.
2283      * @param {Array} columns The column data acquired from getColumnData.
2284      * @param {Array} records The array of records to render
2285      * @param {Ext.data.Store} store The store to render the rows from
2286      * @param {Number} startRow The index of the first row being rendered. Sometimes we only render a subset of
2287      * the rows so this is used to maintain logic for striping etc
2288      * @param {Number} colCount The total number of columns in the column model
2289      * @param {Boolean} stripe True to stripe the rows
2290      * @return {String} A string containing the HTML for the rendered rows
2291      */
2292     doRender : function(columns, records, store, startRow, colCount, stripe) {
2293         var templates = this.templates,
2294             cellTemplate = templates.cell,
2295             rowTemplate = templates.row,
2296             last = colCount - 1,
2297             tstyle = 'width:' + this.getTotalWidth() + ';',
2298             // buffers
2299             rowBuffer = [],
2300             colBuffer = [],
2301             rowParams = {tstyle: tstyle},
2302             meta = {},
2303             len  = records.length,
2304             alt,
2305             column,
2306             record, i, j, rowIndex;
2307
2308         //build up each row's HTML
2309         for (j = 0; j < len; j++) {
2310             record    = records[j];
2311             colBuffer = [];
2312
2313             rowIndex = j + startRow;
2314
2315             //build up each column's HTML
2316             for (i = 0; i < colCount; i++) {
2317                 column = columns[i];
2318                 
2319                 meta.id    = column.id;
2320                 meta.css   = i === 0 ? 'x-grid3-cell-first ' : (i == last ? 'x-grid3-cell-last ' : '');
2321                 meta.attr  = meta.cellAttr = '';
2322                 meta.style = column.style;
2323                 meta.value = column.renderer.call(column.scope, record.data[column.name], meta, record, rowIndex, i, store);
2324
2325                 if (Ext.isEmpty(meta.value)) {
2326                     meta.value = '&#160;';
2327                 }
2328
2329                 if (this.markDirty && record.dirty && typeof record.modified[column.name] != 'undefined') {
2330                     meta.css += ' x-grid3-dirty-cell';
2331                 }
2332
2333                 colBuffer[colBuffer.length] = cellTemplate.apply(meta);
2334             }
2335
2336             alt = [];
2337             //set up row striping and row dirtiness CSS classes
2338             if (stripe && ((rowIndex + 1) % 2 === 0)) {
2339                 alt[0] = 'x-grid3-row-alt';
2340             }
2341
2342             if (record.dirty) {
2343                 alt[1] = ' x-grid3-dirty-row';
2344             }
2345
2346             rowParams.cols = colCount;
2347
2348             if (this.getRowClass) {
2349                 alt[2] = this.getRowClass(record, rowIndex, rowParams, store);
2350             }
2351
2352             rowParams.alt   = alt.join(' ');
2353             rowParams.cells = colBuffer.join('');
2354
2355             rowBuffer[rowBuffer.length] = rowTemplate.apply(rowParams);
2356         }
2357
2358         return rowBuffer.join('');
2359     },
2360
2361     /**
2362      * @private
2363      * Adds CSS classes and rowIndex to each row
2364      * @param {Number} startRow The row to start from (defaults to 0)
2365      */
2366     processRows : function(startRow, skipStripe) {
2367         if (!this.ds || this.ds.getCount() < 1) {
2368             return;
2369         }
2370
2371         var rows   = this.getRows(),
2372             length = rows.length,
2373             row, i;
2374
2375         skipStripe = skipStripe || !this.grid.stripeRows;
2376         startRow   = startRow   || 0;
2377
2378         for (i = 0; i < length; i++) {
2379             row = rows[i];
2380             if (row) {
2381                 row.rowIndex = i;
2382                 if (!skipStripe) {
2383                     row.className = row.className.replace(this.rowClsRe, ' ');
2384                     if ((i + 1) % 2 === 0){
2385                         row.className += ' x-grid3-row-alt';
2386                     }
2387                 }
2388             }
2389         }
2390
2391         // add first/last-row classes
2392         if (startRow === 0) {
2393             Ext.fly(rows[0]).addClass(this.firstRowCls);
2394         }
2395
2396         Ext.fly(rows[length - 1]).addClass(this.lastRowCls);
2397     },
2398     
2399     /**
2400      * @private
2401      */
2402     afterRender : function() {
2403         if (!this.ds || !this.cm) {
2404             return;
2405         }
2406         
2407         this.mainBody.dom.innerHTML = this.renderBody() || '&#160;';
2408         this.processRows(0, true);
2409
2410         if (this.deferEmptyText !== true) {
2411             this.applyEmptyText();
2412         }
2413         
2414         this.grid.fireEvent('viewready', this.grid);
2415     },
2416     
2417     /**
2418      * @private
2419      * This is always intended to be called after renderUI. Sets up listeners on the UI elements
2420      * and sets up options like column menus, moving and resizing.
2421      */
2422     afterRenderUI: function() {
2423         var grid = this.grid;
2424         
2425         this.initElements();
2426
2427         // get mousedowns early
2428         Ext.fly(this.innerHd).on('click', this.handleHdDown, this);
2429
2430         this.mainHd.on({
2431             scope    : this,
2432             mouseover: this.handleHdOver,
2433             mouseout : this.handleHdOut,
2434             mousemove: this.handleHdMove
2435         });
2436
2437         this.scroller.on('scroll', this.syncScroll,  this);
2438         
2439         if (grid.enableColumnResize !== false) {
2440             this.splitZone = new Ext.grid.GridView.SplitDragZone(grid, this.mainHd.dom);
2441         }
2442
2443         if (grid.enableColumnMove) {
2444             this.columnDrag = new Ext.grid.GridView.ColumnDragZone(grid, this.innerHd);
2445             this.columnDrop = new Ext.grid.HeaderDropZone(grid, this.mainHd.dom);
2446         }
2447
2448         if (grid.enableHdMenu !== false) {
2449             this.hmenu = new Ext.menu.Menu({id: grid.id + '-hctx'});
2450             this.hmenu.add(
2451                 {itemId:'asc',  text: this.sortAscText,  cls: 'xg-hmenu-sort-asc'},
2452                 {itemId:'desc', text: this.sortDescText, cls: 'xg-hmenu-sort-desc'}
2453             );
2454
2455             if (grid.enableColumnHide !== false) {
2456                 this.colMenu = new Ext.menu.Menu({id:grid.id + '-hcols-menu'});
2457                 this.colMenu.on({
2458                     scope     : this,
2459                     beforeshow: this.beforeColMenuShow,
2460                     itemclick : this.handleHdMenuClick
2461                 });
2462                 this.hmenu.add('-', {
2463                     itemId:'columns',
2464                     hideOnClick: false,
2465                     text: this.columnsText,
2466                     menu: this.colMenu,
2467                     iconCls: 'x-cols-icon'
2468                 });
2469             }
2470
2471             this.hmenu.on('itemclick', this.handleHdMenuClick, this);
2472         }
2473
2474         if (grid.trackMouseOver) {
2475             this.mainBody.on({
2476                 scope    : this,
2477                 mouseover: this.onRowOver,
2478                 mouseout : this.onRowOut
2479             });
2480         }
2481
2482         if (grid.enableDragDrop || grid.enableDrag) {
2483             this.dragZone = new Ext.grid.GridDragZone(grid, {
2484                 ddGroup : grid.ddGroup || 'GridDD'
2485             });
2486         }
2487
2488         this.updateHeaderSortState();
2489     },
2490
2491     /**
2492      * @private
2493      * Renders each of the UI elements in turn. This is called internally, once, by this.render. It does not
2494      * render rows from the store, just the surrounding UI elements.
2495      */
2496     renderUI : function() {
2497         var templates = this.templates;
2498
2499         return templates.master.apply({
2500             body  : templates.body.apply({rows:'&#160;'}),
2501             header: this.renderHeaders(),
2502             ostyle: 'width:' + this.getOffsetWidth() + ';',
2503             bstyle: 'width:' + this.getTotalWidth()  + ';'
2504         });
2505     },
2506
2507     // private
2508     processEvent : function(name, e) {
2509         var target = e.getTarget(),
2510             grid   = this.grid,
2511             header = this.findHeaderIndex(target),
2512             row, cell, col, body;
2513
2514         grid.fireEvent(name, e);
2515
2516         if (header !== false) {
2517             grid.fireEvent('header' + name, grid, header, e);
2518         } else {
2519             row = this.findRowIndex(target);
2520
2521 //          Grid's value-added events must bubble correctly to allow cancelling via returning false: cell->column->row
2522 //          We must allow a return of false at any of these levels to cancel the event processing.
2523 //          Particularly allowing rowmousedown to be cancellable by prior handlers which need to prevent selection.
2524             if (row !== false) {
2525                 cell = this.findCellIndex(target);
2526                 if (cell !== false) {
2527                     col = grid.colModel.getColumnAt(cell);
2528                     if (grid.fireEvent('cell' + name, grid, row, cell, e) !== false) {
2529                         if (!col || (col.processEvent && (col.processEvent(name, e, grid, row, cell) !== false))) {
2530                             grid.fireEvent('row' + name, grid, row, e);
2531                         }
2532                     }
2533                 } else {
2534                     if (grid.fireEvent('row' + name, grid, row, e) !== false) {
2535                         (body = this.findRowBody(target)) && grid.fireEvent('rowbody' + name, grid, row, e);
2536                     }
2537                 }
2538             } else {
2539                 grid.fireEvent('container' + name, grid, e);
2540             }
2541         }
2542     },
2543
2544     /**
2545      * @private
2546      * Sizes the grid's header and body elements
2547      */
2548     layout : function(initial) {
2549         if (!this.mainBody) {
2550             return; // not rendered
2551         }
2552
2553         var grid       = this.grid,
2554             gridEl     = grid.getGridEl(),
2555             gridSize   = gridEl.getSize(true),
2556             gridWidth  = gridSize.width,
2557             gridHeight = gridSize.height,
2558             scroller   = this.scroller,
2559             scrollStyle, headerHeight, scrollHeight;
2560         
2561         if (gridWidth < 20 || gridHeight < 20) {
2562             return;
2563         }
2564         
2565         if (grid.autoHeight) {
2566             scrollStyle = scroller.dom.style;
2567             scrollStyle.overflow = 'visible';
2568             
2569             if (Ext.isWebKit) {
2570                 scrollStyle.position = 'static';
2571             }
2572         } else {
2573             this.el.setSize(gridWidth, gridHeight);
2574             
2575             headerHeight = this.mainHd.getHeight();
2576             scrollHeight = gridHeight - headerHeight;
2577             
2578             scroller.setSize(gridWidth, scrollHeight);
2579             
2580             if (this.innerHd) {
2581                 this.innerHd.style.width = (gridWidth) + "px";
2582             }
2583         }
2584         
2585         if (this.forceFit || (initial === true && this.autoFill)) {
2586             if (this.lastViewWidth != gridWidth) {
2587                 this.fitColumns(false, false);
2588                 this.lastViewWidth = gridWidth;
2589             }
2590         } else {
2591             this.autoExpand();
2592             this.syncHeaderScroll();
2593         }
2594         
2595         this.onLayout(gridWidth, scrollHeight);
2596     },
2597
2598     // template functions for subclasses and plugins
2599     // these functions include precalculated values
2600     onLayout : function(vw, vh) {
2601         // do nothing
2602     },
2603
2604     onColumnWidthUpdated : function(col, w, tw) {
2605         //template method
2606     },
2607
2608     onAllColumnWidthsUpdated : function(ws, tw) {
2609         //template method
2610     },
2611
2612     onColumnHiddenUpdated : function(col, hidden, tw) {
2613         // template method
2614     },
2615
2616     updateColumnText : function(col, text) {
2617         // template method
2618     },
2619
2620     afterMove : function(colIndex) {
2621         // template method
2622     },
2623
2624     /* ----------------------------------- Core Specific -------------------------------------------*/
2625     // private
2626     init : function(grid) {
2627         this.grid = grid;
2628
2629         this.initTemplates();
2630         this.initData(grid.store, grid.colModel);
2631         this.initUI(grid);
2632     },
2633
2634     // private
2635     getColumnId : function(index){
2636         return this.cm.getColumnId(index);
2637     },
2638
2639     // private
2640     getOffsetWidth : function() {
2641         return (this.cm.getTotalWidth() + this.getScrollOffset()) + 'px';
2642     },
2643
2644     // private
2645     getScrollOffset: function() {
2646         return Ext.num(this.scrollOffset, Ext.getScrollBarWidth());
2647     },
2648
2649     /**
2650      * @private
2651      * Renders the header row using the 'header' template. Does not inject the HTML into the DOM, just
2652      * returns a string.
2653      * @return {String} Rendered header row
2654      */
2655     renderHeaders : function() {
2656         var colModel   = this.cm,
2657             templates  = this.templates,
2658             headerTpl  = templates.hcell,
2659             properties = {},
2660             colCount   = colModel.getColumnCount(),
2661             last       = colCount - 1,
2662             cells      = [],
2663             i, cssCls;
2664         
2665         for (i = 0; i < colCount; i++) {
2666             if (i == 0) {
2667                 cssCls = 'x-grid3-cell-first ';
2668             } else {
2669                 cssCls = i == last ? 'x-grid3-cell-last ' : '';
2670             }
2671             
2672             properties = {
2673                 id     : colModel.getColumnId(i),
2674                 value  : colModel.getColumnHeader(i) || '',
2675                 style  : this.getColumnStyle(i, true),
2676                 css    : cssCls,
2677                 tooltip: this.getColumnTooltip(i)
2678             };
2679             
2680             if (colModel.config[i].align == 'right') {
2681                 properties.istyle = 'padding-right: 16px;';
2682             } else {
2683                 delete properties.istyle;
2684             }
2685             
2686             cells[i] = headerTpl.apply(properties);
2687         }
2688         
2689         return templates.header.apply({
2690             cells : cells.join(""),
2691             tstyle: String.format("width: {0};", this.getTotalWidth())
2692         });
2693     },
2694
2695     /**
2696      * @private
2697      */
2698     getColumnTooltip : function(i) {
2699         var tooltip = this.cm.getColumnTooltip(i);
2700         if (tooltip) {
2701             if (Ext.QuickTips.isEnabled()) {
2702                 return 'ext:qtip="' + tooltip + '"';
2703             } else {
2704                 return 'title="' + tooltip + '"';
2705             }
2706         }
2707         
2708         return '';
2709     },
2710
2711     // private
2712     beforeUpdate : function() {
2713         this.grid.stopEditing(true);
2714     },
2715
2716     /**
2717      * @private
2718      * Re-renders the headers and ensures they are sized correctly
2719      */
2720     updateHeaders : function() {
2721         this.innerHd.firstChild.innerHTML = this.renderHeaders();
2722         
2723         this.updateHeaderWidth(false);
2724     },
2725     
2726     /**
2727      * @private
2728      * Ensures that the header is sized to the total width available to it
2729      * @param {Boolean} updateMain True to update the mainBody's width also (defaults to true)
2730      */
2731     updateHeaderWidth: function(updateMain) {
2732         var innerHdChild = this.innerHd.firstChild,
2733             totalWidth   = this.getTotalWidth();
2734         
2735         innerHdChild.style.width = this.getOffsetWidth();
2736         innerHdChild.firstChild.style.width = totalWidth;
2737         
2738         if (updateMain !== false) {
2739             this.mainBody.dom.style.width = totalWidth;
2740         }
2741     },
2742
2743     /**
2744      * Focuses the specified row.
2745      * @param {Number} row The row index
2746      */
2747     focusRow : function(row) {
2748         this.focusCell(row, 0, false);
2749     },
2750
2751     /**
2752      * Focuses the specified cell.
2753      * @param {Number} row The row index
2754      * @param {Number} col The column index
2755      */
2756     focusCell : function(row, col, hscroll) {
2757         this.syncFocusEl(this.ensureVisible(row, col, hscroll));
2758         
2759         var focusEl = this.focusEl;
2760         
2761         if (Ext.isGecko) {
2762             focusEl.focus();
2763         } else {
2764             focusEl.focus.defer(1, focusEl);
2765         }
2766     },
2767
2768     /**
2769      * @private
2770      * Finds the Elements corresponding to the given row and column indexes
2771      */
2772     resolveCell : function(row, col, hscroll) {
2773         if (!Ext.isNumber(row)) {
2774             row = row.rowIndex;
2775         }
2776         
2777         if (!this.ds) {
2778             return null;
2779         }
2780         
2781         if (row < 0 || row >= this.ds.getCount()) {
2782             return null;
2783         }
2784         col = (col !== undefined ? col : 0);
2785
2786         var rowEl    = this.getRow(row),
2787             colModel = this.cm,
2788             colCount = colModel.getColumnCount(),
2789             cellEl;
2790             
2791         if (!(hscroll === false && col === 0)) {
2792             while (col < colCount && colModel.isHidden(col)) {
2793                 col++;
2794             }
2795             
2796             cellEl = this.getCell(row, col);
2797         }
2798
2799         return {row: rowEl, cell: cellEl};
2800     },
2801
2802     /**
2803      * @private
2804      * Returns the XY co-ordinates of a given row/cell resolution (see {@link #resolveCell})
2805      * @return {Array} X and Y coords
2806      */
2807     getResolvedXY : function(resolved) {
2808         if (!resolved) {
2809             return null;
2810         }
2811         
2812         var cell = resolved.cell,
2813             row  = resolved.row;
2814         
2815         if (cell) {
2816             return Ext.fly(cell).getXY();
2817         } else {
2818             return [this.el.getX(), Ext.fly(row).getY()];
2819         }
2820     },
2821
2822     /**
2823      * @private
2824      * Moves the focus element to the x and y co-ordinates of the given row and column
2825      */
2826     syncFocusEl : function(row, col, hscroll) {
2827         var xy = row;
2828         
2829         if (!Ext.isArray(xy)) {
2830             row = Math.min(row, Math.max(0, this.getRows().length-1));
2831             
2832             if (isNaN(row)) {
2833                 return;
2834             }
2835             
2836             xy = this.getResolvedXY(this.resolveCell(row, col, hscroll));
2837         }
2838         
2839         this.focusEl.setXY(xy || this.scroller.getXY());
2840     },
2841
2842     /**
2843      * @private
2844      */
2845     ensureVisible : function(row, col, hscroll) {
2846         var resolved = this.resolveCell(row, col, hscroll);
2847         
2848         if (!resolved || !resolved.row) {
2849             return null;
2850         }
2851
2852         var rowEl  = resolved.row,
2853             cellEl = resolved.cell,
2854             c = this.scroller.dom,
2855             p = rowEl,
2856             ctop = 0,
2857             stop = this.el.dom;
2858
2859         while (p && p != stop) {
2860             ctop += p.offsetTop;
2861             p = p.offsetParent;
2862         }
2863
2864         ctop -= this.mainHd.dom.offsetHeight;
2865         stop = parseInt(c.scrollTop, 10);
2866
2867         var cbot = ctop + rowEl.offsetHeight,
2868             ch = c.clientHeight,
2869             sbot = stop + ch;
2870
2871
2872         if (ctop < stop) {
2873           c.scrollTop = ctop;
2874         } else if(cbot > sbot) {
2875             c.scrollTop = cbot-ch;
2876         }
2877
2878         if (hscroll !== false) {
2879             var cleft  = parseInt(cellEl.offsetLeft, 10),
2880                 cright = cleft + cellEl.offsetWidth,
2881                 sleft  = parseInt(c.scrollLeft, 10),
2882                 sright = sleft + c.clientWidth;
2883                 
2884             if (cleft < sleft) {
2885                 c.scrollLeft = cleft;
2886             } else if(cright > sright) {
2887                 c.scrollLeft = cright-c.clientWidth;
2888             }
2889         }
2890         
2891         return this.getResolvedXY(resolved);
2892     },
2893
2894     // private
2895     insertRows : function(dm, firstRow, lastRow, isUpdate) {
2896         var last = dm.getCount() - 1;
2897         if( !isUpdate && firstRow === 0 && lastRow >= last) {
2898             this.fireEvent('beforerowsinserted', this, firstRow, lastRow);
2899                 this.refresh();
2900             this.fireEvent('rowsinserted', this, firstRow, lastRow);
2901         } else {
2902             if (!isUpdate) {
2903                 this.fireEvent('beforerowsinserted', this, firstRow, lastRow);
2904             }
2905             var html = this.renderRows(firstRow, lastRow),
2906                 before = this.getRow(firstRow);
2907             if (before) {
2908                 if(firstRow === 0){
2909                     Ext.fly(this.getRow(0)).removeClass(this.firstRowCls);
2910                 }
2911                 Ext.DomHelper.insertHtml('beforeBegin', before, html);
2912             } else {
2913                 var r = this.getRow(last - 1);
2914                 if(r){
2915                     Ext.fly(r).removeClass(this.lastRowCls);
2916                 }
2917                 Ext.DomHelper.insertHtml('beforeEnd', this.mainBody.dom, html);
2918             }
2919             if (!isUpdate) {
2920                 this.processRows(firstRow);
2921                 this.fireEvent('rowsinserted', this, firstRow, lastRow);
2922             } else if (firstRow === 0 || firstRow >= last) {
2923                 //ensure first/last row is kept after an update.
2924                 Ext.fly(this.getRow(firstRow)).addClass(firstRow === 0 ? this.firstRowCls : this.lastRowCls);
2925             }
2926         }
2927         this.syncFocusEl(firstRow);
2928     },
2929
2930     /**
2931      * @private
2932      * DEPRECATED - this doesn't appear to be called anywhere in the library, remove in 4.0. 
2933      */
2934     deleteRows : function(dm, firstRow, lastRow) {
2935         if (dm.getRowCount() < 1) {
2936             this.refresh();
2937         } else {
2938             this.fireEvent('beforerowsdeleted', this, firstRow, lastRow);
2939
2940             this.removeRows(firstRow, lastRow);
2941
2942             this.processRows(firstRow);
2943             this.fireEvent('rowsdeleted', this, firstRow, lastRow);
2944         }
2945     },
2946
2947     /**
2948      * @private
2949      * Builds a CSS string for the given column index
2950      * @param {Number} colIndex The column index
2951      * @param {Boolean} isHeader True if getting the style for the column's header
2952      * @return {String} The CSS string
2953      */
2954     getColumnStyle : function(colIndex, isHeader) {
2955         var colModel  = this.cm,
2956             colConfig = colModel.config,
2957             style     = isHeader ? '' : colConfig[colIndex].css || '',
2958             align     = colConfig[colIndex].align;
2959         
2960         style += String.format("width: {0};", this.getColumnWidth(colIndex));
2961         
2962         if (colModel.isHidden(colIndex)) {
2963             style += 'display: none; ';
2964         }
2965         
2966         if (align) {
2967             style += String.format("text-align: {0};", align);
2968         }
2969         
2970         return style;
2971     },
2972
2973     /**
2974      * @private
2975      * Returns the width of a given column minus its border width
2976      * @return {Number} The column index
2977      * @return {String|Number} The width in pixels
2978      */
2979     getColumnWidth : function(column) {
2980         var columnWidth = this.cm.getColumnWidth(column),
2981             borderWidth = this.borderWidth;
2982         
2983         if (Ext.isNumber(columnWidth)) {
2984             if (Ext.isBorderBox || (Ext.isWebKit && !Ext.isSafari2)) {
2985                 return columnWidth + "px";
2986             } else {
2987                 return Math.max(columnWidth - borderWidth, 0) + "px";
2988             }
2989         } else {
2990             return columnWidth;
2991         }
2992     },
2993
2994     /**
2995      * @private
2996      * Returns the total width of all visible columns
2997      * @return {String} 
2998      */
2999     getTotalWidth : function() {
3000         return this.cm.getTotalWidth() + 'px';
3001     },
3002
3003     /**
3004      * @private
3005      * Resizes each column to fit the available grid width.
3006      * TODO: The second argument isn't even used, remove it in 4.0
3007      * @param {Boolean} preventRefresh True to prevent resizing of each row to the new column sizes (defaults to false)
3008      * @param {null} onlyExpand NOT USED, will be removed in 4.0
3009      * @param {Number} omitColumn The index of a column to leave at its current width. Defaults to undefined
3010      * @return {Boolean} True if the operation succeeded, false if not or undefined if the grid view is not yet initialized
3011      */
3012     fitColumns : function(preventRefresh, onlyExpand, omitColumn) {
3013         var grid          = this.grid,
3014             colModel      = this.cm,
3015             totalColWidth = colModel.getTotalWidth(false),
3016             gridWidth     = this.getGridInnerWidth(),
3017             extraWidth    = gridWidth - totalColWidth,
3018             columns       = [],
3019             extraCol      = 0,
3020             width         = 0,
3021             colWidth, fraction, i;
3022         
3023         // not initialized, so don't screw up the default widths
3024         if (gridWidth < 20 || extraWidth === 0) {
3025             return false;
3026         }
3027         
3028         var visibleColCount = colModel.getColumnCount(true),
3029             totalColCount   = colModel.getColumnCount(false),
3030             adjCount        = visibleColCount - (Ext.isNumber(omitColumn) ? 1 : 0);
3031         
3032         if (adjCount === 0) {
3033             adjCount = 1;
3034             omitColumn = undefined;
3035         }
3036         
3037         //FIXME: the algorithm used here is odd and potentially confusing. Includes this for loop and the while after it.
3038         for (i = 0; i < totalColCount; i++) {
3039             if (!colModel.isFixed(i) && i !== omitColumn) {
3040                 colWidth = colModel.getColumnWidth(i);
3041                 columns.push(i, colWidth);
3042                 
3043                 if (!colModel.isHidden(i)) {
3044                     extraCol = i;
3045                     width += colWidth;
3046                 }
3047             }
3048         }
3049         
3050         fraction = (gridWidth - colModel.getTotalWidth()) / width;
3051         
3052         while (columns.length) {
3053             colWidth = columns.pop();
3054             i        = columns.pop();
3055             
3056             colModel.setColumnWidth(i, Math.max(grid.minColumnWidth, Math.floor(colWidth + colWidth * fraction)), true);
3057         }
3058         
3059         //this has been changed above so remeasure now
3060         totalColWidth = colModel.getTotalWidth(false);
3061         
3062         if (totalColWidth > gridWidth) {
3063             var adjustCol = (adjCount == visibleColCount) ? extraCol : omitColumn,
3064                 newWidth  = Math.max(1, colModel.getColumnWidth(adjustCol) - (totalColWidth - gridWidth));
3065             
3066             colModel.setColumnWidth(adjustCol, newWidth, true);
3067         }
3068         
3069         if (preventRefresh !== true) {
3070             this.updateAllColumnWidths();
3071         }
3072         
3073         return true;
3074     },
3075
3076     /**
3077      * @private
3078      * Resizes the configured autoExpandColumn to take the available width after the other columns have 
3079      * been accounted for
3080      * @param {Boolean} preventUpdate True to prevent the resizing of all rows (defaults to false)
3081      */
3082     autoExpand : function(preventUpdate) {
3083         var grid             = this.grid,
3084             colModel         = this.cm,
3085             gridWidth        = this.getGridInnerWidth(),
3086             totalColumnWidth = colModel.getTotalWidth(false),
3087             autoExpandColumn = grid.autoExpandColumn;
3088         
3089         if (!this.userResized && autoExpandColumn) {
3090             if (gridWidth != totalColumnWidth) {
3091                 //if we are not already using all available width, resize the autoExpandColumn
3092                 var colIndex     = colModel.getIndexById(autoExpandColumn),
3093                     currentWidth = colModel.getColumnWidth(colIndex),
3094                     desiredWidth = gridWidth - totalColumnWidth + currentWidth,
3095                     newWidth     = Math.min(Math.max(desiredWidth, grid.autoExpandMin), grid.autoExpandMax);
3096                 
3097                 if (currentWidth != newWidth) {
3098                     colModel.setColumnWidth(colIndex, newWidth, true);
3099                     
3100                     if (preventUpdate !== true) {
3101                         this.updateColumnWidth(colIndex, newWidth);
3102                     }
3103                 }
3104             }
3105         }
3106     },
3107     
3108     /**
3109      * Returns the total internal width available to the grid, taking the scrollbar into account
3110      * @return {Number} The total width
3111      */
3112     getGridInnerWidth: function() {
3113         return this.grid.getGridEl().getWidth(true) - this.getScrollOffset();
3114     },
3115
3116     /**
3117      * @private
3118      * Returns an array of column configurations - one for each column
3119      * @return {Array} Array of column config objects. This includes the column name, renderer, id style and renderer
3120      */
3121     getColumnData : function() {
3122         var columns  = [],
3123             colModel = this.cm,
3124             colCount = colModel.getColumnCount(),
3125             fields   = this.ds.fields,
3126             i, name;
3127         
3128         for (i = 0; i < colCount; i++) {
3129             name = colModel.getDataIndex(i);
3130             
3131             columns[i] = {
3132                 name    : Ext.isDefined(name) ? name : (fields.get(i) ? fields.get(i).name : undefined),
3133                 renderer: colModel.getRenderer(i),
3134                 scope   : colModel.getRendererScope(i),
3135                 id      : colModel.getColumnId(i),
3136                 style   : this.getColumnStyle(i)
3137             };
3138         }
3139         
3140         return columns;
3141     },
3142
3143     /**
3144      * @private
3145      * Renders rows between start and end indexes
3146      * @param {Number} startRow Index of the first row to render
3147      * @param {Number} endRow Index of the last row to render
3148      */
3149     renderRows : function(startRow, endRow) {
3150         var grid     = this.grid,
3151             store    = grid.store,
3152             stripe   = grid.stripeRows,
3153             colModel = grid.colModel,
3154             colCount = colModel.getColumnCount(),
3155             rowCount = store.getCount(),
3156             records;
3157         
3158         if (rowCount < 1) {
3159             return '';
3160         }
3161         
3162         startRow = startRow || 0;
3163         endRow   = Ext.isDefined(endRow) ? endRow : rowCount - 1;
3164         records  = store.getRange(startRow, endRow);
3165         
3166         return this.doRender(this.getColumnData(), records, store, startRow, colCount, stripe);
3167     },
3168
3169     // private
3170     renderBody : function(){
3171         var markup = this.renderRows() || '&#160;';
3172         return this.templates.body.apply({rows: markup});
3173     },
3174
3175     /**
3176      * @private
3177      * Refreshes a row by re-rendering it. Fires the rowupdated event when done
3178      */
3179     refreshRow: function(record) {
3180         var store     = this.ds,
3181             colCount  = this.cm.getColumnCount(),
3182             columns   = this.getColumnData(),
3183             last      = colCount - 1,
3184             cls       = ['x-grid3-row'],
3185             rowParams = {
3186                 tstyle: String.format("width: {0};", this.getTotalWidth())
3187             },
3188             colBuffer = [],
3189             cellTpl   = this.templates.cell,
3190             rowIndex, row, column, meta, css, i;
3191         
3192         if (Ext.isNumber(record)) {
3193             rowIndex = record;
3194             record   = store.getAt(rowIndex);
3195         } else {
3196             rowIndex = store.indexOf(record);
3197         }
3198         
3199         //the record could not be found
3200         if (!record || rowIndex < 0) {
3201             return;
3202         }
3203         
3204         //builds each column in this row
3205         for (i = 0; i < colCount; i++) {
3206             column = columns[i];
3207             
3208             if (i == 0) {
3209                 css = 'x-grid3-cell-first';
3210             } else {
3211                 css = (i == last) ? 'x-grid3-cell-last ' : '';
3212             }
3213             
3214             meta = {
3215                 id      : column.id,
3216                 style   : column.style,
3217                 css     : css,
3218                 attr    : "",
3219                 cellAttr: ""
3220             };
3221             // Need to set this after, because we pass meta to the renderer
3222             meta.value = column.renderer.call(column.scope, record.data[column.name], meta, record, rowIndex, i, store);
3223             
3224             if (Ext.isEmpty(meta.value)) {
3225                 meta.value = '&#160;';
3226             }
3227             
3228             if (this.markDirty && record.dirty && typeof record.modified[column.name] != 'undefined') {
3229                 meta.css += ' x-grid3-dirty-cell';
3230             }
3231             
3232             colBuffer[i] = cellTpl.apply(meta);
3233         }
3234         
3235         row = this.getRow(rowIndex);
3236         row.className = '';
3237         
3238         if (this.grid.stripeRows && ((rowIndex + 1) % 2 === 0)) {
3239             cls.push('x-grid3-row-alt');
3240         }
3241         
3242         if (this.getRowClass) {
3243             rowParams.cols = colCount;
3244             cls.push(this.getRowClass(record, rowIndex, rowParams, store));
3245         }
3246         
3247         this.fly(row).addClass(cls).setStyle(rowParams.tstyle);
3248         rowParams.cells = colBuffer.join("");
3249         row.innerHTML = this.templates.rowInner.apply(rowParams);
3250         
3251         this.fireEvent('rowupdated', this, rowIndex, record);
3252     },
3253
3254     /**
3255      * Refreshs the grid UI
3256      * @param {Boolean} headersToo (optional) True to also refresh the headers
3257      */
3258     refresh : function(headersToo) {
3259         this.fireEvent('beforerefresh', this);
3260         this.grid.stopEditing(true);
3261
3262         var result = this.renderBody();
3263         this.mainBody.update(result).setWidth(this.getTotalWidth());
3264         if (headersToo === true) {
3265             this.updateHeaders();
3266             this.updateHeaderSortState();
3267         }
3268         this.processRows(0, true);
3269         this.layout();
3270         this.applyEmptyText();
3271         this.fireEvent('refresh', this);
3272     },
3273
3274     /**
3275      * @private
3276      * Displays the configured emptyText if there are currently no rows to display
3277      */
3278     applyEmptyText : function() {
3279         if (this.emptyText && !this.hasRows()) {
3280             this.mainBody.update('<div class="x-grid-empty">' + this.emptyText + '</div>');
3281         }
3282     },
3283
3284     /**
3285      * @private
3286      * Adds sorting classes to the column headers based on the bound store's sortInfo. Fires the 'sortchange' event
3287      * if the sorting has changed since this function was last run.
3288      */
3289     updateHeaderSortState : function() {
3290         var state = this.ds.getSortState();
3291         if (!state) {
3292             return;
3293         }
3294
3295         if (!this.sortState || (this.sortState.field != state.field || this.sortState.direction != state.direction)) {
3296             this.grid.fireEvent('sortchange', this.grid, state);
3297         }
3298
3299         this.sortState = state;
3300
3301         var sortColumn = this.cm.findColumnIndex(state.field);
3302         if (sortColumn != -1) {
3303             var sortDir = state.direction;
3304             this.updateSortIcon(sortColumn, sortDir);
3305         }
3306     },
3307
3308     /**
3309      * @private
3310      * Removes any sorting indicator classes from the column headers
3311      */
3312     clearHeaderSortState : function() {
3313         if (!this.sortState) {
3314             return;
3315         }
3316         this.grid.fireEvent('sortchange', this.grid, null);
3317         this.mainHd.select('td').removeClass(this.sortClasses);
3318         delete this.sortState;
3319     },
3320
3321     /**
3322      * @private
3323      * Destroys all objects associated with the GridView
3324      */
3325     destroy : function() {
3326         var me              = this,
3327             grid            = me.grid,
3328             gridEl          = grid.getGridEl(),
3329             dragZone        = me.dragZone,
3330             splitZone       = me.splitZone,
3331             columnDrag      = me.columnDrag,
3332             columnDrop      = me.columnDrop,
3333             scrollToTopTask = me.scrollToTopTask,
3334             columnDragData,
3335             columnDragProxy;
3336         
3337         if (scrollToTopTask && scrollToTopTask.cancel) {
3338             scrollToTopTask.cancel();
3339         }
3340         
3341         Ext.destroyMembers(me, 'colMenu', 'hmenu');
3342
3343         me.initData(null, null);
3344         me.purgeListeners();
3345         
3346         Ext.fly(me.innerHd).un("click", me.handleHdDown, me);
3347
3348         if (grid.enableColumnMove) {
3349             columnDragData = columnDrag.dragData;
3350             columnDragProxy = columnDrag.proxy;
3351             Ext.destroy(
3352                 columnDrag.el,
3353                 columnDragProxy.ghost,
3354                 columnDragProxy.el,
3355                 columnDrop.el,
3356                 columnDrop.proxyTop,
3357                 columnDrop.proxyBottom,
3358                 columnDragData.ddel,
3359                 columnDragData.header
3360             );
3361             
3362             if (columnDragProxy.anim) {
3363                 Ext.destroy(columnDragProxy.anim);
3364             }
3365             
3366             delete columnDragProxy.ghost;
3367             delete columnDragData.ddel;
3368             delete columnDragData.header;
3369             columnDrag.destroy();
3370             
3371             delete Ext.dd.DDM.locationCache[columnDrag.id];
3372             delete columnDrag._domRef;
3373
3374             delete columnDrop.proxyTop;
3375             delete columnDrop.proxyBottom;
3376             columnDrop.destroy();
3377             delete Ext.dd.DDM.locationCache["gridHeader" + gridEl.id];
3378             delete columnDrop._domRef;
3379             delete Ext.dd.DDM.ids[columnDrop.ddGroup];
3380         }
3381
3382         if (splitZone) { // enableColumnResize
3383             splitZone.destroy();
3384             delete splitZone._domRef;
3385             delete Ext.dd.DDM.ids["gridSplitters" + gridEl.id];
3386         }
3387
3388         Ext.fly(me.innerHd).removeAllListeners();
3389         Ext.removeNode(me.innerHd);
3390         delete me.innerHd;
3391
3392         Ext.destroy(
3393             me.el,
3394             me.mainWrap,
3395             me.mainHd,
3396             me.scroller,
3397             me.mainBody,
3398             me.focusEl,
3399             me.resizeMarker,
3400             me.resizeProxy,
3401             me.activeHdBtn,
3402             me._flyweight,
3403             dragZone,
3404             splitZone
3405         );
3406
3407         delete grid.container;
3408
3409         if (dragZone) {
3410             dragZone.destroy();
3411         }
3412
3413         Ext.dd.DDM.currentTarget = null;
3414         delete Ext.dd.DDM.locationCache[gridEl.id];
3415
3416         Ext.EventManager.removeResizeListener(me.onWindowResize, me);
3417     },
3418
3419     // private
3420     onDenyColumnHide : function() {
3421
3422     },
3423
3424     // private
3425     render : function() {
3426         if (this.autoFill) {
3427             var ct = this.grid.ownerCt;
3428             
3429             if (ct && ct.getLayout()) {
3430                 ct.on('afterlayout', function() {
3431                     this.fitColumns(true, true);
3432                     this.updateHeaders();
3433                     this.updateHeaderSortState();
3434                 }, this, {single: true});
3435             }
3436         } else if (this.forceFit) {
3437             this.fitColumns(true, false);
3438         } else if (this.grid.autoExpandColumn) {
3439             this.autoExpand(true);
3440         }
3441         
3442         this.grid.getGridEl().dom.innerHTML = this.renderUI();
3443         
3444         this.afterRenderUI();
3445     },
3446
3447     /* --------------------------------- Model Events and Handlers --------------------------------*/
3448     
3449     /**
3450      * @private
3451      * Binds a new Store and ColumnModel to this GridView. Removes any listeners from the old objects (if present)
3452      * and adds listeners to the new ones
3453      * @param {Ext.data.Store} newStore The new Store instance
3454      * @param {Ext.grid.ColumnModel} newColModel The new ColumnModel instance
3455      */
3456     initData : function(newStore, newColModel) {
3457         var me = this;
3458         
3459         if (me.ds) {
3460             var oldStore = me.ds;
3461             
3462             oldStore.un('add', me.onAdd, me);
3463             oldStore.un('load', me.onLoad, me);
3464             oldStore.un('clear', me.onClear, me);
3465             oldStore.un('remove', me.onRemove, me);
3466             oldStore.un('update', me.onUpdate, me);
3467             oldStore.un('datachanged', me.onDataChange, me);
3468             
3469             if (oldStore !== newStore && oldStore.autoDestroy) {
3470                 oldStore.destroy();
3471             }
3472         }
3473         
3474         if (newStore) {
3475             newStore.on({
3476                 scope      : me,
3477                 load       : me.onLoad,
3478                 add        : me.onAdd,
3479                 remove     : me.onRemove,
3480                 update     : me.onUpdate,
3481                 clear      : me.onClear,
3482                 datachanged: me.onDataChange
3483             });
3484         }
3485         
3486         if (me.cm) {
3487             var oldColModel = me.cm;
3488             
3489             oldColModel.un('configchange', me.onColConfigChange, me);
3490             oldColModel.un('widthchange',  me.onColWidthChange, me);
3491             oldColModel.un('headerchange', me.onHeaderChange, me);
3492             oldColModel.un('hiddenchange', me.onHiddenChange, me);
3493             oldColModel.un('columnmoved',  me.onColumnMove, me);
3494         }
3495         
3496         if (newColModel) {
3497             delete me.lastViewWidth;
3498             
3499             newColModel.on({
3500                 scope       : me,
3501                 configchange: me.onColConfigChange,
3502                 widthchange : me.onColWidthChange,
3503                 headerchange: me.onHeaderChange,
3504                 hiddenchange: me.onHiddenChange,
3505                 columnmoved : me.onColumnMove
3506             });
3507         }
3508         
3509         me.ds = newStore;
3510         me.cm = newColModel;
3511     },
3512
3513     // private
3514     onDataChange : function(){
3515         this.refresh(true);
3516         this.updateHeaderSortState();
3517         this.syncFocusEl(0);
3518     },
3519
3520     // private
3521     onClear : function() {
3522         this.refresh();
3523         this.syncFocusEl(0);
3524     },
3525
3526     // private
3527     onUpdate : function(store, record) {
3528         this.refreshRow(record);
3529     },
3530
3531     // private
3532     onAdd : function(store, records, index) {
3533         this.insertRows(store, index, index + (records.length-1));
3534     },
3535
3536     // private
3537     onRemove : function(store, record, index, isUpdate) {
3538         if (isUpdate !== true) {
3539             this.fireEvent('beforerowremoved', this, index, record);
3540         }
3541         
3542         this.removeRow(index);
3543         
3544         if (isUpdate !== true) {
3545             this.processRows(index);
3546             this.applyEmptyText();
3547             this.fireEvent('rowremoved', this, index, record);
3548         }
3549     },
3550
3551     /**
3552      * @private
3553      * Called when a store is loaded, scrolls to the top row
3554      */
3555     onLoad : function() {
3556         if (Ext.isGecko) {
3557             if (!this.scrollToTopTask) {
3558                 this.scrollToTopTask = new Ext.util.DelayedTask(this.scrollToTop, this);
3559             }
3560             this.scrollToTopTask.delay(1);
3561         } else {
3562             this.scrollToTop();
3563         }
3564     },
3565
3566     // private
3567     onColWidthChange : function(cm, col, width) {
3568         this.updateColumnWidth(col, width);
3569     },
3570
3571     // private
3572     onHeaderChange : function(cm, col, text) {
3573         this.updateHeaders();
3574     },
3575
3576     // private
3577     onHiddenChange : function(cm, col, hidden) {
3578         this.updateColumnHidden(col, hidden);
3579     },
3580
3581     // private
3582     onColumnMove : function(cm, oldIndex, newIndex) {
3583         this.indexMap = null;
3584         this.refresh(true);
3585         this.restoreScroll(this.getScrollState());
3586         
3587         this.afterMove(newIndex);
3588         this.grid.fireEvent('columnmove', oldIndex, newIndex);
3589     },
3590
3591     // private
3592     onColConfigChange : function() {
3593         delete this.lastViewWidth;
3594         this.indexMap = null;
3595         this.refresh(true);
3596     },
3597
3598     /* -------------------- UI Events and Handlers ------------------------------ */
3599     // private
3600     initUI : function(grid) {
3601         grid.on('headerclick', this.onHeaderClick, this);
3602     },
3603
3604     // private
3605     initEvents : Ext.emptyFn,
3606
3607     // private
3608     onHeaderClick : function(g, index) {
3609         if (this.headersDisabled || !this.cm.isSortable(index)) {
3610             return;
3611         }
3612         g.stopEditing(true);
3613         g.store.sort(this.cm.getDataIndex(index));
3614     },
3615
3616     /**
3617      * @private
3618      * Adds the hover class to a row when hovered over
3619      */
3620     onRowOver : function(e, target) {
3621         var row = this.findRowIndex(target);
3622         
3623         if (row !== false) {
3624             this.addRowClass(row, this.rowOverCls);
3625         }
3626     },
3627
3628     /**
3629      * @private
3630      * Removes the hover class from a row on mouseout
3631      */
3632     onRowOut : function(e, target) {
3633         var row = this.findRowIndex(target);
3634         
3635         if (row !== false && !e.within(this.getRow(row), true)) {
3636             this.removeRowClass(row, this.rowOverCls);
3637         }
3638     },
3639
3640     // private
3641     onRowSelect : function(row) {
3642         this.addRowClass(row, this.selectedRowClass);
3643     },
3644
3645     // private
3646     onRowDeselect : function(row) {
3647         this.removeRowClass(row, this.selectedRowClass);
3648     },
3649
3650     // private
3651     onCellSelect : function(row, col) {
3652         var cell = this.getCell(row, col);
3653         if (cell) {
3654             this.fly(cell).addClass('x-grid3-cell-selected');
3655         }
3656     },
3657
3658     // private
3659     onCellDeselect : function(row, col) {
3660         var cell = this.getCell(row, col);
3661         if (cell) {
3662             this.fly(cell).removeClass('x-grid3-cell-selected');
3663         }
3664     },
3665
3666     // private
3667     handleWheel : function(e) {
3668         e.stopPropagation();
3669     },
3670
3671     /**
3672      * @private
3673      * Called by the SplitDragZone when a drag has been completed. Resizes the columns
3674      */
3675     onColumnSplitterMoved : function(cellIndex, width) {
3676         this.userResized = true;
3677         this.grid.colModel.setColumnWidth(cellIndex, width, true);
3678
3679         if (this.forceFit) {
3680             this.fitColumns(true, false, cellIndex);
3681             this.updateAllColumnWidths();
3682         } else {
3683             this.updateColumnWidth(cellIndex, width);
3684             this.syncHeaderScroll();
3685         }
3686
3687         this.grid.fireEvent('columnresize', cellIndex, width);
3688     },
3689
3690     /**
3691      * @private
3692      * Click handler for the shared column dropdown menu, called on beforeshow. Builds the menu
3693      * which displays the list of columns for the user to show or hide.
3694      */
3695     beforeColMenuShow : function() {
3696         var colModel = this.cm,
3697             colCount = colModel.getColumnCount(),
3698             colMenu  = this.colMenu,
3699             i;
3700
3701         colMenu.removeAll();
3702
3703         for (i = 0; i < colCount; i++) {
3704             if (colModel.config[i].hideable !== false) {
3705                 colMenu.add(new Ext.menu.CheckItem({
3706                     text       : colModel.getColumnHeader(i),
3707                     itemId     : 'col-' + colModel.getColumnId(i),
3708                     checked    : !colModel.isHidden(i),
3709                     disabled   : colModel.config[i].hideable === false,
3710                     hideOnClick: false
3711                 }));
3712             }
3713         }
3714     },
3715     
3716     /**
3717      * @private
3718      * Attached as the 'itemclick' handler to the header menu and the column show/hide submenu (if available).
3719      * Performs sorting if the sorter buttons were clicked, otherwise hides/shows the column that was clicked.
3720      */
3721     handleHdMenuClick : function(item) {
3722         var store     = this.ds,
3723             dataIndex = this.cm.getDataIndex(this.hdCtxIndex);
3724
3725         switch (item.getItemId()) {
3726             case 'asc':
3727                 store.sort(dataIndex, 'ASC');
3728                 break;
3729             case 'desc':
3730                 store.sort(dataIndex, 'DESC');
3731                 break;
3732             default:
3733                 this.handleHdMenuClickDefault(item);
3734         }
3735         return true;
3736     },
3737     
3738     /**
3739      * Called by handleHdMenuClick if any button except a sort ASC/DESC button was clicked. The default implementation provides
3740      * the column hide/show functionality based on the check state of the menu item. A different implementation can be provided
3741      * if needed.
3742      * @param {Ext.menu.BaseItem} item The menu item that was clicked
3743      */
3744     handleHdMenuClickDefault: function(item) {
3745         var colModel = this.cm,
3746             itemId   = item.getItemId(),
3747             index    = colModel.getIndexById(itemId.substr(4));
3748
3749         if (index != -1) {
3750             if (item.checked && colModel.getColumnsBy(this.isHideableColumn, this).length <= 1) {
3751                 this.onDenyColumnHide();
3752                 return;
3753             }
3754             colModel.setHidden(index, item.checked);
3755         }
3756     },
3757
3758     /**
3759      * @private
3760      * Called when a header cell is clicked - shows the menu if the click happened over a trigger button
3761      */
3762     handleHdDown : function(e, target) {
3763         if (Ext.fly(target).hasClass('x-grid3-hd-btn')) {
3764             e.stopEvent();
3765             
3766             var colModel  = this.cm,
3767                 header    = this.findHeaderCell(target),
3768                 index     = this.getCellIndex(header),
3769                 sortable  = colModel.isSortable(index),
3770                 menu      = this.hmenu,
3771                 menuItems = menu.items,
3772                 menuCls   = this.headerMenuOpenCls;
3773             
3774             this.hdCtxIndex = index;
3775             
3776             Ext.fly(header).addClass(menuCls);
3777             menuItems.get('asc').setDisabled(!sortable);
3778             menuItems.get('desc').setDisabled(!sortable);
3779             
3780             menu.on('hide', function() {
3781                 Ext.fly(header).removeClass(menuCls);
3782             }, this, {single:true});
3783             
3784             menu.show(target, 'tl-bl?');
3785         }
3786     },
3787
3788     /**
3789      * @private
3790      * Attached to the headers' mousemove event. This figures out the CSS cursor to use based on where the mouse is currently
3791      * pointed. If the mouse is currently hovered over the extreme left or extreme right of any header cell and the cell next 
3792      * to it is resizable it is given the resize cursor, otherwise the cursor is set to an empty string.
3793      */
3794     handleHdMove : function(e) {
3795         var header = this.findHeaderCell(this.activeHdRef);
3796         
3797         if (header && !this.headersDisabled) {
3798             var handleWidth  = this.splitHandleWidth || 5,
3799                 activeRegion = this.activeHdRegion,
3800                 headerStyle  = header.style,
3801                 colModel     = this.cm,
3802                 cursor       = '',
3803                 pageX        = e.getPageX();
3804                 
3805             if (this.grid.enableColumnResize !== false) {
3806                 var activeHeaderIndex = this.activeHdIndex,
3807                     previousVisible   = this.getPreviousVisible(activeHeaderIndex),
3808                     currentResizable  = colModel.isResizable(activeHeaderIndex),
3809                     previousResizable = previousVisible && colModel.isResizable(previousVisible),
3810                     inLeftResizer     = pageX - activeRegion.left <= handleWidth,
3811                     inRightResizer    = activeRegion.right - pageX <= (!this.activeHdBtn ? handleWidth : 2);
3812                 
3813                 if (inLeftResizer && previousResizable) {
3814                     cursor = Ext.isAir ? 'move' : Ext.isWebKit ? 'e-resize' : 'col-resize'; // col-resize not always supported
3815                 } else if (inRightResizer && currentResizable) {
3816                     cursor = Ext.isAir ? 'move' : Ext.isWebKit ? 'w-resize' : 'col-resize';
3817                 }
3818             }
3819             
3820             headerStyle.cursor = cursor;
3821         }
3822     },
3823     
3824     /**
3825      * @private
3826      * Returns the index of the nearest currently visible header to the left of the given index.
3827      * @param {Number} index The header index
3828      * @return {Number/undefined} The index of the nearest visible header
3829      */
3830     getPreviousVisible: function(index) {
3831         while (index > 0) {
3832             if (!this.cm.isHidden(index - 1)) {
3833                 return index;
3834             }
3835             index--;
3836         }
3837         return undefined;
3838     },
3839
3840     /**
3841      * @private
3842      * Tied to the header element's mouseover event - adds the over class to the header cell if the menu is not disabled
3843      * for that cell
3844      */
3845     handleHdOver : function(e, target) {
3846         var header = this.findHeaderCell(target);
3847         
3848         if (header && !this.headersDisabled) {
3849             var fly = this.fly(header);
3850             
3851             this.activeHdRef = target;
3852             this.activeHdIndex = this.getCellIndex(header);
3853             this.activeHdRegion = fly.getRegion();
3854             
3855             if (!this.isMenuDisabled(this.activeHdIndex, fly)) {
3856                 fly.addClass('x-grid3-hd-over');
3857                 this.activeHdBtn = fly.child('.x-grid3-hd-btn');
3858                 
3859                 if (this.activeHdBtn) {
3860                     this.activeHdBtn.dom.style.height = (header.firstChild.offsetHeight - 1) + 'px';
3861                 }
3862             }
3863         }
3864     },
3865
3866     /**
3867      * @private
3868      * Tied to the header element's mouseout event. Removes the hover class from the header cell
3869      */
3870     handleHdOut : function(e, target) {
3871         var header = this.findHeaderCell(target);
3872         
3873         if (header && (!Ext.isIE || !e.within(header, true))) {
3874             this.activeHdRef = null;
3875             this.fly(header).removeClass('x-grid3-hd-over');
3876             header.style.cursor = '';
3877         }
3878     },
3879     
3880     /**
3881      * @private
3882      * Used by {@link #handleHdOver} to determine whether or not to show the header menu class on cell hover
3883      * @param {Number} cellIndex The header cell index
3884      * @param {Ext.Element} el The cell element currently being hovered over
3885      */
3886     isMenuDisabled: function(cellIndex, el) {
3887         return this.cm.isMenuDisabled(cellIndex);
3888     },
3889
3890     /**
3891      * @private
3892      * Returns true if there are any rows rendered into the GridView
3893      * @return {Boolean} True if any rows have been rendered
3894      */
3895     hasRows : function() {
3896         var fc = this.mainBody.dom.firstChild;
3897         return fc && fc.nodeType == 1 && fc.className != 'x-grid-empty';
3898     },
3899     
3900     /**
3901      * @private
3902      */
3903     isHideableColumn : function(c) {
3904         return !c.hidden;
3905     },
3906
3907     /**
3908      * @private
3909      * DEPRECATED - will be removed in Ext JS 5.0
3910      */
3911     bind : function(d, c) {
3912         this.initData(d, c);
3913     }
3914 });
3915
3916
3917 // private
3918 // This is a support class used internally by the Grid components
3919 Ext.grid.GridView.SplitDragZone = Ext.extend(Ext.dd.DDProxy, {
3920
3921     constructor: function(grid, hd){
3922         this.grid = grid;
3923         this.view = grid.getView();
3924         this.marker = this.view.resizeMarker;
3925         this.proxy = this.view.resizeProxy;
3926         Ext.grid.GridView.SplitDragZone.superclass.constructor.call(this, hd,
3927             'gridSplitters' + this.grid.getGridEl().id, {
3928             dragElId : Ext.id(this.proxy.dom), resizeFrame:false
3929         });
3930         this.scroll = false;
3931         this.hw = this.view.splitHandleWidth || 5;
3932     },
3933
3934     b4StartDrag : function(x, y){
3935         this.dragHeadersDisabled = this.view.headersDisabled;
3936         this.view.headersDisabled = true;
3937         var h = this.view.mainWrap.getHeight();
3938         this.marker.setHeight(h);
3939         this.marker.show();
3940         this.marker.alignTo(this.view.getHeaderCell(this.cellIndex), 'tl-tl', [-2, 0]);
3941         this.proxy.setHeight(h);
3942         var w = this.cm.getColumnWidth(this.cellIndex),
3943             minw = Math.max(w-this.grid.minColumnWidth, 0);
3944         this.resetConstraints();
3945         this.setXConstraint(minw, 1000);
3946         this.setYConstraint(0, 0);
3947         this.minX = x - minw;
3948         this.maxX = x + 1000;
3949         this.startPos = x;
3950         Ext.dd.DDProxy.prototype.b4StartDrag.call(this, x, y);
3951     },
3952
3953     allowHeaderDrag : function(e){
3954         return true;
3955     },
3956
3957     handleMouseDown : function(e){
3958         var t = this.view.findHeaderCell(e.getTarget());
3959         if(t && this.allowHeaderDrag(e)){
3960             var xy = this.view.fly(t).getXY(), 
3961                 x = xy[0],
3962                 exy = e.getXY(), 
3963                 ex = exy[0],
3964                 w = t.offsetWidth, 
3965                 adjust = false;
3966                 
3967             if((ex - x) <= this.hw){
3968                 adjust = -1;
3969             }else if((x+w) - ex <= this.hw){
3970                 adjust = 0;
3971             }
3972             if(adjust !== false){
3973                 this.cm = this.grid.colModel;
3974                 var ci = this.view.getCellIndex(t);
3975                 if(adjust == -1){
3976                   if (ci + adjust < 0) {
3977                     return;
3978                   }
3979                     while(this.cm.isHidden(ci+adjust)){
3980                         --adjust;
3981                         if(ci+adjust < 0){
3982                             return;
3983                         }
3984                     }
3985                 }
3986                 this.cellIndex = ci+adjust;
3987                 this.split = t.dom;
3988                 if(this.cm.isResizable(this.cellIndex) && !this.cm.isFixed(this.cellIndex)){
3989                     Ext.grid.GridView.SplitDragZone.superclass.handleMouseDown.apply(this, arguments);
3990                 }
3991             }else if(this.view.columnDrag){
3992                 this.view.columnDrag.callHandleMouseDown(e);
3993             }
3994         }
3995     },
3996
3997     endDrag : function(e){
3998         this.marker.hide();
3999         var v = this.view,
4000             endX = Math.max(this.minX, e.getPageX()),
4001             diff = endX - this.startPos,
4002             disabled = this.dragHeadersDisabled;
4003             
4004         v.onColumnSplitterMoved(this.cellIndex, this.cm.getColumnWidth(this.cellIndex)+diff);
4005         setTimeout(function(){
4006             v.headersDisabled = disabled;
4007         }, 50);
4008     },
4009
4010     autoOffset : function(){
4011         this.setDelta(0,0);
4012     }
4013 });
4014 /**
4015  * @class Ext.grid.PivotGridView
4016  * @extends Ext.grid.GridView
4017  * Specialised GridView for rendering Pivot Grid components. Config can be passed to the PivotGridView via the PivotGrid constructor's
4018  * viewConfig option:
4019 <pre><code>
4020 new Ext.grid.PivotGrid({
4021     viewConfig: {
4022         title: 'My Pivot Grid',
4023         getCellCls: function(value) {
4024             return value > 10 'red' : 'green';
4025         }
4026     }
4027 });
4028 </code></pre>
4029  * <p>Currently {@link #title} and {@link #getCellCls} are the only configuration options accepted by PivotGridView. All other 
4030  * interaction is performed via the {@link Ext.grid.PivotGrid PivotGrid} class.</p>
4031  */
4032 Ext.grid.PivotGridView = Ext.extend(Ext.grid.GridView, {
4033     
4034     /**
4035      * The CSS class added to all group header cells. Defaults to 'grid-hd-group-cell'
4036      * @property colHeaderCellCls
4037      * @type String
4038      */
4039     colHeaderCellCls: 'grid-hd-group-cell',
4040     
4041     /**
4042      * @cfg {String} title Optional title to be placed in the top left corner of the PivotGrid. Defaults to an empty string.
4043      */
4044     title: '',
4045     
4046     /**
4047      * @cfg {Function} getCellCls Optional function which should return a CSS class name for each cell value. This is useful when
4048      * color coding cells based on their value. Defaults to undefined.
4049      */
4050     
4051     /**
4052      * Returns the headers to be rendered at the top of the grid. Should be a 2-dimensional array, where each item specifies the number
4053      * of columns it groups (column in this case refers to normal grid columns). In the example below we have 5 city groups, which are
4054      * each part of a continent supergroup. The colspan for each city group refers to the number of normal grid columns that group spans,
4055      * so in this case the grid would be expected to have a total of 12 columns:
4056 <pre><code>
4057 [
4058     {
4059         items: [
4060             {header: 'England',   colspan: 5},
4061             {header: 'USA',       colspan: 3}
4062         ]
4063     },
4064     {
4065         items: [
4066             {header: 'London',    colspan: 2},
4067             {header: 'Cambridge', colspan: 3},
4068             {header: 'Palo Alto', colspan: 3}
4069         ]
4070     }
4071 ]
4072 </code></pre>
4073      * In the example above we have cities nested under countries. The nesting could be deeper if desired - e.g. Continent -> Country ->
4074      * State -> City, or any other structure. The only constaint is that the same depth must be used throughout the structure.
4075      * @return {Array} A tree structure containing the headers to be rendered. Must include the colspan property at each level, which should
4076      * be the sum of all child nodes beneath this node.
4077      */
4078     getColumnHeaders: function() {
4079         return this.grid.topAxis.buildHeaders();;
4080     },
4081     
4082     /**
4083      * Returns the headers to be rendered on the left of the grid. Should be a 2-dimensional array, where each item specifies the number
4084      * of rows it groups. In the example below we have 5 city groups, which are each part of a continent supergroup. The rowspan for each 
4085      * city group refers to the number of normal grid columns that group spans, so in this case the grid would be expected to have a 
4086      * total of 12 rows:
4087 <pre><code>
4088 [
4089     {
4090         width: 90,
4091         items: [
4092             {header: 'England',   rowspan: 5},
4093             {header: 'USA',       rowspan: 3}
4094         ]
4095     },
4096     {
4097         width: 50,
4098         items: [
4099             {header: 'London',    rowspan: 2},
4100             {header: 'Cambridge', rowspan: 3},
4101             {header: 'Palo Alto', rowspan: 3}
4102         ]
4103     }
4104 ]
4105 </code></pre>
4106      * In the example above we have cities nested under countries. The nesting could be deeper if desired - e.g. Continent -> Country ->
4107      * State -> City, or any other structure. The only constaint is that the same depth must be used throughout the structure.
4108      * @return {Array} A tree structure containing the headers to be rendered. Must include the colspan property at each level, which should
4109      * be the sum of all child nodes beneath this node.
4110      * Each group may specify the width it should be rendered with.
4111      * @return {Array} The row groups
4112      */
4113     getRowHeaders: function() {
4114         return this.grid.leftAxis.buildHeaders();
4115     },
4116     
4117     /**
4118      * @private
4119      * Renders rows between start and end indexes
4120      * @param {Number} startRow Index of the first row to render
4121      * @param {Number} endRow Index of the last row to render
4122      */
4123     renderRows : function(startRow, endRow) {
4124         var grid          = this.grid,
4125             rows          = grid.extractData(),
4126             rowCount      = rows.length,
4127             templates     = this.templates,
4128             renderer      = grid.renderer,
4129             hasRenderer   = typeof renderer == 'function',
4130             getCellCls    = this.getCellCls,
4131             hasGetCellCls = typeof getCellCls == 'function',
4132             cellTemplate  = templates.cell,
4133             rowTemplate   = templates.row,
4134             rowBuffer     = [],
4135             meta          = {},
4136             tstyle        = 'width:' + this.getGridInnerWidth() + 'px;',
4137             colBuffer, column, i;
4138         
4139         startRow = startRow || 0;
4140         endRow   = Ext.isDefined(endRow) ? endRow : rowCount - 1;
4141         
4142         for (i = 0; i < rowCount; i++) {
4143             row = rows[i];
4144             colCount  = row.length;
4145             colBuffer = [];
4146             
4147             rowIndex = startRow + i;
4148
4149             //build up each column's HTML
4150             for (j = 0; j < colCount; j++) {
4151                 cell = row[j];
4152
4153                 meta.css   = j === 0 ? 'x-grid3-cell-first ' : (j == (colCount - 1) ? 'x-grid3-cell-last ' : '');
4154                 meta.attr  = meta.cellAttr = '';
4155                 meta.value = cell;
4156
4157                 if (Ext.isEmpty(meta.value)) {
4158                     meta.value = '&#160;';
4159                 }
4160                 
4161                 if (hasRenderer) {
4162                     meta.value = renderer(meta.value);
4163                 }
4164                 
4165                 if (hasGetCellCls) {
4166                     meta.css += getCellCls(meta.value) + ' ';
4167                 }
4168
4169                 colBuffer[colBuffer.length] = cellTemplate.apply(meta);
4170             }
4171             
4172             rowBuffer[rowBuffer.length] = rowTemplate.apply({
4173                 tstyle: tstyle,
4174                 cols  : colCount,
4175                 cells : colBuffer.join(""),
4176                 alt   : ''
4177             });
4178         }
4179         
4180         return rowBuffer.join("");
4181     },
4182     
4183     /**
4184      * The master template to use when rendering the GridView. Has a default template
4185      * @property Ext.Template
4186      * @type masterTpl
4187      */
4188     masterTpl: new Ext.Template(
4189         '<div class="x-grid3 x-pivotgrid" hidefocus="true">',
4190             '<div class="x-grid3-viewport">',
4191                 '<div class="x-grid3-header">',
4192                     '<div class="x-grid3-header-title"><span>{title}</span></div>',
4193                     '<div class="x-grid3-header-inner">',
4194                         '<div class="x-grid3-header-offset" style="{ostyle}"></div>',
4195                     '</div>',
4196                     '<div class="x-clear"></div>',
4197                 '</div>',
4198                 '<div class="x-grid3-scroller">',
4199                     '<div class="x-grid3-row-headers"></div>',
4200                     '<div class="x-grid3-body" style="{bstyle}">{body}</div>',
4201                     '<a href="#" class="x-grid3-focus" tabIndex="-1"></a>',
4202                 '</div>',
4203             '</div>',
4204             '<div class="x-grid3-resize-marker">&#160;</div>',
4205             '<div class="x-grid3-resize-proxy">&#160;</div>',
4206         '</div>'
4207     ),
4208     
4209     /**
4210      * @private
4211      * Adds a gcell template to the internal templates object. This is used to render the headers in a multi-level column header.
4212      */
4213     initTemplates: function() {
4214         Ext.grid.PivotGridView.superclass.initTemplates.apply(this, arguments);
4215         
4216         var templates = this.templates || {};
4217         if (!templates.gcell) {
4218             templates.gcell = new Ext.XTemplate(
4219                 '<td class="x-grid3-hd x-grid3-gcell x-grid3-td-{id} ux-grid-hd-group-row-{row} ' + this.colHeaderCellCls + '" style="{style}">',
4220                     '<div {tooltip} class="x-grid3-hd-inner x-grid3-hd-{id}" unselectable="on" style="{istyle}">', 
4221                         this.grid.enableHdMenu ? '<a class="x-grid3-hd-btn" href="#"></a>' : '', '{value}',
4222                     '</div>',
4223                 '</td>'
4224             );
4225         }
4226         
4227         this.templates = templates;
4228         this.hrowRe = new RegExp("ux-grid-hd-group-row-(\\d+)", "");
4229     },
4230     
4231     /**
4232      * @private
4233      * Sets up the reference to the row headers element
4234      */
4235     initElements: function() {
4236         Ext.grid.PivotGridView.superclass.initElements.apply(this, arguments);
4237         
4238         /**
4239          * @property rowHeadersEl
4240          * @type Ext.Element
4241          * The element containing all row headers
4242          */
4243         this.rowHeadersEl = new Ext.Element(this.scroller.child('div.x-grid3-row-headers'));
4244         
4245         /**
4246          * @property headerTitleEl
4247          * @type Ext.Element
4248          * The element that contains the optional title (top left section of the pivot grid)
4249          */
4250         this.headerTitleEl = new Ext.Element(this.mainHd.child('div.x-grid3-header-title'));
4251     },
4252     
4253     /**
4254      * @private
4255      * Takes row headers into account when calculating total available width
4256      */
4257     getGridInnerWidth: function() {
4258         var previousWidth = Ext.grid.PivotGridView.superclass.getGridInnerWidth.apply(this, arguments);
4259         
4260         return previousWidth - this.getTotalRowHeaderWidth();
4261     },
4262     
4263     /**
4264      * Returns the total width of all row headers as specified by {@link #getRowHeaders}
4265      * @return {Number} The total width
4266      */
4267     getTotalRowHeaderWidth: function() {
4268         var headers = this.getRowHeaders(),
4269             length  = headers.length,
4270             total   = 0,
4271             i;
4272         
4273         for (i = 0; i< length; i++) {
4274             total += headers[i].width;
4275         }
4276         
4277         return total;
4278     },
4279     
4280     /**
4281      * @private
4282      * Returns the total height of all column headers
4283      * @return {Number} The total height
4284      */
4285     getTotalColumnHeaderHeight: function() {
4286         return this.getColumnHeaders().length * 21;
4287     },
4288     
4289     /**
4290      * @private
4291      * Slight specialisation of the GridView renderUI - just adds the row headers
4292      */
4293     renderUI : function() {
4294         var templates  = this.templates,
4295             innerWidth = this.getGridInnerWidth();
4296             
4297         return templates.master.apply({
4298             body  : templates.body.apply({rows:'&#160;'}),
4299             ostyle: 'width:' + innerWidth + 'px',
4300             bstyle: 'width:' + innerWidth + 'px'
4301         });
4302     },
4303     
4304     /**
4305      * @private
4306      * Make sure that the headers and rows are all sized correctly during layout
4307      */
4308     onLayout: function(width, height) {
4309         Ext.grid.PivotGridView.superclass.onLayout.apply(this, arguments);
4310         
4311         var width = this.getGridInnerWidth();
4312         
4313         this.resizeColumnHeaders(width);
4314         this.resizeAllRows(width);
4315     },
4316     
4317     /**
4318      * Refreshs the grid UI
4319      * @param {Boolean} headersToo (optional) True to also refresh the headers
4320      */
4321     refresh : function(headersToo) {
4322         this.fireEvent('beforerefresh', this);
4323         this.grid.stopEditing(true);
4324         
4325         var result = this.renderBody();
4326         this.mainBody.update(result).setWidth(this.getGridInnerWidth());
4327         if (headersToo === true) {
4328             this.updateHeaders();
4329             this.updateHeaderSortState();
4330         }
4331         this.processRows(0, true);
4332         this.layout();
4333         this.applyEmptyText();
4334         this.fireEvent('refresh', this);
4335     },
4336     
4337     /**
4338      * @private
4339      * Bypasses GridView's renderHeaders as they are taken care of separately by the PivotAxis instances
4340      */
4341     renderHeaders: Ext.emptyFn,
4342     
4343     /**
4344      * @private
4345      * Taken care of by PivotAxis
4346      */
4347     fitColumns: Ext.emptyFn,
4348     
4349     /**
4350      * @private
4351      * Called on layout, ensures that the width of each column header is correct. Omitting this can lead to faulty
4352      * layouts when nested in a container.
4353      * @param {Number} width The new width
4354      */
4355     resizeColumnHeaders: function(width) {
4356         var topAxis = this.grid.topAxis;
4357         
4358         if (topAxis.rendered) {
4359             topAxis.el.setWidth(width);
4360         }
4361     },
4362     
4363     /**
4364      * @private
4365      * Sets the row header div to the correct width. Should be called after rendering and reconfiguration of headers
4366      */
4367     resizeRowHeaders: function() {
4368         var rowHeaderWidth = this.getTotalRowHeaderWidth(),
4369             marginStyle    = String.format("margin-left: {0}px;", rowHeaderWidth);
4370         
4371         this.rowHeadersEl.setWidth(rowHeaderWidth);
4372         this.mainBody.applyStyles(marginStyle);
4373         Ext.fly(this.innerHd).applyStyles(marginStyle);
4374         
4375         this.headerTitleEl.setWidth(rowHeaderWidth);
4376         this.headerTitleEl.setHeight(this.getTotalColumnHeaderHeight());
4377     },
4378     
4379     /**
4380      * @private
4381      * Resizes all rendered rows to the given width. Usually called by onLayout
4382      * @param {Number} width The new width
4383      */
4384     resizeAllRows: function(width) {
4385         var rows   = this.getRows(),
4386             length = rows.length,
4387             i;
4388         
4389         for (i = 0; i < length; i++) {
4390             Ext.fly(rows[i]).setWidth(width);
4391             Ext.fly(rows[i]).child('table').setWidth(width);
4392         }
4393     },
4394     
4395     /**
4396      * @private
4397      * Updates the Row Headers, deferring the updating of Column Headers to GridView
4398      */
4399     updateHeaders: function() {
4400         this.renderGroupRowHeaders();
4401         this.renderGroupColumnHeaders();
4402     },
4403     
4404     /**
4405      * @private
4406      * Renders all row header groups at all levels based on the structure fetched from {@link #getGroupRowHeaders}
4407      */
4408     renderGroupRowHeaders: function() {
4409         var leftAxis = this.grid.leftAxis;
4410         
4411         this.resizeRowHeaders();
4412         leftAxis.rendered = false;
4413         leftAxis.render(this.rowHeadersEl);
4414         
4415         this.setTitle(this.title);
4416     },
4417     
4418     /**
4419      * Sets the title text in the top left segment of the PivotGridView
4420      * @param {String} title The title
4421      */
4422     setTitle: function(title) {
4423         this.headerTitleEl.child('span').dom.innerHTML = title;
4424     },
4425     
4426     /**
4427      * @private
4428      * Renders all column header groups at all levels based on the structure fetched from {@link #getColumnHeaders}
4429      */
4430     renderGroupColumnHeaders: function() {
4431         var topAxis = this.grid.topAxis;
4432         
4433         topAxis.rendered = false;
4434         topAxis.render(this.innerHd.firstChild);
4435     },
4436     
4437     /**
4438      * @private
4439      * Overridden to test whether the user is hovering over a group cell, in which case we don't show the menu
4440      */
4441     isMenuDisabled: function(cellIndex, el) {
4442         return true;
4443     }
4444 });/**
4445  * @class Ext.grid.PivotAxis
4446  * @extends Ext.Component
4447  * <p>PivotAxis is a class that supports a {@link Ext.grid.PivotGrid}. Each PivotGrid contains two PivotAxis instances - the left
4448  * axis and the top axis. Each PivotAxis defines an ordered set of dimensions, each of which should correspond to a field in a
4449  * Store's Record (see {@link Ext.grid.PivotGrid} documentation for further explanation).</p>
4450  * <p>Developers should have little interaction with the PivotAxis instances directly as most of their management is performed by
4451  * the PivotGrid. An exception is the dynamic reconfiguration of axes at run time - to achieve this we use PivotAxis's 
4452  * {@link #setDimensions} function and refresh the grid:</p>
4453 <pre><code>
4454 var pivotGrid = new Ext.grid.PivotGrid({
4455     //some PivotGrid config here
4456 });
4457
4458 //change the left axis dimensions
4459 pivotGrid.leftAxis.setDimensions([
4460     {
4461         dataIndex: 'person',
4462         direction: 'DESC',
4463         width    : 100
4464     },
4465     {
4466         dataIndex: 'product',
4467         direction: 'ASC',
4468         width    : 80
4469     }
4470 ]);
4471
4472 pivotGrid.view.refresh(true);
4473 </code></pre>
4474  * This clears the previous dimensions on the axis and redraws the grid with the new dimensions.
4475  */
4476 Ext.grid.PivotAxis = Ext.extend(Ext.Component, {
4477     /**
4478      * @cfg {String} orientation One of 'vertical' or 'horizontal'. Defaults to horizontal
4479      */
4480     orientation: 'horizontal',
4481     
4482     /**
4483      * @cfg {Number} defaultHeaderWidth The width to render each row header that does not have a width specified via 
4484      {@link #getRowGroupHeaders}. Defaults to 80.
4485      */
4486     defaultHeaderWidth: 80,
4487     
4488     /**
4489      * @private
4490      * @cfg {Number} paddingWidth The amount of padding used by each cell.
4491      * TODO: From 4.x onwards this can be removed as it won't be needed. For now it is used to account for the differences between
4492      * the content box and border box measurement models
4493      */
4494     paddingWidth: 7,
4495     
4496     /**
4497      * Updates the dimensions used by this axis
4498      * @param {Array} dimensions The new dimensions
4499      */
4500     setDimensions: function(dimensions) {
4501         this.dimensions = dimensions;
4502     },
4503     
4504     /**
4505      * @private
4506      * Builds the html table that contains the dimensions for this axis. This branches internally between vertical
4507      * and horizontal orientations because the table structure is slightly different in each case
4508      */
4509     onRender: function(ct, position) {
4510         var rows = this.orientation == 'horizontal'
4511                  ? this.renderHorizontalRows()
4512                  : this.renderVerticalRows();
4513         
4514         this.el = Ext.DomHelper.overwrite(ct.dom, {tag: 'table', cn: rows}, true);
4515     },
4516     
4517     /**
4518      * @private
4519      * Specialised renderer for horizontal oriented axes
4520      * @return {Object} The HTML Domspec for a horizontal oriented axis
4521      */
4522     renderHorizontalRows: function() {
4523         var headers  = this.buildHeaders(),
4524             rowCount = headers.length,
4525             rows     = [],
4526             cells, cols, colCount, i, j;
4527         
4528         for (i = 0; i < rowCount; i++) {
4529             cells = [];
4530             cols  = headers[i].items;
4531             colCount = cols.length;
4532
4533             for (j = 0; j < colCount; j++) {
4534                 cells.push({
4535                     tag: 'td',
4536                     html: cols[j].header,
4537                     colspan: cols[j].span
4538                 });
4539             }
4540
4541             rows[i] = {
4542                 tag: 'tr',
4543                 cn: cells
4544             };
4545         }
4546         
4547         return rows;
4548     },
4549     
4550     /**
4551      * @private
4552      * Specialised renderer for vertical oriented axes
4553      * @return {Object} The HTML Domspec for a vertical oriented axis
4554      */
4555     renderVerticalRows: function() {
4556         var headers  = this.buildHeaders(),
4557             colCount = headers.length,
4558             rowCells = [],
4559             rows     = [],
4560             rowCount, col, row, colWidth, i, j;
4561         
4562         for (i = 0; i < colCount; i++) {
4563             col = headers[i];
4564             colWidth = col.width || 80;
4565             rowCount = col.items.length;
4566             
4567             for (j = 0; j < rowCount; j++) {
4568                 row = col.items[j];
4569                 
4570                 rowCells[row.start] = rowCells[row.start] || [];
4571                 rowCells[row.start].push({
4572                     tag    : 'td',
4573                     html   : row.header,
4574                     rowspan: row.span,
4575                     width  : Ext.isBorderBox ? colWidth : colWidth - this.paddingWidth
4576                 });
4577             }
4578         }
4579         
4580         rowCount = rowCells.length;
4581         for (i = 0; i < rowCount; i++) {
4582             rows[i] = {
4583                 tag: 'tr',
4584                 cn : rowCells[i]
4585             };
4586         }
4587         
4588         return rows;
4589     },
4590     
4591     /**
4592      * @private
4593      * Returns the set of all unique tuples based on the bound store and dimension definitions.
4594      * Internally we construct a new, temporary store to make use of the multi-sort capabilities of Store. In
4595      * 4.x this functionality should have been moved to MixedCollection so this step should not be needed.
4596      * @return {Array} All unique tuples
4597      */
4598     getTuples: function() {
4599         var newStore = new Ext.data.Store({});
4600         
4601         newStore.data = this.store.data.clone();
4602         newStore.fields = this.store.fields;
4603         
4604         var sorters    = [],
4605             dimensions = this.dimensions,
4606             length     = dimensions.length,
4607             i;
4608         
4609         for (i = 0; i < length; i++) {
4610             sorters.push({
4611                 field    : dimensions[i].dataIndex,
4612                 direction: dimensions[i].direction || 'ASC'
4613             });
4614         }
4615         
4616         newStore.sort(sorters);
4617         
4618         var records = newStore.data.items,
4619             hashes  = [],
4620             tuples  = [],
4621             recData, hash, info, data, key;
4622         
4623         length = records.length;
4624         
4625         for (i = 0; i < length; i++) {
4626             info = this.getRecordInfo(records[i]);
4627             data = info.data;
4628             hash = "";
4629             
4630             for (key in data) {
4631                 hash += data[key] + '---';
4632             }
4633             
4634             if (hashes.indexOf(hash) == -1) {
4635                 hashes.push(hash);
4636                 tuples.push(info);
4637             }
4638         }
4639         
4640         newStore.destroy();
4641         
4642         return tuples;
4643     },
4644     
4645     /**
4646      * @private
4647      */
4648     getRecordInfo: function(record) {
4649         var dimensions = this.dimensions,
4650             length  = dimensions.length,
4651             data    = {},
4652             dimension, dataIndex, i;
4653         
4654         //get an object containing just the data we are interested in based on the configured dimensions
4655         for (i = 0; i < length; i++) {
4656             dimension = dimensions[i];
4657             dataIndex = dimension.dataIndex;
4658             
4659             data[dataIndex] = record.get(dataIndex);
4660         }
4661         
4662         //creates a specialised matcher function for a given tuple. The returned function will return
4663         //true if the record passed to it matches the dataIndex values of each dimension in this axis
4664         var createMatcherFunction = function(data) {
4665             return function(record) {
4666                 for (var dataIndex in data) {
4667                     if (record.get(dataIndex) != data[dataIndex]) {
4668                         return false;
4669                     }
4670                 }
4671                 
4672                 return true;
4673             };
4674         };
4675         
4676         return {
4677             data: data,
4678             matcher: createMatcherFunction(data)
4679         };
4680     },
4681     
4682     /**
4683      * @private
4684      * Uses the calculated set of tuples to build an array of headers that can be rendered into a table using rowspan or
4685      * colspan. Basically this takes the set of tuples and spans any cells that run into one another, so if we had dimensions
4686      * of Person and Product and several tuples containing different Products for the same Person, those Products would be
4687      * spanned.
4688      * @return {Array} The headers
4689      */
4690     buildHeaders: function() {
4691         var tuples     = this.getTuples(),
4692             rowCount   = tuples.length,
4693             dimensions = this.dimensions,
4694             colCount   = dimensions.length,
4695             headers    = [],
4696             tuple, rows, currentHeader, previousHeader, span, start, isLast, changed, i, j;
4697         
4698         for (i = 0; i < colCount; i++) {
4699             dimension = dimensions[i];
4700             rows  = [];
4701             span  = 0;
4702             start = 0;
4703             
4704             for (j = 0; j < rowCount; j++) {
4705                 tuple  = tuples[j];
4706                 isLast = j == (rowCount - 1);
4707                 currentHeader = tuple.data[dimension.dataIndex];
4708                 
4709                 /*
4710                  * 'changed' indicates that we need to create a new cell. This should be true whenever the cell
4711                  * above (previousHeader) is different from this cell, or when the cell on the previous dimension
4712                  * changed (e.g. if the current dimension is Product and the previous was Person, we need to start
4713                  * a new cell if Product is the same but Person changed, so we check the previous dimension and tuple)
4714                  */
4715                 changed = previousHeader != undefined && previousHeader != currentHeader;
4716                 if (i > 0 && j > 0) {
4717                     changed = changed || tuple.data[dimensions[i-1].dataIndex] != tuples[j-1].data[dimensions[i-1].dataIndex];
4718                 }
4719                 
4720                 if (changed) {                    
4721                     rows.push({
4722                         header: previousHeader,
4723                         span  : span,
4724                         start : start
4725                     });
4726                     
4727                     start += span;
4728                     span = 0;
4729                 }
4730                 
4731                 if (isLast) {
4732                     rows.push({
4733                         header: currentHeader,
4734                         span  : span + 1,
4735                         start : start
4736                     });
4737                     
4738                     start += span;
4739                     span = 0;
4740                 }
4741                 
4742                 previousHeader = currentHeader;
4743                 span++;
4744             }
4745             
4746             headers.push({
4747                 items: rows,
4748                 width: dimension.width || this.defaultHeaderWidth
4749             });
4750             
4751             previousHeader = undefined;
4752         }
4753         
4754         return headers;
4755     }
4756 });
4757 // private
4758 // This is a support class used internally by the Grid components
4759 Ext.grid.HeaderDragZone = Ext.extend(Ext.dd.DragZone, {
4760     maxDragWidth: 120,
4761     
4762     constructor : function(grid, hd, hd2){
4763         this.grid = grid;
4764         this.view = grid.getView();
4765         this.ddGroup = "gridHeader" + this.grid.getGridEl().id;
4766         Ext.grid.HeaderDragZone.superclass.constructor.call(this, hd);
4767         if(hd2){
4768             this.setHandleElId(Ext.id(hd));
4769             this.setOuterHandleElId(Ext.id(hd2));
4770         }
4771         this.scroll = false;
4772     },
4773     
4774     getDragData : function(e){
4775         var t = Ext.lib.Event.getTarget(e),
4776             h = this.view.findHeaderCell(t);
4777         if(h){
4778             return {ddel: h.firstChild, header:h};
4779         }
4780         return false;
4781     },
4782
4783     onInitDrag : function(e){
4784         // keep the value here so we can restore it;
4785         this.dragHeadersDisabled = this.view.headersDisabled;
4786         this.view.headersDisabled = true;
4787         var clone = this.dragData.ddel.cloneNode(true);
4788         clone.id = Ext.id();
4789         clone.style.width = Math.min(this.dragData.header.offsetWidth,this.maxDragWidth) + "px";
4790         this.proxy.update(clone);
4791         return true;
4792     },
4793
4794     afterValidDrop : function(){
4795         this.completeDrop();
4796     },
4797
4798     afterInvalidDrop : function(){
4799         this.completeDrop();
4800     },
4801     
4802     completeDrop: function(){
4803         var v = this.view,
4804             disabled = this.dragHeadersDisabled;
4805         setTimeout(function(){
4806             v.headersDisabled = disabled;
4807         }, 50);
4808     }
4809 });
4810
4811 // private
4812 // This is a support class used internally by the Grid components
4813 Ext.grid.HeaderDropZone = Ext.extend(Ext.dd.DropZone, {
4814     proxyOffsets : [-4, -9],
4815     fly: Ext.Element.fly,
4816     
4817     constructor : function(grid, hd, hd2){
4818         this.grid = grid;
4819         this.view = grid.getView();
4820         // split the proxies so they don't interfere with mouse events
4821         this.proxyTop = Ext.DomHelper.append(document.body, {
4822             cls:"col-move-top", html:"&#160;"
4823         }, true);
4824         this.proxyBottom = Ext.DomHelper.append(document.body, {
4825             cls:"col-move-bottom", html:"&#160;"
4826         }, true);
4827         this.proxyTop.hide = this.proxyBottom.hide = function(){
4828             this.setLeftTop(-100,-100);
4829             this.setStyle("visibility", "hidden");
4830         };
4831         this.ddGroup = "gridHeader" + this.grid.getGridEl().id;
4832         // temporarily disabled
4833         //Ext.dd.ScrollManager.register(this.view.scroller.dom);
4834         Ext.grid.HeaderDropZone.superclass.constructor.call(this, grid.getGridEl().dom);
4835     },
4836
4837     getTargetFromEvent : function(e){
4838         var t = Ext.lib.Event.getTarget(e),
4839             cindex = this.view.findCellIndex(t);
4840         if(cindex !== false){
4841             return this.view.getHeaderCell(cindex);
4842         }
4843     },
4844
4845     nextVisible : function(h){
4846         var v = this.view, cm = this.grid.colModel;
4847         h = h.nextSibling;
4848         while(h){
4849             if(!cm.isHidden(v.getCellIndex(h))){
4850                 return h;
4851             }
4852             h = h.nextSibling;
4853         }
4854         return null;
4855     },
4856
4857     prevVisible : function(h){
4858         var v = this.view, cm = this.grid.colModel;
4859         h = h.prevSibling;
4860         while(h){
4861             if(!cm.isHidden(v.getCellIndex(h))){
4862                 return h;
4863             }
4864             h = h.prevSibling;
4865         }
4866         return null;
4867     },
4868
4869     positionIndicator : function(h, n, e){
4870         var x = Ext.lib.Event.getPageX(e),
4871             r = Ext.lib.Dom.getRegion(n.firstChild),
4872             px, 
4873             pt, 
4874             py = r.top + this.proxyOffsets[1];
4875         if((r.right - x) <= (r.right-r.left)/2){
4876             px = r.right+this.view.borderWidth;
4877             pt = "after";
4878         }else{
4879             px = r.left;
4880             pt = "before";
4881         }
4882
4883         if(this.grid.colModel.isFixed(this.view.getCellIndex(n))){
4884             return false;
4885         }
4886
4887         px +=  this.proxyOffsets[0];
4888         this.proxyTop.setLeftTop(px, py);
4889         this.proxyTop.show();
4890         if(!this.bottomOffset){
4891             this.bottomOffset = this.view.mainHd.getHeight();
4892         }
4893         this.proxyBottom.setLeftTop(px, py+this.proxyTop.dom.offsetHeight+this.bottomOffset);
4894         this.proxyBottom.show();
4895         return pt;
4896     },
4897
4898     onNodeEnter : function(n, dd, e, data){
4899         if(data.header != n){
4900             this.positionIndicator(data.header, n, e);
4901         }
4902     },
4903
4904     onNodeOver : function(n, dd, e, data){
4905         var result = false;
4906         if(data.header != n){
4907             result = this.positionIndicator(data.header, n, e);
4908         }
4909         if(!result){
4910             this.proxyTop.hide();
4911             this.proxyBottom.hide();
4912         }
4913         return result ? this.dropAllowed : this.dropNotAllowed;
4914     },
4915
4916     onNodeOut : function(n, dd, e, data){
4917         this.proxyTop.hide();
4918         this.proxyBottom.hide();
4919     },
4920
4921     onNodeDrop : function(n, dd, e, data){
4922         var h = data.header;
4923         if(h != n){
4924             var cm = this.grid.colModel,
4925                 x = Ext.lib.Event.getPageX(e),
4926                 r = Ext.lib.Dom.getRegion(n.firstChild),
4927                 pt = (r.right - x) <= ((r.right-r.left)/2) ? "after" : "before",
4928                 oldIndex = this.view.getCellIndex(h),
4929                 newIndex = this.view.getCellIndex(n);
4930             if(pt == "after"){
4931                 newIndex++;
4932             }
4933             if(oldIndex < newIndex){
4934                 newIndex--;
4935             }
4936             cm.moveColumn(oldIndex, newIndex);
4937             return true;
4938         }
4939         return false;
4940     }
4941 });
4942
4943 Ext.grid.GridView.ColumnDragZone = Ext.extend(Ext.grid.HeaderDragZone, {
4944     
4945     constructor : function(grid, hd){
4946         Ext.grid.GridView.ColumnDragZone.superclass.constructor.call(this, grid, hd, null);
4947         this.proxy.el.addClass('x-grid3-col-dd');
4948     },
4949     
4950     handleMouseDown : function(e){
4951     },
4952
4953     callHandleMouseDown : function(e){
4954         Ext.grid.GridView.ColumnDragZone.superclass.handleMouseDown.call(this, e);
4955     }
4956 });// private
4957 // This is a support class used internally by the Grid components
4958 Ext.grid.SplitDragZone = Ext.extend(Ext.dd.DDProxy, {
4959     fly: Ext.Element.fly,
4960     
4961     constructor : function(grid, hd, hd2){
4962         this.grid = grid;
4963         this.view = grid.getView();
4964         this.proxy = this.view.resizeProxy;
4965         Ext.grid.SplitDragZone.superclass.constructor.call(this, hd,
4966             "gridSplitters" + this.grid.getGridEl().id, {
4967             dragElId : Ext.id(this.proxy.dom), resizeFrame:false
4968         });
4969         this.setHandleElId(Ext.id(hd));
4970         this.setOuterHandleElId(Ext.id(hd2));
4971         this.scroll = false;
4972     },
4973
4974     b4StartDrag : function(x, y){
4975         this.view.headersDisabled = true;
4976         this.proxy.setHeight(this.view.mainWrap.getHeight());
4977         var w = this.cm.getColumnWidth(this.cellIndex);
4978         var minw = Math.max(w-this.grid.minColumnWidth, 0);
4979         this.resetConstraints();
4980         this.setXConstraint(minw, 1000);
4981         this.setYConstraint(0, 0);
4982         this.minX = x - minw;
4983         this.maxX = x + 1000;
4984         this.startPos = x;
4985         Ext.dd.DDProxy.prototype.b4StartDrag.call(this, x, y);
4986     },
4987
4988
4989     handleMouseDown : function(e){
4990         var ev = Ext.EventObject.setEvent(e);
4991         var t = this.fly(ev.getTarget());
4992         if(t.hasClass("x-grid-split")){
4993             this.cellIndex = this.view.getCellIndex(t.dom);
4994             this.split = t.dom;
4995             this.cm = this.grid.colModel;
4996             if(this.cm.isResizable(this.cellIndex) && !this.cm.isFixed(this.cellIndex)){
4997                 Ext.grid.SplitDragZone.superclass.handleMouseDown.apply(this, arguments);
4998             }
4999         }
5000     },
5001
5002     endDrag : function(e){
5003         this.view.headersDisabled = false;
5004         var endX = Math.max(this.minX, Ext.lib.Event.getPageX(e));
5005         var diff = endX - this.startPos;
5006         this.view.onColumnSplitterMoved(this.cellIndex, this.cm.getColumnWidth(this.cellIndex)+diff);
5007     },
5008
5009     autoOffset : function(){
5010         this.setDelta(0,0);
5011     }
5012 });/**
5013  * @class Ext.grid.GridDragZone
5014  * @extends Ext.dd.DragZone
5015  * <p>A customized implementation of a {@link Ext.dd.DragZone DragZone} which provides default implementations of two of the
5016  * template methods of DragZone to enable dragging of the selected rows of a GridPanel.</p>
5017  * <p>A cooperating {@link Ext.dd.DropZone DropZone} must be created who's template method implementations of
5018  * {@link Ext.dd.DropZone#onNodeEnter onNodeEnter}, {@link Ext.dd.DropZone#onNodeOver onNodeOver},
5019  * {@link Ext.dd.DropZone#onNodeOut onNodeOut} and {@link Ext.dd.DropZone#onNodeDrop onNodeDrop}</p> are able
5020  * to process the {@link #getDragData data} which is provided.
5021  */
5022 Ext.grid.GridDragZone = function(grid, config){
5023     this.view = grid.getView();
5024     Ext.grid.GridDragZone.superclass.constructor.call(this, this.view.mainBody.dom, config);
5025     this.scroll = false;
5026     this.grid = grid;
5027     this.ddel = document.createElement('div');
5028     this.ddel.className = 'x-grid-dd-wrap';
5029 };
5030
5031 Ext.extend(Ext.grid.GridDragZone, Ext.dd.DragZone, {
5032     ddGroup : "GridDD",
5033
5034     /**
5035      * <p>The provided implementation of the getDragData method which collects the data to be dragged from the GridPanel on mousedown.</p>
5036      * <p>This data is available for processing in the {@link Ext.dd.DropZone#onNodeEnter onNodeEnter}, {@link Ext.dd.DropZone#onNodeOver onNodeOver},
5037      * {@link Ext.dd.DropZone#onNodeOut onNodeOut} and {@link Ext.dd.DropZone#onNodeDrop onNodeDrop} methods of a cooperating {@link Ext.dd.DropZone DropZone}.</p>
5038      * <p>The data object contains the following properties:<ul>
5039      * <li><b>grid</b> : Ext.Grid.GridPanel<div class="sub-desc">The GridPanel from which the data is being dragged.</div></li>
5040      * <li><b>ddel</b> : htmlElement<div class="sub-desc">An htmlElement which provides the "picture" of the data being dragged.</div></li>
5041      * <li><b>rowIndex</b> : Number<div class="sub-desc">The index of the row which receieved the mousedown gesture which triggered the drag.</div></li>
5042      * <li><b>selections</b> : Array<div class="sub-desc">An Array of the selected Records which are being dragged from the GridPanel.</div></li>
5043      * </ul></p>
5044      */
5045     getDragData : function(e){
5046         var t = Ext.lib.Event.getTarget(e);
5047         var rowIndex = this.view.findRowIndex(t);
5048         if(rowIndex !== false){
5049             var sm = this.grid.selModel;
5050             if(!sm.isSelected(rowIndex) || e.hasModifier()){
5051                 sm.handleMouseDown(this.grid, rowIndex, e);
5052             }
5053             return {grid: this.grid, ddel: this.ddel, rowIndex: rowIndex, selections:sm.getSelections()};
5054         }
5055         return false;
5056     },
5057
5058     /**
5059      * <p>The provided implementation of the onInitDrag method. Sets the <tt>innerHTML</tt> of the drag proxy which provides the "picture"
5060      * of the data being dragged.</p>
5061      * <p>The <tt>innerHTML</tt> data is found by calling the owning GridPanel's {@link Ext.grid.GridPanel#getDragDropText getDragDropText}.</p>
5062      */
5063     onInitDrag : function(e){
5064         var data = this.dragData;
5065         this.ddel.innerHTML = this.grid.getDragDropText();
5066         this.proxy.update(this.ddel);
5067         // fire start drag?
5068     },
5069
5070     /**
5071      * An empty immplementation. Implement this to provide behaviour after a repair of an invalid drop. An implementation might highlight
5072      * the selected rows to show that they have not been dragged.
5073      */
5074     afterRepair : function(){
5075         this.dragging = false;
5076     },
5077
5078     /**
5079      * <p>An empty implementation. Implement this to provide coordinates for the drag proxy to slide back to after an invalid drop.</p>
5080      * <p>Called before a repair of an invalid drop to get the XY to animate to.</p>
5081      * @param {EventObject} e The mouse up event
5082      * @return {Array} The xy location (e.g. [100, 200])
5083      */
5084     getRepairXY : function(e, data){
5085         return false;
5086     },
5087
5088     onEndDrag : function(data, e){
5089         // fire end drag?
5090     },
5091
5092     onValidDrop : function(dd, e, id){
5093         // fire drag drop?
5094         this.hideProxy();
5095     },
5096
5097     beforeInvalidDrop : function(e, id){
5098
5099     }
5100 });
5101 /**
5102  * @class Ext.grid.ColumnModel
5103  * @extends Ext.util.Observable
5104  * <p>After the data has been read into the client side cache (<b>{@link Ext.data.Store Store}</b>),
5105  * the ColumnModel is used to configure how and what parts of that data will be displayed in the
5106  * vertical slices (columns) of the grid. The Ext.grid.ColumnModel Class is the default implementation
5107  * of a ColumnModel used by implentations of {@link Ext.grid.GridPanel GridPanel}.</p>
5108  * <p>Data is mapped into the store's records and then indexed into the ColumnModel using the
5109  * <tt>{@link Ext.grid.Column#dataIndex dataIndex}</tt>:</p>
5110  * <pre><code>
5111 {data source} == mapping ==> {data store} == <b><tt>{@link Ext.grid.Column#dataIndex dataIndex}</tt></b> ==> {ColumnModel}
5112  * </code></pre>
5113  * <p>Each {@link Ext.grid.Column Column} in the grid's ColumnModel is configured with a
5114  * <tt>{@link Ext.grid.Column#dataIndex dataIndex}</tt> to specify how the data within
5115  * each record in the store is indexed into the ColumnModel.</p>
5116  * <p>There are two ways to initialize the ColumnModel class:</p>
5117  * <p><u>Initialization Method 1: an Array</u></p>
5118 <pre><code>
5119  var colModel = new Ext.grid.ColumnModel([
5120     { header: "Ticker", width: 60, sortable: true},
5121     { header: "Company Name", width: 150, sortable: true, id: 'company'},
5122     { header: "Market Cap.", width: 100, sortable: true},
5123     { header: "$ Sales", width: 100, sortable: true, renderer: money},
5124     { header: "Employees", width: 100, sortable: true, resizable: false}
5125  ]);
5126  </code></pre>
5127  * <p>The ColumnModel may be initialized with an Array of {@link Ext.grid.Column} column configuration
5128  * objects to define the initial layout / display of the columns in the Grid. The order of each
5129  * {@link Ext.grid.Column} column configuration object within the specified Array defines the initial
5130  * order of the column display.  A Column's display may be initially hidden using the
5131  * <tt>{@link Ext.grid.Column#hidden hidden}</tt></b> config property (and then shown using the column
5132  * header menu).  Fields that are not included in the ColumnModel will not be displayable at all.</p>
5133  * <p>How each column in the grid correlates (maps) to the {@link Ext.data.Record} field in the
5134  * {@link Ext.data.Store Store} the column draws its data from is configured through the
5135  * <b><tt>{@link Ext.grid.Column#dataIndex dataIndex}</tt></b>.  If the
5136  * <b><tt>{@link Ext.grid.Column#dataIndex dataIndex}</tt></b> is not explicitly defined (as shown in the
5137  * example above) it will use the column configuration's index in the Array as the index.</p>
5138  * <p>See <b><tt>{@link Ext.grid.Column}</tt></b> for additional configuration options for each column.</p>
5139  * <p><u>Initialization Method 2: an Object</u></p>
5140  * <p>In order to use configuration options from <tt>Ext.grid.ColumnModel</tt>, an Object may be used to
5141  * initialize the ColumnModel.  The column configuration Array will be specified in the <tt><b>{@link #columns}</b></tt>
5142  * config property. The <tt><b>{@link #defaults}</b></tt> config property can be used to apply defaults
5143  * for all columns, e.g.:</p><pre><code>
5144  var colModel = new Ext.grid.ColumnModel({
5145     columns: [
5146         { header: "Ticker", width: 60, menuDisabled: false},
5147         { header: "Company Name", width: 150, id: 'company'},
5148         { header: "Market Cap."},
5149         { header: "$ Sales", renderer: money},
5150         { header: "Employees", resizable: false}
5151     ],
5152     defaults: {
5153         sortable: true,
5154         menuDisabled: true,
5155         width: 100
5156     },
5157     listeners: {
5158         {@link #hiddenchange}: function(cm, colIndex, hidden) {
5159             saveConfig(colIndex, hidden);
5160         }
5161     }
5162 });
5163  </code></pre>
5164  * <p>In both examples above, the ability to apply a CSS class to all cells in a column (including the
5165  * header) is demonstrated through the use of the <b><tt>{@link Ext.grid.Column#id id}</tt></b> config
5166  * option. This column could be styled by including the following css:</p><pre><code>
5167  //add this css *after* the core css is loaded
5168 .x-grid3-td-company {
5169     color: red; // entire column will have red font
5170 }
5171 // modify the header row only, adding an icon to the column header
5172 .x-grid3-hd-company {
5173     background: transparent
5174         url(../../resources/images/icons/silk/building.png)
5175         no-repeat 3px 3px ! important;
5176         padding-left:20px;
5177 }
5178  </code></pre>
5179  * Note that the "Company Name" column could be specified as the
5180  * <b><tt>{@link Ext.grid.GridPanel}.{@link Ext.grid.GridPanel#autoExpandColumn autoExpandColumn}</tt></b>.
5181  * @constructor
5182  * @param {Mixed} config Specify either an Array of {@link Ext.grid.Column} configuration objects or specify
5183  * a configuration Object (see introductory section discussion utilizing Initialization Method 2 above).
5184  */
5185 Ext.grid.ColumnModel = Ext.extend(Ext.util.Observable, {
5186     /**
5187      * @cfg {Number} defaultWidth (optional) The width of columns which have no <tt>{@link #width}</tt>
5188      * specified (defaults to <tt>100</tt>).  This property shall preferably be configured through the
5189      * <tt><b>{@link #defaults}</b></tt> config property.
5190      */
5191     defaultWidth: 100,
5192
5193     /**
5194      * @cfg {Boolean} defaultSortable (optional) Default sortable of columns which have no
5195      * sortable specified (defaults to <tt>false</tt>).  This property shall preferably be configured
5196      * through the <tt><b>{@link #defaults}</b></tt> config property.
5197      */
5198     defaultSortable: false,
5199
5200     /**
5201      * @cfg {Array} columns An Array of object literals.  The config options defined by
5202      * <b>{@link Ext.grid.Column}</b> are the options which may appear in the object literal for each
5203      * individual column definition.
5204      */
5205
5206     /**
5207      * @cfg {Object} defaults Object literal which will be used to apply {@link Ext.grid.Column}
5208      * configuration options to all <tt><b>{@link #columns}</b></tt>.  Configuration options specified with
5209      * individual {@link Ext.grid.Column column} configs will supersede these <tt><b>{@link #defaults}</b></tt>.
5210      */
5211
5212     constructor : function(config) {
5213         /**
5214              * An Array of {@link Ext.grid.Column Column definition} objects representing the configuration
5215              * of this ColumnModel.  See {@link Ext.grid.Column} for the configuration properties that may
5216              * be specified.
5217              * @property config
5218              * @type Array
5219              */
5220             if (config.columns) {
5221                 Ext.apply(this, config);
5222                 this.setConfig(config.columns, true);
5223             } else {
5224                 this.setConfig(config, true);
5225             }
5226             
5227             this.addEvents(
5228                 /**
5229                  * @event widthchange
5230                  * Fires when the width of a column is programmaticially changed using
5231                  * <code>{@link #setColumnWidth}</code>.
5232                  * Note internal resizing suppresses the event from firing. See also
5233                  * {@link Ext.grid.GridPanel}.<code>{@link #columnresize}</code>.
5234                  * @param {ColumnModel} this
5235                  * @param {Number} columnIndex The column index
5236                  * @param {Number} newWidth The new width
5237                  */
5238                 "widthchange",
5239                 
5240                 /**
5241                  * @event headerchange
5242                  * Fires when the text of a header changes.
5243                  * @param {ColumnModel} this
5244                  * @param {Number} columnIndex The column index
5245                  * @param {String} newText The new header text
5246                  */
5247                 "headerchange",
5248                 
5249                 /**
5250                  * @event hiddenchange
5251                  * Fires when a column is hidden or "unhidden".
5252                  * @param {ColumnModel} this
5253                  * @param {Number} columnIndex The column index
5254                  * @param {Boolean} hidden true if hidden, false otherwise
5255                  */
5256                 "hiddenchange",
5257                 
5258                 /**
5259                  * @event columnmoved
5260                  * Fires when a column is moved.
5261                  * @param {ColumnModel} this
5262                  * @param {Number} oldIndex
5263                  * @param {Number} newIndex
5264                  */
5265                 "columnmoved",
5266                 
5267                 /**
5268                  * @event configchange
5269                  * Fires when the configuration is changed
5270                  * @param {ColumnModel} this
5271                  */
5272                 "configchange"
5273             );
5274             
5275             Ext.grid.ColumnModel.superclass.constructor.call(this);
5276     },
5277
5278     /**
5279      * Returns the id of the column at the specified index.
5280      * @param {Number} index The column index
5281      * @return {String} the id
5282      */
5283     getColumnId : function(index) {
5284         return this.config[index].id;
5285     },
5286
5287     getColumnAt : function(index) {
5288         return this.config[index];
5289     },
5290
5291     /**
5292      * <p>Reconfigures this column model according to the passed Array of column definition objects.
5293      * For a description of the individual properties of a column definition object, see the
5294      * <a href="#Ext.grid.ColumnModel-configs">Config Options</a>.</p>
5295      * <p>Causes the {@link #configchange} event to be fired. A {@link Ext.grid.GridPanel GridPanel}
5296      * using this ColumnModel will listen for this event and refresh its UI automatically.</p>
5297      * @param {Array} config Array of Column definition objects.
5298      * @param {Boolean} initial Specify <tt>true</tt> to bypass cleanup which deletes the <tt>totalWidth</tt>
5299      * and destroys existing editors.
5300      */
5301     setConfig : function(config, initial) {
5302         var i, c, len;
5303         
5304         if (!initial) { // cleanup
5305             delete this.totalWidth;
5306             
5307             for (i = 0, len = this.config.length; i < len; i++) {
5308                 c = this.config[i];
5309                 
5310                 if (c.setEditor) {
5311                     //check here, in case we have a special column like a CheckboxSelectionModel
5312                     c.setEditor(null);
5313                 }
5314             }
5315         }
5316
5317         // backward compatibility
5318         this.defaults = Ext.apply({
5319             width: this.defaultWidth,
5320             sortable: this.defaultSortable
5321         }, this.defaults);
5322
5323         this.config = config;
5324         this.lookup = {};
5325
5326         for (i = 0, len = config.length; i < len; i++) {
5327             c = Ext.applyIf(config[i], this.defaults);
5328             
5329             // if no id, create one using column's ordinal position
5330             if (Ext.isEmpty(c.id)) {
5331                 c.id = i;
5332             }
5333             
5334             if (!c.isColumn) {
5335                 var Cls = Ext.grid.Column.types[c.xtype || 'gridcolumn'];
5336                 c = new Cls(c);
5337                 config[i] = c;
5338             }
5339             
5340             this.lookup[c.id] = c;
5341         }
5342         
5343         if (!initial) {
5344             this.fireEvent('configchange', this);
5345         }
5346     },
5347
5348     /**
5349      * Returns the column for a specified id.
5350      * @param {String} id The column id
5351      * @return {Object} the column
5352      */
5353     getColumnById : function(id) {
5354         return this.lookup[id];
5355     },
5356
5357     /**
5358      * Returns the index for a specified column id.
5359      * @param {String} id The column id
5360      * @return {Number} the index, or -1 if not found
5361      */
5362     getIndexById : function(id) {
5363         for (var i = 0, len = this.config.length; i < len; i++) {
5364             if (this.config[i].id == id) {
5365                 return i;
5366             }
5367         }
5368         return -1;
5369     },
5370
5371     /**
5372      * Moves a column from one position to another.
5373      * @param {Number} oldIndex The index of the column to move.
5374      * @param {Number} newIndex The position at which to reinsert the coolumn.
5375      */
5376     moveColumn : function(oldIndex, newIndex) {
5377         var config = this.config,
5378             c      = config[oldIndex];
5379             
5380         config.splice(oldIndex, 1);
5381         config.splice(newIndex, 0, c);
5382         this.dataMap = null;
5383         this.fireEvent("columnmoved", this, oldIndex, newIndex);
5384     },
5385
5386     /**
5387      * Returns the number of columns.
5388      * @param {Boolean} visibleOnly Optional. Pass as true to only include visible columns.
5389      * @return {Number}
5390      */
5391     getColumnCount : function(visibleOnly) {
5392         var length = this.config.length,
5393             c = 0,
5394             i;
5395         
5396         if (visibleOnly === true) {
5397             for (i = 0; i < length; i++) {
5398                 if (!this.isHidden(i)) {
5399                     c++;
5400                 }
5401             }
5402             
5403             return c;
5404         }
5405         
5406         return length;
5407     },
5408
5409     /**
5410      * Returns the column configs that return true by the passed function that is called
5411      * with (columnConfig, index)
5412 <pre><code>
5413 // returns an array of column config objects for all hidden columns
5414 var columns = grid.getColumnModel().getColumnsBy(function(c){
5415   return c.hidden;
5416 });
5417 </code></pre>
5418      * @param {Function} fn A function which, when passed a {@link Ext.grid.Column Column} object, must
5419      * return <code>true</code> if the column is to be included in the returned Array.
5420      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function
5421      * is executed. Defaults to this ColumnModel.
5422      * @return {Array} result
5423      */
5424     getColumnsBy : function(fn, scope) {
5425         var config = this.config,
5426             length = config.length,
5427             result = [],
5428             i, c;
5429             
5430         for (i = 0; i < length; i++){
5431             c = config[i];
5432             
5433             if (fn.call(scope || this, c, i) === true) {
5434                 result[result.length] = c;
5435             }
5436         }
5437         
5438         return result;
5439     },
5440
5441     /**
5442      * Returns true if the specified column is sortable.
5443      * @param {Number} col The column index
5444      * @return {Boolean}
5445      */
5446     isSortable : function(col) {
5447         return !!this.config[col].sortable;
5448     },
5449
5450     /**
5451      * Returns true if the specified column menu is disabled.
5452      * @param {Number} col The column index
5453      * @return {Boolean}
5454      */
5455     isMenuDisabled : function(col) {
5456         return !!this.config[col].menuDisabled;
5457     },
5458
5459     /**
5460      * Returns the rendering (formatting) function defined for the column.
5461      * @param {Number} col The column index.
5462      * @return {Function} The function used to render the cell. See {@link #setRenderer}.
5463      */
5464     getRenderer : function(col) {
5465         return this.config[col].renderer || Ext.grid.ColumnModel.defaultRenderer;
5466     },
5467
5468     getRendererScope : function(col) {
5469         return this.config[col].scope;
5470     },
5471
5472     /**
5473      * Sets the rendering (formatting) function for a column.  See {@link Ext.util.Format} for some
5474      * default formatting functions.
5475      * @param {Number} col The column index
5476      * @param {Function} fn The function to use to process the cell's raw data
5477      * to return HTML markup for the grid view. The render function is called with
5478      * the following parameters:<ul>
5479      * <li><b>value</b> : Object<p class="sub-desc">The data value for the cell.</p></li>
5480      * <li><b>metadata</b> : Object<p class="sub-desc">An object in which you may set the following attributes:<ul>
5481      * <li><b>css</b> : String<p class="sub-desc">A CSS class name to add to the cell's TD element.</p></li>
5482      * <li><b>attr</b> : String<p class="sub-desc">An HTML attribute definition string to apply to the data container element <i>within</i> the table cell
5483      * (e.g. 'style="color:red;"').</p></li></ul></p></li>
5484      * <li><b>record</b> : Ext.data.record<p class="sub-desc">The {@link Ext.data.Record} from which the data was extracted.</p></li>
5485      * <li><b>rowIndex</b> : Number<p class="sub-desc">Row index</p></li>
5486      * <li><b>colIndex</b> : Number<p class="sub-desc">Column index</p></li>
5487      * <li><b>store</b> : Ext.data.Store<p class="sub-desc">The {@link Ext.data.Store} object from which the Record was extracted.</p></li></ul>
5488      */
5489     setRenderer : function(col, fn) {
5490         this.config[col].renderer = fn;
5491     },
5492
5493     /**
5494      * Returns the width for the specified column.
5495      * @param {Number} col The column index
5496      * @return {Number}
5497      */
5498     getColumnWidth : function(col) {
5499         var width = this.config[col].width;
5500         if(typeof width != 'number'){
5501             width = this.defaultWidth;
5502         }
5503         return width;
5504     },
5505
5506     /**
5507      * Sets the width for a column.
5508      * @param {Number} col The column index
5509      * @param {Number} width The new width
5510      * @param {Boolean} suppressEvent True to suppress firing the <code>{@link #widthchange}</code>
5511      * event. Defaults to false.
5512      */
5513     setColumnWidth : function(col, width, suppressEvent) {
5514         this.config[col].width = width;
5515         this.totalWidth = null;
5516         
5517         if (!suppressEvent) {
5518              this.fireEvent("widthchange", this, col, width);
5519         }
5520     },
5521
5522     /**
5523      * Returns the total width of all columns.
5524      * @param {Boolean} includeHidden True to include hidden column widths
5525      * @return {Number}
5526      */
5527     getTotalWidth : function(includeHidden) {
5528         if (!this.totalWidth) {
5529             this.totalWidth = 0;
5530             for (var i = 0, len = this.config.length; i < len; i++) {
5531                 if (includeHidden || !this.isHidden(i)) {
5532                     this.totalWidth += this.getColumnWidth(i);
5533                 }
5534             }
5535         }
5536         return this.totalWidth;
5537     },
5538
5539     /**
5540      * Returns the header for the specified column.
5541      * @param {Number} col The column index
5542      * @return {String}
5543      */
5544     getColumnHeader : function(col) {
5545         return this.config[col].header;
5546     },
5547
5548     /**
5549      * Sets the header for a column.
5550      * @param {Number} col The column index
5551      * @param {String} header The new header
5552      */
5553     setColumnHeader : function(col, header) {
5554         this.config[col].header = header;
5555         this.fireEvent("headerchange", this, col, header);
5556     },
5557
5558     /**
5559      * Returns the tooltip for the specified column.
5560      * @param {Number} col The column index
5561      * @return {String}
5562      */
5563     getColumnTooltip : function(col) {
5564             return this.config[col].tooltip;
5565     },
5566     /**
5567      * Sets the tooltip for a column.
5568      * @param {Number} col The column index
5569      * @param {String} tooltip The new tooltip
5570      */
5571     setColumnTooltip : function(col, tooltip) {
5572             this.config[col].tooltip = tooltip;
5573     },
5574
5575     /**
5576      * Returns the dataIndex for the specified column.
5577 <pre><code>
5578 // Get field name for the column
5579 var fieldName = grid.getColumnModel().getDataIndex(columnIndex);
5580 </code></pre>
5581      * @param {Number} col The column index
5582      * @return {String} The column's dataIndex
5583      */
5584     getDataIndex : function(col) {
5585         return this.config[col].dataIndex;
5586     },
5587
5588     /**
5589      * Sets the dataIndex for a column.
5590      * @param {Number} col The column index
5591      * @param {String} dataIndex The new dataIndex
5592      */
5593     setDataIndex : function(col, dataIndex) {
5594         this.config[col].dataIndex = dataIndex;
5595     },
5596
5597     /**
5598      * Finds the index of the first matching column for the given dataIndex.
5599      * @param {String} col The dataIndex to find
5600      * @return {Number} The column index, or -1 if no match was found
5601      */
5602     findColumnIndex : function(dataIndex) {
5603         var c = this.config;
5604         for(var i = 0, len = c.length; i < len; i++){
5605             if(c[i].dataIndex == dataIndex){
5606                 return i;
5607             }
5608         }
5609         return -1;
5610     },
5611
5612     /**
5613      * Returns true if the cell is editable.
5614 <pre><code>
5615 var store = new Ext.data.Store({...});
5616 var colModel = new Ext.grid.ColumnModel({
5617   columns: [...],
5618   isCellEditable: function(col, row) {
5619     var record = store.getAt(row);
5620     if (record.get('readonly')) { // replace with your condition
5621       return false;
5622     }
5623     return Ext.grid.ColumnModel.prototype.isCellEditable.call(this, col, row);
5624   }
5625 });
5626 var grid = new Ext.grid.GridPanel({
5627   store: store,
5628   colModel: colModel,
5629   ...
5630 });
5631 </code></pre>
5632      * @param {Number} colIndex The column index
5633      * @param {Number} rowIndex The row index
5634      * @return {Boolean}
5635      */
5636     isCellEditable : function(colIndex, rowIndex) {
5637         var c = this.config[colIndex],
5638             ed = c.editable;
5639
5640         //force boolean
5641         return !!(ed || (!Ext.isDefined(ed) && c.editor));
5642     },
5643
5644     /**
5645      * Returns the editor defined for the cell/column.
5646      * @param {Number} colIndex The column index
5647      * @param {Number} rowIndex The row index
5648      * @return {Ext.Editor} The {@link Ext.Editor Editor} that was created to wrap
5649      * the {@link Ext.form.Field Field} used to edit the cell.
5650      */
5651     getCellEditor : function(colIndex, rowIndex) {
5652         return this.config[colIndex].getCellEditor(rowIndex);
5653     },
5654
5655     /**
5656      * Sets if a column is editable.
5657      * @param {Number} col The column index
5658      * @param {Boolean} editable True if the column is editable
5659      */
5660     setEditable : function(col, editable) {
5661         this.config[col].editable = editable;
5662     },
5663
5664     /**
5665      * Returns <tt>true</tt> if the column is <code>{@link Ext.grid.Column#hidden hidden}</code>,
5666      * <tt>false</tt> otherwise.
5667      * @param {Number} colIndex The column index
5668      * @return {Boolean}
5669      */
5670     isHidden : function(colIndex) {
5671         return !!this.config[colIndex].hidden; // ensure returns boolean
5672     },
5673
5674     /**
5675      * Returns <tt>true</tt> if the column is <code>{@link Ext.grid.Column#fixed fixed}</code>,
5676      * <tt>false</tt> otherwise.
5677      * @param {Number} colIndex The column index
5678      * @return {Boolean}
5679      */
5680     isFixed : function(colIndex) {
5681         return !!this.config[colIndex].fixed;
5682     },
5683
5684     /**
5685      * Returns true if the column can be resized
5686      * @return {Boolean}
5687      */
5688     isResizable : function(colIndex) {
5689         return colIndex >= 0 && this.config[colIndex].resizable !== false && this.config[colIndex].fixed !== true;
5690     },
5691     
5692     /**
5693      * Sets if a column is hidden.
5694 <pre><code>
5695 myGrid.getColumnModel().setHidden(0, true); // hide column 0 (0 = the first column).
5696 </code></pre>
5697      * @param {Number} colIndex The column index
5698      * @param {Boolean} hidden True if the column is hidden
5699      */
5700     setHidden : function(colIndex, hidden) {
5701         var c = this.config[colIndex];
5702         if(c.hidden !== hidden){
5703             c.hidden = hidden;
5704             this.totalWidth = null;
5705             this.fireEvent("hiddenchange", this, colIndex, hidden);
5706         }
5707     },
5708
5709     /**
5710      * Sets the editor for a column and destroys the prior editor.
5711      * @param {Number} col The column index
5712      * @param {Object} editor The editor object
5713      */
5714     setEditor : function(col, editor) {
5715         this.config[col].setEditor(editor);
5716     },
5717
5718     /**
5719      * Destroys this column model by purging any event listeners. Destroys and dereferences all Columns.
5720      */
5721     destroy : function() {
5722         var length = this.config.length,
5723             i = 0;
5724
5725         for (; i < length; i++){
5726             this.config[i].destroy(); // Column's destroy encapsulates all cleanup.
5727         }
5728         delete this.config;
5729         delete this.lookup;
5730         this.purgeListeners();
5731     },
5732
5733     /**
5734      * @private
5735      * Setup any saved state for the column, ensures that defaults are applied.
5736      */
5737     setState : function(col, state) {
5738         state = Ext.applyIf(state, this.defaults);
5739         Ext.apply(this.config[col], state);
5740     }
5741 });
5742
5743 // private
5744 Ext.grid.ColumnModel.defaultRenderer = function(value) {
5745     if (typeof value == "string" && value.length < 1) {
5746         return "&#160;";
5747     }
5748     return value;
5749 };/**
5750  * @class Ext.grid.AbstractSelectionModel
5751  * @extends Ext.util.Observable
5752  * Abstract base class for grid SelectionModels.  It provides the interface that should be
5753  * implemented by descendant classes.  This class should not be directly instantiated.
5754  * @constructor
5755  */
5756 Ext.grid.AbstractSelectionModel = Ext.extend(Ext.util.Observable,  {
5757     /**
5758      * The GridPanel for which this SelectionModel is handling selection. Read-only.
5759      * @type Object
5760      * @property grid
5761      */
5762
5763     constructor : function(){
5764         this.locked = false;
5765         Ext.grid.AbstractSelectionModel.superclass.constructor.call(this);
5766     },
5767
5768     /** @ignore Called by the grid automatically. Do not call directly. */
5769     init : function(grid){
5770         this.grid = grid;
5771         if(this.lockOnInit){
5772             delete this.lockOnInit;
5773             this.locked = false;
5774             this.lock();
5775         }
5776         this.initEvents();
5777     },
5778
5779     /**
5780      * Locks the selections.
5781      */
5782     lock : function(){
5783         if(!this.locked){
5784             this.locked = true;
5785             // If the grid has been set, then the view is already initialized.
5786             var g = this.grid;
5787             if(g){
5788                 g.getView().on({
5789                     scope: this,
5790                     beforerefresh: this.sortUnLock,
5791                     refresh: this.sortLock
5792                 });
5793             }else{
5794                 this.lockOnInit = true;
5795             }
5796         }
5797     },
5798
5799     // set the lock states before and after a view refresh
5800     sortLock : function() {
5801         this.locked = true;
5802     },
5803
5804     // set the lock states before and after a view refresh
5805     sortUnLock : function() {
5806         this.locked = false;
5807     },
5808
5809     /**
5810      * Unlocks the selections.
5811      */
5812     unlock : function(){
5813         if(this.locked){
5814             this.locked = false;
5815             var g = this.grid,
5816                 gv;
5817                 
5818             // If the grid has been set, then the view is already initialized.
5819             if(g){
5820                 gv = g.getView();
5821                 gv.un('beforerefresh', this.sortUnLock, this);
5822                 gv.un('refresh', this.sortLock, this);    
5823             }else{
5824                 delete this.lockOnInit;
5825             }
5826         }
5827     },
5828
5829     /**
5830      * Returns true if the selections are locked.
5831      * @return {Boolean}
5832      */
5833     isLocked : function(){
5834         return this.locked;
5835     },
5836
5837     destroy: function(){
5838         this.unlock();
5839         this.purgeListeners();
5840     }
5841 });/**
5842  * @class Ext.grid.RowSelectionModel
5843  * @extends Ext.grid.AbstractSelectionModel
5844  * The default SelectionModel used by {@link Ext.grid.GridPanel}.
5845  * It supports multiple selections and keyboard selection/navigation. The objects stored
5846  * as selections and returned by {@link #getSelected}, and {@link #getSelections} are
5847  * the {@link Ext.data.Record Record}s which provide the data for the selected rows.
5848  * @constructor
5849  * @param {Object} config
5850  */
5851 Ext.grid.RowSelectionModel = Ext.extend(Ext.grid.AbstractSelectionModel,  {
5852     /**
5853      * @cfg {Boolean} singleSelect
5854      * <tt>true</tt> to allow selection of only one row at a time (defaults to <tt>false</tt>
5855      * allowing multiple selections)
5856      */
5857     singleSelect : false,
5858     
5859     constructor : function(config){
5860         Ext.apply(this, config);
5861         this.selections = new Ext.util.MixedCollection(false, function(o){
5862             return o.id;
5863         });
5864
5865         this.last = false;
5866         this.lastActive = false;
5867
5868         this.addEvents(
5869                 /**
5870                  * @event selectionchange
5871                  * Fires when the selection changes
5872                  * @param {SelectionModel} this
5873                  */
5874                 'selectionchange',
5875                 /**
5876                  * @event beforerowselect
5877                  * Fires before a row is selected, return false to cancel the selection.
5878                  * @param {SelectionModel} this
5879                  * @param {Number} rowIndex The index to be selected
5880                  * @param {Boolean} keepExisting False if other selections will be cleared
5881                  * @param {Record} record The record to be selected
5882                  */
5883                 'beforerowselect',
5884                 /**
5885                  * @event rowselect
5886                  * Fires when a row is selected.
5887                  * @param {SelectionModel} this
5888                  * @param {Number} rowIndex The selected index
5889                  * @param {Ext.data.Record} r The selected record
5890                  */
5891                 'rowselect',
5892                 /**
5893                  * @event rowdeselect
5894                  * Fires when a row is deselected.  To prevent deselection
5895                  * {@link Ext.grid.AbstractSelectionModel#lock lock the selections}. 
5896                  * @param {SelectionModel} this
5897                  * @param {Number} rowIndex
5898                  * @param {Record} record
5899                  */
5900                 'rowdeselect'
5901         );
5902         Ext.grid.RowSelectionModel.superclass.constructor.call(this);
5903     },
5904
5905     /**
5906      * @cfg {Boolean} moveEditorOnEnter
5907      * <tt>false</tt> to turn off moving the editor to the next row down when the enter key is pressed
5908      * or the next row up when shift + enter keys are pressed.
5909      */
5910     // private
5911     initEvents : function(){
5912
5913         if(!this.grid.enableDragDrop && !this.grid.enableDrag){
5914             this.grid.on('rowmousedown', this.handleMouseDown, this);
5915         }
5916
5917         this.rowNav = new Ext.KeyNav(this.grid.getGridEl(), {
5918             up: this.onKeyPress, 
5919             down: this.onKeyPress,
5920             scope: this
5921         });
5922
5923         this.grid.getView().on({
5924             scope: this,
5925             refresh: this.onRefresh,
5926             rowupdated: this.onRowUpdated,
5927             rowremoved: this.onRemove
5928         });
5929     },
5930     
5931     onKeyPress : function(e, name){
5932         var up = name == 'up',
5933             method = up ? 'selectPrevious' : 'selectNext',
5934             add = up ? -1 : 1,
5935             last;
5936         if(!e.shiftKey || this.singleSelect){
5937             this[method](false);
5938         }else if(this.last !== false && this.lastActive !== false){
5939             last = this.last;
5940             this.selectRange(this.last,  this.lastActive + add);
5941             this.grid.getView().focusRow(this.lastActive);
5942             if(last !== false){
5943                 this.last = last;
5944             }
5945         }else{
5946            this.selectFirstRow();
5947         }
5948     },
5949
5950     // private
5951     onRefresh : function(){
5952         var ds = this.grid.store,
5953             s = this.getSelections(),
5954             i = 0,
5955             len = s.length, 
5956             index, r;
5957             
5958         this.silent = true;
5959         this.clearSelections(true);
5960         for(; i < len; i++){
5961             r = s[i];
5962             if((index = ds.indexOfId(r.id)) != -1){
5963                 this.selectRow(index, true);
5964             }
5965         }
5966         if(s.length != this.selections.getCount()){
5967             this.fireEvent('selectionchange', this);
5968         }
5969         this.silent = false;
5970     },
5971
5972     // private
5973     onRemove : function(v, index, r){
5974         if(this.selections.remove(r) !== false){
5975             this.fireEvent('selectionchange', this);
5976         }
5977     },
5978
5979     // private
5980     onRowUpdated : function(v, index, r){
5981         if(this.isSelected(r)){
5982             v.onRowSelect(index);
5983         }
5984     },
5985
5986     /**
5987      * Select records.
5988      * @param {Array} records The records to select
5989      * @param {Boolean} keepExisting (optional) <tt>true</tt> to keep existing selections
5990      */
5991     selectRecords : function(records, keepExisting){
5992         if(!keepExisting){
5993             this.clearSelections();
5994         }
5995         var ds = this.grid.store,
5996             i = 0,
5997             len = records.length;
5998         for(; i < len; i++){
5999             this.selectRow(ds.indexOf(records[i]), true);
6000         }
6001     },
6002
6003     /**
6004      * Gets the number of selected rows.
6005      * @return {Number}
6006      */
6007     getCount : function(){
6008         return this.selections.length;
6009     },
6010
6011     /**
6012      * Selects the first row in the grid.
6013      */
6014     selectFirstRow : function(){
6015         this.selectRow(0);
6016     },
6017
6018     /**
6019      * Select the last row.
6020      * @param {Boolean} keepExisting (optional) <tt>true</tt> to keep existing selections
6021      */
6022     selectLastRow : function(keepExisting){
6023         this.selectRow(this.grid.store.getCount() - 1, keepExisting);
6024     },
6025
6026     /**
6027      * Selects the row immediately following the last selected row.
6028      * @param {Boolean} keepExisting (optional) <tt>true</tt> to keep existing selections
6029      * @return {Boolean} <tt>true</tt> if there is a next row, else <tt>false</tt>
6030      */
6031     selectNext : function(keepExisting){
6032         if(this.hasNext()){
6033             this.selectRow(this.last+1, keepExisting);
6034             this.grid.getView().focusRow(this.last);
6035             return true;
6036         }
6037         return false;
6038     },
6039
6040     /**
6041      * Selects the row that precedes the last selected row.
6042      * @param {Boolean} keepExisting (optional) <tt>true</tt> to keep existing selections
6043      * @return {Boolean} <tt>true</tt> if there is a previous row, else <tt>false</tt>
6044      */
6045     selectPrevious : function(keepExisting){
6046         if(this.hasPrevious()){
6047             this.selectRow(this.last-1, keepExisting);
6048             this.grid.getView().focusRow(this.last);
6049             return true;
6050         }
6051         return false;
6052     },
6053
6054     /**
6055      * Returns true if there is a next record to select
6056      * @return {Boolean}
6057      */
6058     hasNext : function(){
6059         return this.last !== false && (this.last+1) < this.grid.store.getCount();
6060     },
6061
6062     /**
6063      * Returns true if there is a previous record to select
6064      * @return {Boolean}
6065      */
6066     hasPrevious : function(){
6067         return !!this.last;
6068     },
6069
6070
6071     /**
6072      * Returns the selected records
6073      * @return {Array} Array of selected records
6074      */
6075     getSelections : function(){
6076         return [].concat(this.selections.items);
6077     },
6078
6079     /**
6080      * Returns the first selected record.
6081      * @return {Record}
6082      */
6083     getSelected : function(){
6084         return this.selections.itemAt(0);
6085     },
6086
6087     /**
6088      * Calls the passed function with each selection. If the function returns
6089      * <tt>false</tt>, iteration is stopped and this function returns
6090      * <tt>false</tt>. Otherwise it returns <tt>true</tt>.
6091      * @param {Function} fn The function to call upon each iteration. It is passed the selected {@link Ext.data.Record Record}.
6092      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to this RowSelectionModel.
6093      * @return {Boolean} true if all selections were iterated
6094      */
6095     each : function(fn, scope){
6096         var s = this.getSelections(),
6097             i = 0,
6098             len = s.length;
6099             
6100         for(; i < len; i++){
6101             if(fn.call(scope || this, s[i], i) === false){
6102                 return false;
6103             }
6104         }
6105         return true;
6106     },
6107
6108     /**
6109      * Clears all selections if the selection model
6110      * {@link Ext.grid.AbstractSelectionModel#isLocked is not locked}.
6111      * @param {Boolean} fast (optional) <tt>true</tt> to bypass the
6112      * conditional checks and events described in {@link #deselectRow}.
6113      */
6114     clearSelections : function(fast){
6115         if(this.isLocked()){
6116             return;
6117         }
6118         if(fast !== true){
6119             var ds = this.grid.store,
6120                 s = this.selections;
6121             s.each(function(r){
6122                 this.deselectRow(ds.indexOfId(r.id));
6123             }, this);
6124             s.clear();
6125         }else{
6126             this.selections.clear();
6127         }
6128         this.last = false;
6129     },
6130
6131
6132     /**
6133      * Selects all rows if the selection model
6134      * {@link Ext.grid.AbstractSelectionModel#isLocked is not locked}. 
6135      */
6136     selectAll : function(){
6137         if(this.isLocked()){
6138             return;
6139         }
6140         this.selections.clear();
6141         for(var i = 0, len = this.grid.store.getCount(); i < len; i++){
6142             this.selectRow(i, true);
6143         }
6144     },
6145
6146     /**
6147      * Returns <tt>true</tt> if there is a selection.
6148      * @return {Boolean}
6149      */
6150     hasSelection : function(){
6151         return this.selections.length > 0;
6152     },
6153
6154     /**
6155      * Returns <tt>true</tt> if the specified row is selected.
6156      * @param {Number/Record} index The record or index of the record to check
6157      * @return {Boolean}
6158      */
6159     isSelected : function(index){
6160         var r = Ext.isNumber(index) ? this.grid.store.getAt(index) : index;
6161         return (r && this.selections.key(r.id) ? true : false);
6162     },
6163
6164     /**
6165      * Returns <tt>true</tt> if the specified record id is selected.
6166      * @param {String} id The id of record to check
6167      * @return {Boolean}
6168      */
6169     isIdSelected : function(id){
6170         return (this.selections.key(id) ? true : false);
6171     },
6172
6173     // private
6174     handleMouseDown : function(g, rowIndex, e){
6175         if(e.button !== 0 || this.isLocked()){
6176             return;
6177         }
6178         var view = this.grid.getView();
6179         if(e.shiftKey && !this.singleSelect && this.last !== false){
6180             var last = this.last;
6181             this.selectRange(last, rowIndex, e.ctrlKey);
6182             this.last = last; // reset the last
6183             view.focusRow(rowIndex);
6184         }else{
6185             var isSelected = this.isSelected(rowIndex);
6186             if(e.ctrlKey && isSelected){
6187                 this.deselectRow(rowIndex);
6188             }else if(!isSelected || this.getCount() > 1){
6189                 this.selectRow(rowIndex, e.ctrlKey || e.shiftKey);
6190                 view.focusRow(rowIndex);
6191             }
6192         }
6193     },
6194
6195     /**
6196      * Selects multiple rows.
6197      * @param {Array} rows Array of the indexes of the row to select
6198      * @param {Boolean} keepExisting (optional) <tt>true</tt> to keep
6199      * existing selections (defaults to <tt>false</tt>)
6200      */
6201     selectRows : function(rows, keepExisting){
6202         if(!keepExisting){
6203             this.clearSelections();
6204         }
6205         for(var i = 0, len = rows.length; i < len; i++){
6206             this.selectRow(rows[i], true);
6207         }
6208     },
6209
6210     /**
6211      * Selects a range of rows if the selection model
6212      * {@link Ext.grid.AbstractSelectionModel#isLocked is not locked}.
6213      * All rows in between startRow and endRow are also selected.
6214      * @param {Number} startRow The index of the first row in the range
6215      * @param {Number} endRow The index of the last row in the range
6216      * @param {Boolean} keepExisting (optional) True to retain existing selections
6217      */
6218     selectRange : function(startRow, endRow, keepExisting){
6219         var i;
6220         if(this.isLocked()){
6221             return;
6222         }
6223         if(!keepExisting){
6224             this.clearSelections();
6225         }
6226         if(startRow <= endRow){
6227             for(i = startRow; i <= endRow; i++){
6228                 this.selectRow(i, true);
6229             }
6230         }else{
6231             for(i = startRow; i >= endRow; i--){
6232                 this.selectRow(i, true);
6233             }
6234         }
6235     },
6236
6237     /**
6238      * Deselects a range of rows if the selection model
6239      * {@link Ext.grid.AbstractSelectionModel#isLocked is not locked}.  
6240      * All rows in between startRow and endRow are also deselected.
6241      * @param {Number} startRow The index of the first row in the range
6242      * @param {Number} endRow The index of the last row in the range
6243      */
6244     deselectRange : function(startRow, endRow, preventViewNotify){
6245         if(this.isLocked()){
6246             return;
6247         }
6248         for(var i = startRow; i <= endRow; i++){
6249             this.deselectRow(i, preventViewNotify);
6250         }
6251     },
6252
6253     /**
6254      * Selects a row.  Before selecting a row, checks if the selection model
6255      * {@link Ext.grid.AbstractSelectionModel#isLocked is locked} and fires the
6256      * {@link #beforerowselect} event.  If these checks are satisfied the row
6257      * will be selected and followed up by  firing the {@link #rowselect} and
6258      * {@link #selectionchange} events.
6259      * @param {Number} row The index of the row to select
6260      * @param {Boolean} keepExisting (optional) <tt>true</tt> to keep existing selections
6261      * @param {Boolean} preventViewNotify (optional) Specify <tt>true</tt> to
6262      * prevent notifying the view (disables updating the selected appearance)
6263      */
6264     selectRow : function(index, keepExisting, preventViewNotify){
6265         if(this.isLocked() || (index < 0 || index >= this.grid.store.getCount()) || (keepExisting && this.isSelected(index))){
6266             return;
6267         }
6268         var r = this.grid.store.getAt(index);
6269         if(r && this.fireEvent('beforerowselect', this, index, keepExisting, r) !== false){
6270             if(!keepExisting || this.singleSelect){
6271                 this.clearSelections();
6272             }
6273             this.selections.add(r);
6274             this.last = this.lastActive = index;
6275             if(!preventViewNotify){
6276                 this.grid.getView().onRowSelect(index);
6277             }
6278             if(!this.silent){
6279                 this.fireEvent('rowselect', this, index, r);
6280                 this.fireEvent('selectionchange', this);
6281             }
6282         }
6283     },
6284
6285     /**
6286      * Deselects a row.  Before deselecting a row, checks if the selection model
6287      * {@link Ext.grid.AbstractSelectionModel#isLocked is locked}.
6288      * If this check is satisfied the row will be deselected and followed up by
6289      * firing the {@link #rowdeselect} and {@link #selectionchange} events.
6290      * @param {Number} row The index of the row to deselect
6291      * @param {Boolean} preventViewNotify (optional) Specify <tt>true</tt> to
6292      * prevent notifying the view (disables updating the selected appearance)
6293      */
6294     deselectRow : function(index, preventViewNotify){
6295         if(this.isLocked()){
6296             return;
6297         }
6298         if(this.last == index){
6299             this.last = false;
6300         }
6301         if(this.lastActive == index){
6302             this.lastActive = false;
6303         }
6304         var r = this.grid.store.getAt(index);
6305         if(r){
6306             this.selections.remove(r);
6307             if(!preventViewNotify){
6308                 this.grid.getView().onRowDeselect(index);
6309             }
6310             this.fireEvent('rowdeselect', this, index, r);
6311             this.fireEvent('selectionchange', this);
6312         }
6313     },
6314
6315     // private
6316     acceptsNav : function(row, col, cm){
6317         return !cm.isHidden(col) && cm.isCellEditable(col, row);
6318     },
6319
6320     // private
6321     onEditorKey : function(field, e){
6322         var k = e.getKey(), 
6323             newCell, 
6324             g = this.grid, 
6325             last = g.lastEdit,
6326             ed = g.activeEditor,
6327             shift = e.shiftKey,
6328             ae, last, r, c;
6329             
6330         if(k == e.TAB){
6331             e.stopEvent();
6332             ed.completeEdit();
6333             if(shift){
6334                 newCell = g.walkCells(ed.row, ed.col-1, -1, this.acceptsNav, this);
6335             }else{
6336                 newCell = g.walkCells(ed.row, ed.col+1, 1, this.acceptsNav, this);
6337             }
6338         }else if(k == e.ENTER){
6339             if(this.moveEditorOnEnter !== false){
6340                 if(shift){
6341                     newCell = g.walkCells(last.row - 1, last.col, -1, this.acceptsNav, this);
6342                 }else{
6343                     newCell = g.walkCells(last.row + 1, last.col, 1, this.acceptsNav, this);
6344                 }
6345             }
6346         }
6347         if(newCell){
6348             r = newCell[0];
6349             c = newCell[1];
6350
6351             this.onEditorSelect(r, last.row);
6352
6353             if(g.isEditor && g.editing){ // *** handle tabbing while editorgrid is in edit mode
6354                 ae = g.activeEditor;
6355                 if(ae && ae.field.triggerBlur){
6356                     // *** if activeEditor is a TriggerField, explicitly call its triggerBlur() method
6357                     ae.field.triggerBlur();
6358                 }
6359             }
6360             g.startEditing(r, c);
6361         }
6362     },
6363     
6364     onEditorSelect: function(row, lastRow){
6365         if(lastRow != row){
6366             this.selectRow(row); // *** highlight newly-selected cell and update selection
6367         }
6368     },
6369     
6370     destroy : function(){
6371         Ext.destroy(this.rowNav);
6372         this.rowNav = null;
6373         Ext.grid.RowSelectionModel.superclass.destroy.call(this);
6374     }
6375 });
6376 /**
6377  * @class Ext.grid.Column
6378  * <p>This class encapsulates column configuration data to be used in the initialization of a
6379  * {@link Ext.grid.ColumnModel ColumnModel}.</p>
6380  * <p>While subclasses are provided to render data in different ways, this class renders a passed
6381  * data field unchanged and is usually used for textual columns.</p>
6382  */
6383 Ext.grid.Column = Ext.extend(Ext.util.Observable, {
6384     /**
6385      * @cfg {Boolean} editable Optional. Defaults to <tt>true</tt>, enabling the configured
6386      * <tt>{@link #editor}</tt>.  Set to <tt>false</tt> to initially disable editing on this column.
6387      * The initial configuration may be dynamically altered using
6388      * {@link Ext.grid.ColumnModel}.{@link Ext.grid.ColumnModel#setEditable setEditable()}.
6389      */
6390     /**
6391      * @cfg {String} id Optional. A name which identifies this column (defaults to the column's initial
6392      * ordinal position.) The <tt>id</tt> is used to create a CSS <b>class</b> name which is applied to all
6393      * table cells (including headers) in that column (in this context the <tt>id</tt> does not need to be
6394      * unique). The class name takes the form of <pre>x-grid3-td-<b>id</b></pre>
6395      * Header cells will also receive this class name, but will also have the class <pre>x-grid3-hd</pre>
6396      * So, to target header cells, use CSS selectors such as:<pre>.x-grid3-hd-row .x-grid3-td-<b>id</b></pre>
6397      * The {@link Ext.grid.GridPanel#autoExpandColumn} grid config option references the column via this
6398      * unique identifier.
6399      */
6400     /**
6401      * @cfg {String} header Optional. The header text to be used as innerHTML
6402      * (html tags are accepted) to display in the Grid view.  <b>Note</b>: to
6403      * have a clickable header with no text displayed use <tt>'&amp;#160;'</tt>.
6404      */
6405     /**
6406      * @cfg {Boolean} groupable Optional. If the grid is being rendered by an {@link Ext.grid.GroupingView}, this option
6407      * may be used to disable the header menu item to group by the column selected. Defaults to <tt>true</tt>,
6408      * which enables the header menu group option.  Set to <tt>false</tt> to disable (but still show) the
6409      * group option in the header menu for the column. See also <code>{@link #groupName}</code>.
6410      */
6411     /**
6412      * @cfg {String} groupName Optional. If the grid is being rendered by an {@link Ext.grid.GroupingView}, this option
6413      * may be used to specify the text with which to prefix the group field value in the group header line.
6414      * See also {@link #groupRenderer} and
6415      * {@link Ext.grid.GroupingView}.{@link Ext.grid.GroupingView#showGroupName showGroupName}.
6416      */
6417     /**
6418      * @cfg {Function} groupRenderer <p>Optional. If the grid is being rendered by an {@link Ext.grid.GroupingView}, this option
6419      * may be used to specify the function used to format the grouping field value for display in the group
6420      * {@link #groupName header}.  If a <tt><b>groupRenderer</b></tt> is not specified, the configured
6421      * <tt><b>{@link #renderer}</b></tt> will be called; if a <tt><b>{@link #renderer}</b></tt> is also not specified
6422      * the new value of the group field will be used.</p>
6423      * <p>The called function (either the <tt><b>groupRenderer</b></tt> or <tt><b>{@link #renderer}</b></tt>) will be
6424      * passed the following parameters:
6425      * <div class="mdetail-params"><ul>
6426      * <li><b>v</b> : Object<p class="sub-desc">The new value of the group field.</p></li>
6427      * <li><b>unused</b> : undefined<p class="sub-desc">Unused parameter.</p></li>
6428      * <li><b>r</b> : Ext.data.Record<p class="sub-desc">The Record providing the data
6429      * for the row which caused group change.</p></li>
6430      * <li><b>rowIndex</b> : Number<p class="sub-desc">The row index of the Record which caused group change.</p></li>
6431      * <li><b>colIndex</b> : Number<p class="sub-desc">The column index of the group field.</p></li>
6432      * <li><b>ds</b> : Ext.data.Store<p class="sub-desc">The Store which is providing the data Model.</p></li>
6433      * </ul></div></p>
6434      * <p>The function should return a string value.</p>
6435      */
6436     /**
6437      * @cfg {String} emptyGroupText Optional. If the grid is being rendered by an {@link Ext.grid.GroupingView}, this option
6438      * may be used to specify the text to display when there is an empty group value. Defaults to the
6439      * {@link Ext.grid.GroupingView}.{@link Ext.grid.GroupingView#emptyGroupText emptyGroupText}.
6440      */
6441     /**
6442      * @cfg {String} dataIndex <p><b>Required</b>. The name of the field in the
6443      * grid's {@link Ext.data.Store}'s {@link Ext.data.Record} definition from
6444      * which to draw the column's value.</p>
6445      */
6446     /**
6447      * @cfg {Number} width
6448      * Optional. The initial width in pixels of the column.
6449      * The width of each column can also be affected if any of the following are configured:
6450      * <div class="mdetail-params"><ul>
6451      * <li>{@link Ext.grid.GridPanel}.<tt>{@link Ext.grid.GridPanel#autoExpandColumn autoExpandColumn}</tt></li>
6452      * <li>{@link Ext.grid.GridView}.<tt>{@link Ext.grid.GridView#forceFit forceFit}</tt>
6453      * <div class="sub-desc">
6454      * <p>By specifying <tt>forceFit:true</tt>, {@link #fixed non-fixed width} columns will be
6455      * re-proportioned (based on the relative initial widths) to fill the width of the grid so
6456      * that no horizontal scrollbar is shown.</p>
6457      * </div></li>
6458      * <li>{@link Ext.grid.GridView}.<tt>{@link Ext.grid.GridView#autoFill autoFill}</tt></li>
6459      * <li>{@link Ext.grid.GridPanel}.<tt>{@link Ext.grid.GridPanel#minColumnWidth minColumnWidth}</tt></li>
6460      * <br><p><b>Note</b>: when the width of each column is determined, a space on the right side
6461      * is reserved for the vertical scrollbar.  The
6462      * {@link Ext.grid.GridView}.<tt>{@link Ext.grid.GridView#scrollOffset scrollOffset}</tt>
6463      * can be modified to reduce or eliminate the reserved offset.</p>
6464      */
6465     /**
6466      * @cfg {Boolean} sortable Optional. <tt>true</tt> if sorting is to be allowed on this column.
6467      * Defaults to the value of the <code>{@link Ext.grid.ColumnModel#defaultSortable}</code> property.
6468      * Whether local/remote sorting is used is specified in <code>{@link Ext.data.Store#remoteSort}</code>.
6469      */
6470     /**
6471      * @cfg {Boolean} fixed Optional. <tt>true</tt> if the column width cannot be changed.  Defaults to <tt>false</tt>.
6472      */
6473     /**
6474      * @cfg {Boolean} resizable Optional. <tt>false</tt> to disable column resizing. Defaults to <tt>true</tt>.
6475      */
6476     /**
6477      * @cfg {Boolean} menuDisabled Optional. <tt>true</tt> to disable the column menu. Defaults to <tt>false</tt>.
6478      */
6479     /**
6480      * @cfg {Boolean} hidden
6481      * Optional. <tt>true</tt> to initially hide this column. Defaults to <tt>false</tt>.
6482      * A hidden column {@link Ext.grid.GridPanel#enableColumnHide may be shown via the header row menu}.
6483      * If a column is never to be shown, simply do not include this column in the Column Model at all.
6484      */
6485     /**
6486      * @cfg {String} tooltip Optional. A text string to use as the column header's tooltip.  If Quicktips
6487      * are enabled, this value will be used as the text of the quick tip, otherwise it will be set as the
6488      * header's HTML title attribute. Defaults to ''.
6489      */
6490     /**
6491      * @cfg {Mixed} renderer
6492      * <p>For an alternative to specifying a renderer see <code>{@link #xtype}</code></p>
6493      * <p>Optional. A renderer is an 'interceptor' method which can be used transform data (value,
6494      * appearance, etc.) before it is rendered). This may be specified in either of three ways:
6495      * <div class="mdetail-params"><ul>
6496      * <li>A renderer function used to return HTML markup for a cell given the cell's data value.</li>
6497      * <li>A string which references a property name of the {@link Ext.util.Format} class which
6498      * provides a renderer function.</li>
6499      * <li>An object specifying both the renderer function, and its execution scope (<tt><b>this</b></tt>
6500      * reference) e.g.:<pre style="margin-left:1.2em"><code>
6501 {
6502     fn: this.gridRenderer,
6503     scope: this
6504 }
6505 </code></pre></li></ul></div>
6506      * If not specified, the default renderer uses the raw data value.</p>
6507      * <p>For information about the renderer function (passed parameters, etc.), see
6508      * {@link Ext.grid.ColumnModel#setRenderer}. An example of specifying renderer function inline:</p><pre><code>
6509 var companyColumn = {
6510    header: 'Company Name',
6511    dataIndex: 'company',
6512    renderer: function(value, metaData, record, rowIndex, colIndex, store) {
6513       // provide the logic depending on business rules
6514       // name of your own choosing to manipulate the cell depending upon
6515       // the data in the underlying Record object.
6516       if (value == 'whatever') {
6517           //metaData.css : String : A CSS class name to add to the TD element of the cell.
6518           //metaData.attr : String : An html attribute definition string to apply to
6519           //                         the data container element within the table
6520           //                         cell (e.g. 'style="color:red;"').
6521           metaData.css = 'name-of-css-class-you-will-define';
6522       }
6523       return value;
6524    }
6525 }
6526      * </code></pre>
6527      * See also {@link #scope}.
6528      */
6529     /**
6530      * @cfg {String} xtype Optional. A String which references a predefined {@link Ext.grid.Column} subclass
6531      * type which is preconfigured with an appropriate <code>{@link #renderer}</code> to be easily
6532      * configured into a ColumnModel. The predefined {@link Ext.grid.Column} subclass types are:
6533      * <div class="mdetail-params"><ul>
6534      * <li><b><tt>gridcolumn</tt></b> : {@link Ext.grid.Column} (<b>Default</b>)<p class="sub-desc"></p></li>
6535      * <li><b><tt>booleancolumn</tt></b> : {@link Ext.grid.BooleanColumn}<p class="sub-desc"></p></li>
6536      * <li><b><tt>numbercolumn</tt></b> : {@link Ext.grid.NumberColumn}<p class="sub-desc"></p></li>
6537      * <li><b><tt>datecolumn</tt></b> : {@link Ext.grid.DateColumn}<p class="sub-desc"></p></li>
6538      * <li><b><tt>templatecolumn</tt></b> : {@link Ext.grid.TemplateColumn}<p class="sub-desc"></p></li>
6539      * </ul></div>
6540      * <p>Configuration properties for the specified <code>xtype</code> may be specified with
6541      * the Column configuration properties, for example:</p>
6542      * <pre><code>
6543 var grid = new Ext.grid.GridPanel({
6544     ...
6545     columns: [{
6546         header: 'Last Updated',
6547         dataIndex: 'lastChange',
6548         width: 85,
6549         sortable: true,
6550         //renderer: Ext.util.Format.dateRenderer('m/d/Y'),
6551         xtype: 'datecolumn', // use xtype instead of renderer
6552         format: 'M/d/Y' // configuration property for {@link Ext.grid.DateColumn}
6553     }, {
6554         ...
6555     }]
6556 });
6557      * </code></pre>
6558      */
6559     /**
6560      * @cfg {Object} scope Optional. The scope (<tt><b>this</b></tt> reference) in which to execute the
6561      * renderer.  Defaults to the Column configuration object.
6562      */
6563     /**
6564      * @cfg {String} align Optional. Set the CSS text-align property of the column.  Defaults to undefined.
6565      */
6566     /**
6567      * @cfg {String} css Optional. An inline style definition string which is applied to all table cells in the column
6568      * (excluding headers). Defaults to undefined.
6569      */
6570     /**
6571      * @cfg {Boolean} hideable Optional. Specify as <tt>false</tt> to prevent the user from hiding this column
6572      * (defaults to true).  To disallow column hiding globally for all columns in the grid, use
6573      * {@link Ext.grid.GridPanel#enableColumnHide} instead.
6574      */
6575     /**
6576      * @cfg {Ext.form.Field} editor Optional. The {@link Ext.form.Field} to use when editing values in this column
6577      * if editing is supported by the grid. See <tt>{@link #editable}</tt> also.
6578      */
6579
6580     /**
6581      * @private
6582      * @cfg {Boolean} isColumn
6583      * Used by ColumnModel setConfig method to avoid reprocessing a Column
6584      * if <code>isColumn</code> is not set ColumnModel will recreate a new Ext.grid.Column
6585      * Defaults to true.
6586      */
6587     isColumn : true,
6588
6589     constructor : function(config){
6590         Ext.apply(this, config);
6591
6592         if(Ext.isString(this.renderer)){
6593             this.renderer = Ext.util.Format[this.renderer];
6594         }else if(Ext.isObject(this.renderer)){
6595             this.scope = this.renderer.scope;
6596             this.renderer = this.renderer.fn;
6597         }
6598         if(!this.scope){
6599             this.scope = this;
6600         }
6601
6602         var ed = this.editor;
6603         delete this.editor;
6604         this.setEditor(ed);
6605         this.addEvents(
6606             /**
6607              * @event click
6608              * Fires when this Column is clicked.
6609              * @param {Column} this
6610              * @param {Grid} The owning GridPanel
6611              * @param {Number} rowIndex
6612              * @param {Ext.EventObject} e
6613              */
6614             'click',
6615             /**
6616              * @event contextmenu
6617              * Fires when this Column is right clicked.
6618              * @param {Column} this
6619              * @param {Grid} The owning GridPanel
6620              * @param {Number} rowIndex
6621              * @param {Ext.EventObject} e
6622              */
6623             'contextmenu',
6624             /**
6625              * @event dblclick
6626              * Fires when this Column is double clicked.
6627              * @param {Column} this
6628              * @param {Grid} The owning GridPanel
6629              * @param {Number} rowIndex
6630              * @param {Ext.EventObject} e
6631              */
6632             'dblclick',
6633             /**
6634              * @event mousedown
6635              * Fires when this Column receives a mousedown event.
6636              * @param {Column} this
6637              * @param {Grid} The owning GridPanel
6638              * @param {Number} rowIndex
6639              * @param {Ext.EventObject} e
6640              */
6641             'mousedown'
6642         );
6643         Ext.grid.Column.superclass.constructor.call(this);
6644     },
6645
6646     /**
6647      * @private
6648      * Process and refire events routed from the GridView's processEvent method.
6649      * Returns the event handler's status to allow cancelling of GridView's bubbling process.
6650      */
6651     processEvent : function(name, e, grid, rowIndex, colIndex){
6652         return this.fireEvent(name, this, grid, rowIndex, e);
6653     },
6654
6655     /**
6656      * @private
6657      * Clean up. Remove any Editor. Remove any listeners.
6658      */
6659     destroy: function() {
6660         if(this.setEditor){
6661             this.setEditor(null);
6662         }
6663         this.purgeListeners();
6664     },
6665
6666     /**
6667      * Optional. A function which returns displayable data when passed the following parameters:
6668      * <div class="mdetail-params"><ul>
6669      * <li><b>value</b> : Object<p class="sub-desc">The data value for the cell.</p></li>
6670      * <li><b>metadata</b> : Object<p class="sub-desc">An object in which you may set the following attributes:<ul>
6671      * <li><b>css</b> : String<p class="sub-desc">A CSS class name to add to the cell's TD element.</p></li>
6672      * <li><b>attr</b> : String<p class="sub-desc">An HTML attribute definition string to apply to the data container
6673      * element <i>within</i> the table cell (e.g. 'style="color:red;"').</p></li></ul></p></li>
6674      * <li><b>record</b> : Ext.data.record<p class="sub-desc">The {@link Ext.data.Record} from which the data was
6675      * extracted.</p></li>
6676      * <li><b>rowIndex</b> : Number<p class="sub-desc">Row index</p></li>
6677      * <li><b>colIndex</b> : Number<p class="sub-desc">Column index</p></li>
6678      * <li><b>store</b> : Ext.data.Store<p class="sub-desc">The {@link Ext.data.Store} object from which the Record
6679      * was extracted.</p></li>
6680      * </ul></div>
6681      * @property renderer
6682      * @type Function
6683      */
6684     renderer : function(value){
6685         return value;
6686     },
6687
6688     // private
6689     getEditor: function(rowIndex){
6690         return this.editable !== false ? this.editor : null;
6691     },
6692
6693     /**
6694      * Sets a new editor for this column.
6695      * @param {Ext.Editor/Ext.form.Field} editor The editor to set
6696      */
6697     setEditor : function(editor){
6698         var ed = this.editor;
6699         if(ed){
6700             if(ed.gridEditor){
6701                 ed.gridEditor.destroy();
6702                 delete ed.gridEditor;
6703             }else{
6704                 ed.destroy();
6705             }
6706         }
6707         this.editor = null;
6708         if(editor){
6709             //not an instance, create it
6710             if(!editor.isXType){
6711                 editor = Ext.create(editor, 'textfield');
6712             }
6713             this.editor = editor;
6714         }
6715     },
6716
6717     /**
6718      * Returns the {@link Ext.Editor editor} defined for this column that was created to wrap the {@link Ext.form.Field Field}
6719      * used to edit the cell.
6720      * @param {Number} rowIndex The row index
6721      * @return {Ext.Editor}
6722      */
6723     getCellEditor: function(rowIndex){
6724         var ed = this.getEditor(rowIndex);
6725         if(ed){
6726             if(!ed.startEdit){
6727                 if(!ed.gridEditor){
6728                     ed.gridEditor = new Ext.grid.GridEditor(ed);
6729                 }
6730                 ed = ed.gridEditor;
6731             }
6732         }
6733         return ed;
6734     }
6735 });
6736
6737 /**
6738  * @class Ext.grid.BooleanColumn
6739  * @extends Ext.grid.Column
6740  * <p>A Column definition class which renders boolean data fields.  See the {@link Ext.grid.Column#xtype xtype}
6741  * config option of {@link Ext.grid.Column} for more details.</p>
6742  */
6743 Ext.grid.BooleanColumn = Ext.extend(Ext.grid.Column, {
6744     /**
6745      * @cfg {String} trueText
6746      * The string returned by the renderer when the column value is not falsy (defaults to <tt>'true'</tt>).
6747      */
6748     trueText: 'true',
6749     /**
6750      * @cfg {String} falseText
6751      * The string returned by the renderer when the column value is falsy (but not undefined) (defaults to
6752      * <tt>'false'</tt>).
6753      */
6754     falseText: 'false',
6755     /**
6756      * @cfg {String} undefinedText
6757      * The string returned by the renderer when the column value is undefined (defaults to <tt>'&amp;#160;'</tt>).
6758      */
6759     undefinedText: '&#160;',
6760
6761     constructor: function(cfg){
6762         Ext.grid.BooleanColumn.superclass.constructor.call(this, cfg);
6763         var t = this.trueText, f = this.falseText, u = this.undefinedText;
6764         this.renderer = function(v){
6765             if(v === undefined){
6766                 return u;
6767             }
6768             if(!v || v === 'false'){
6769                 return f;
6770             }
6771             return t;
6772         };
6773     }
6774 });
6775
6776 /**
6777  * @class Ext.grid.NumberColumn
6778  * @extends Ext.grid.Column
6779  * <p>A Column definition class which renders a numeric data field according to a {@link #format} string.  See the
6780  * {@link Ext.grid.Column#xtype xtype} config option of {@link Ext.grid.Column} for more details.</p>
6781  */
6782 Ext.grid.NumberColumn = Ext.extend(Ext.grid.Column, {
6783     /**
6784      * @cfg {String} format
6785      * A formatting string as used by {@link Ext.util.Format#number} to format a numeric value for this Column
6786      * (defaults to <tt>'0,000.00'</tt>).
6787      */
6788     format : '0,000.00',
6789     constructor: function(cfg){
6790         Ext.grid.NumberColumn.superclass.constructor.call(this, cfg);
6791         this.renderer = Ext.util.Format.numberRenderer(this.format);
6792     }
6793 });
6794
6795 /**
6796  * @class Ext.grid.DateColumn
6797  * @extends Ext.grid.Column
6798  * <p>A Column definition class which renders a passed date according to the default locale, or a configured
6799  * {@link #format}. See the {@link Ext.grid.Column#xtype xtype} config option of {@link Ext.grid.Column}
6800  * for more details.</p>
6801  */
6802 Ext.grid.DateColumn = Ext.extend(Ext.grid.Column, {
6803     /**
6804      * @cfg {String} format
6805      * A formatting string as used by {@link Date#format} to format a Date for this Column
6806      * (defaults to <tt>'m/d/Y'</tt>).
6807      */
6808     format : 'm/d/Y',
6809     constructor: function(cfg){
6810         Ext.grid.DateColumn.superclass.constructor.call(this, cfg);
6811         this.renderer = Ext.util.Format.dateRenderer(this.format);
6812     }
6813 });
6814
6815 /**
6816  * @class Ext.grid.TemplateColumn
6817  * @extends Ext.grid.Column
6818  * <p>A Column definition class which renders a value by processing a {@link Ext.data.Record Record}'s
6819  * {@link Ext.data.Record#data data} using a {@link #tpl configured} {@link Ext.XTemplate XTemplate}.
6820  * See the {@link Ext.grid.Column#xtype xtype} config option of {@link Ext.grid.Column} for more
6821  * details.</p>
6822  */
6823 Ext.grid.TemplateColumn = Ext.extend(Ext.grid.Column, {
6824     /**
6825      * @cfg {String/XTemplate} tpl
6826      * An {@link Ext.XTemplate XTemplate}, or an XTemplate <i>definition string</i> to use to process a
6827      * {@link Ext.data.Record Record}'s {@link Ext.data.Record#data data} to produce a column's rendered value.
6828      */
6829     constructor: function(cfg){
6830         Ext.grid.TemplateColumn.superclass.constructor.call(this, cfg);
6831         var tpl = (!Ext.isPrimitive(this.tpl) && this.tpl.compile) ? this.tpl : new Ext.XTemplate(this.tpl);
6832         this.renderer = function(value, p, r){
6833             return tpl.apply(r.data);
6834         };
6835         this.tpl = tpl;
6836     }
6837 });
6838
6839 /**
6840  * @class Ext.grid.ActionColumn
6841  * @extends Ext.grid.Column
6842  * <p>A Grid column type which renders an icon, or a series of icons in a grid cell, and offers a scoped click
6843  * handler for each icon. Example usage:</p>
6844 <pre><code>
6845 new Ext.grid.GridPanel({
6846     store: myStore,
6847     columns: [
6848         {
6849             xtype: 'actioncolumn',
6850             width: 50,
6851             items: [
6852                 {
6853                     icon   : 'sell.gif',                // Use a URL in the icon config
6854                     tooltip: 'Sell stock',
6855                     handler: function(grid, rowIndex, colIndex) {
6856                         var rec = store.getAt(rowIndex);
6857                         alert("Sell " + rec.get('company'));
6858                     }
6859                 },
6860                 {
6861                     getClass: function(v, meta, rec) {  // Or return a class from a function
6862                         if (rec.get('change') < 0) {
6863                             this.items[1].tooltip = 'Do not buy!';
6864                             return 'alert-col';
6865                         } else {
6866                             this.items[1].tooltip = 'Buy stock';
6867                             return 'buy-col';
6868                         }
6869                     },
6870                     handler: function(grid, rowIndex, colIndex) {
6871                         var rec = store.getAt(rowIndex);
6872                         alert("Buy " + rec.get('company'));
6873                     }
6874                 }
6875             ]
6876         }
6877         //any other columns here
6878     ]
6879 });
6880 </pre></code>
6881  * <p>The action column can be at any index in the columns array, and a grid can have any number of
6882  * action columns. </p>
6883  */
6884 Ext.grid.ActionColumn = Ext.extend(Ext.grid.Column, {
6885     /**
6886      * @cfg {String} icon
6887      * The URL of an image to display as the clickable element in the column. 
6888      * Optional - defaults to <code>{@link Ext#BLANK_IMAGE_URL Ext.BLANK_IMAGE_URL}</code>.
6889      */
6890     /**
6891      * @cfg {String} iconCls
6892      * A CSS class to apply to the icon image. To determine the class dynamically, configure the Column with a <code>{@link #getClass}</code> function.
6893      */
6894     /**
6895      * @cfg {Function} handler A function called when the icon is clicked.
6896      * The handler is passed the following parameters:<div class="mdetail-params"><ul>
6897      * <li><code>grid</code> : GridPanel<div class="sub-desc">The owning GridPanel.</div></li>
6898      * <li><code>rowIndex</code> : Number<div class="sub-desc">The row index clicked on.</div></li>
6899      * <li><code>colIndex</code> : Number<div class="sub-desc">The column index clicked on.</div></li>
6900      * <li><code>item</code> : Object<div class="sub-desc">The clicked item (or this Column if multiple 
6901      * {@link #items} were not configured).</div></li>
6902      * <li><code>e</code> : Event<div class="sub-desc">The click event.</div></li>
6903      * </ul></div>
6904      */
6905     /**
6906      * @cfg {Object} scope The scope (<tt><b>this</b></tt> reference) in which the <code>{@link #handler}</code>
6907      * and <code>{@link #getClass}</code> fuctions are executed. Defaults to this Column.
6908      */
6909     /**
6910      * @cfg {String} tooltip A tooltip message to be displayed on hover. {@link Ext.QuickTips#init Ext.QuickTips} must have 
6911      * been initialized.
6912      */
6913     /**
6914      * @cfg {Boolean} stopSelection Defaults to <code>true</code>. Prevent grid <i>row</i> selection upon mousedown.
6915      */
6916     /**
6917      * @cfg {Function} getClass A function which returns the CSS class to apply to the icon image.
6918      * The function is passed the following parameters:<div class="mdetail-params"><ul>
6919      *     <li><b>v</b> : Object<p class="sub-desc">The value of the column's configured field (if any).</p></li>
6920      *     <li><b>metadata</b> : Object<p class="sub-desc">An object in which you may set the following attributes:<ul>
6921      *         <li><b>css</b> : String<p class="sub-desc">A CSS class name to add to the cell's TD element.</p></li>
6922      *         <li><b>attr</b> : String<p class="sub-desc">An HTML attribute definition string to apply to the data container element <i>within</i> the table cell
6923      *         (e.g. 'style="color:red;"').</p></li>
6924      *     </ul></p></li>
6925      *     <li><b>r</b> : Ext.data.Record<p class="sub-desc">The Record providing the data.</p></li>
6926      *     <li><b>rowIndex</b> : Number<p class="sub-desc">The row index..</p></li>
6927      *     <li><b>colIndex</b> : Number<p class="sub-desc">The column index.</p></li>
6928      *     <li><b>store</b> : Ext.data.Store<p class="sub-desc">The Store which is providing the data Model.</p></li>
6929      * </ul></div>
6930      */
6931     /**
6932      * @cfg {Array} items An Array which may contain multiple icon definitions, each element of which may contain:
6933      * <div class="mdetail-params"><ul>
6934      * <li><code>icon</code> : String<div class="sub-desc">The url of an image to display as the clickable element 
6935      * in the column.</div></li>
6936      * <li><code>iconCls</code> : String<div class="sub-desc">A CSS class to apply to the icon image.
6937      * To determine the class dynamically, configure the item with a <code>getClass</code> function.</div></li>
6938      * <li><code>getClass</code> : Function<div class="sub-desc">A function which returns the CSS class to apply to the icon image.
6939      * The function is passed the following parameters:<ul>
6940      *     <li><b>v</b> : Object<p class="sub-desc">The value of the column's configured field (if any).</p></li>
6941      *     <li><b>metadata</b> : Object<p class="sub-desc">An object in which you may set the following attributes:<ul>
6942      *         <li><b>css</b> : String<p class="sub-desc">A CSS class name to add to the cell's TD element.</p></li>
6943      *         <li><b>attr</b> : String<p class="sub-desc">An HTML attribute definition string to apply to the data container element <i>within</i> the table cell
6944      *         (e.g. 'style="color:red;"').</p></li>
6945      *     </ul></p></li>
6946      *     <li><b>r</b> : Ext.data.Record<p class="sub-desc">The Record providing the data.</p></li>
6947      *     <li><b>rowIndex</b> : Number<p class="sub-desc">The row index..</p></li>
6948      *     <li><b>colIndex</b> : Number<p class="sub-desc">The column index.</p></li>
6949      *     <li><b>store</b> : Ext.data.Store<p class="sub-desc">The Store which is providing the data Model.</p></li>
6950      * </ul></div></li>
6951      * <li><code>handler</code> : Function<div class="sub-desc">A function called when the icon is clicked.</div></li>
6952      * <li><code>scope</code> : Scope<div class="sub-desc">The scope (<code><b>this</b></code> reference) in which the 
6953      * <code>handler</code> and <code>getClass</code> functions are executed. Fallback defaults are this Column's
6954      * configured scope, then this Column.</div></li>
6955      * <li><code>tooltip</code> : String<div class="sub-desc">A tooltip message to be displayed on hover. 
6956      * {@link Ext.QuickTips#init Ext.QuickTips} must have been initialized.</div></li>
6957      * </ul></div>
6958      */
6959     header: '&#160;',
6960
6961     actionIdRe: /x-action-col-(\d+)/,
6962     
6963     /**
6964      * @cfg {String} altText The alt text to use for the image element. Defaults to <tt>''</tt>.
6965      */
6966     altText: '',
6967
6968     constructor: function(cfg) {
6969         var me = this,
6970             items = cfg.items || (me.items = [me]),
6971             l = items.length,
6972             i,
6973             item;
6974
6975         Ext.grid.ActionColumn.superclass.constructor.call(me, cfg);
6976
6977 //      Renderer closure iterates through items creating an <img> element for each and tagging with an identifying 
6978 //      class name x-action-col-{n}
6979         me.renderer = function(v, meta) {
6980 //          Allow a configured renderer to create initial value (And set the other values in the "metadata" argument!)
6981             v = Ext.isFunction(cfg.renderer) ? cfg.renderer.apply(this, arguments)||'' : '';
6982
6983             meta.css += ' x-action-col-cell';
6984             for (i = 0; i < l; i++) {
6985                 item = items[i];
6986                 v += '<img alt="' + me.altText + '" src="' + (item.icon || Ext.BLANK_IMAGE_URL) +
6987                     '" class="x-action-col-icon x-action-col-' + String(i) + ' ' + (item.iconCls || '') +
6988                     ' ' + (Ext.isFunction(item.getClass) ? item.getClass.apply(item.scope||this.scope||this, arguments) : '') + '"' +
6989                     ((item.tooltip) ? ' ext:qtip="' + item.tooltip + '"' : '') + ' />';
6990             }
6991             return v;
6992         };
6993     },
6994
6995     destroy: function() {
6996         delete this.items;
6997         delete this.renderer;
6998         return Ext.grid.ActionColumn.superclass.destroy.apply(this, arguments);
6999     },
7000
7001     /**
7002      * @private
7003      * Process and refire events routed from the GridView's processEvent method.
7004      * Also fires any configured click handlers. By default, cancels the mousedown event to prevent selection.
7005      * Returns the event handler's status to allow cancelling of GridView's bubbling process.
7006      */
7007     processEvent : function(name, e, grid, rowIndex, colIndex){
7008         var m = e.getTarget().className.match(this.actionIdRe),
7009             item, fn;
7010         if (m && (item = this.items[parseInt(m[1], 10)])) {
7011             if (name == 'click') {
7012                 (fn = item.handler || this.handler) && fn.call(item.scope||this.scope||this, grid, rowIndex, colIndex, item, e);
7013             } else if ((name == 'mousedown') && (item.stopSelection !== false)) {
7014                 return false;
7015             }
7016         }
7017         return Ext.grid.ActionColumn.superclass.processEvent.apply(this, arguments);
7018     }
7019 });
7020
7021 /*
7022  * @property types
7023  * @type Object
7024  * @member Ext.grid.Column
7025  * @static
7026  * <p>An object containing predefined Column classes keyed by a mnemonic code which may be referenced
7027  * by the {@link Ext.grid.ColumnModel#xtype xtype} config option of ColumnModel.</p>
7028  * <p>This contains the following properties</p><div class="mdesc-details"><ul>
7029  * <li>gridcolumn : <b>{@link Ext.grid.Column Column constructor}</b></li>
7030  * <li>booleancolumn : <b>{@link Ext.grid.BooleanColumn BooleanColumn constructor}</b></li>
7031  * <li>numbercolumn : <b>{@link Ext.grid.NumberColumn NumberColumn constructor}</b></li>
7032  * <li>datecolumn : <b>{@link Ext.grid.DateColumn DateColumn constructor}</b></li>
7033  * <li>templatecolumn : <b>{@link Ext.grid.TemplateColumn TemplateColumn constructor}</b></li>
7034  * </ul></div>
7035  */
7036 Ext.grid.Column.types = {
7037     gridcolumn : Ext.grid.Column,
7038     booleancolumn: Ext.grid.BooleanColumn,
7039     numbercolumn: Ext.grid.NumberColumn,
7040     datecolumn: Ext.grid.DateColumn,
7041     templatecolumn: Ext.grid.TemplateColumn,
7042     actioncolumn: Ext.grid.ActionColumn
7043 };/**
7044  * @class Ext.grid.RowNumberer
7045  * This is a utility class that can be passed into a {@link Ext.grid.ColumnModel} as a column config that provides
7046  * an automatic row numbering column.
7047  * <br>Usage:<br>
7048  <pre><code>
7049  // This is a typical column config with the first column providing row numbers
7050  var colModel = new Ext.grid.ColumnModel([
7051     new Ext.grid.RowNumberer(),
7052     {header: "Name", width: 80, sortable: true},
7053     {header: "Code", width: 50, sortable: true},
7054     {header: "Description", width: 200, sortable: true}
7055  ]);
7056  </code></pre>
7057  * @constructor
7058  * @param {Object} config The configuration options
7059  */
7060 Ext.grid.RowNumberer = Ext.extend(Object, {
7061     /**
7062      * @cfg {String} header Any valid text or HTML fragment to display in the header cell for the row
7063      * number column (defaults to '').
7064      */
7065     header: "",
7066     /**
7067      * @cfg {Number} width The default width in pixels of the row number column (defaults to 23).
7068      */
7069     width: 23,
7070     /**
7071      * @cfg {Boolean} sortable True if the row number column is sortable (defaults to false).
7072      * @hide
7073      */
7074     sortable: false,
7075     
7076     constructor : function(config){
7077         Ext.apply(this, config);
7078         if(this.rowspan){
7079             this.renderer = this.renderer.createDelegate(this);
7080         }
7081     },
7082
7083     // private
7084     fixed:true,
7085     hideable: false,
7086     menuDisabled:true,
7087     dataIndex: '',
7088     id: 'numberer',
7089     rowspan: undefined,
7090
7091     // private
7092     renderer : function(v, p, record, rowIndex){
7093         if(this.rowspan){
7094             p.cellAttr = 'rowspan="'+this.rowspan+'"';
7095         }
7096         return rowIndex+1;
7097     }
7098 });/**
7099  * @class Ext.grid.CheckboxSelectionModel
7100  * @extends Ext.grid.RowSelectionModel
7101  * A custom selection model that renders a column of checkboxes that can be toggled to select or deselect rows.
7102  * @constructor
7103  * @param {Object} config The configuration options
7104  */
7105 Ext.grid.CheckboxSelectionModel = Ext.extend(Ext.grid.RowSelectionModel, {
7106
7107     /**
7108      * @cfg {Boolean} checkOnly <tt>true</tt> if rows can only be selected by clicking on the
7109      * checkbox column (defaults to <tt>false</tt>).
7110      */
7111     /**
7112      * @cfg {String} header Any valid text or HTML fragment to display in the header cell for the
7113      * checkbox column.  Defaults to:<pre><code>
7114      * '&lt;div class="x-grid3-hd-checker">&#38;#160;&lt;/div>'</tt>
7115      * </code></pre>
7116      * The default CSS class of <tt>'x-grid3-hd-checker'</tt> displays a checkbox in the header
7117      * and provides support for automatic check all/none behavior on header click. This string
7118      * can be replaced by any valid HTML fragment, including a simple text string (e.g.,
7119      * <tt>'Select Rows'</tt>), but the automatic check all/none behavior will only work if the
7120      * <tt>'x-grid3-hd-checker'</tt> class is supplied.
7121      */
7122     header : '<div class="x-grid3-hd-checker">&#160;</div>',
7123     /**
7124      * @cfg {Number} width The default width in pixels of the checkbox column (defaults to <tt>20</tt>).
7125      */
7126     width : 20,
7127     /**
7128      * @cfg {Boolean} sortable <tt>true</tt> if the checkbox column is sortable (defaults to
7129      * <tt>false</tt>).
7130      */
7131     sortable : false,
7132
7133     // private
7134     menuDisabled : true,
7135     fixed : true,
7136     hideable: false,
7137     dataIndex : '',
7138     id : 'checker',
7139     isColumn: true, // So that ColumnModel doesn't feed this through the Column constructor
7140
7141     constructor : function(){
7142         Ext.grid.CheckboxSelectionModel.superclass.constructor.apply(this, arguments);
7143         if(this.checkOnly){
7144             this.handleMouseDown = Ext.emptyFn;
7145         }
7146     },
7147
7148     // private
7149     initEvents : function(){
7150         Ext.grid.CheckboxSelectionModel.superclass.initEvents.call(this);
7151         this.grid.on('render', function(){
7152             Ext.fly(this.grid.getView().innerHd).on('mousedown', this.onHdMouseDown, this);
7153         }, this);
7154     },
7155
7156     /**
7157      * @private
7158      * Process and refire events routed from the GridView's processEvent method.
7159      */
7160     processEvent : function(name, e, grid, rowIndex, colIndex){
7161         if (name == 'mousedown') {
7162             this.onMouseDown(e, e.getTarget());
7163             return false;
7164         } else {
7165             return Ext.grid.Column.prototype.processEvent.apply(this, arguments);
7166         }
7167     },
7168
7169     // private
7170     onMouseDown : function(e, t){
7171         if(e.button === 0 && t.className == 'x-grid3-row-checker'){ // Only fire if left-click
7172             e.stopEvent();
7173             var row = e.getTarget('.x-grid3-row');
7174             if(row){
7175                 var index = row.rowIndex;
7176                 if(this.isSelected(index)){
7177                     this.deselectRow(index);
7178                 }else{
7179                     this.selectRow(index, true);
7180                     this.grid.getView().focusRow(index);
7181                 }
7182             }
7183         }
7184     },
7185
7186     // private
7187     onHdMouseDown : function(e, t) {
7188         if(t.className == 'x-grid3-hd-checker'){
7189             e.stopEvent();
7190             var hd = Ext.fly(t.parentNode);
7191             var isChecked = hd.hasClass('x-grid3-hd-checker-on');
7192             if(isChecked){
7193                 hd.removeClass('x-grid3-hd-checker-on');
7194                 this.clearSelections();
7195             }else{
7196                 hd.addClass('x-grid3-hd-checker-on');
7197                 this.selectAll();
7198             }
7199         }
7200     },
7201
7202     // private
7203     renderer : function(v, p, record){
7204         return '<div class="x-grid3-row-checker">&#160;</div>';
7205     },
7206     
7207     onEditorSelect: function(row, lastRow){
7208         if(lastRow != row && !this.checkOnly){
7209             this.selectRow(row); // *** highlight newly-selected cell and update selection
7210         }
7211     }
7212 });