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