Upgrade to ExtJS 3.2.1 - Released 04/27/2010
[extjs.git] / pkgs / pkg-grid-foundation-debug.js
1 /*!
2  * Ext JS Library 3.2.1
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      * @cfg {Number} autoExpandMax The maximum width the <tt>{@link #autoExpandColumn}</tt>
102      * can have (if enabled). Defaults to <tt>1000</tt>.
103      */
104     autoExpandMax : 1000,
105     /**
106      * @cfg {Number} autoExpandMin The minimum width the <tt>{@link #autoExpandColumn}</tt>
107      * can have (if enabled). Defaults to <tt>50</tt>.
108      */
109     autoExpandMin : 50,
110     /**
111      * @cfg {Boolean} columnLines <tt>true</tt> to add css for column separation lines.
112      * Default is <tt>false</tt>.
113      */
114     columnLines : false,
115     /**
116      * @cfg {Object} cm Shorthand for <tt>{@link #colModel}</tt>.
117      */
118     /**
119      * @cfg {Object} colModel The {@link Ext.grid.ColumnModel} to use when rendering the grid (required).
120      */
121     /**
122      * @cfg {Array} columns An array of {@link Ext.grid.Column columns} to auto create a
123      * {@link Ext.grid.ColumnModel}.  The ColumnModel may be explicitly created via the
124      * <tt>{@link #colModel}</tt> configuration property.
125      */
126     /**
127      * @cfg {String} ddGroup The DD group this GridPanel belongs to. Defaults to <tt>'GridDD'</tt> if not specified.
128      */
129     /**
130      * @cfg {String} ddText
131      * Configures the text in the drag proxy.  Defaults to:
132      * <pre><code>
133      * ddText : '{0} selected row{1}'
134      * </code></pre>
135      * <tt>{0}</tt> is replaced with the number of selected rows.
136      */
137     ddText : '{0} selected row{1}',
138     /**
139      * @cfg {Boolean} deferRowRender <P>Defaults to <tt>true</tt> to enable deferred row rendering.</p>
140      * <p>This allows the GridPanel to be initially rendered empty, with the expensive update of the row
141      * structure deferred so that layouts with GridPanels appear more quickly.</p>
142      */
143     deferRowRender : true,
144     /**
145      * @cfg {Boolean} disableSelection <p><tt>true</tt> to disable selections in the grid. Defaults to <tt>false</tt>.</p>
146      * <p>Ignored if a {@link #selModel SelectionModel} is specified.</p>
147      */
148     /**
149      * @cfg {Boolean} enableColumnResize <tt>false</tt> to turn off column resizing for the whole grid. Defaults to <tt>true</tt>.
150      */
151     /**
152      * @cfg {Boolean} enableColumnHide
153      * Defaults to <tt>true</tt> to enable {@link Ext.grid.Column#hidden hiding of columns}
154      * with the {@link #enableHdMenu header menu}.
155      */
156     enableColumnHide : true,
157     /**
158      * @cfg {Boolean} enableColumnMove Defaults to <tt>true</tt> to enable drag and drop reorder of columns. <tt>false</tt>
159      * to turn off column reordering via drag drop.
160      */
161     enableColumnMove : true,
162     /**
163      * @cfg {Boolean} enableDragDrop <p>Enables dragging of the selected rows of the GridPanel. Defaults to <tt>false</tt>.</p>
164      * <p>Setting this to <b><tt>true</tt></b> causes this GridPanel's {@link #getView GridView} to
165      * create an instance of {@link Ext.grid.GridDragZone}. <b>Note</b>: this is available only <b>after</b>
166      * the Grid has been rendered as the GridView's <tt>{@link Ext.grid.GridView#dragZone dragZone}</tt>
167      * property.</p>
168      * <p>A cooperating {@link Ext.dd.DropZone DropZone} must be created who's implementations of
169      * {@link Ext.dd.DropZone#onNodeEnter onNodeEnter}, {@link Ext.dd.DropZone#onNodeOver onNodeOver},
170      * {@link Ext.dd.DropZone#onNodeOut onNodeOut} and {@link Ext.dd.DropZone#onNodeDrop onNodeDrop} are able
171      * to process the {@link Ext.grid.GridDragZone#getDragData data} which is provided.</p>
172      */
173     enableDragDrop : false,
174     /**
175      * @cfg {Boolean} enableHdMenu Defaults to <tt>true</tt> to enable the drop down button for menu in the headers.
176      */
177     enableHdMenu : true,
178     /**
179      * @cfg {Boolean} hideHeaders True to hide the grid's header. Defaults to <code>false</code>.
180      */
181     /**
182      * @cfg {Object} loadMask An {@link Ext.LoadMask} config or true to mask the grid while
183      * loading. Defaults to <code>false</code>.
184      */
185     loadMask : false,
186     /**
187      * @cfg {Number} maxHeight Sets the maximum height of the grid - ignored if <tt>autoHeight</tt> is not on.
188      */
189     /**
190      * @cfg {Number} minColumnWidth The minimum width a column can be resized to. Defaults to <tt>25</tt>.
191      */
192     minColumnWidth : 25,
193     /**
194      * @cfg {Object} sm Shorthand for <tt>{@link #selModel}</tt>.
195      */
196     /**
197      * @cfg {Object} selModel Any subclass of {@link Ext.grid.AbstractSelectionModel} that will provide
198      * the selection model for the grid (defaults to {@link Ext.grid.RowSelectionModel} if not specified).
199      */
200     /**
201      * @cfg {Ext.data.Store} store The {@link Ext.data.Store} the grid should use as its data source (required).
202      */
203     /**
204      * @cfg {Boolean} stripeRows <tt>true</tt> to stripe the rows. Default is <tt>false</tt>.
205      * <p>This causes the CSS class <tt><b>x-grid3-row-alt</b></tt> to be added to alternate rows of
206      * the grid. A default CSS rule is provided which sets a background colour, but you can override this
207      * with a rule which either overrides the <b>background-color</b> style using the '!important'
208      * modifier, or which uses a CSS selector of higher specificity.</p>
209      */
210     stripeRows : false,
211     /**
212      * @cfg {Boolean} trackMouseOver True to highlight rows when the mouse is over. Default is <tt>true</tt>
213      * for GridPanel, but <tt>false</tt> for EditorGridPanel.
214      */
215     trackMouseOver : true,
216     /**
217      * @cfg {Array} stateEvents
218      * An array of events that, when fired, should trigger this component to save its state.
219      * Defaults to:<pre><code>
220      * stateEvents: ['columnmove', 'columnresize', 'sortchange', 'groupchange']
221      * </code></pre>
222      * <p>These can be any types of events supported by this component, including browser or
223      * custom events (e.g., <tt>['click', 'customerchange']</tt>).</p>
224      * <p>See {@link Ext.Component#stateful} for an explanation of saving and restoring
225      * Component state.</p>
226      */
227     stateEvents : ['columnmove', 'columnresize', 'sortchange', 'groupchange'],
228     /**
229      * @cfg {Object} view The {@link Ext.grid.GridView} used by the grid. This can be set
230      * before a call to {@link Ext.Component#render render()}.
231      */
232     view : null,
233
234     /**
235      * @cfg {Array} bubbleEvents
236      * <p>An array of events that, when fired, should be bubbled to any parent container.
237      * See {@link Ext.util.Observable#enableBubble}.
238      * Defaults to <tt>[]</tt>.
239      */
240     bubbleEvents: [],
241
242     /**
243      * @cfg {Object} viewConfig A config object that will be applied to the grid's UI view.  Any of
244      * the config options available for {@link Ext.grid.GridView} can be specified here. This option
245      * is ignored if <tt>{@link #view}</tt> is specified.
246      */
247
248     // private
249     rendered : false,
250     // private
251     viewReady : false,
252
253     // private
254     initComponent : function(){
255         Ext.grid.GridPanel.superclass.initComponent.call(this);
256
257         if(this.columnLines){
258             this.cls = (this.cls || '') + ' x-grid-with-col-lines';
259         }
260         // override any provided value since it isn't valid
261         // and is causing too many bug reports ;)
262         this.autoScroll = false;
263         this.autoWidth = false;
264
265         if(Ext.isArray(this.columns)){
266             this.colModel = new Ext.grid.ColumnModel(this.columns);
267             delete this.columns;
268         }
269
270         // check and correct shorthanded configs
271         if(this.ds){
272             this.store = this.ds;
273             delete this.ds;
274         }
275         if(this.cm){
276             this.colModel = this.cm;
277             delete this.cm;
278         }
279         if(this.sm){
280             this.selModel = this.sm;
281             delete this.sm;
282         }
283         this.store = Ext.StoreMgr.lookup(this.store);
284
285         this.addEvents(
286             // raw events
287             /**
288              * @event click
289              * The raw click event for the entire grid.
290              * @param {Ext.EventObject} e
291              */
292             'click',
293             /**
294              * @event dblclick
295              * The raw dblclick event for the entire grid.
296              * @param {Ext.EventObject} e
297              */
298             'dblclick',
299             /**
300              * @event contextmenu
301              * The raw contextmenu event for the entire grid.
302              * @param {Ext.EventObject} e
303              */
304             'contextmenu',
305             /**
306              * @event mousedown
307              * The raw mousedown event for the entire grid.
308              * @param {Ext.EventObject} e
309              */
310             'mousedown',
311             /**
312              * @event mouseup
313              * The raw mouseup event for the entire grid.
314              * @param {Ext.EventObject} e
315              */
316             'mouseup',
317             /**
318              * @event mouseover
319              * The raw mouseover event for the entire grid.
320              * @param {Ext.EventObject} e
321              */
322             'mouseover',
323             /**
324              * @event mouseout
325              * The raw mouseout event for the entire grid.
326              * @param {Ext.EventObject} e
327              */
328             'mouseout',
329             /**
330              * @event keypress
331              * The raw keypress event for the entire grid.
332              * @param {Ext.EventObject} e
333              */
334             'keypress',
335             /**
336              * @event keydown
337              * The raw keydown event for the entire grid.
338              * @param {Ext.EventObject} e
339              */
340             'keydown',
341
342             // custom events
343             /**
344              * @event cellmousedown
345              * Fires before a cell is clicked
346              * @param {Grid} this
347              * @param {Number} rowIndex
348              * @param {Number} columnIndex
349              * @param {Ext.EventObject} e
350              */
351             'cellmousedown',
352             /**
353              * @event rowmousedown
354              * Fires before a row is clicked
355              * @param {Grid} this
356              * @param {Number} rowIndex
357              * @param {Ext.EventObject} e
358              */
359             'rowmousedown',
360             /**
361              * @event headermousedown
362              * Fires before a header is clicked
363              * @param {Grid} this
364              * @param {Number} columnIndex
365              * @param {Ext.EventObject} e
366              */
367             'headermousedown',
368
369             /**
370              * @event groupmousedown
371              * Fires before a group header is clicked. <b>Only applies for grids with a {@link Ext.grid.GroupingView GroupingView}</b>.
372              * @param {Grid} this
373              * @param {String} groupField
374              * @param {String} groupValue
375              * @param {Ext.EventObject} e
376              */
377             'groupmousedown',
378
379             /**
380              * @event rowbodymousedown
381              * Fires before the row body is clicked. <b>Only applies for grids with {@link Ext.grid.GridView#enableRowBody enableRowBody} configured.</b>
382              * @param {Grid} this
383              * @param {Number} rowIndex
384              * @param {Ext.EventObject} e
385              */
386             'rowbodymousedown',
387
388             /**
389              * @event containermousedown
390              * Fires before the container is clicked. The container consists of any part of the grid body that is not covered by a row.
391              * @param {Grid} this
392              * @param {Ext.EventObject} e
393              */
394             'containermousedown',
395
396             /**
397              * @event cellclick
398              * Fires when a cell is clicked.
399              * The data for the cell is drawn from the {@link Ext.data.Record Record}
400              * for this row. To access the data in the listener function use the
401              * following technique:
402              * <pre><code>
403 function(grid, rowIndex, columnIndex, e) {
404     var record = grid.getStore().getAt(rowIndex);  // Get the Record
405     var fieldName = grid.getColumnModel().getDataIndex(columnIndex); // Get field name
406     var data = record.get(fieldName);
407 }
408 </code></pre>
409              * @param {Grid} this
410              * @param {Number} rowIndex
411              * @param {Number} columnIndex
412              * @param {Ext.EventObject} e
413              */
414             'cellclick',
415             /**
416              * @event celldblclick
417              * Fires when a cell is double clicked
418              * @param {Grid} this
419              * @param {Number} rowIndex
420              * @param {Number} columnIndex
421              * @param {Ext.EventObject} e
422              */
423             'celldblclick',
424             /**
425              * @event rowclick
426              * Fires when a row is clicked
427              * @param {Grid} this
428              * @param {Number} rowIndex
429              * @param {Ext.EventObject} e
430              */
431             'rowclick',
432             /**
433              * @event rowdblclick
434              * Fires when a row is double clicked
435              * @param {Grid} this
436              * @param {Number} rowIndex
437              * @param {Ext.EventObject} e
438              */
439             'rowdblclick',
440             /**
441              * @event headerclick
442              * Fires when a header is clicked
443              * @param {Grid} this
444              * @param {Number} columnIndex
445              * @param {Ext.EventObject} e
446              */
447             'headerclick',
448             /**
449              * @event headerdblclick
450              * Fires when a header cell is double clicked
451              * @param {Grid} this
452              * @param {Number} columnIndex
453              * @param {Ext.EventObject} e
454              */
455             'headerdblclick',
456             /**
457              * @event groupclick
458              * Fires when group header is clicked. <b>Only applies for grids with a {@link Ext.grid.GroupingView GroupingView}</b>.
459              * @param {Grid} this
460              * @param {String} groupField
461              * @param {String} groupValue
462              * @param {Ext.EventObject} e
463              */
464             'groupclick',
465             /**
466              * @event groupdblclick
467              * Fires when group header is double clicked. <b>Only applies for grids with a {@link Ext.grid.GroupingView GroupingView}</b>.
468              * @param {Grid} this
469              * @param {String} groupField
470              * @param {String} groupValue
471              * @param {Ext.EventObject} e
472              */
473             'groupdblclick',
474             /**
475              * @event containerclick
476              * Fires when the container is clicked. The container consists of any part of the grid body that is not covered by a row.
477              * @param {Grid} this
478              * @param {Ext.EventObject} e
479              */
480             'containerclick',
481             /**
482              * @event containerdblclick
483              * Fires when the container is double clicked. The container consists of any part of the grid body that is not covered by a row.
484              * @param {Grid} this
485              * @param {Ext.EventObject} e
486              */
487             'containerdblclick',
488
489             /**
490              * @event rowbodyclick
491              * Fires when the row body is clicked. <b>Only applies for grids with {@link Ext.grid.GridView#enableRowBody enableRowBody} configured.</b>
492              * @param {Grid} this
493              * @param {Number} rowIndex
494              * @param {Ext.EventObject} e
495              */
496             'rowbodyclick',
497             /**
498              * @event rowbodydblclick
499              * Fires when the row body is double clicked. <b>Only applies for grids with {@link Ext.grid.GridView#enableRowBody enableRowBody} configured.</b>
500              * @param {Grid} this
501              * @param {Number} rowIndex
502              * @param {Ext.EventObject} e
503              */
504             'rowbodydblclick',
505
506             /**
507              * @event rowcontextmenu
508              * Fires when a row is right clicked
509              * @param {Grid} this
510              * @param {Number} rowIndex
511              * @param {Ext.EventObject} e
512              */
513             'rowcontextmenu',
514             /**
515              * @event cellcontextmenu
516              * Fires when a cell is right clicked
517              * @param {Grid} this
518              * @param {Number} rowIndex
519              * @param {Number} cellIndex
520              * @param {Ext.EventObject} e
521              */
522             'cellcontextmenu',
523             /**
524              * @event headercontextmenu
525              * Fires when a header is right clicked
526              * @param {Grid} this
527              * @param {Number} columnIndex
528              * @param {Ext.EventObject} e
529              */
530             'headercontextmenu',
531             /**
532              * @event groupcontextmenu
533              * Fires when group header is right clicked. <b>Only applies for grids with a {@link Ext.grid.GroupingView GroupingView}</b>.
534              * @param {Grid} this
535              * @param {String} groupField
536              * @param {String} groupValue
537              * @param {Ext.EventObject} e
538              */
539             'groupcontextmenu',
540             /**
541              * @event containercontextmenu
542              * Fires when the container is right clicked. The container consists of any part of the grid body that is not covered by a row.
543              * @param {Grid} this
544              * @param {Ext.EventObject} e
545              */
546             'containercontextmenu',
547             /**
548              * @event rowbodycontextmenu
549              * Fires when the row body is right clicked. <b>Only applies for grids with {@link Ext.grid.GridView#enableRowBody enableRowBody} configured.</b>
550              * @param {Grid} this
551              * @param {Number} rowIndex
552              * @param {Ext.EventObject} e
553              */
554             'rowbodycontextmenu',
555             /**
556              * @event bodyscroll
557              * Fires when the body element is scrolled
558              * @param {Number} scrollLeft
559              * @param {Number} scrollTop
560              */
561             'bodyscroll',
562             /**
563              * @event columnresize
564              * Fires when the user resizes a column
565              * @param {Number} columnIndex
566              * @param {Number} newSize
567              */
568             'columnresize',
569             /**
570              * @event columnmove
571              * Fires when the user moves a column
572              * @param {Number} oldIndex
573              * @param {Number} newIndex
574              */
575             'columnmove',
576             /**
577              * @event sortchange
578              * Fires when the grid's store sort changes
579              * @param {Grid} this
580              * @param {Object} sortInfo An object with the keys field and direction
581              */
582             'sortchange',
583             /**
584              * @event groupchange
585              * Fires when the grid's grouping changes (only applies for grids with a {@link Ext.grid.GroupingView GroupingView})
586              * @param {Grid} this
587              * @param {String} groupField A string with the grouping field, null if the store is not grouped.
588              */
589             'groupchange',
590             /**
591              * @event reconfigure
592              * Fires when the grid is reconfigured with a new store and/or column model.
593              * @param {Grid} this
594              * @param {Ext.data.Store} store The new store
595              * @param {Ext.grid.ColumnModel} colModel The new column model
596              */
597             'reconfigure',
598             /**
599              * @event viewready
600              * Fires when the grid view is available (use this for selecting a default row).
601              * @param {Grid} this
602              */
603             'viewready'
604         );
605     },
606
607     // private
608     onRender : function(ct, position){
609         Ext.grid.GridPanel.superclass.onRender.apply(this, arguments);
610
611         var c = this.getGridEl();
612
613         this.el.addClass('x-grid-panel');
614
615         this.mon(c, {
616             scope: this,
617             mousedown: this.onMouseDown,
618             click: this.onClick,
619             dblclick: this.onDblClick,
620             contextmenu: this.onContextMenu
621         });
622
623         this.relayEvents(c, ['mousedown','mouseup','mouseover','mouseout','keypress', 'keydown']);
624
625         var view = this.getView();
626         view.init(this);
627         view.render();
628         this.getSelectionModel().init(this);
629     },
630
631     // private
632     initEvents : function(){
633         Ext.grid.GridPanel.superclass.initEvents.call(this);
634
635         if(this.loadMask){
636             this.loadMask = new Ext.LoadMask(this.bwrap,
637                     Ext.apply({store:this.store}, this.loadMask));
638         }
639     },
640
641     initStateEvents : function(){
642         Ext.grid.GridPanel.superclass.initStateEvents.call(this);
643         this.mon(this.colModel, 'hiddenchange', this.saveState, this, {delay: 100});
644     },
645
646     applyState : function(state){
647         var cm = this.colModel,
648             cs = state.columns,
649             store = this.store,
650             s,
651             c,
652             oldIndex;
653
654         if(cs){
655             for(var i = 0, len = cs.length; i < len; i++){
656                 s = cs[i];
657                 c = cm.getColumnById(s.id);
658                 if(c){
659                     c.hidden = s.hidden;
660                     c.width = s.width;
661                     oldIndex = cm.getIndexById(s.id);
662                     if(oldIndex != i){
663                         cm.moveColumn(oldIndex, i);
664                     }
665                 }
666             }
667         }
668         if(store){
669             s = state.sort;
670             if(s){
671                 store[store.remoteSort ? 'setDefaultSort' : 'sort'](s.field, s.direction);
672             }
673             s = state.group;
674             if(store.groupBy){
675                 if(s){
676                     store.groupBy(s);
677                 }else{
678                     store.clearGrouping();
679                 }
680             }
681
682         }
683         var o = Ext.apply({}, state);
684         delete o.columns;
685         delete o.sort;
686         Ext.grid.GridPanel.superclass.applyState.call(this, o);
687     },
688
689     getState : function(){
690         var o = {columns: []},
691             store = this.store,
692             ss,
693             gs;
694
695         for(var i = 0, c; (c = this.colModel.config[i]); i++){
696             o.columns[i] = {
697                 id: c.id,
698                 width: c.width
699             };
700             if(c.hidden){
701                 o.columns[i].hidden = true;
702             }
703         }
704         if(store){
705             ss = store.getSortState();
706             if(ss){
707                 o.sort = ss;
708             }
709             if(store.getGroupState){
710                 gs = store.getGroupState();
711                 if(gs){
712                     o.group = gs;
713                 }
714             }
715         }
716         return o;
717     },
718
719     // private
720     afterRender : function(){
721         Ext.grid.GridPanel.superclass.afterRender.call(this);
722         var v = this.view;
723         this.on('bodyresize', v.layout, v);
724         v.layout();
725         if(this.deferRowRender){
726             if (!this.deferRowRenderTask){
727                 this.deferRowRenderTask = new Ext.util.DelayedTask(v.afterRender, this.view);
728             }
729             this.deferRowRenderTask.delay(10);
730         }else{
731             v.afterRender();
732         }
733         this.viewReady = true;
734     },
735
736     /**
737      * <p>Reconfigures the grid to use a different Store and Column Model
738      * and fires the 'reconfigure' event. The View will be bound to the new
739      * objects and refreshed.</p>
740      * <p>Be aware that upon reconfiguring a GridPanel, certain existing settings <i>may</i> become
741      * invalidated. For example the configured {@link #autoExpandColumn} may no longer exist in the
742      * new ColumnModel. Also, an existing {@link Ext.PagingToolbar PagingToolbar} will still be bound
743      * to the old Store, and will need rebinding. Any {@link #plugins} might also need reconfiguring
744      * with the new data.</p>
745      * @param {Ext.data.Store} store The new {@link Ext.data.Store} object
746      * @param {Ext.grid.ColumnModel} colModel The new {@link Ext.grid.ColumnModel} object
747      */
748     reconfigure : function(store, colModel){
749         var rendered = this.rendered;
750         if(rendered){
751             if(this.loadMask){
752                 this.loadMask.destroy();
753                 this.loadMask = new Ext.LoadMask(this.bwrap,
754                         Ext.apply({}, {store:store}, this.initialConfig.loadMask));
755             }
756         }
757         if(this.view){
758             this.view.initData(store, colModel);
759         }
760         this.store = store;
761         this.colModel = colModel;
762         if(rendered){
763             this.view.refresh(true);
764         }
765         this.fireEvent('reconfigure', this, store, colModel);
766     },
767
768     // private
769     onDestroy : function(){
770         if (this.deferRowRenderTask && this.deferRowRenderTask.cancel){
771             this.deferRowRenderTask.cancel();
772         }
773         if(this.rendered){
774             Ext.destroy(this.view, this.loadMask);
775         }else if(this.store && this.store.autoDestroy){
776             this.store.destroy();
777         }
778         Ext.destroy(this.colModel, this.selModel);
779         this.store = this.selModel = this.colModel = this.view = this.loadMask = null;
780         Ext.grid.GridPanel.superclass.onDestroy.call(this);
781     },
782
783     // private
784     processEvent : function(name, e){
785         this.view.processEvent(name, e);
786     },
787
788     // private
789     onClick : function(e){
790         this.processEvent('click', e);
791     },
792
793     // private
794     onMouseDown : function(e){
795         this.processEvent('mousedown', e);
796     },
797
798     // private
799     onContextMenu : function(e, t){
800         this.processEvent('contextmenu', e);
801     },
802
803     // private
804     onDblClick : function(e){
805         this.processEvent('dblclick', e);
806     },
807
808     // private
809     walkCells : function(row, col, step, fn, scope){
810         var cm    = this.colModel,
811             clen  = cm.getColumnCount(),
812             ds    = this.store,
813             rlen  = ds.getCount(),
814             first = true;
815
816         if(step < 0){
817             if(col < 0){
818                 row--;
819                 first = false;
820             }
821             while(row >= 0){
822                 if(!first){
823                     col = clen-1;
824                 }
825                 first = false;
826                 while(col >= 0){
827                     if(fn.call(scope || this, row, col, cm) === true){
828                         return [row, col];
829                     }
830                     col--;
831                 }
832                 row--;
833             }
834         } else {
835             if(col >= clen){
836                 row++;
837                 first = false;
838             }
839             while(row < rlen){
840                 if(!first){
841                     col = 0;
842                 }
843                 first = false;
844                 while(col < clen){
845                     if(fn.call(scope || this, row, col, cm) === true){
846                         return [row, col];
847                     }
848                     col++;
849                 }
850                 row++;
851             }
852         }
853         return null;
854     },
855
856     /**
857      * Returns the grid's underlying element.
858      * @return {Element} The element
859      */
860     getGridEl : function(){
861         return this.body;
862     },
863
864     // private for compatibility, overridden by editor grid
865     stopEditing : Ext.emptyFn,
866
867     /**
868      * Returns the grid's selection model configured by the <code>{@link #selModel}</code>
869      * configuration option. If no selection model was configured, this will create
870      * and return a {@link Ext.grid.RowSelectionModel RowSelectionModel}.
871      * @return {SelectionModel}
872      */
873     getSelectionModel : function(){
874         if(!this.selModel){
875             this.selModel = new Ext.grid.RowSelectionModel(
876                     this.disableSelection ? {selectRow: Ext.emptyFn} : null);
877         }
878         return this.selModel;
879     },
880
881     /**
882      * Returns the grid's data store.
883      * @return {Ext.data.Store} The store
884      */
885     getStore : function(){
886         return this.store;
887     },
888
889     /**
890      * Returns the grid's ColumnModel.
891      * @return {Ext.grid.ColumnModel} The column model
892      */
893     getColumnModel : function(){
894         return this.colModel;
895     },
896
897     /**
898      * Returns the grid's GridView object.
899      * @return {Ext.grid.GridView} The grid view
900      */
901     getView : function(){
902         if(!this.view){
903             this.view = new Ext.grid.GridView(this.viewConfig);
904         }
905         return this.view;
906     },
907     /**
908      * Called to get grid's drag proxy text, by default returns this.ddText.
909      * @return {String} The text
910      */
911     getDragDropText : function(){
912         var count = this.selModel.getCount();
913         return String.format(this.ddText, count, count == 1 ? '' : 's');
914     }
915
916     /**
917      * @cfg {String/Number} activeItem
918      * @hide
919      */
920     /**
921      * @cfg {Boolean} autoDestroy
922      * @hide
923      */
924     /**
925      * @cfg {Object/String/Function} autoLoad
926      * @hide
927      */
928     /**
929      * @cfg {Boolean} autoWidth
930      * @hide
931      */
932     /**
933      * @cfg {Boolean/Number} bufferResize
934      * @hide
935      */
936     /**
937      * @cfg {String} defaultType
938      * @hide
939      */
940     /**
941      * @cfg {Object} defaults
942      * @hide
943      */
944     /**
945      * @cfg {Boolean} hideBorders
946      * @hide
947      */
948     /**
949      * @cfg {Mixed} items
950      * @hide
951      */
952     /**
953      * @cfg {String} layout
954      * @hide
955      */
956     /**
957      * @cfg {Object} layoutConfig
958      * @hide
959      */
960     /**
961      * @cfg {Boolean} monitorResize
962      * @hide
963      */
964     /**
965      * @property items
966      * @hide
967      */
968     /**
969      * @method add
970      * @hide
971      */
972     /**
973      * @method cascade
974      * @hide
975      */
976     /**
977      * @method doLayout
978      * @hide
979      */
980     /**
981      * @method find
982      * @hide
983      */
984     /**
985      * @method findBy
986      * @hide
987      */
988     /**
989      * @method findById
990      * @hide
991      */
992     /**
993      * @method findByType
994      * @hide
995      */
996     /**
997      * @method getComponent
998      * @hide
999      */
1000     /**
1001      * @method getLayout
1002      * @hide
1003      */
1004     /**
1005      * @method getUpdater
1006      * @hide
1007      */
1008     /**
1009      * @method insert
1010      * @hide
1011      */
1012     /**
1013      * @method load
1014      * @hide
1015      */
1016     /**
1017      * @method remove
1018      * @hide
1019      */
1020     /**
1021      * @event add
1022      * @hide
1023      */
1024     /**
1025      * @event afterlayout
1026      * @hide
1027      */
1028     /**
1029      * @event beforeadd
1030      * @hide
1031      */
1032     /**
1033      * @event beforeremove
1034      * @hide
1035      */
1036     /**
1037      * @event remove
1038      * @hide
1039      */
1040
1041
1042
1043     /**
1044      * @cfg {String} allowDomMove  @hide
1045      */
1046     /**
1047      * @cfg {String} autoEl @hide
1048      */
1049     /**
1050      * @cfg {String} applyTo  @hide
1051      */
1052     /**
1053      * @cfg {String} autoScroll  @hide
1054      */
1055     /**
1056      * @cfg {String} bodyBorder  @hide
1057      */
1058     /**
1059      * @cfg {String} bodyStyle  @hide
1060      */
1061     /**
1062      * @cfg {String} contentEl  @hide
1063      */
1064     /**
1065      * @cfg {String} disabledClass  @hide
1066      */
1067     /**
1068      * @cfg {String} elements  @hide
1069      */
1070     /**
1071      * @cfg {String} html  @hide
1072      */
1073     /**
1074      * @cfg {Boolean} preventBodyReset
1075      * @hide
1076      */
1077     /**
1078      * @property disabled
1079      * @hide
1080      */
1081     /**
1082      * @method applyToMarkup
1083      * @hide
1084      */
1085     /**
1086      * @method enable
1087      * @hide
1088      */
1089     /**
1090      * @method disable
1091      * @hide
1092      */
1093     /**
1094      * @method setDisabled
1095      * @hide
1096      */
1097 });
1098 Ext.reg('grid', Ext.grid.GridPanel);/**
1099  * @class Ext.grid.GridView
1100  * @extends Ext.util.Observable
1101  * <p>This class encapsulates the user interface of an {@link Ext.grid.GridPanel}.
1102  * Methods of this class may be used to access user interface elements to enable
1103  * special display effects. Do not change the DOM structure of the user interface.</p>
1104  * <p>This class does not provide ways to manipulate the underlying data. The data
1105  * model of a Grid is held in an {@link Ext.data.Store}.</p>
1106  * @constructor
1107  * @param {Object} config
1108  */
1109 Ext.grid.GridView = Ext.extend(Ext.util.Observable, {
1110     /**
1111      * Override this function to apply custom CSS classes to rows during rendering.  You can also supply custom
1112      * parameters to the row template for the current row to customize how it is rendered using the <b>rowParams</b>
1113      * parameter.  This function should return the CSS class name (or empty string '' for none) that will be added
1114      * to the row's wrapping div.  To apply multiple class names, simply return them space-delimited within the string
1115      * (e.g., 'my-class another-class'). Example usage:
1116     <pre><code>
1117 viewConfig: {
1118     forceFit: true,
1119     showPreview: true, // custom property
1120     enableRowBody: true, // required to create a second, full-width row to show expanded Record data
1121     getRowClass: function(record, rowIndex, rp, ds){ // rp = rowParams
1122         if(this.showPreview){
1123             rp.body = '&lt;p>'+record.data.excerpt+'&lt;/p>';
1124             return 'x-grid3-row-expanded';
1125         }
1126         return 'x-grid3-row-collapsed';
1127     }
1128 },
1129     </code></pre>
1130      * @param {Record} record The {@link Ext.data.Record} corresponding to the current row.
1131      * @param {Number} index The row index.
1132      * @param {Object} rowParams A config object that is passed to the row template during rendering that allows
1133      * customization of various aspects of a grid row.
1134      * <p>If {@link #enableRowBody} is configured <b><tt></tt>true</b>, then the following properties may be set
1135      * by this function, and will be used to render a full-width expansion row below each grid row:</p>
1136      * <ul>
1137      * <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>
1138      * <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>
1139      * </ul>
1140      * The following property will be passed in, and may be appended to:
1141      * <ul>
1142      * <li><code>tstyle</code> : String <div class="sub-desc">A CSS style specification that willl be applied to the &lt;table> element which encapsulates
1143      * both the standard grid row, and any expansion row.</div></li>
1144      * </ul>
1145      * @param {Store} store The {@link Ext.data.Store} this grid is bound to
1146      * @method getRowClass
1147      * @return {String} a CSS class name to add to the row.
1148      */
1149
1150     /**
1151      * @cfg {Boolean} enableRowBody True to add a second TR element per row that can be used to provide a row body
1152      * that spans beneath the data row.  Use the {@link #getRowClass} method's rowParams config to customize the row body.
1153      */
1154
1155     /**
1156      * @cfg {String} emptyText Default text (html tags are accepted) to display in the grid body when no rows
1157      * are available (defaults to ''). This value will be used to update the <tt>{@link #mainBody}</tt>:
1158     <pre><code>
1159     this.mainBody.update('&lt;div class="x-grid-empty">' + this.emptyText + '&lt;/div>');
1160     </code></pre>
1161      */
1162
1163     /**
1164      * @cfg {Boolean} headersDisabled True to disable the grid column headers (defaults to <tt>false</tt>).
1165      * Use the {@link Ext.grid.ColumnModel ColumnModel} <tt>{@link Ext.grid.ColumnModel#menuDisabled menuDisabled}</tt>
1166      * config to disable the <i>menu</i> for individual columns.  While this config is true the
1167      * following will be disabled:<div class="mdetail-params"><ul>
1168      * <li>clicking on header to sort</li>
1169      * <li>the trigger to reveal the menu.</li>
1170      * </ul></div>
1171      */
1172
1173     /**
1174      * <p>A customized implementation of a {@link Ext.dd.DragZone DragZone} which provides default implementations
1175      * of the template methods of DragZone to enable dragging of the selected rows of a GridPanel.
1176      * See {@link Ext.grid.GridDragZone} for details.</p>
1177      * <p>This will <b>only</b> be present:<div class="mdetail-params"><ul>
1178      * <li><i>if</i> the owning GridPanel was configured with {@link Ext.grid.GridPanel#enableDragDrop enableDragDrop}: <tt>true</tt>.</li>
1179      * <li><i>after</i> the owning GridPanel has been rendered.</li>
1180      * </ul></div>
1181      * @property dragZone
1182      * @type {Ext.grid.GridDragZone}
1183      */
1184
1185     /**
1186      * @cfg {Boolean} deferEmptyText True to defer <tt>{@link #emptyText}</tt> being applied until the store's
1187      * first load (defaults to <tt>true</tt>).
1188      */
1189     deferEmptyText : true,
1190
1191     /**
1192      * @cfg {Number} scrollOffset The amount of space to reserve for the vertical scrollbar
1193      * (defaults to <tt>undefined</tt>). If an explicit value isn't specified, this will be automatically
1194      * calculated.
1195      */
1196     scrollOffset : undefined,
1197
1198     /**
1199      * @cfg {Boolean} autoFill
1200      * Defaults to <tt>false</tt>.  Specify <tt>true</tt> to have the column widths re-proportioned
1201      * when the grid is <b>initially rendered</b>.  The
1202      * {@link Ext.grid.Column#width initially configured width}</tt> of each column will be adjusted
1203      * to fit the grid width and prevent horizontal scrolling. If columns are later resized (manually
1204      * or programmatically), the other columns in the grid will <b>not</b> be resized to fit the grid width.
1205      * See <tt>{@link #forceFit}</tt> also.
1206      */
1207     autoFill : false,
1208
1209     /**
1210      * @cfg {Boolean} forceFit
1211      * Defaults to <tt>false</tt>.  Specify <tt>true</tt> to have the column widths re-proportioned
1212      * at <b>all times</b>.  The {@link Ext.grid.Column#width initially configured width}</tt> of each
1213      * column will be adjusted to fit the grid width and prevent horizontal scrolling. If columns are
1214      * later resized (manually or programmatically), the other columns in the grid <b>will</b> be resized
1215      * to fit the grid width. See <tt>{@link #autoFill}</tt> also.
1216      */
1217     forceFit : false,
1218
1219     /**
1220      * @cfg {Array} sortClasses The CSS classes applied to a header when it is sorted. (defaults to <tt>['sort-asc', 'sort-desc']</tt>)
1221      */
1222     sortClasses : ['sort-asc', 'sort-desc'],
1223
1224     /**
1225      * @cfg {String} sortAscText The text displayed in the 'Sort Ascending' menu item (defaults to <tt>'Sort Ascending'</tt>)
1226      */
1227     sortAscText : 'Sort Ascending',
1228
1229     /**
1230      * @cfg {String} sortDescText The text displayed in the 'Sort Descending' menu item (defaults to <tt>'Sort Descending'</tt>)
1231      */
1232     sortDescText : 'Sort Descending',
1233
1234     /**
1235      * @cfg {String} columnsText The text displayed in the 'Columns' menu item (defaults to <tt>'Columns'</tt>)
1236      */
1237     columnsText : 'Columns',
1238
1239     /**
1240      * @cfg {String} selectedRowClass The CSS class applied to a selected row (defaults to <tt>'x-grid3-row-selected'</tt>). An
1241      * example overriding the default styling:
1242     <pre><code>
1243     .x-grid3-row-selected {background-color: yellow;}
1244     </code></pre>
1245      * Note that this only controls the row, and will not do anything for the text inside it.  To style inner
1246      * facets (like text) use something like:
1247     <pre><code>
1248     .x-grid3-row-selected .x-grid3-cell-inner {
1249         color: #FFCC00;
1250     }
1251     </code></pre>
1252      * @type String
1253      */
1254     selectedRowClass : 'x-grid3-row-selected',
1255
1256     // private
1257     borderWidth : 2,
1258     tdClass : 'x-grid3-cell',
1259     hdCls : 'x-grid3-hd',
1260     markDirty : true,
1261
1262     /**
1263      * @cfg {Number} cellSelectorDepth The number of levels to search for cells in event delegation (defaults to <tt>4</tt>)
1264      */
1265     cellSelectorDepth : 4,
1266     /**
1267      * @cfg {Number} rowSelectorDepth The number of levels to search for rows in event delegation (defaults to <tt>10</tt>)
1268      */
1269     rowSelectorDepth : 10,
1270
1271     /**
1272      * @cfg {Number} rowBodySelectorDepth The number of levels to search for row bodies in event delegation (defaults to <tt>10</tt>)
1273      */
1274     rowBodySelectorDepth : 10,
1275
1276     /**
1277      * @cfg {String} cellSelector The selector used to find cells internally (defaults to <tt>'td.x-grid3-cell'</tt>)
1278      */
1279     cellSelector : 'td.x-grid3-cell',
1280     /**
1281      * @cfg {String} rowSelector The selector used to find rows internally (defaults to <tt>'div.x-grid3-row'</tt>)
1282      */
1283     rowSelector : 'div.x-grid3-row',
1284
1285     /**
1286      * @cfg {String} rowBodySelector The selector used to find row bodies internally (defaults to <tt>'div.x-grid3-row'</tt>)
1287      */
1288     rowBodySelector : 'div.x-grid3-row-body',
1289
1290     // private
1291     firstRowCls: 'x-grid3-row-first',
1292     lastRowCls: 'x-grid3-row-last',
1293     rowClsRe: /(?:^|\s+)x-grid3-row-(first|last|alt)(?:\s+|$)/g,
1294
1295     constructor : function(config){
1296         Ext.apply(this, config);
1297         // These events are only used internally by the grid components
1298         this.addEvents(
1299             /**
1300              * @event beforerowremoved
1301              * Internal UI Event. Fired before a row is removed.
1302              * @param {Ext.grid.GridView} view
1303              * @param {Number} rowIndex The index of the row to be removed.
1304              * @param {Ext.data.Record} record The Record to be removed
1305              */
1306             'beforerowremoved',
1307             /**
1308              * @event beforerowsinserted
1309              * Internal UI Event. Fired before rows are inserted.
1310              * @param {Ext.grid.GridView} view
1311              * @param {Number} firstRow The index of the first row to be inserted.
1312              * @param {Number} lastRow The index of the last row to be inserted.
1313              */
1314             'beforerowsinserted',
1315             /**
1316              * @event beforerefresh
1317              * Internal UI Event. Fired before the view is refreshed.
1318              * @param {Ext.grid.GridView} view
1319              */
1320             'beforerefresh',
1321             /**
1322              * @event rowremoved
1323              * Internal UI Event. Fired after a row is removed.
1324              * @param {Ext.grid.GridView} view
1325              * @param {Number} rowIndex The index of the row that was removed.
1326              * @param {Ext.data.Record} record The Record that was removed
1327              */
1328             'rowremoved',
1329             /**
1330              * @event rowsinserted
1331              * Internal UI Event. Fired after rows are inserted.
1332              * @param {Ext.grid.GridView} view
1333              * @param {Number} firstRow The index of the first inserted.
1334              * @param {Number} lastRow The index of the last row inserted.
1335              */
1336             'rowsinserted',
1337             /**
1338              * @event rowupdated
1339              * Internal UI Event. Fired after a row has been updated.
1340              * @param {Ext.grid.GridView} view
1341              * @param {Number} firstRow The index of the row updated.
1342              * @param {Ext.data.record} record The Record backing the row updated.
1343              */
1344             'rowupdated',
1345             /**
1346              * @event refresh
1347              * Internal UI Event. Fired after the GridView's body has been refreshed.
1348              * @param {Ext.grid.GridView} view
1349              */
1350             'refresh'
1351         );
1352         Ext.grid.GridView.superclass.constructor.call(this);
1353     },
1354
1355     /* -------------------------------- UI Specific ----------------------------- */
1356
1357     // private
1358     initTemplates : function(){
1359         var ts = this.templates || {};
1360         if(!ts.master){
1361             ts.master = new Ext.Template(
1362                 '<div class="x-grid3" hidefocus="true">',
1363                     '<div class="x-grid3-viewport">',
1364                         '<div class="x-grid3-header"><div class="x-grid3-header-inner"><div class="x-grid3-header-offset" style="{ostyle}">{header}</div></div><div class="x-clear"></div></div>',
1365                         '<div class="x-grid3-scroller"><div class="x-grid3-body" style="{bstyle}">{body}</div><a href="#" class="x-grid3-focus" tabIndex="-1"></a></div>',
1366                     '</div>',
1367                     '<div class="x-grid3-resize-marker">&#160;</div>',
1368                     '<div class="x-grid3-resize-proxy">&#160;</div>',
1369                 '</div>'
1370             );
1371         }
1372
1373         if(!ts.header){
1374             ts.header = new Ext.Template(
1375                 '<table border="0" cellspacing="0" cellpadding="0" style="{tstyle}">',
1376                 '<thead><tr class="x-grid3-hd-row">{cells}</tr></thead>',
1377                 '</table>'
1378             );
1379         }
1380
1381         if(!ts.hcell){
1382             ts.hcell = new Ext.Template(
1383                 '<td class="x-grid3-hd x-grid3-cell x-grid3-td-{id} {css}" style="{style}"><div {tooltip} {attr} class="x-grid3-hd-inner x-grid3-hd-{id}" unselectable="on" style="{istyle}">', this.grid.enableHdMenu ? '<a class="x-grid3-hd-btn" href="#"></a>' : '',
1384                 '{value}<img class="x-grid3-sort-icon" src="', Ext.BLANK_IMAGE_URL, '" />',
1385                 '</div></td>'
1386             );
1387         }
1388
1389         if(!ts.body){
1390             ts.body = new Ext.Template('{rows}');
1391         }
1392
1393         if(!ts.row){
1394             ts.row = new Ext.Template(
1395                 '<div class="x-grid3-row {alt}" style="{tstyle}"><table class="x-grid3-row-table" border="0" cellspacing="0" cellpadding="0" style="{tstyle}">',
1396                 '<tbody><tr>{cells}</tr>',
1397                 (this.enableRowBody ? '<tr class="x-grid3-row-body-tr" style="{bodyStyle}"><td colspan="{cols}" class="x-grid3-body-cell" tabIndex="0" hidefocus="on"><div class="x-grid3-row-body">{body}</div></td></tr>' : ''),
1398                 '</tbody></table></div>'
1399             );
1400         }
1401
1402         if(!ts.cell){
1403             ts.cell = new Ext.Template(
1404                     '<td class="x-grid3-col x-grid3-cell x-grid3-td-{id} {css}" style="{style}" tabIndex="0" {cellAttr}>',
1405                     '<div class="x-grid3-cell-inner x-grid3-col-{id}" unselectable="on" {attr}>{value}</div>',
1406                     '</td>'
1407                     );
1408         }
1409
1410         for(var k in ts){
1411             var t = ts[k];
1412             if(t && Ext.isFunction(t.compile) && !t.compiled){
1413                 t.disableFormats = true;
1414                 t.compile();
1415             }
1416         }
1417
1418         this.templates = ts;
1419         this.colRe = new RegExp('x-grid3-td-([^\\s]+)', '');
1420     },
1421
1422     // private
1423     fly : function(el){
1424         if(!this._flyweight){
1425             this._flyweight = new Ext.Element.Flyweight(document.body);
1426         }
1427         this._flyweight.dom = el;
1428         return this._flyweight;
1429     },
1430
1431     // private
1432     getEditorParent : function(){
1433         return this.scroller.dom;
1434     },
1435
1436     // private
1437     initElements : function(){
1438         var E = Ext.Element;
1439
1440         var el = this.grid.getGridEl().dom.firstChild;
1441         var cs = el.childNodes;
1442
1443         this.el = new E(el);
1444
1445         this.mainWrap = new E(cs[0]);
1446         this.mainHd = new E(this.mainWrap.dom.firstChild);
1447
1448         if(this.grid.hideHeaders){
1449             this.mainHd.setDisplayed(false);
1450         }
1451
1452         this.innerHd = this.mainHd.dom.firstChild;
1453         this.scroller = new E(this.mainWrap.dom.childNodes[1]);
1454         if(this.forceFit){
1455             this.scroller.setStyle('overflow-x', 'hidden');
1456         }
1457         /**
1458          * <i>Read-only</i>. The GridView's body Element which encapsulates all rows in the Grid.
1459          * This {@link Ext.Element Element} is only available after the GridPanel has been rendered.
1460          * @type Ext.Element
1461          * @property mainBody
1462          */
1463         this.mainBody = new E(this.scroller.dom.firstChild);
1464
1465         this.focusEl = new E(this.scroller.dom.childNodes[1]);
1466         this.focusEl.swallowEvent('click', true);
1467
1468         this.resizeMarker = new E(cs[1]);
1469         this.resizeProxy = new E(cs[2]);
1470     },
1471
1472     // private
1473     getRows : function(){
1474         return this.hasRows() ? this.mainBody.dom.childNodes : [];
1475     },
1476
1477     // finder methods, used with delegation
1478
1479     // private
1480     findCell : function(el){
1481         if(!el){
1482             return false;
1483         }
1484         return this.fly(el).findParent(this.cellSelector, this.cellSelectorDepth);
1485     },
1486
1487     /**
1488      * <p>Return the index of the grid column which contains the passed HTMLElement.</p>
1489      * See also {@link #findRowIndex}
1490      * @param {HTMLElement} el The target element
1491      * @return {Number} The column index, or <b>false</b> if the target element is not within a row of this GridView.
1492      */
1493     findCellIndex : function(el, requiredCls){
1494         var cell = this.findCell(el);
1495         if(cell && (!requiredCls || this.fly(cell).hasClass(requiredCls))){
1496             return this.getCellIndex(cell);
1497         }
1498         return false;
1499     },
1500
1501     // private
1502     getCellIndex : function(el){
1503         if(el){
1504             var m = el.className.match(this.colRe);
1505             if(m && m[1]){
1506                 return this.cm.getIndexById(m[1]);
1507             }
1508         }
1509         return false;
1510     },
1511
1512     // private
1513     findHeaderCell : function(el){
1514         var cell = this.findCell(el);
1515         return cell && this.fly(cell).hasClass(this.hdCls) ? cell : null;
1516     },
1517
1518     // private
1519     findHeaderIndex : function(el){
1520         return this.findCellIndex(el, this.hdCls);
1521     },
1522
1523     /**
1524      * Return the HtmlElement representing the grid row which contains the passed element.
1525      * @param {HTMLElement} el The target HTMLElement
1526      * @return {HTMLElement} The row element, or null if the target element is not within a row of this GridView.
1527      */
1528     findRow : function(el){
1529         if(!el){
1530             return false;
1531         }
1532         return this.fly(el).findParent(this.rowSelector, this.rowSelectorDepth);
1533     },
1534
1535     /**
1536      * <p>Return the index of the grid row which contains the passed HTMLElement.</p>
1537      * See also {@link #findCellIndex}
1538      * @param {HTMLElement} el The target HTMLElement
1539      * @return {Number} The row index, or <b>false</b> if the target element is not within a row of this GridView.
1540      */
1541     findRowIndex : function(el){
1542         var r = this.findRow(el);
1543         return r ? r.rowIndex : false;
1544     },
1545
1546     /**
1547      * Return the HtmlElement representing the grid row body which contains the passed element.
1548      * @param {HTMLElement} el The target HTMLElement
1549      * @return {HTMLElement} The row body element, or null if the target element is not within a row body of this GridView.
1550      */
1551     findRowBody : function(el){
1552         if(!el){
1553             return false;
1554         }
1555         return this.fly(el).findParent(this.rowBodySelector, this.rowBodySelectorDepth);
1556     },
1557
1558     // getter methods for fetching elements dynamically in the grid
1559
1560     /**
1561      * Return the <tt>&lt;div></tt> HtmlElement which represents a Grid row for the specified index.
1562      * @param {Number} index The row index
1563      * @return {HtmlElement} The div element.
1564      */
1565     getRow : function(row){
1566         return this.getRows()[row];
1567     },
1568
1569     /**
1570      * Returns the grid's <tt>&lt;td></tt> HtmlElement at the specified coordinates.
1571      * @param {Number} row The row index in which to find the cell.
1572      * @param {Number} col The column index of the cell.
1573      * @return {HtmlElement} The td at the specified coordinates.
1574      */
1575     getCell : function(row, col){
1576         return this.getRow(row).getElementsByTagName('td')[col];
1577     },
1578
1579     /**
1580      * Return the <tt>&lt;td></tt> HtmlElement which represents the Grid's header cell for the specified column index.
1581      * @param {Number} index The column index
1582      * @return {HtmlElement} The td element.
1583      */
1584     getHeaderCell : function(index){
1585         return this.mainHd.dom.getElementsByTagName('td')[index];
1586     },
1587
1588     // manipulating elements
1589
1590     // private - use getRowClass to apply custom row classes
1591     addRowClass : function(row, cls){
1592         var r = this.getRow(row);
1593         if(r){
1594             this.fly(r).addClass(cls);
1595         }
1596     },
1597
1598     // private
1599     removeRowClass : function(row, cls){
1600         var r = this.getRow(row);
1601         if(r){
1602             this.fly(r).removeClass(cls);
1603         }
1604     },
1605
1606     // private
1607     removeRow : function(row){
1608         Ext.removeNode(this.getRow(row));
1609         this.syncFocusEl(row);
1610     },
1611
1612     // private
1613     removeRows : function(firstRow, lastRow){
1614         var bd = this.mainBody.dom;
1615         for(var rowIndex = firstRow; rowIndex <= lastRow; rowIndex++){
1616             Ext.removeNode(bd.childNodes[firstRow]);
1617         }
1618         this.syncFocusEl(firstRow);
1619     },
1620
1621     // scrolling stuff
1622
1623     // private
1624     getScrollState : function(){
1625         var sb = this.scroller.dom;
1626         return {left: sb.scrollLeft, top: sb.scrollTop};
1627     },
1628
1629     // private
1630     restoreScroll : function(state){
1631         var sb = this.scroller.dom;
1632         sb.scrollLeft = state.left;
1633         sb.scrollTop = state.top;
1634     },
1635
1636     /**
1637      * Scrolls the grid to the top
1638      */
1639     scrollToTop : function(){
1640         this.scroller.dom.scrollTop = 0;
1641         this.scroller.dom.scrollLeft = 0;
1642     },
1643
1644     // private
1645     syncScroll : function(){
1646         this.syncHeaderScroll();
1647         var mb = this.scroller.dom;
1648         this.grid.fireEvent('bodyscroll', mb.scrollLeft, mb.scrollTop);
1649     },
1650
1651     // private
1652     syncHeaderScroll : function(){
1653         var mb = this.scroller.dom;
1654         this.innerHd.scrollLeft = mb.scrollLeft;
1655         this.innerHd.scrollLeft = mb.scrollLeft; // second time for IE (1/2 time first fails, other browsers ignore)
1656     },
1657
1658     // private
1659     updateSortIcon : function(col, dir){
1660         var sc = this.sortClasses;
1661         var hds = this.mainHd.select('td').removeClass(sc);
1662         hds.item(col).addClass(sc[dir == 'DESC' ? 1 : 0]);
1663     },
1664
1665     // private
1666     updateAllColumnWidths : function(){
1667         var tw   = this.getTotalWidth(),
1668             clen = this.cm.getColumnCount(),
1669             ws   = [],
1670             len,
1671             i;
1672
1673         for(i = 0; i < clen; i++){
1674             ws[i] = this.getColumnWidth(i);
1675         }
1676
1677         this.innerHd.firstChild.style.width = this.getOffsetWidth();
1678         this.innerHd.firstChild.firstChild.style.width = tw;
1679         this.mainBody.dom.style.width = tw;
1680
1681         for(i = 0; i < clen; i++){
1682             var hd = this.getHeaderCell(i);
1683             hd.style.width = ws[i];
1684         }
1685
1686         var ns = this.getRows(), row, trow;
1687         for(i = 0, len = ns.length; i < len; i++){
1688             row = ns[i];
1689             row.style.width = tw;
1690             if(row.firstChild){
1691                 row.firstChild.style.width = tw;
1692                 trow = row.firstChild.rows[0];
1693                 for (var j = 0; j < clen; j++) {
1694                    trow.childNodes[j].style.width = ws[j];
1695                 }
1696             }
1697         }
1698
1699         this.onAllColumnWidthsUpdated(ws, tw);
1700     },
1701
1702     // private
1703     updateColumnWidth : function(col, width){
1704         var w = this.getColumnWidth(col);
1705         var tw = this.getTotalWidth();
1706         this.innerHd.firstChild.style.width = this.getOffsetWidth();
1707         this.innerHd.firstChild.firstChild.style.width = tw;
1708         this.mainBody.dom.style.width = tw;
1709         var hd = this.getHeaderCell(col);
1710         hd.style.width = w;
1711
1712         var ns = this.getRows(), row;
1713         for(var i = 0, len = ns.length; i < len; i++){
1714             row = ns[i];
1715             row.style.width = tw;
1716             if(row.firstChild){
1717                 row.firstChild.style.width = tw;
1718                 row.firstChild.rows[0].childNodes[col].style.width = w;
1719             }
1720         }
1721
1722         this.onColumnWidthUpdated(col, w, tw);
1723     },
1724
1725     // private
1726     updateColumnHidden : function(col, hidden){
1727         var tw = this.getTotalWidth();
1728         this.innerHd.firstChild.style.width = this.getOffsetWidth();
1729         this.innerHd.firstChild.firstChild.style.width = tw;
1730         this.mainBody.dom.style.width = tw;
1731         var display = hidden ? 'none' : '';
1732
1733         var hd = this.getHeaderCell(col);
1734         hd.style.display = display;
1735
1736         var ns = this.getRows(), row;
1737         for(var i = 0, len = ns.length; i < len; i++){
1738             row = ns[i];
1739             row.style.width = tw;
1740             if(row.firstChild){
1741                 row.firstChild.style.width = tw;
1742                 row.firstChild.rows[0].childNodes[col].style.display = display;
1743             }
1744         }
1745
1746         this.onColumnHiddenUpdated(col, hidden, tw);
1747         delete this.lastViewWidth; // force recalc
1748         this.layout();
1749     },
1750
1751     /**
1752      * @private
1753      * Renders all of the rows to a string buffer and returns the string. This is called internally
1754      * by renderRows and performs the actual string building for the rows - it does not inject HTML into the DOM.
1755      * @param {Array} columns The column data acquired from getColumnData.
1756      * @param {Array} records The array of records to render
1757      * @param {Ext.data.Store} store The store to render the rows from
1758      * @param {Number} startRow The index of the first row being rendered. Sometimes we only render a subset of
1759      * the rows so this is used to maintain logic for striping etc
1760      * @param {Number} colCount The total number of columns in the column model
1761      * @param {Boolean} stripe True to stripe the rows
1762      * @return {String} A string containing the HTML for the rendered rows
1763      */
1764     doRender : function(columns, records, store, startRow, colCount, stripe) {
1765         var templates    = this.templates,
1766             cellTemplate = templates.cell,
1767             rowTemplate  = templates.row,
1768             last         = colCount - 1;
1769
1770         var tstyle = 'width:' + this.getTotalWidth() + ';';
1771
1772         // buffers
1773         var rowBuffer = [],
1774             colBuffer = [],
1775             rowParams = {tstyle: tstyle},
1776             meta      = {},
1777             column,
1778             record;
1779
1780         //build up each row's HTML
1781         for (var j = 0, len = records.length; j < len; j++) {
1782             record    = records[j];
1783             colBuffer = [];
1784
1785             var rowIndex = j + startRow;
1786
1787             //build up each column's HTML
1788             for (var i = 0; i < colCount; i++) {
1789                 column = columns[i];
1790
1791                 meta.id    = column.id;
1792                 meta.css   = i === 0 ? 'x-grid3-cell-first ' : (i == last ? 'x-grid3-cell-last ' : '');
1793                 meta.attr  = meta.cellAttr = '';
1794                 meta.style = column.style;
1795                 meta.value = column.renderer.call(column.scope, record.data[column.name], meta, record, rowIndex, i, store);
1796
1797                 if (Ext.isEmpty(meta.value)) {
1798                     meta.value = '&#160;';
1799                 }
1800
1801                 if (this.markDirty && record.dirty && Ext.isDefined(record.modified[column.name])) {
1802                     meta.css += ' x-grid3-dirty-cell';
1803                 }
1804
1805                 colBuffer[colBuffer.length] = cellTemplate.apply(meta);
1806             }
1807
1808             //set up row striping and row dirtiness CSS classes
1809             var alt = [];
1810
1811             if (stripe && ((rowIndex + 1) % 2 === 0)) {
1812                 alt[0] = 'x-grid3-row-alt';
1813             }
1814
1815             if (record.dirty) {
1816                 alt[1] = ' x-grid3-dirty-row';
1817             }
1818
1819             rowParams.cols = colCount;
1820
1821             if (this.getRowClass) {
1822                 alt[2] = this.getRowClass(record, rowIndex, rowParams, store);
1823             }
1824
1825             rowParams.alt   = alt.join(' ');
1826             rowParams.cells = colBuffer.join('');
1827
1828             rowBuffer[rowBuffer.length] = rowTemplate.apply(rowParams);
1829         }
1830
1831         return rowBuffer.join('');
1832     },
1833
1834     // private
1835     processRows : function(startRow, skipStripe) {
1836         if (!this.ds || this.ds.getCount() < 1) {
1837             return;
1838         }
1839
1840         var rows = this.getRows(),
1841             len  = rows.length,
1842             i, r;
1843
1844         skipStripe = skipStripe || !this.grid.stripeRows;
1845         startRow   = startRow   || 0;
1846
1847         for (i = 0; i<len; i++) {
1848             r = rows[i];
1849             if (r) {
1850                 r.rowIndex = i;
1851                 if (!skipStripe) {
1852                     r.className = r.className.replace(this.rowClsRe, ' ');
1853                     if ((i + 1) % 2 === 0){
1854                         r.className += ' x-grid3-row-alt';
1855                     }
1856                 }
1857             }
1858         }
1859
1860         // add first/last-row classes
1861         if (startRow === 0) {
1862             Ext.fly(rows[0]).addClass(this.firstRowCls);
1863         }
1864
1865         Ext.fly(rows[rows.length - 1]).addClass(this.lastRowCls);
1866     },
1867
1868     afterRender : function(){
1869         if(!this.ds || !this.cm){
1870             return;
1871         }
1872         this.mainBody.dom.innerHTML = this.renderRows() || '&#160;';
1873         this.processRows(0, true);
1874
1875         if(this.deferEmptyText !== true){
1876             this.applyEmptyText();
1877         }
1878         this.grid.fireEvent('viewready', this.grid);
1879     },
1880
1881     /**
1882      * @private
1883      * Renders each of the UI elements in turn. This is called internally, once, by this.render. It does not
1884      * render rows from the store, just the surrounding UI elements. It also sets up listeners on the UI elements
1885      * and sets up options like column menus, moving and resizing.
1886      */
1887     renderUI : function() {
1888         var templates = this.templates,
1889             header    = this.renderHeaders(),
1890             body      = templates.body.apply({rows:'&#160;'});
1891
1892         var html = templates.master.apply({
1893             body  : body,
1894             header: header,
1895             ostyle: 'width:' + this.getOffsetWidth() + ';',
1896             bstyle: 'width:' + this.getTotalWidth()  + ';'
1897         });
1898
1899         var g = this.grid;
1900
1901         g.getGridEl().dom.innerHTML = html;
1902
1903         this.initElements();
1904
1905         // get mousedowns early
1906         Ext.fly(this.innerHd).on('click', this.handleHdDown, this);
1907
1908         this.mainHd.on({
1909             scope    : this,
1910             mouseover: this.handleHdOver,
1911             mouseout : this.handleHdOut,
1912             mousemove: this.handleHdMove
1913         });
1914
1915         this.scroller.on('scroll', this.syncScroll,  this);
1916         if (g.enableColumnResize !== false) {
1917             this.splitZone = new Ext.grid.GridView.SplitDragZone(g, this.mainHd.dom);
1918         }
1919
1920         if (g.enableColumnMove) {
1921             this.columnDrag = new Ext.grid.GridView.ColumnDragZone(g, this.innerHd);
1922             this.columnDrop = new Ext.grid.HeaderDropZone(g, this.mainHd.dom);
1923         }
1924
1925         if (g.enableHdMenu !== false) {
1926             this.hmenu = new Ext.menu.Menu({id: g.id + '-hctx'});
1927             this.hmenu.add(
1928                 {itemId:'asc',  text: this.sortAscText,  cls: 'xg-hmenu-sort-asc'},
1929                 {itemId:'desc', text: this.sortDescText, cls: 'xg-hmenu-sort-desc'}
1930             );
1931
1932             if (g.enableColumnHide !== false) {
1933                 this.colMenu = new Ext.menu.Menu({id:g.id + '-hcols-menu'});
1934                 this.colMenu.on({
1935                     scope     : this,
1936                     beforeshow: this.beforeColMenuShow,
1937                     itemclick : this.handleHdMenuClick
1938                 });
1939                 this.hmenu.add('-', {
1940                     itemId:'columns',
1941                     hideOnClick: false,
1942                     text: this.columnsText,
1943                     menu: this.colMenu,
1944                     iconCls: 'x-cols-icon'
1945                 });
1946             }
1947
1948             this.hmenu.on('itemclick', this.handleHdMenuClick, this);
1949         }
1950
1951         if (g.trackMouseOver) {
1952             this.mainBody.on({
1953                 scope    : this,
1954                 mouseover: this.onRowOver,
1955                 mouseout : this.onRowOut
1956             });
1957         }
1958
1959         if (g.enableDragDrop || g.enableDrag) {
1960             this.dragZone = new Ext.grid.GridDragZone(g, {
1961                 ddGroup : g.ddGroup || 'GridDD'
1962             });
1963         }
1964
1965         this.updateHeaderSortState();
1966     },
1967
1968     // private
1969     processEvent : function(name, e) {
1970         var t = e.getTarget(),
1971             g = this.grid,
1972             header = this.findHeaderIndex(t);
1973         g.fireEvent(name, e);
1974         if (header !== false) {
1975             g.fireEvent('header' + name, g, header, e);
1976         } else {
1977             var row = this.findRowIndex(t),
1978                 cell,
1979                 body;
1980             if (row !== false) {
1981                 g.fireEvent('row' + name, g, row, e);
1982                 cell = this.findCellIndex(t);
1983                 if (cell !== false) {
1984                     g.fireEvent('cell' + name, g, row, cell, e);
1985                 } else {
1986                     body = this.findRowBody(t);
1987                     if (body) {
1988                         g.fireEvent('rowbody' + name, g, row, e);
1989                     }
1990                 }
1991             } else {
1992                 g.fireEvent('container' + name, g, e);
1993             }
1994         }
1995     },
1996
1997     // private
1998     layout : function() {
1999         if(!this.mainBody){
2000             return; // not rendered
2001         }
2002         var g = this.grid;
2003         var c = g.getGridEl();
2004         var csize = c.getSize(true);
2005         var vw = csize.width;
2006
2007         if(!g.hideHeaders && (vw < 20 || csize.height < 20)){ // display: none?
2008             return;
2009         }
2010
2011         if(g.autoHeight){
2012             this.scroller.dom.style.overflow = 'visible';
2013             if(Ext.isWebKit){
2014                 this.scroller.dom.style.position = 'static';
2015             }
2016         }else{
2017             this.el.setSize(csize.width, csize.height);
2018
2019             var hdHeight = this.mainHd.getHeight();
2020             var vh = csize.height - (hdHeight);
2021
2022             this.scroller.setSize(vw, vh);
2023             if(this.innerHd){
2024                 this.innerHd.style.width = (vw)+'px';
2025             }
2026         }
2027         if(this.forceFit){
2028             if(this.lastViewWidth != vw){
2029                 this.fitColumns(false, false);
2030                 this.lastViewWidth = vw;
2031             }
2032         }else {
2033             this.autoExpand();
2034             this.syncHeaderScroll();
2035         }
2036         this.onLayout(vw, vh);
2037     },
2038
2039     // template functions for subclasses and plugins
2040     // these functions include precalculated values
2041     onLayout : function(vw, vh){
2042         // do nothing
2043     },
2044
2045     onColumnWidthUpdated : function(col, w, tw){
2046         //template method
2047     },
2048
2049     onAllColumnWidthsUpdated : function(ws, tw){
2050         //template method
2051     },
2052
2053     onColumnHiddenUpdated : function(col, hidden, tw){
2054         // template method
2055     },
2056
2057     updateColumnText : function(col, text){
2058         // template method
2059     },
2060
2061     afterMove : function(colIndex){
2062         // template method
2063     },
2064
2065     /* ----------------------------------- Core Specific -------------------------------------------*/
2066     // private
2067     init : function(grid){
2068         this.grid = grid;
2069
2070         this.initTemplates();
2071         this.initData(grid.store, grid.colModel);
2072         this.initUI(grid);
2073     },
2074
2075     // private
2076     getColumnId : function(index){
2077       return this.cm.getColumnId(index);
2078     },
2079
2080     // private
2081     getOffsetWidth : function() {
2082         return (this.cm.getTotalWidth() + this.getScrollOffset()) + 'px';
2083     },
2084
2085     getScrollOffset: function(){
2086         return Ext.num(this.scrollOffset, Ext.getScrollBarWidth());
2087     },
2088
2089     /**
2090      * @private
2091      * Renders the header row using the 'header' template. Does not inject the HTML into the DOM, just
2092      * returns a string.
2093      * @return {String} Rendered header row
2094      */
2095     renderHeaders : function() {
2096         var cm   = this.cm,
2097             ts   = this.templates,
2098             ct   = ts.hcell,
2099             cb   = [],
2100             p    = {},
2101             len  = cm.getColumnCount(),
2102             last = len - 1;
2103
2104         for (var i = 0; i < len; i++) {
2105             p.id = cm.getColumnId(i);
2106             p.value = cm.getColumnHeader(i) || '';
2107             p.style = this.getColumnStyle(i, true);
2108             p.tooltip = this.getColumnTooltip(i);
2109             p.css = i === 0 ? 'x-grid3-cell-first ' : (i == last ? 'x-grid3-cell-last ' : '');
2110
2111             if (cm.config[i].align == 'right') {
2112                 p.istyle = 'padding-right:16px';
2113             } else {
2114                 delete p.istyle;
2115             }
2116             cb[cb.length] = ct.apply(p);
2117         }
2118         return ts.header.apply({cells: cb.join(''), tstyle:'width:'+this.getTotalWidth()+';'});
2119     },
2120
2121     // private
2122     getColumnTooltip : function(i){
2123         var tt = this.cm.getColumnTooltip(i);
2124         if(tt){
2125             if(Ext.QuickTips.isEnabled()){
2126                 return 'ext:qtip="'+tt+'"';
2127             }else{
2128                 return 'title="'+tt+'"';
2129             }
2130         }
2131         return '';
2132     },
2133
2134     // private
2135     beforeUpdate : function(){
2136         this.grid.stopEditing(true);
2137     },
2138
2139     // private
2140     updateHeaders : function(){
2141         this.innerHd.firstChild.innerHTML = this.renderHeaders();
2142         this.innerHd.firstChild.style.width = this.getOffsetWidth();
2143         this.innerHd.firstChild.firstChild.style.width = this.getTotalWidth();
2144     },
2145
2146     /**
2147      * Focuses the specified row.
2148      * @param {Number} row The row index
2149      */
2150     focusRow : function(row){
2151         this.focusCell(row, 0, false);
2152     },
2153
2154     /**
2155      * Focuses the specified cell.
2156      * @param {Number} row The row index
2157      * @param {Number} col The column index
2158      */
2159     focusCell : function(row, col, hscroll){
2160         this.syncFocusEl(this.ensureVisible(row, col, hscroll));
2161         if(Ext.isGecko){
2162             this.focusEl.focus();
2163         }else{
2164             this.focusEl.focus.defer(1, this.focusEl);
2165         }
2166     },
2167
2168     resolveCell : function(row, col, hscroll){
2169         if(!Ext.isNumber(row)){
2170             row = row.rowIndex;
2171         }
2172         if(!this.ds){
2173             return null;
2174         }
2175         if(row < 0 || row >= this.ds.getCount()){
2176             return null;
2177         }
2178         col = (col !== undefined ? col : 0);
2179
2180         var rowEl = this.getRow(row),
2181             cm = this.cm,
2182             colCount = cm.getColumnCount(),
2183             cellEl;
2184         if(!(hscroll === false && col === 0)){
2185             while(col < colCount && cm.isHidden(col)){
2186                 col++;
2187             }
2188             cellEl = this.getCell(row, col);
2189         }
2190
2191         return {row: rowEl, cell: cellEl};
2192     },
2193
2194     getResolvedXY : function(resolved){
2195         if(!resolved){
2196             return null;
2197         }
2198         var s = this.scroller.dom, c = resolved.cell, r = resolved.row;
2199         return c ? Ext.fly(c).getXY() : [this.el.getX(), Ext.fly(r).getY()];
2200     },
2201
2202     syncFocusEl : function(row, col, hscroll){
2203         var xy = row;
2204         if(!Ext.isArray(xy)){
2205             row = Math.min(row, Math.max(0, this.getRows().length-1));
2206             if (isNaN(row)) {
2207                 return;
2208             }
2209             xy = this.getResolvedXY(this.resolveCell(row, col, hscroll));
2210         }
2211         this.focusEl.setXY(xy||this.scroller.getXY());
2212     },
2213
2214     ensureVisible : function(row, col, hscroll){
2215         var resolved = this.resolveCell(row, col, hscroll);
2216         if(!resolved || !resolved.row){
2217             return;
2218         }
2219
2220         var rowEl = resolved.row,
2221             cellEl = resolved.cell,
2222             c = this.scroller.dom,
2223             ctop = 0,
2224             p = rowEl,
2225             stop = this.el.dom;
2226
2227         while(p && p != stop){
2228             ctop += p.offsetTop;
2229             p = p.offsetParent;
2230         }
2231
2232         ctop -= this.mainHd.dom.offsetHeight;
2233         stop = parseInt(c.scrollTop, 10);
2234
2235         var cbot = ctop + rowEl.offsetHeight,
2236             ch = c.clientHeight,
2237             sbot = stop + ch;
2238
2239
2240         if(ctop < stop){
2241           c.scrollTop = ctop;
2242         }else if(cbot > sbot){
2243             c.scrollTop = cbot-ch;
2244         }
2245
2246         if(hscroll !== false){
2247             var cleft = parseInt(cellEl.offsetLeft, 10);
2248             var cright = cleft + cellEl.offsetWidth;
2249
2250             var sleft = parseInt(c.scrollLeft, 10);
2251             var sright = sleft + c.clientWidth;
2252             if(cleft < sleft){
2253                 c.scrollLeft = cleft;
2254             }else if(cright > sright){
2255                 c.scrollLeft = cright-c.clientWidth;
2256             }
2257         }
2258         return this.getResolvedXY(resolved);
2259     },
2260
2261     // private
2262     insertRows : function(dm, firstRow, lastRow, isUpdate) {
2263         var last = dm.getCount() - 1;
2264         if( !isUpdate && firstRow === 0 && lastRow >= last) {
2265             this.fireEvent('beforerowsinserted', this, firstRow, lastRow);
2266                 this.refresh();
2267             this.fireEvent('rowsinserted', this, firstRow, lastRow);
2268         } else {
2269             if (!isUpdate) {
2270                 this.fireEvent('beforerowsinserted', this, firstRow, lastRow);
2271             }
2272             var html = this.renderRows(firstRow, lastRow),
2273                 before = this.getRow(firstRow);
2274             if (before) {
2275                 if(firstRow === 0){
2276                     Ext.fly(this.getRow(0)).removeClass(this.firstRowCls);
2277                 }
2278                 Ext.DomHelper.insertHtml('beforeBegin', before, html);
2279             } else {
2280                 var r = this.getRow(last - 1);
2281                 if(r){
2282                     Ext.fly(r).removeClass(this.lastRowCls);
2283                 }
2284                 Ext.DomHelper.insertHtml('beforeEnd', this.mainBody.dom, html);
2285             }
2286             if (!isUpdate) {
2287                 this.fireEvent('rowsinserted', this, firstRow, lastRow);
2288                 this.processRows(firstRow);
2289             } else if (firstRow === 0 || firstRow >= last) {
2290                 //ensure first/last row is kept after an update.
2291                 Ext.fly(this.getRow(firstRow)).addClass(firstRow === 0 ? this.firstRowCls : this.lastRowCls);
2292             }
2293         }
2294         this.syncFocusEl(firstRow);
2295     },
2296
2297     // private
2298     deleteRows : function(dm, firstRow, lastRow){
2299         if(dm.getRowCount()<1){
2300             this.refresh();
2301         }else{
2302             this.fireEvent('beforerowsdeleted', this, firstRow, lastRow);
2303
2304             this.removeRows(firstRow, lastRow);
2305
2306             this.processRows(firstRow);
2307             this.fireEvent('rowsdeleted', this, firstRow, lastRow);
2308         }
2309     },
2310
2311     // private
2312     getColumnStyle : function(col, isHeader){
2313         var style = !isHeader ? (this.cm.config[col].css || '') : '';
2314         style += 'width:'+this.getColumnWidth(col)+';';
2315         if(this.cm.isHidden(col)){
2316             style += 'display:none;';
2317         }
2318         var align = this.cm.config[col].align;
2319         if(align){
2320             style += 'text-align:'+align+';';
2321         }
2322         return style;
2323     },
2324
2325     // private
2326     getColumnWidth : function(col){
2327         var w = this.cm.getColumnWidth(col);
2328         if(Ext.isNumber(w)){
2329             return (Ext.isBorderBox || (Ext.isWebKit && !Ext.isSafari2) ? w : (w - this.borderWidth > 0 ? w - this.borderWidth : 0)) + 'px';
2330         }
2331         return w;
2332     },
2333
2334     // private
2335     getTotalWidth : function(){
2336         return this.cm.getTotalWidth()+'px';
2337     },
2338
2339     // private
2340     fitColumns : function(preventRefresh, onlyExpand, omitColumn){
2341         var cm = this.cm, i;
2342         var tw = cm.getTotalWidth(false);
2343         var aw = this.grid.getGridEl().getWidth(true)-this.getScrollOffset();
2344
2345         if(aw < 20){ // not initialized, so don't screw up the default widths
2346             return;
2347         }
2348         var extra = aw - tw;
2349
2350         if(extra === 0){
2351             return false;
2352         }
2353
2354         var vc = cm.getColumnCount(true);
2355         var ac = vc-(Ext.isNumber(omitColumn) ? 1 : 0);
2356         if(ac === 0){
2357             ac = 1;
2358             omitColumn = undefined;
2359         }
2360         var colCount = cm.getColumnCount();
2361         var cols = [];
2362         var extraCol = 0;
2363         var width = 0;
2364         var w;
2365         for (i = 0; i < colCount; i++){
2366             if(!cm.isHidden(i) && !cm.isFixed(i) && i !== omitColumn){
2367                 w = cm.getColumnWidth(i);
2368                 cols.push(i);
2369                 extraCol = i;
2370                 cols.push(w);
2371                 width += w;
2372             }
2373         }
2374         var frac = (aw - cm.getTotalWidth())/width;
2375         while (cols.length){
2376             w = cols.pop();
2377             i = cols.pop();
2378             cm.setColumnWidth(i, Math.max(this.grid.minColumnWidth, Math.floor(w + w*frac)), true);
2379         }
2380
2381         if((tw = cm.getTotalWidth(false)) > aw){
2382             var adjustCol = ac != vc ? omitColumn : extraCol;
2383              cm.setColumnWidth(adjustCol, Math.max(1,
2384                      cm.getColumnWidth(adjustCol)- (tw-aw)), true);
2385         }
2386
2387         if(preventRefresh !== true){
2388             this.updateAllColumnWidths();
2389         }
2390
2391
2392         return true;
2393     },
2394
2395     // private
2396     autoExpand : function(preventUpdate){
2397         var g = this.grid, cm = this.cm;
2398         if(!this.userResized && g.autoExpandColumn){
2399             var tw = cm.getTotalWidth(false);
2400             var aw = this.grid.getGridEl().getWidth(true)-this.getScrollOffset();
2401             if(tw != aw){
2402                 var ci = cm.getIndexById(g.autoExpandColumn);
2403                 var currentWidth = cm.getColumnWidth(ci);
2404                 var cw = Math.min(Math.max(((aw-tw)+currentWidth), g.autoExpandMin), g.autoExpandMax);
2405                 if(cw != currentWidth){
2406                     cm.setColumnWidth(ci, cw, true);
2407                     if(preventUpdate !== true){
2408                         this.updateColumnWidth(ci, cw);
2409                     }
2410                 }
2411             }
2412         }
2413     },
2414
2415     /**
2416      * @private
2417      * Returns an array of column configurations - one for each column
2418      * @return {Array} Array of column config objects. This includes the column name, renderer, id style and renderer
2419      */
2420     getColumnData : function(){
2421         // build a map for all the columns
2422         var cs       = [],
2423             cm       = this.cm,
2424             colCount = cm.getColumnCount();
2425
2426         for (var i = 0; i < colCount; i++) {
2427             var name = cm.getDataIndex(i);
2428
2429             cs[i] = {
2430                 name    : (!Ext.isDefined(name) ? this.ds.fields.get(i).name : name),
2431                 renderer: cm.getRenderer(i),
2432                 scope   : cm.getRendererScope(i),
2433                 id      : cm.getColumnId(i),
2434                 style   : this.getColumnStyle(i)
2435             };
2436         }
2437
2438         return cs;
2439     },
2440
2441     // private
2442     renderRows : function(startRow, endRow){
2443         // pull in all the crap needed to render rows
2444         var g = this.grid, cm = g.colModel, ds = g.store, stripe = g.stripeRows;
2445         var colCount = cm.getColumnCount();
2446
2447         if(ds.getCount() < 1){
2448             return '';
2449         }
2450
2451         var cs = this.getColumnData();
2452
2453         startRow = startRow || 0;
2454         endRow = !Ext.isDefined(endRow) ? ds.getCount()-1 : endRow;
2455
2456         // records to render
2457         var rs = ds.getRange(startRow, endRow);
2458
2459         return this.doRender(cs, rs, ds, startRow, colCount, stripe);
2460     },
2461
2462     // private
2463     renderBody : function(){
2464         var markup = this.renderRows() || '&#160;';
2465         return this.templates.body.apply({rows: markup});
2466     },
2467
2468     // private
2469     refreshRow : function(record){
2470         var ds = this.ds, index;
2471         if(Ext.isNumber(record)){
2472             index = record;
2473             record = ds.getAt(index);
2474             if(!record){
2475                 return;
2476             }
2477         }else{
2478             index = ds.indexOf(record);
2479             if(index < 0){
2480                 return;
2481             }
2482         }
2483         this.insertRows(ds, index, index, true);
2484         this.getRow(index).rowIndex = index;
2485         this.onRemove(ds, record, index+1, true);
2486         this.fireEvent('rowupdated', this, index, record);
2487     },
2488
2489     /**
2490      * Refreshs the grid UI
2491      * @param {Boolean} headersToo (optional) True to also refresh the headers
2492      */
2493     refresh : function(headersToo){
2494         this.fireEvent('beforerefresh', this);
2495         this.grid.stopEditing(true);
2496
2497         var result = this.renderBody();
2498         this.mainBody.update(result).setWidth(this.getTotalWidth());
2499         if(headersToo === true){
2500             this.updateHeaders();
2501             this.updateHeaderSortState();
2502         }
2503         this.processRows(0, true);
2504         this.layout();
2505         this.applyEmptyText();
2506         this.fireEvent('refresh', this);
2507     },
2508
2509     /**
2510      * @private
2511      * Displays the configured emptyText if there are currently no rows to display
2512      */
2513     applyEmptyText : function(){
2514         if(this.emptyText && !this.hasRows()){
2515             this.mainBody.update('<div class="x-grid-empty">' + this.emptyText + '</div>');
2516         }
2517     },
2518
2519     /**
2520      * @private
2521      * Adds sorting classes to the column headers based on the bound store's sortInfo. Fires the 'sortchange' event
2522      * if the sorting has changed since this function was last run.
2523      */
2524     updateHeaderSortState : function(){
2525         var state = this.ds.getSortState();
2526         if (!state) {
2527             return;
2528         }
2529
2530         if (!this.sortState || (this.sortState.field != state.field || this.sortState.direction != state.direction)) {
2531             this.grid.fireEvent('sortchange', this.grid, state);
2532         }
2533
2534         this.sortState = state;
2535
2536         var sortColumn = this.cm.findColumnIndex(state.field);
2537         if (sortColumn != -1){
2538             var sortDir = state.direction;
2539             this.updateSortIcon(sortColumn, sortDir);
2540         }
2541     },
2542
2543     /**
2544      * @private
2545      * Removes any sorting indicator classes from the column headers
2546      */
2547     clearHeaderSortState : function(){
2548         if (!this.sortState) {
2549             return;
2550         }
2551         this.grid.fireEvent('sortchange', this.grid, null);
2552         this.mainHd.select('td').removeClass(this.sortClasses);
2553         delete this.sortState;
2554     },
2555
2556     // private
2557     destroy : function(){
2558         if (this.scrollToTopTask && this.scrollToTopTask.cancel){
2559             this.scrollToTopTask.cancel();
2560         }
2561         if(this.colMenu){
2562             Ext.menu.MenuMgr.unregister(this.colMenu);
2563             this.colMenu.destroy();
2564             delete this.colMenu;
2565         }
2566         if(this.hmenu){
2567             Ext.menu.MenuMgr.unregister(this.hmenu);
2568             this.hmenu.destroy();
2569             delete this.hmenu;
2570         }
2571
2572         this.initData(null, null);
2573         this.purgeListeners();
2574         Ext.fly(this.innerHd).un("click", this.handleHdDown, this);
2575
2576         if(this.grid.enableColumnMove){
2577             Ext.destroy(
2578                 this.columnDrag.el,
2579                 this.columnDrag.proxy.ghost,
2580                 this.columnDrag.proxy.el,
2581                 this.columnDrop.el,
2582                 this.columnDrop.proxyTop,
2583                 this.columnDrop.proxyBottom,
2584                 this.columnDrag.dragData.ddel,
2585                 this.columnDrag.dragData.header
2586             );
2587             if (this.columnDrag.proxy.anim) {
2588                 Ext.destroy(this.columnDrag.proxy.anim);
2589             }
2590             delete this.columnDrag.proxy.ghost;
2591             delete this.columnDrag.dragData.ddel;
2592             delete this.columnDrag.dragData.header;
2593             this.columnDrag.destroy();
2594             delete Ext.dd.DDM.locationCache[this.columnDrag.id];
2595             delete this.columnDrag._domRef;
2596
2597             delete this.columnDrop.proxyTop;
2598             delete this.columnDrop.proxyBottom;
2599             this.columnDrop.destroy();
2600             delete Ext.dd.DDM.locationCache["gridHeader" + this.grid.getGridEl().id];
2601             delete this.columnDrop._domRef;
2602             delete Ext.dd.DDM.ids[this.columnDrop.ddGroup];
2603         }
2604
2605         if (this.splitZone){ // enableColumnResize
2606             this.splitZone.destroy();
2607             delete this.splitZone._domRef;
2608             delete Ext.dd.DDM.ids["gridSplitters" + this.grid.getGridEl().id];
2609         }
2610
2611         Ext.fly(this.innerHd).removeAllListeners();
2612         Ext.removeNode(this.innerHd);
2613         delete this.innerHd;
2614
2615         Ext.destroy(
2616             this.el,
2617             this.mainWrap,
2618             this.mainHd,
2619             this.scroller,
2620             this.mainBody,
2621             this.focusEl,
2622             this.resizeMarker,
2623             this.resizeProxy,
2624             this.activeHdBtn,
2625             this.dragZone,
2626             this.splitZone,
2627             this._flyweight
2628         );
2629
2630         delete this.grid.container;
2631
2632         if(this.dragZone){
2633             this.dragZone.destroy();
2634         }
2635
2636         Ext.dd.DDM.currentTarget = null;
2637         delete Ext.dd.DDM.locationCache[this.grid.getGridEl().id];
2638
2639         Ext.EventManager.removeResizeListener(this.onWindowResize, this);
2640     },
2641
2642     // private
2643     onDenyColumnHide : function(){
2644
2645     },
2646
2647     // private
2648     render : function(){
2649         if(this.autoFill){
2650             var ct = this.grid.ownerCt;
2651             if (ct && ct.getLayout()){
2652                 ct.on('afterlayout', function(){
2653                     this.fitColumns(true, true);
2654                     this.updateHeaders();
2655                 }, this, {single: true});
2656             }else{
2657                 this.fitColumns(true, true);
2658             }
2659         }else if(this.forceFit){
2660             this.fitColumns(true, false);
2661         }else if(this.grid.autoExpandColumn){
2662             this.autoExpand(true);
2663         }
2664
2665         this.renderUI();
2666     },
2667
2668     /* --------------------------------- Model Events and Handlers --------------------------------*/
2669     // private
2670     initData : function(ds, cm){
2671         if(this.ds){
2672             this.ds.un('load', this.onLoad, this);
2673             this.ds.un('datachanged', this.onDataChange, this);
2674             this.ds.un('add', this.onAdd, this);
2675             this.ds.un('remove', this.onRemove, this);
2676             this.ds.un('update', this.onUpdate, this);
2677             this.ds.un('clear', this.onClear, this);
2678             if(this.ds !== ds && this.ds.autoDestroy){
2679                 this.ds.destroy();
2680             }
2681         }
2682         if(ds){
2683             ds.on({
2684                 scope: this,
2685                 load: this.onLoad,
2686                 datachanged: this.onDataChange,
2687                 add: this.onAdd,
2688                 remove: this.onRemove,
2689                 update: this.onUpdate,
2690                 clear: this.onClear
2691             });
2692         }
2693         this.ds = ds;
2694
2695         if(this.cm){
2696             this.cm.un('configchange', this.onColConfigChange, this);
2697             this.cm.un('widthchange', this.onColWidthChange, this);
2698             this.cm.un('headerchange', this.onHeaderChange, this);
2699             this.cm.un('hiddenchange', this.onHiddenChange, this);
2700             this.cm.un('columnmoved', this.onColumnMove, this);
2701         }
2702         if(cm){
2703             delete this.lastViewWidth;
2704             cm.on({
2705                 scope: this,
2706                 configchange: this.onColConfigChange,
2707                 widthchange: this.onColWidthChange,
2708                 headerchange: this.onHeaderChange,
2709                 hiddenchange: this.onHiddenChange,
2710                 columnmoved: this.onColumnMove
2711             });
2712         }
2713         this.cm = cm;
2714     },
2715
2716     // private
2717     onDataChange : function(){
2718         this.refresh();
2719         this.updateHeaderSortState();
2720         this.syncFocusEl(0);
2721     },
2722
2723     // private
2724     onClear : function(){
2725         this.refresh();
2726         this.syncFocusEl(0);
2727     },
2728
2729     // private
2730     onUpdate : function(ds, record){
2731         this.refreshRow(record);
2732     },
2733
2734     // private
2735     onAdd : function(ds, records, index){
2736         this.insertRows(ds, index, index + (records.length-1));
2737     },
2738
2739     // private
2740     onRemove : function(ds, record, index, isUpdate){
2741         if(isUpdate !== true){
2742             this.fireEvent('beforerowremoved', this, index, record);
2743         }
2744         this.removeRow(index);
2745         if(isUpdate !== true){
2746             this.processRows(index);
2747             this.applyEmptyText();
2748             this.fireEvent('rowremoved', this, index, record);
2749         }
2750     },
2751
2752     // private
2753     onLoad : function(){
2754         if (Ext.isGecko){
2755             if (!this.scrollToTopTask) {
2756                 this.scrollToTopTask = new Ext.util.DelayedTask(this.scrollToTop, this);
2757             }
2758             this.scrollToTopTask.delay(1);
2759         }else{
2760             this.scrollToTop();
2761         }
2762     },
2763
2764     // private
2765     onColWidthChange : function(cm, col, width){
2766         this.updateColumnWidth(col, width);
2767     },
2768
2769     // private
2770     onHeaderChange : function(cm, col, text){
2771         this.updateHeaders();
2772     },
2773
2774     // private
2775     onHiddenChange : function(cm, col, hidden){
2776         this.updateColumnHidden(col, hidden);
2777     },
2778
2779     // private
2780     onColumnMove : function(cm, oldIndex, newIndex){
2781         this.indexMap = null;
2782         var s = this.getScrollState();
2783         this.refresh(true);
2784         this.restoreScroll(s);
2785         this.afterMove(newIndex);
2786         this.grid.fireEvent('columnmove', oldIndex, newIndex);
2787     },
2788
2789     // private
2790     onColConfigChange : function(){
2791         delete this.lastViewWidth;
2792         this.indexMap = null;
2793         this.refresh(true);
2794     },
2795
2796     /* -------------------- UI Events and Handlers ------------------------------ */
2797     // private
2798     initUI : function(grid){
2799         grid.on('headerclick', this.onHeaderClick, this);
2800     },
2801
2802     // private
2803     initEvents : function(){
2804     },
2805
2806     // private
2807     onHeaderClick : function(g, index){
2808         if(this.headersDisabled || !this.cm.isSortable(index)){
2809             return;
2810         }
2811         g.stopEditing(true);
2812         g.store.sort(this.cm.getDataIndex(index));
2813     },
2814
2815     // private
2816     onRowOver : function(e, t){
2817         var row;
2818         if((row = this.findRowIndex(t)) !== false){
2819             this.addRowClass(row, 'x-grid3-row-over');
2820         }
2821     },
2822
2823     // private
2824     onRowOut : function(e, t){
2825         var row;
2826         if((row = this.findRowIndex(t)) !== false && !e.within(this.getRow(row), true)){
2827             this.removeRowClass(row, 'x-grid3-row-over');
2828         }
2829     },
2830
2831     // private
2832     handleWheel : function(e){
2833         e.stopPropagation();
2834     },
2835
2836     // private
2837     onRowSelect : function(row){
2838         this.addRowClass(row, this.selectedRowClass);
2839     },
2840
2841     // private
2842     onRowDeselect : function(row){
2843         this.removeRowClass(row, this.selectedRowClass);
2844     },
2845
2846     // private
2847     onCellSelect : function(row, col){
2848         var cell = this.getCell(row, col);
2849         if(cell){
2850             this.fly(cell).addClass('x-grid3-cell-selected');
2851         }
2852     },
2853
2854     // private
2855     onCellDeselect : function(row, col){
2856         var cell = this.getCell(row, col);
2857         if(cell){
2858             this.fly(cell).removeClass('x-grid3-cell-selected');
2859         }
2860     },
2861
2862     // private
2863     onColumnSplitterMoved : function(i, w){
2864         this.userResized = true;
2865         var cm = this.grid.colModel;
2866         cm.setColumnWidth(i, w, true);
2867
2868         if(this.forceFit){
2869             this.fitColumns(true, false, i);
2870             this.updateAllColumnWidths();
2871         }else{
2872             this.updateColumnWidth(i, w);
2873             this.syncHeaderScroll();
2874         }
2875
2876         this.grid.fireEvent('columnresize', i, w);
2877     },
2878
2879     // private
2880     handleHdMenuClick : function(item){
2881         var index = this.hdCtxIndex,
2882             cm = this.cm,
2883             ds = this.ds,
2884             id = item.getItemId();
2885         switch(id){
2886             case 'asc':
2887                 ds.sort(cm.getDataIndex(index), 'ASC');
2888                 break;
2889             case 'desc':
2890                 ds.sort(cm.getDataIndex(index), 'DESC');
2891                 break;
2892             default:
2893                 index = cm.getIndexById(id.substr(4));
2894                 if(index != -1){
2895                     if(item.checked && cm.getColumnsBy(this.isHideableColumn, this).length <= 1){
2896                         this.onDenyColumnHide();
2897                         return false;
2898                     }
2899                     cm.setHidden(index, item.checked);
2900                 }
2901         }
2902         return true;
2903     },
2904
2905     // private
2906     isHideableColumn : function(c){
2907         return !c.hidden;
2908     },
2909
2910     // private
2911     beforeColMenuShow : function(){
2912         var cm = this.cm,  colCount = cm.getColumnCount();
2913         this.colMenu.removeAll();
2914         for(var i = 0; i < colCount; i++){
2915             if(cm.config[i].hideable !== false){
2916                 this.colMenu.add(new Ext.menu.CheckItem({
2917                     itemId: 'col-'+cm.getColumnId(i),
2918                     text: cm.getColumnHeader(i),
2919                     checked: !cm.isHidden(i),
2920                     hideOnClick:false,
2921                     disabled: cm.config[i].hideable === false
2922                 }));
2923             }
2924         }
2925     },
2926
2927     // private
2928     handleHdDown : function(e, t){
2929         if(Ext.fly(t).hasClass('x-grid3-hd-btn')){
2930             e.stopEvent();
2931             var hd = this.findHeaderCell(t);
2932             Ext.fly(hd).addClass('x-grid3-hd-menu-open');
2933             var index = this.getCellIndex(hd);
2934             this.hdCtxIndex = index;
2935             var ms = this.hmenu.items, cm = this.cm;
2936             ms.get('asc').setDisabled(!cm.isSortable(index));
2937             ms.get('desc').setDisabled(!cm.isSortable(index));
2938             this.hmenu.on('hide', function(){
2939                 Ext.fly(hd).removeClass('x-grid3-hd-menu-open');
2940             }, this, {single:true});
2941             this.hmenu.show(t, 'tl-bl?');
2942         }
2943     },
2944
2945     // private
2946     handleHdOver : function(e, t){
2947         var hd = this.findHeaderCell(t);
2948         if(hd && !this.headersDisabled){
2949             this.activeHdRef = t;
2950             this.activeHdIndex = this.getCellIndex(hd);
2951             var fly = this.fly(hd);
2952             this.activeHdRegion = fly.getRegion();
2953             if(!this.cm.isMenuDisabled(this.activeHdIndex)){
2954                 fly.addClass('x-grid3-hd-over');
2955                 this.activeHdBtn = fly.child('.x-grid3-hd-btn');
2956                 if(this.activeHdBtn){
2957                     this.activeHdBtn.dom.style.height = (hd.firstChild.offsetHeight-1)+'px';
2958                 }
2959             }
2960         }
2961     },
2962
2963     // private
2964     handleHdMove : function(e, t){
2965         var hd = this.findHeaderCell(this.activeHdRef);
2966         if(hd && !this.headersDisabled){
2967             var hw = this.splitHandleWidth || 5,
2968                 r = this.activeHdRegion,
2969                 x = e.getPageX(),
2970                 ss = hd.style,
2971                 cur = '';
2972             if(this.grid.enableColumnResize !== false){
2973                 if(x - r.left <= hw && this.cm.isResizable(this.activeHdIndex-1)){
2974                     cur = Ext.isAir ? 'move' : Ext.isWebKit ? 'e-resize' : 'col-resize'; // col-resize not always supported
2975                 }else if(r.right - x <= (!this.activeHdBtn ? hw : 2) && this.cm.isResizable(this.activeHdIndex)){
2976                     cur = Ext.isAir ? 'move' : Ext.isWebKit ? 'w-resize' : 'col-resize';
2977                 }
2978             }
2979             ss.cursor = cur;
2980         }
2981     },
2982
2983     // private
2984     handleHdOut : function(e, t){
2985         var hd = this.findHeaderCell(t);
2986         if(hd && (!Ext.isIE || !e.within(hd, true))){
2987             this.activeHdRef = null;
2988             this.fly(hd).removeClass('x-grid3-hd-over');
2989             hd.style.cursor = '';
2990         }
2991     },
2992
2993     // private
2994     hasRows : function(){
2995         var fc = this.mainBody.dom.firstChild;
2996         return fc && fc.nodeType == 1 && fc.className != 'x-grid-empty';
2997     },
2998
2999     // back compat
3000     bind : function(d, c){
3001         this.initData(d, c);
3002     }
3003 });
3004
3005
3006 // private
3007 // This is a support class used internally by the Grid components
3008 Ext.grid.GridView.SplitDragZone = Ext.extend(Ext.dd.DDProxy, {
3009     
3010     constructor: function(grid, hd){
3011         this.grid = grid;
3012         this.view = grid.getView();
3013         this.marker = this.view.resizeMarker;
3014         this.proxy = this.view.resizeProxy;
3015         Ext.grid.GridView.SplitDragZone.superclass.constructor.call(this, hd,
3016             'gridSplitters' + this.grid.getGridEl().id, {
3017             dragElId : Ext.id(this.proxy.dom), resizeFrame:false
3018         });
3019         this.scroll = false;
3020         this.hw = this.view.splitHandleWidth || 5;
3021     },
3022
3023     b4StartDrag : function(x, y){
3024         this.dragHeadersDisabled = this.view.headersDisabled;
3025         this.view.headersDisabled = true;
3026         var h = this.view.mainWrap.getHeight();
3027         this.marker.setHeight(h);
3028         this.marker.show();
3029         this.marker.alignTo(this.view.getHeaderCell(this.cellIndex), 'tl-tl', [-2, 0]);
3030         this.proxy.setHeight(h);
3031         var w = this.cm.getColumnWidth(this.cellIndex),
3032             minw = Math.max(w-this.grid.minColumnWidth, 0);
3033         this.resetConstraints();
3034         this.setXConstraint(minw, 1000);
3035         this.setYConstraint(0, 0);
3036         this.minX = x - minw;
3037         this.maxX = x + 1000;
3038         this.startPos = x;
3039         Ext.dd.DDProxy.prototype.b4StartDrag.call(this, x, y);
3040     },
3041
3042     allowHeaderDrag : function(e){
3043         return true;
3044     },
3045
3046     handleMouseDown : function(e){
3047         var t = this.view.findHeaderCell(e.getTarget());
3048         if(t && this.allowHeaderDrag(e)){
3049             var xy = this.view.fly(t).getXY(), 
3050                 x = xy[0], 
3051                 y = xy[1],
3052                 exy = e.getXY(), ex = exy[0],
3053                 w = t.offsetWidth, adjust = false;
3054                 
3055             if((ex - x) <= this.hw){
3056                 adjust = -1;
3057             }else if((x+w) - ex <= this.hw){
3058                 adjust = 0;
3059             }
3060             if(adjust !== false){
3061                 this.cm = this.grid.colModel;
3062                 var ci = this.view.getCellIndex(t);
3063                 if(adjust == -1){
3064                   if (ci + adjust < 0) {
3065                     return;
3066                   }
3067                     while(this.cm.isHidden(ci+adjust)){
3068                         --adjust;
3069                         if(ci+adjust < 0){
3070                             return;
3071                         }
3072                     }
3073                 }
3074                 this.cellIndex = ci+adjust;
3075                 this.split = t.dom;
3076                 if(this.cm.isResizable(this.cellIndex) && !this.cm.isFixed(this.cellIndex)){
3077                     Ext.grid.GridView.SplitDragZone.superclass.handleMouseDown.apply(this, arguments);
3078                 }
3079             }else if(this.view.columnDrag){
3080                 this.view.columnDrag.callHandleMouseDown(e);
3081             }
3082         }
3083     },
3084
3085     endDrag : function(e){
3086         this.marker.hide();
3087         var v = this.view,
3088             endX = Math.max(this.minX, e.getPageX()),
3089             diff = endX - this.startPos,
3090             disabled = this.dragHeadersDisabled;
3091             
3092         v.onColumnSplitterMoved(this.cellIndex, this.cm.getColumnWidth(this.cellIndex)+diff);
3093         setTimeout(function(){
3094             v.headersDisabled = disabled;
3095         }, 50);
3096     },
3097
3098     autoOffset : function(){
3099         this.setDelta(0,0);
3100     }
3101 });
3102 // private
3103 // This is a support class used internally by the Grid components
3104 Ext.grid.HeaderDragZone = Ext.extend(Ext.dd.DragZone, {
3105     maxDragWidth: 120,
3106     
3107     constructor : function(grid, hd, hd2){
3108         this.grid = grid;
3109         this.view = grid.getView();
3110         this.ddGroup = "gridHeader" + this.grid.getGridEl().id;
3111         Ext.grid.HeaderDragZone.superclass.constructor.call(this, hd);
3112         if(hd2){
3113             this.setHandleElId(Ext.id(hd));
3114             this.setOuterHandleElId(Ext.id(hd2));
3115         }
3116         this.scroll = false;
3117     },
3118     
3119     getDragData : function(e){
3120         var t = Ext.lib.Event.getTarget(e),
3121             h = this.view.findHeaderCell(t);
3122         if(h){
3123             return {ddel: h.firstChild, header:h};
3124         }
3125         return false;
3126     },
3127
3128     onInitDrag : function(e){
3129         // keep the value here so we can restore it;
3130         this.dragHeadersDisabled = this.view.headersDisabled;
3131         this.view.headersDisabled = true;
3132         var clone = this.dragData.ddel.cloneNode(true);
3133         clone.id = Ext.id();
3134         clone.style.width = Math.min(this.dragData.header.offsetWidth,this.maxDragWidth) + "px";
3135         this.proxy.update(clone);
3136         return true;
3137     },
3138
3139     afterValidDrop : function(){
3140         this.completeDrop();
3141     },
3142
3143     afterInvalidDrop : function(){
3144         this.completeDrop();
3145     },
3146     
3147     completeDrop: function(){
3148         var v = this.view,
3149             disabled = this.dragHeadersDisabled;
3150         setTimeout(function(){
3151             v.headersDisabled = disabled;
3152         }, 50);
3153     }
3154 });
3155
3156 // private
3157 // This is a support class used internally by the Grid components
3158 Ext.grid.HeaderDropZone = Ext.extend(Ext.dd.DropZone, {
3159     proxyOffsets : [-4, -9],
3160     fly: Ext.Element.fly,
3161     
3162     constructor : function(grid, hd, hd2){
3163         this.grid = grid;
3164         this.view = grid.getView();
3165         // split the proxies so they don't interfere with mouse events
3166         this.proxyTop = Ext.DomHelper.append(document.body, {
3167             cls:"col-move-top", html:"&#160;"
3168         }, true);
3169         this.proxyBottom = Ext.DomHelper.append(document.body, {
3170             cls:"col-move-bottom", html:"&#160;"
3171         }, true);
3172         this.proxyTop.hide = this.proxyBottom.hide = function(){
3173             this.setLeftTop(-100,-100);
3174             this.setStyle("visibility", "hidden");
3175         };
3176         this.ddGroup = "gridHeader" + this.grid.getGridEl().id;
3177         // temporarily disabled
3178         //Ext.dd.ScrollManager.register(this.view.scroller.dom);
3179         Ext.grid.HeaderDropZone.superclass.constructor.call(this, grid.getGridEl().dom);
3180     },
3181
3182     getTargetFromEvent : function(e){
3183         var t = Ext.lib.Event.getTarget(e),
3184             cindex = this.view.findCellIndex(t);
3185         if(cindex !== false){
3186             return this.view.getHeaderCell(cindex);
3187         }
3188     },
3189
3190     nextVisible : function(h){
3191         var v = this.view, cm = this.grid.colModel;
3192         h = h.nextSibling;
3193         while(h){
3194             if(!cm.isHidden(v.getCellIndex(h))){
3195                 return h;
3196             }
3197             h = h.nextSibling;
3198         }
3199         return null;
3200     },
3201
3202     prevVisible : function(h){
3203         var v = this.view, cm = this.grid.colModel;
3204         h = h.prevSibling;
3205         while(h){
3206             if(!cm.isHidden(v.getCellIndex(h))){
3207                 return h;
3208             }
3209             h = h.prevSibling;
3210         }
3211         return null;
3212     },
3213
3214     positionIndicator : function(h, n, e){
3215         var x = Ext.lib.Event.getPageX(e),
3216             r = Ext.lib.Dom.getRegion(n.firstChild),
3217             px, 
3218             pt, 
3219             py = r.top + this.proxyOffsets[1];
3220         if((r.right - x) <= (r.right-r.left)/2){
3221             px = r.right+this.view.borderWidth;
3222             pt = "after";
3223         }else{
3224             px = r.left;
3225             pt = "before";
3226         }
3227
3228         if(this.grid.colModel.isFixed(this.view.getCellIndex(n))){
3229             return false;
3230         }
3231
3232         px +=  this.proxyOffsets[0];
3233         this.proxyTop.setLeftTop(px, py);
3234         this.proxyTop.show();
3235         if(!this.bottomOffset){
3236             this.bottomOffset = this.view.mainHd.getHeight();
3237         }
3238         this.proxyBottom.setLeftTop(px, py+this.proxyTop.dom.offsetHeight+this.bottomOffset);
3239         this.proxyBottom.show();
3240         return pt;
3241     },
3242
3243     onNodeEnter : function(n, dd, e, data){
3244         if(data.header != n){
3245             this.positionIndicator(data.header, n, e);
3246         }
3247     },
3248
3249     onNodeOver : function(n, dd, e, data){
3250         var result = false;
3251         if(data.header != n){
3252             result = this.positionIndicator(data.header, n, e);
3253         }
3254         if(!result){
3255             this.proxyTop.hide();
3256             this.proxyBottom.hide();
3257         }
3258         return result ? this.dropAllowed : this.dropNotAllowed;
3259     },
3260
3261     onNodeOut : function(n, dd, e, data){
3262         this.proxyTop.hide();
3263         this.proxyBottom.hide();
3264     },
3265
3266     onNodeDrop : function(n, dd, e, data){
3267         var h = data.header;
3268         if(h != n){
3269             var cm = this.grid.colModel,
3270                 x = Ext.lib.Event.getPageX(e),
3271                 r = Ext.lib.Dom.getRegion(n.firstChild),
3272                 pt = (r.right - x) <= ((r.right-r.left)/2) ? "after" : "before",
3273                 oldIndex = this.view.getCellIndex(h),
3274                 newIndex = this.view.getCellIndex(n);
3275             if(pt == "after"){
3276                 newIndex++;
3277             }
3278             if(oldIndex < newIndex){
3279                 newIndex--;
3280             }
3281             cm.moveColumn(oldIndex, newIndex);
3282             return true;
3283         }
3284         return false;
3285     }
3286 });
3287
3288 Ext.grid.GridView.ColumnDragZone = Ext.extend(Ext.grid.HeaderDragZone, {
3289     
3290     constructor : function(grid, hd){
3291         Ext.grid.GridView.ColumnDragZone.superclass.constructor.call(this, grid, hd, null);
3292         this.proxy.el.addClass('x-grid3-col-dd');
3293     },
3294     
3295     handleMouseDown : function(e){
3296     },
3297
3298     callHandleMouseDown : function(e){
3299         Ext.grid.GridView.ColumnDragZone.superclass.handleMouseDown.call(this, e);
3300     }
3301 });// private
3302 // This is a support class used internally by the Grid components
3303 Ext.grid.SplitDragZone = Ext.extend(Ext.dd.DDProxy, {
3304     fly: Ext.Element.fly,
3305     
3306     constructor : function(grid, hd, hd2){
3307         this.grid = grid;
3308         this.view = grid.getView();
3309         this.proxy = this.view.resizeProxy;
3310         Ext.grid.SplitDragZone.superclass.constructor.call(this, hd,
3311             "gridSplitters" + this.grid.getGridEl().id, {
3312             dragElId : Ext.id(this.proxy.dom), resizeFrame:false
3313         });
3314         this.setHandleElId(Ext.id(hd));
3315         this.setOuterHandleElId(Ext.id(hd2));
3316         this.scroll = false;
3317     },
3318
3319     b4StartDrag : function(x, y){
3320         this.view.headersDisabled = true;
3321         this.proxy.setHeight(this.view.mainWrap.getHeight());
3322         var w = this.cm.getColumnWidth(this.cellIndex);
3323         var minw = Math.max(w-this.grid.minColumnWidth, 0);
3324         this.resetConstraints();
3325         this.setXConstraint(minw, 1000);
3326         this.setYConstraint(0, 0);
3327         this.minX = x - minw;
3328         this.maxX = x + 1000;
3329         this.startPos = x;
3330         Ext.dd.DDProxy.prototype.b4StartDrag.call(this, x, y);
3331     },
3332
3333
3334     handleMouseDown : function(e){
3335         var ev = Ext.EventObject.setEvent(e);
3336         var t = this.fly(ev.getTarget());
3337         if(t.hasClass("x-grid-split")){
3338             this.cellIndex = this.view.getCellIndex(t.dom);
3339             this.split = t.dom;
3340             this.cm = this.grid.colModel;
3341             if(this.cm.isResizable(this.cellIndex) && !this.cm.isFixed(this.cellIndex)){
3342                 Ext.grid.SplitDragZone.superclass.handleMouseDown.apply(this, arguments);
3343             }
3344         }
3345     },
3346
3347     endDrag : function(e){
3348         this.view.headersDisabled = false;
3349         var endX = Math.max(this.minX, Ext.lib.Event.getPageX(e));
3350         var diff = endX - this.startPos;
3351         this.view.onColumnSplitterMoved(this.cellIndex, this.cm.getColumnWidth(this.cellIndex)+diff);
3352     },
3353
3354     autoOffset : function(){
3355         this.setDelta(0,0);
3356     }
3357 });/**
3358  * @class Ext.grid.GridDragZone
3359  * @extends Ext.dd.DragZone
3360  * <p>A customized implementation of a {@link Ext.dd.DragZone DragZone} which provides default implementations of two of the
3361  * template methods of DragZone to enable dragging of the selected rows of a GridPanel.</p>
3362  * <p>A cooperating {@link Ext.dd.DropZone DropZone} must be created who's template method implementations of
3363  * {@link Ext.dd.DropZone#onNodeEnter onNodeEnter}, {@link Ext.dd.DropZone#onNodeOver onNodeOver},
3364  * {@link Ext.dd.DropZone#onNodeOut onNodeOut} and {@link Ext.dd.DropZone#onNodeDrop onNodeDrop}</p> are able
3365  * to process the {@link #getDragData data} which is provided.
3366  */
3367 Ext.grid.GridDragZone = function(grid, config){
3368     this.view = grid.getView();
3369     Ext.grid.GridDragZone.superclass.constructor.call(this, this.view.mainBody.dom, config);
3370     this.scroll = false;
3371     this.grid = grid;
3372     this.ddel = document.createElement('div');
3373     this.ddel.className = 'x-grid-dd-wrap';
3374 };
3375
3376 Ext.extend(Ext.grid.GridDragZone, Ext.dd.DragZone, {
3377     ddGroup : "GridDD",
3378
3379     /**
3380      * <p>The provided implementation of the getDragData method which collects the data to be dragged from the GridPanel on mousedown.</p>
3381      * <p>This data is available for processing in the {@link Ext.dd.DropZone#onNodeEnter onNodeEnter}, {@link Ext.dd.DropZone#onNodeOver onNodeOver},
3382      * {@link Ext.dd.DropZone#onNodeOut onNodeOut} and {@link Ext.dd.DropZone#onNodeDrop onNodeDrop} methods of a cooperating {@link Ext.dd.DropZone DropZone}.</p>
3383      * <p>The data object contains the following properties:<ul>
3384      * <li><b>grid</b> : Ext.Grid.GridPanel<div class="sub-desc">The GridPanel from which the data is being dragged.</div></li>
3385      * <li><b>ddel</b> : htmlElement<div class="sub-desc">An htmlElement which provides the "picture" of the data being dragged.</div></li>
3386      * <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>
3387      * <li><b>selections</b> : Array<div class="sub-desc">An Array of the selected Records which are being dragged from the GridPanel.</div></li>
3388      * </ul></p>
3389      */
3390     getDragData : function(e){
3391         var t = Ext.lib.Event.getTarget(e);
3392         var rowIndex = this.view.findRowIndex(t);
3393         if(rowIndex !== false){
3394             var sm = this.grid.selModel;
3395             if(!sm.isSelected(rowIndex) || e.hasModifier()){
3396                 sm.handleMouseDown(this.grid, rowIndex, e);
3397             }
3398             return {grid: this.grid, ddel: this.ddel, rowIndex: rowIndex, selections:sm.getSelections()};
3399         }
3400         return false;
3401     },
3402
3403     /**
3404      * <p>The provided implementation of the onInitDrag method. Sets the <tt>innerHTML</tt> of the drag proxy which provides the "picture"
3405      * of the data being dragged.</p>
3406      * <p>The <tt>innerHTML</tt> data is found by calling the owning GridPanel's {@link Ext.grid.GridPanel#getDragDropText getDragDropText}.</p>
3407      */
3408     onInitDrag : function(e){
3409         var data = this.dragData;
3410         this.ddel.innerHTML = this.grid.getDragDropText();
3411         this.proxy.update(this.ddel);
3412         // fire start drag?
3413     },
3414
3415     /**
3416      * An empty immplementation. Implement this to provide behaviour after a repair of an invalid drop. An implementation might highlight
3417      * the selected rows to show that they have not been dragged.
3418      */
3419     afterRepair : function(){
3420         this.dragging = false;
3421     },
3422
3423     /**
3424      * <p>An empty implementation. Implement this to provide coordinates for the drag proxy to slide back to after an invalid drop.</p>
3425      * <p>Called before a repair of an invalid drop to get the XY to animate to.</p>
3426      * @param {EventObject} e The mouse up event
3427      * @return {Array} The xy location (e.g. [100, 200])
3428      */
3429     getRepairXY : function(e, data){
3430         return false;
3431     },
3432
3433     onEndDrag : function(data, e){
3434         // fire end drag?
3435     },
3436
3437     onValidDrop : function(dd, e, id){
3438         // fire drag drop?
3439         this.hideProxy();
3440     },
3441
3442     beforeInvalidDrop : function(e, id){
3443
3444     }
3445 });
3446 /**
3447  * @class Ext.grid.ColumnModel
3448  * @extends Ext.util.Observable
3449  * <p>After the data has been read into the client side cache (<b>{@link Ext.data.Store Store}</b>),
3450  * the ColumnModel is used to configure how and what parts of that data will be displayed in the
3451  * vertical slices (columns) of the grid. The Ext.grid.ColumnModel Class is the default implementation
3452  * of a ColumnModel used by implentations of {@link Ext.grid.GridPanel GridPanel}.</p>
3453  * <p>Data is mapped into the store's records and then indexed into the ColumnModel using the
3454  * <tt>{@link Ext.grid.Column#dataIndex dataIndex}</tt>:</p>
3455  * <pre><code>
3456 {data source} == mapping ==> {data store} == <b><tt>{@link Ext.grid.Column#dataIndex dataIndex}</tt></b> ==> {ColumnModel}
3457  * </code></pre>
3458  * <p>Each {@link Ext.grid.Column Column} in the grid's ColumnModel is configured with a
3459  * <tt>{@link Ext.grid.Column#dataIndex dataIndex}</tt> to specify how the data within
3460  * each record in the store is indexed into the ColumnModel.</p>
3461  * <p>There are two ways to initialize the ColumnModel class:</p>
3462  * <p><u>Initialization Method 1: an Array</u></p>
3463 <pre><code>
3464  var colModel = new Ext.grid.ColumnModel([
3465     { header: "Ticker", width: 60, sortable: true},
3466     { header: "Company Name", width: 150, sortable: true, id: 'company'},
3467     { header: "Market Cap.", width: 100, sortable: true},
3468     { header: "$ Sales", width: 100, sortable: true, renderer: money},
3469     { header: "Employees", width: 100, sortable: true, resizable: false}
3470  ]);
3471  </code></pre>
3472  * <p>The ColumnModel may be initialized with an Array of {@link Ext.grid.Column} column configuration
3473  * objects to define the initial layout / display of the columns in the Grid. The order of each
3474  * {@link Ext.grid.Column} column configuration object within the specified Array defines the initial
3475  * order of the column display.  A Column's display may be initially hidden using the
3476  * <tt>{@link Ext.grid.Column#hidden hidden}</tt></b> config property (and then shown using the column
3477  * header menu).  Fields that are not included in the ColumnModel will not be displayable at all.</p>
3478  * <p>How each column in the grid correlates (maps) to the {@link Ext.data.Record} field in the
3479  * {@link Ext.data.Store Store} the column draws its data from is configured through the
3480  * <b><tt>{@link Ext.grid.Column#dataIndex dataIndex}</tt></b>.  If the
3481  * <b><tt>{@link Ext.grid.Column#dataIndex dataIndex}</tt></b> is not explicitly defined (as shown in the
3482  * example above) it will use the column configuration's index in the Array as the index.</p>
3483  * <p>See <b><tt>{@link Ext.grid.Column}</tt></b> for additional configuration options for each column.</p>
3484  * <p><u>Initialization Method 2: an Object</u></p>
3485  * <p>In order to use configuration options from <tt>Ext.grid.ColumnModel</tt>, an Object may be used to
3486  * initialize the ColumnModel.  The column configuration Array will be specified in the <tt><b>{@link #columns}</b></tt>
3487  * config property. The <tt><b>{@link #defaults}</b></tt> config property can be used to apply defaults
3488  * for all columns, e.g.:</p><pre><code>
3489  var colModel = new Ext.grid.ColumnModel({
3490     columns: [
3491         { header: "Ticker", width: 60, menuDisabled: false},
3492         { header: "Company Name", width: 150, id: 'company'},
3493         { header: "Market Cap."},
3494         { header: "$ Sales", renderer: money},
3495         { header: "Employees", resizable: false}
3496     ],
3497     defaults: {
3498         sortable: true,
3499         menuDisabled: true,
3500         width: 100
3501     },
3502     listeners: {
3503         {@link #hiddenchange}: function(cm, colIndex, hidden) {
3504             saveConfig(colIndex, hidden);
3505         }
3506     }
3507 });
3508  </code></pre>
3509  * <p>In both examples above, the ability to apply a CSS class to all cells in a column (including the
3510  * header) is demonstrated through the use of the <b><tt>{@link Ext.grid.Column#id id}</tt></b> config
3511  * option. This column could be styled by including the following css:</p><pre><code>
3512  //add this css *after* the core css is loaded
3513 .x-grid3-td-company {
3514     color: red; // entire column will have red font
3515 }
3516 // modify the header row only, adding an icon to the column header
3517 .x-grid3-hd-company {
3518     background: transparent
3519         url(../../resources/images/icons/silk/building.png)
3520         no-repeat 3px 3px ! important;
3521         padding-left:20px;
3522 }
3523  </code></pre>
3524  * Note that the "Company Name" column could be specified as the
3525  * <b><tt>{@link Ext.grid.GridPanel}.{@link Ext.grid.GridPanel#autoExpandColumn autoExpandColumn}</tt></b>.
3526  * @constructor
3527  * @param {Mixed} config Specify either an Array of {@link Ext.grid.Column} configuration objects or specify
3528  * a configuration Object (see introductory section discussion utilizing Initialization Method 2 above).
3529  */
3530 Ext.grid.ColumnModel = Ext.extend(Ext.util.Observable, {
3531     /**
3532      * @cfg {Number} defaultWidth (optional) The width of columns which have no <tt>{@link #width}</tt>
3533      * specified (defaults to <tt>100</tt>).  This property shall preferably be configured through the
3534      * <tt><b>{@link #defaults}</b></tt> config property.
3535      */
3536     defaultWidth: 100,
3537     /**
3538      * @cfg {Boolean} defaultSortable (optional) Default sortable of columns which have no
3539      * sortable specified (defaults to <tt>false</tt>).  This property shall preferably be configured
3540      * through the <tt><b>{@link #defaults}</b></tt> config property.
3541      */
3542     defaultSortable: false,
3543     /**
3544      * @cfg {Array} columns An Array of object literals.  The config options defined by
3545      * <b>{@link Ext.grid.Column}</b> are the options which may appear in the object literal for each
3546      * individual column definition.
3547      */
3548     /**
3549      * @cfg {Object} defaults Object literal which will be used to apply {@link Ext.grid.Column}
3550      * configuration options to all <tt><b>{@link #columns}</b></tt>.  Configuration options specified with
3551      * individual {@link Ext.grid.Column column} configs will supersede these <tt><b>{@link #defaults}</b></tt>.
3552      */
3553
3554     constructor : function(config){
3555         /**
3556              * An Array of {@link Ext.grid.Column Column definition} objects representing the configuration
3557              * of this ColumnModel.  See {@link Ext.grid.Column} for the configuration properties that may
3558              * be specified.
3559              * @property config
3560              * @type Array
3561              */
3562             if(config.columns){
3563                 Ext.apply(this, config);
3564                 this.setConfig(config.columns, true);
3565             }else{
3566                 this.setConfig(config, true);
3567             }
3568             this.addEvents(
3569                 /**
3570                  * @event widthchange
3571                  * Fires when the width of a column is programmaticially changed using
3572                  * <code>{@link #setColumnWidth}</code>.
3573                  * Note internal resizing suppresses the event from firing. See also
3574                  * {@link Ext.grid.GridPanel}.<code>{@link #columnresize}</code>.
3575                  * @param {ColumnModel} this
3576                  * @param {Number} columnIndex The column index
3577                  * @param {Number} newWidth The new width
3578                  */
3579                 "widthchange",
3580                 /**
3581                  * @event headerchange
3582                  * Fires when the text of a header changes.
3583                  * @param {ColumnModel} this
3584                  * @param {Number} columnIndex The column index
3585                  * @param {String} newText The new header text
3586                  */
3587                 "headerchange",
3588                 /**
3589                  * @event hiddenchange
3590                  * Fires when a column is hidden or "unhidden".
3591                  * @param {ColumnModel} this
3592                  * @param {Number} columnIndex The column index
3593                  * @param {Boolean} hidden true if hidden, false otherwise
3594                  */
3595                 "hiddenchange",
3596                 /**
3597                  * @event columnmoved
3598                  * Fires when a column is moved.
3599                  * @param {ColumnModel} this
3600                  * @param {Number} oldIndex
3601                  * @param {Number} newIndex
3602                  */
3603                 "columnmoved",
3604                 /**
3605                  * @event configchange
3606                  * Fires when the configuration is changed
3607                  * @param {ColumnModel} this
3608                  */
3609                 "configchange"
3610             );
3611             Ext.grid.ColumnModel.superclass.constructor.call(this);
3612     },
3613
3614     /**
3615      * Returns the id of the column at the specified index.
3616      * @param {Number} index The column index
3617      * @return {String} the id
3618      */
3619     getColumnId : function(index){
3620         return this.config[index].id;
3621     },
3622
3623     getColumnAt : function(index){
3624         return this.config[index];
3625     },
3626
3627     /**
3628      * <p>Reconfigures this column model according to the passed Array of column definition objects.
3629      * For a description of the individual properties of a column definition object, see the
3630      * <a href="#Ext.grid.ColumnModel-configs">Config Options</a>.</p>
3631      * <p>Causes the {@link #configchange} event to be fired. A {@link Ext.grid.GridPanel GridPanel}
3632      * using this ColumnModel will listen for this event and refresh its UI automatically.</p>
3633      * @param {Array} config Array of Column definition objects.
3634      * @param {Boolean} initial Specify <tt>true</tt> to bypass cleanup which deletes the <tt>totalWidth</tt>
3635      * and destroys existing editors.
3636      */
3637     setConfig : function(config, initial){
3638         var i, c, len;
3639         if(!initial){ // cleanup
3640             delete this.totalWidth;
3641             for(i = 0, len = this.config.length; i < len; i++){
3642                 c = this.config[i];
3643                 if(c.setEditor){
3644                     //check here, in case we have a special column like a CheckboxSelectionModel
3645                     c.setEditor(null);
3646                 }
3647             }
3648         }
3649
3650         // backward compatibility
3651         this.defaults = Ext.apply({
3652             width: this.defaultWidth,
3653             sortable: this.defaultSortable
3654         }, this.defaults);
3655
3656         this.config = config;
3657         this.lookup = {};
3658
3659         for(i = 0, len = config.length; i < len; i++){
3660             c = Ext.applyIf(config[i], this.defaults);
3661             // if no id, create one using column's ordinal position
3662             if(Ext.isEmpty(c.id)){
3663                 c.id = i;
3664             }
3665             if(!c.isColumn){
3666                 var Cls = Ext.grid.Column.types[c.xtype || 'gridcolumn'];
3667                 c = new Cls(c);
3668                 config[i] = c;
3669             }
3670             this.lookup[c.id] = c;
3671         }
3672         if(!initial){
3673             this.fireEvent('configchange', this);
3674         }
3675     },
3676
3677     /**
3678      * Returns the column for a specified id.
3679      * @param {String} id The column id
3680      * @return {Object} the column
3681      */
3682     getColumnById : function(id){
3683         return this.lookup[id];
3684     },
3685
3686     /**
3687      * Returns the index for a specified column id.
3688      * @param {String} id The column id
3689      * @return {Number} the index, or -1 if not found
3690      */
3691     getIndexById : function(id){
3692         for(var i = 0, len = this.config.length; i < len; i++){
3693             if(this.config[i].id == id){
3694                 return i;
3695             }
3696         }
3697         return -1;
3698     },
3699
3700     /**
3701      * Moves a column from one position to another.
3702      * @param {Number} oldIndex The index of the column to move.
3703      * @param {Number} newIndex The position at which to reinsert the coolumn.
3704      */
3705     moveColumn : function(oldIndex, newIndex){
3706         var c = this.config[oldIndex];
3707         this.config.splice(oldIndex, 1);
3708         this.config.splice(newIndex, 0, c);
3709         this.dataMap = null;
3710         this.fireEvent("columnmoved", this, oldIndex, newIndex);
3711     },
3712
3713     /**
3714      * Returns the number of columns.
3715      * @param {Boolean} visibleOnly Optional. Pass as true to only include visible columns.
3716      * @return {Number}
3717      */
3718     getColumnCount : function(visibleOnly){
3719         if(visibleOnly === true){
3720             var c = 0;
3721             for(var i = 0, len = this.config.length; i < len; i++){
3722                 if(!this.isHidden(i)){
3723                     c++;
3724                 }
3725             }
3726             return c;
3727         }
3728         return this.config.length;
3729     },
3730
3731     /**
3732      * Returns the column configs that return true by the passed function that is called
3733      * with (columnConfig, index)
3734 <pre><code>
3735 // returns an array of column config objects for all hidden columns
3736 var columns = grid.getColumnModel().getColumnsBy(function(c){
3737   return c.hidden;
3738 });
3739 </code></pre>
3740      * @param {Function} fn A function which, when passed a {@link Ext.grid.Column Column} object, must
3741      * return <code>true</code> if the column is to be included in the returned Array.
3742      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function
3743      * is executed. Defaults to this ColumnModel.
3744      * @return {Array} result
3745      */
3746     getColumnsBy : function(fn, scope){
3747         var r = [];
3748         for(var i = 0, len = this.config.length; i < len; i++){
3749             var c = this.config[i];
3750             if(fn.call(scope||this, c, i) === true){
3751                 r[r.length] = c;
3752             }
3753         }
3754         return r;
3755     },
3756
3757     /**
3758      * Returns true if the specified column is sortable.
3759      * @param {Number} col The column index
3760      * @return {Boolean}
3761      */
3762     isSortable : function(col){
3763         return !!this.config[col].sortable;
3764     },
3765
3766     /**
3767      * Returns true if the specified column menu is disabled.
3768      * @param {Number} col The column index
3769      * @return {Boolean}
3770      */
3771     isMenuDisabled : function(col){
3772         return !!this.config[col].menuDisabled;
3773     },
3774
3775     /**
3776      * Returns the rendering (formatting) function defined for the column.
3777      * @param {Number} col The column index.
3778      * @return {Function} The function used to render the cell. See {@link #setRenderer}.
3779      */
3780     getRenderer : function(col){
3781         if(!this.config[col].renderer){
3782             return Ext.grid.ColumnModel.defaultRenderer;
3783         }
3784         return this.config[col].renderer;
3785     },
3786
3787     getRendererScope : function(col){
3788         return this.config[col].scope;
3789     },
3790
3791     /**
3792      * Sets the rendering (formatting) function for a column.  See {@link Ext.util.Format} for some
3793      * default formatting functions.
3794      * @param {Number} col The column index
3795      * @param {Function} fn The function to use to process the cell's raw data
3796      * to return HTML markup for the grid view. The render function is called with
3797      * the following parameters:<ul>
3798      * <li><b>value</b> : Object<p class="sub-desc">The data value for the cell.</p></li>
3799      * <li><b>metadata</b> : Object<p class="sub-desc">An object in which you may set the following attributes:<ul>
3800      * <li><b>css</b> : String<p class="sub-desc">A CSS class name to add to the cell's TD element.</p></li>
3801      * <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
3802      * (e.g. 'style="color:red;"').</p></li></ul></p></li>
3803      * <li><b>record</b> : Ext.data.record<p class="sub-desc">The {@link Ext.data.Record} from which the data was extracted.</p></li>
3804      * <li><b>rowIndex</b> : Number<p class="sub-desc">Row index</p></li>
3805      * <li><b>colIndex</b> : Number<p class="sub-desc">Column index</p></li>
3806      * <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>
3807      */
3808     setRenderer : function(col, fn){
3809         this.config[col].renderer = fn;
3810     },
3811
3812     /**
3813      * Returns the width for the specified column.
3814      * @param {Number} col The column index
3815      * @return {Number}
3816      */
3817     getColumnWidth : function(col){
3818         return this.config[col].width;
3819     },
3820
3821     /**
3822      * Sets the width for a column.
3823      * @param {Number} col The column index
3824      * @param {Number} width The new width
3825      * @param {Boolean} suppressEvent True to suppress firing the <code>{@link #widthchange}</code>
3826      * event. Defaults to false.
3827      */
3828     setColumnWidth : function(col, width, suppressEvent){
3829         this.config[col].width = width;
3830         this.totalWidth = null;
3831         if(!suppressEvent){
3832              this.fireEvent("widthchange", this, col, width);
3833         }
3834     },
3835
3836     /**
3837      * Returns the total width of all columns.
3838      * @param {Boolean} includeHidden True to include hidden column widths
3839      * @return {Number}
3840      */
3841     getTotalWidth : function(includeHidden){
3842         if(!this.totalWidth){
3843             this.totalWidth = 0;
3844             for(var i = 0, len = this.config.length; i < len; i++){
3845                 if(includeHidden || !this.isHidden(i)){
3846                     this.totalWidth += this.getColumnWidth(i);
3847                 }
3848             }
3849         }
3850         return this.totalWidth;
3851     },
3852
3853     /**
3854      * Returns the header for the specified column.
3855      * @param {Number} col The column index
3856      * @return {String}
3857      */
3858     getColumnHeader : function(col){
3859         return this.config[col].header;
3860     },
3861
3862     /**
3863      * Sets the header for a column.
3864      * @param {Number} col The column index
3865      * @param {String} header The new header
3866      */
3867     setColumnHeader : function(col, header){
3868         this.config[col].header = header;
3869         this.fireEvent("headerchange", this, col, header);
3870     },
3871
3872     /**
3873      * Returns the tooltip for the specified column.
3874      * @param {Number} col The column index
3875      * @return {String}
3876      */
3877     getColumnTooltip : function(col){
3878             return this.config[col].tooltip;
3879     },
3880     /**
3881      * Sets the tooltip for a column.
3882      * @param {Number} col The column index
3883      * @param {String} tooltip The new tooltip
3884      */
3885     setColumnTooltip : function(col, tooltip){
3886             this.config[col].tooltip = tooltip;
3887     },
3888
3889     /**
3890      * Returns the dataIndex for the specified column.
3891 <pre><code>
3892 // Get field name for the column
3893 var fieldName = grid.getColumnModel().getDataIndex(columnIndex);
3894 </code></pre>
3895      * @param {Number} col The column index
3896      * @return {String} The column's dataIndex
3897      */
3898     getDataIndex : function(col){
3899         return this.config[col].dataIndex;
3900     },
3901
3902     /**
3903      * Sets the dataIndex for a column.
3904      * @param {Number} col The column index
3905      * @param {String} dataIndex The new dataIndex
3906      */
3907     setDataIndex : function(col, dataIndex){
3908         this.config[col].dataIndex = dataIndex;
3909     },
3910
3911     /**
3912      * Finds the index of the first matching column for the given dataIndex.
3913      * @param {String} col The dataIndex to find
3914      * @return {Number} The column index, or -1 if no match was found
3915      */
3916     findColumnIndex : function(dataIndex){
3917         var c = this.config;
3918         for(var i = 0, len = c.length; i < len; i++){
3919             if(c[i].dataIndex == dataIndex){
3920                 return i;
3921             }
3922         }
3923         return -1;
3924     },
3925
3926     /**
3927      * Returns true if the cell is editable.
3928 <pre><code>
3929 var store = new Ext.data.Store({...});
3930 var colModel = new Ext.grid.ColumnModel({
3931   columns: [...],
3932   isCellEditable: function(col, row) {
3933     var record = store.getAt(row);
3934     if (record.get('readonly')) { // replace with your condition
3935       return false;
3936     }
3937     return Ext.grid.ColumnModel.prototype.isCellEditable.call(this, col, row);
3938   }
3939 });
3940 var grid = new Ext.grid.GridPanel({
3941   store: store,
3942   colModel: colModel,
3943   ...
3944 });
3945 </code></pre>
3946      * @param {Number} colIndex The column index
3947      * @param {Number} rowIndex The row index
3948      * @return {Boolean}
3949      */
3950     isCellEditable : function(colIndex, rowIndex){
3951         var c = this.config[colIndex],
3952             ed = c.editable;
3953
3954         //force boolean
3955         return !!(ed || (!Ext.isDefined(ed) && c.editor));
3956     },
3957
3958     /**
3959      * Returns the editor defined for the cell/column.
3960      * @param {Number} colIndex The column index
3961      * @param {Number} rowIndex The row index
3962      * @return {Ext.Editor} The {@link Ext.Editor Editor} that was created to wrap
3963      * the {@link Ext.form.Field Field} used to edit the cell.
3964      */
3965     getCellEditor : function(colIndex, rowIndex){
3966         return this.config[colIndex].getCellEditor(rowIndex);
3967     },
3968
3969     /**
3970      * Sets if a column is editable.
3971      * @param {Number} col The column index
3972      * @param {Boolean} editable True if the column is editable
3973      */
3974     setEditable : function(col, editable){
3975         this.config[col].editable = editable;
3976     },
3977
3978     /**
3979      * Returns <tt>true</tt> if the column is <code>{@link Ext.grid.Column#hidden hidden}</code>,
3980      * <tt>false</tt> otherwise.
3981      * @param {Number} colIndex The column index
3982      * @return {Boolean}
3983      */
3984     isHidden : function(colIndex){
3985         return !!this.config[colIndex].hidden; // ensure returns boolean
3986     },
3987
3988     /**
3989      * Returns <tt>true</tt> if the column is <code>{@link Ext.grid.Column#fixed fixed}</code>,
3990      * <tt>false</tt> otherwise.
3991      * @param {Number} colIndex The column index
3992      * @return {Boolean}
3993      */
3994     isFixed : function(colIndex){
3995         return !!this.config[colIndex].fixed;
3996     },
3997
3998     /**
3999      * Returns true if the column can be resized
4000      * @return {Boolean}
4001      */
4002     isResizable : function(colIndex){
4003         return colIndex >= 0 && this.config[colIndex].resizable !== false && this.config[colIndex].fixed !== true;
4004     },
4005     /**
4006      * Sets if a column is hidden.
4007 <pre><code>
4008 myGrid.getColumnModel().setHidden(0, true); // hide column 0 (0 = the first column).
4009 </code></pre>
4010      * @param {Number} colIndex The column index
4011      * @param {Boolean} hidden True if the column is hidden
4012      */
4013     setHidden : function(colIndex, hidden){
4014         var c = this.config[colIndex];
4015         if(c.hidden !== hidden){
4016             c.hidden = hidden;
4017             this.totalWidth = null;
4018             this.fireEvent("hiddenchange", this, colIndex, hidden);
4019         }
4020     },
4021
4022     /**
4023      * Sets the editor for a column and destroys the prior editor.
4024      * @param {Number} col The column index
4025      * @param {Object} editor The editor object
4026      */
4027     setEditor : function(col, editor){
4028         this.config[col].setEditor(editor);
4029     },
4030
4031     /**
4032      * Destroys this column model by purging any event listeners, and removing any editors.
4033      */
4034     destroy : function(){
4035         var c;
4036         for(var i = 0, len = this.config.length; i < len; i++){
4037             c = this.config[i];
4038             if(c.setEditor){
4039                 c.setEditor(null);
4040             }
4041         }
4042         this.purgeListeners();
4043     }
4044 });
4045
4046 // private
4047 Ext.grid.ColumnModel.defaultRenderer = function(value){
4048     if(typeof value == "string" && value.length < 1){
4049         return "&#160;";
4050     }
4051     return value;
4052 };/**
4053  * @class Ext.grid.AbstractSelectionModel
4054  * @extends Ext.util.Observable
4055  * Abstract base class for grid SelectionModels.  It provides the interface that should be
4056  * implemented by descendant classes.  This class should not be directly instantiated.
4057  * @constructor
4058  */
4059 Ext.grid.AbstractSelectionModel = Ext.extend(Ext.util.Observable,  {
4060     /**
4061      * The GridPanel for which this SelectionModel is handling selection. Read-only.
4062      * @type Object
4063      * @property grid
4064      */
4065
4066     constructor : function(){
4067         this.locked = false;
4068         Ext.grid.AbstractSelectionModel.superclass.constructor.call(this);
4069     },
4070
4071     /** @ignore Called by the grid automatically. Do not call directly. */
4072     init : function(grid){
4073         this.grid = grid;
4074         if(this.lockOnInit){
4075             delete this.lockOnInit;
4076             this.locked = false;
4077             this.lock();
4078         }
4079         this.initEvents();
4080     },
4081
4082     /**
4083      * Locks the selections.
4084      */
4085     lock : function(){
4086         if(!this.locked){
4087             this.locked = true;
4088             // If the grid has been set, then the view is already initialized.
4089             var g = this.grid;
4090             if(g){
4091                 g.getView().on({
4092                     scope: this,
4093                     beforerefresh: this.sortUnLock,
4094                     refresh: this.sortLock
4095                 });
4096             }else{
4097                 this.lockOnInit = true;
4098             }
4099         }
4100     },
4101
4102     // set the lock states before and after a view refresh
4103     sortLock : function() {
4104         this.locked = true;
4105     },
4106
4107     // set the lock states before and after a view refresh
4108     sortUnLock : function() {
4109         this.locked = false;
4110     },
4111
4112     /**
4113      * Unlocks the selections.
4114      */
4115     unlock : function(){
4116         if(this.locked){
4117             this.locked = false;
4118             var g = this.grid,
4119                 gv;
4120                 
4121             // If the grid has been set, then the view is already initialized.
4122             if(g){
4123                 gv = g.getView();
4124                 gv.un('beforerefresh', this.sortUnLock, this);
4125                 gv.un('refresh', this.sortLock, this);    
4126             }else{
4127                 delete this.lockOnInit;
4128             }
4129         }
4130     },
4131
4132     /**
4133      * Returns true if the selections are locked.
4134      * @return {Boolean}
4135      */
4136     isLocked : function(){
4137         return this.locked;
4138     },
4139
4140     destroy: function(){
4141         this.unlock();
4142         this.purgeListeners();
4143     }
4144 });/**
4145  * @class Ext.grid.RowSelectionModel
4146  * @extends Ext.grid.AbstractSelectionModel
4147  * The default SelectionModel used by {@link Ext.grid.GridPanel}.
4148  * It supports multiple selections and keyboard selection/navigation. The objects stored
4149  * as selections and returned by {@link #getSelected}, and {@link #getSelections} are
4150  * the {@link Ext.data.Record Record}s which provide the data for the selected rows.
4151  * @constructor
4152  * @param {Object} config
4153  */
4154 Ext.grid.RowSelectionModel = Ext.extend(Ext.grid.AbstractSelectionModel,  {
4155     /**
4156      * @cfg {Boolean} singleSelect
4157      * <tt>true</tt> to allow selection of only one row at a time (defaults to <tt>false</tt>
4158      * allowing multiple selections)
4159      */
4160     singleSelect : false,
4161     
4162     constructor : function(config){
4163         Ext.apply(this, config);
4164         this.selections = new Ext.util.MixedCollection(false, function(o){
4165             return o.id;
4166         });
4167
4168         this.last = false;
4169         this.lastActive = false;
4170
4171         this.addEvents(
4172                 /**
4173                  * @event selectionchange
4174                  * Fires when the selection changes
4175                  * @param {SelectionModel} this
4176                  */
4177                 'selectionchange',
4178                 /**
4179                  * @event beforerowselect
4180                  * Fires before a row is selected, return false to cancel the selection.
4181                  * @param {SelectionModel} this
4182                  * @param {Number} rowIndex The index to be selected
4183                  * @param {Boolean} keepExisting False if other selections will be cleared
4184                  * @param {Record} record The record to be selected
4185                  */
4186                 'beforerowselect',
4187                 /**
4188                  * @event rowselect
4189                  * Fires when a row is selected.
4190                  * @param {SelectionModel} this
4191                  * @param {Number} rowIndex The selected index
4192                  * @param {Ext.data.Record} r The selected record
4193                  */
4194                 'rowselect',
4195                 /**
4196                  * @event rowdeselect
4197                  * Fires when a row is deselected.  To prevent deselection
4198                  * {@link Ext.grid.AbstractSelectionModel#lock lock the selections}. 
4199                  * @param {SelectionModel} this
4200                  * @param {Number} rowIndex
4201                  * @param {Record} record
4202                  */
4203                 'rowdeselect'
4204         );
4205         Ext.grid.RowSelectionModel.superclass.constructor.call(this);
4206     },
4207
4208     /**
4209      * @cfg {Boolean} moveEditorOnEnter
4210      * <tt>false</tt> to turn off moving the editor to the next row down when the enter key is pressed
4211      * or the next row up when shift + enter keys are pressed.
4212      */
4213     // private
4214     initEvents : function(){
4215
4216         if(!this.grid.enableDragDrop && !this.grid.enableDrag){
4217             this.grid.on('rowmousedown', this.handleMouseDown, this);
4218         }
4219
4220         this.rowNav = new Ext.KeyNav(this.grid.getGridEl(), {
4221             'up' : function(e){
4222                 if(!e.shiftKey || this.singleSelect){
4223                     this.selectPrevious(false);
4224                 }else if(this.last !== false && this.lastActive !== false){
4225                     var last = this.last;
4226                     this.selectRange(this.last,  this.lastActive-1);
4227                     this.grid.getView().focusRow(this.lastActive);
4228                     if(last !== false){
4229                         this.last = last;
4230                     }
4231                 }else{
4232                     this.selectFirstRow();
4233                 }
4234             },
4235             'down' : function(e){
4236                 if(!e.shiftKey || this.singleSelect){
4237                     this.selectNext(false);
4238                 }else if(this.last !== false && this.lastActive !== false){
4239                     var last = this.last;
4240                     this.selectRange(this.last,  this.lastActive+1);
4241                     this.grid.getView().focusRow(this.lastActive);
4242                     if(last !== false){
4243                         this.last = last;
4244                     }
4245                 }else{
4246                     this.selectFirstRow();
4247                 }
4248             },
4249             scope: this
4250         });
4251
4252         this.grid.getView().on({
4253             scope: this,
4254             refresh: this.onRefresh,
4255             rowupdated: this.onRowUpdated,
4256             rowremoved: this.onRemove
4257         });
4258     },
4259
4260     // private
4261     onRefresh : function(){
4262         var ds = this.grid.store, index;
4263         var s = this.getSelections();
4264         this.clearSelections(true);
4265         for(var i = 0, len = s.length; i < len; i++){
4266             var r = s[i];
4267             if((index = ds.indexOfId(r.id)) != -1){
4268                 this.selectRow(index, true);
4269             }
4270         }
4271         if(s.length != this.selections.getCount()){
4272             this.fireEvent('selectionchange', this);
4273         }
4274     },
4275
4276     // private
4277     onRemove : function(v, index, r){
4278         if(this.selections.remove(r) !== false){
4279             this.fireEvent('selectionchange', this);
4280         }
4281     },
4282
4283     // private
4284     onRowUpdated : function(v, index, r){
4285         if(this.isSelected(r)){
4286             v.onRowSelect(index);
4287         }
4288     },
4289
4290     /**
4291      * Select records.
4292      * @param {Array} records The records to select
4293      * @param {Boolean} keepExisting (optional) <tt>true</tt> to keep existing selections
4294      */
4295     selectRecords : function(records, keepExisting){
4296         if(!keepExisting){
4297             this.clearSelections();
4298         }
4299         var ds = this.grid.store;
4300         for(var i = 0, len = records.length; i < len; i++){
4301             this.selectRow(ds.indexOf(records[i]), true);
4302         }
4303     },
4304
4305     /**
4306      * Gets the number of selected rows.
4307      * @return {Number}
4308      */
4309     getCount : function(){
4310         return this.selections.length;
4311     },
4312
4313     /**
4314      * Selects the first row in the grid.
4315      */
4316     selectFirstRow : function(){
4317         this.selectRow(0);
4318     },
4319
4320     /**
4321      * Select the last row.
4322      * @param {Boolean} keepExisting (optional) <tt>true</tt> to keep existing selections
4323      */
4324     selectLastRow : function(keepExisting){
4325         this.selectRow(this.grid.store.getCount() - 1, keepExisting);
4326     },
4327
4328     /**
4329      * Selects the row immediately following the last selected row.
4330      * @param {Boolean} keepExisting (optional) <tt>true</tt> to keep existing selections
4331      * @return {Boolean} <tt>true</tt> if there is a next row, else <tt>false</tt>
4332      */
4333     selectNext : function(keepExisting){
4334         if(this.hasNext()){
4335             this.selectRow(this.last+1, keepExisting);
4336             this.grid.getView().focusRow(this.last);
4337             return true;
4338         }
4339         return false;
4340     },
4341
4342     /**
4343      * Selects the row that precedes the last selected row.
4344      * @param {Boolean} keepExisting (optional) <tt>true</tt> to keep existing selections
4345      * @return {Boolean} <tt>true</tt> if there is a previous row, else <tt>false</tt>
4346      */
4347     selectPrevious : function(keepExisting){
4348         if(this.hasPrevious()){
4349             this.selectRow(this.last-1, keepExisting);
4350             this.grid.getView().focusRow(this.last);
4351             return true;
4352         }
4353         return false;
4354     },
4355
4356     /**
4357      * Returns true if there is a next record to select
4358      * @return {Boolean}
4359      */
4360     hasNext : function(){
4361         return this.last !== false && (this.last+1) < this.grid.store.getCount();
4362     },
4363
4364     /**
4365      * Returns true if there is a previous record to select
4366      * @return {Boolean}
4367      */
4368     hasPrevious : function(){
4369         return !!this.last;
4370     },
4371
4372
4373     /**
4374      * Returns the selected records
4375      * @return {Array} Array of selected records
4376      */
4377     getSelections : function(){
4378         return [].concat(this.selections.items);
4379     },
4380
4381     /**
4382      * Returns the first selected record.
4383      * @return {Record}
4384      */
4385     getSelected : function(){
4386         return this.selections.itemAt(0);
4387     },
4388
4389     /**
4390      * Calls the passed function with each selection. If the function returns
4391      * <tt>false</tt>, iteration is stopped and this function returns
4392      * <tt>false</tt>. Otherwise it returns <tt>true</tt>.
4393      * @param {Function} fn The function to call upon each iteration. It is passed the selected {@link Ext.data.Record Record}.
4394      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to this RowSelectionModel.
4395      * @return {Boolean} true if all selections were iterated
4396      */
4397     each : function(fn, scope){
4398         var s = this.getSelections();
4399         for(var i = 0, len = s.length; i < len; i++){
4400             if(fn.call(scope || this, s[i], i) === false){
4401                 return false;
4402             }
4403         }
4404         return true;
4405     },
4406
4407     /**
4408      * Clears all selections if the selection model
4409      * {@link Ext.grid.AbstractSelectionModel#isLocked is not locked}.
4410      * @param {Boolean} fast (optional) <tt>true</tt> to bypass the
4411      * conditional checks and events described in {@link #deselectRow}.
4412      */
4413     clearSelections : function(fast){
4414         if(this.isLocked()){
4415             return;
4416         }
4417         if(fast !== true){
4418             var ds = this.grid.store;
4419             var s = this.selections;
4420             s.each(function(r){
4421                 this.deselectRow(ds.indexOfId(r.id));
4422             }, this);
4423             s.clear();
4424         }else{
4425             this.selections.clear();
4426         }
4427         this.last = false;
4428     },
4429
4430
4431     /**
4432      * Selects all rows if the selection model
4433      * {@link Ext.grid.AbstractSelectionModel#isLocked is not locked}. 
4434      */
4435     selectAll : function(){
4436         if(this.isLocked()){
4437             return;
4438         }
4439         this.selections.clear();
4440         for(var i = 0, len = this.grid.store.getCount(); i < len; i++){
4441             this.selectRow(i, true);
4442         }
4443     },
4444
4445     /**
4446      * Returns <tt>true</tt> if there is a selection.
4447      * @return {Boolean}
4448      */
4449     hasSelection : function(){
4450         return this.selections.length > 0;
4451     },
4452
4453     /**
4454      * Returns <tt>true</tt> if the specified row is selected.
4455      * @param {Number/Record} index The record or index of the record to check
4456      * @return {Boolean}
4457      */
4458     isSelected : function(index){
4459         var r = Ext.isNumber(index) ? this.grid.store.getAt(index) : index;
4460         return (r && this.selections.key(r.id) ? true : false);
4461     },
4462
4463     /**
4464      * Returns <tt>true</tt> if the specified record id is selected.
4465      * @param {String} id The id of record to check
4466      * @return {Boolean}
4467      */
4468     isIdSelected : function(id){
4469         return (this.selections.key(id) ? true : false);
4470     },
4471
4472     // private
4473     handleMouseDown : function(g, rowIndex, e){
4474         if(e.button !== 0 || this.isLocked()){
4475             return;
4476         }
4477         var view = this.grid.getView();
4478         if(e.shiftKey && !this.singleSelect && this.last !== false){
4479             var last = this.last;
4480             this.selectRange(last, rowIndex, e.ctrlKey);
4481             this.last = last; // reset the last
4482             view.focusRow(rowIndex);
4483         }else{
4484             var isSelected = this.isSelected(rowIndex);
4485             if(e.ctrlKey && isSelected){
4486                 this.deselectRow(rowIndex);
4487             }else if(!isSelected || this.getCount() > 1){
4488                 this.selectRow(rowIndex, e.ctrlKey || e.shiftKey);
4489                 view.focusRow(rowIndex);
4490             }
4491         }
4492     },
4493
4494     /**
4495      * Selects multiple rows.
4496      * @param {Array} rows Array of the indexes of the row to select
4497      * @param {Boolean} keepExisting (optional) <tt>true</tt> to keep
4498      * existing selections (defaults to <tt>false</tt>)
4499      */
4500     selectRows : function(rows, keepExisting){
4501         if(!keepExisting){
4502             this.clearSelections();
4503         }
4504         for(var i = 0, len = rows.length; i < len; i++){
4505             this.selectRow(rows[i], true);
4506         }
4507     },
4508
4509     /**
4510      * Selects a range of rows if the selection model
4511      * {@link Ext.grid.AbstractSelectionModel#isLocked is not locked}.
4512      * All rows in between startRow and endRow are also selected.
4513      * @param {Number} startRow The index of the first row in the range
4514      * @param {Number} endRow The index of the last row in the range
4515      * @param {Boolean} keepExisting (optional) True to retain existing selections
4516      */
4517     selectRange : function(startRow, endRow, keepExisting){
4518         var i;
4519         if(this.isLocked()){
4520             return;
4521         }
4522         if(!keepExisting){
4523             this.clearSelections();
4524         }
4525         if(startRow <= endRow){
4526             for(i = startRow; i <= endRow; i++){
4527                 this.selectRow(i, true);
4528             }
4529         }else{
4530             for(i = startRow; i >= endRow; i--){
4531                 this.selectRow(i, true);
4532             }
4533         }
4534     },
4535
4536     /**
4537      * Deselects a range of rows if the selection model
4538      * {@link Ext.grid.AbstractSelectionModel#isLocked is not locked}.  
4539      * All rows in between startRow and endRow are also deselected.
4540      * @param {Number} startRow The index of the first row in the range
4541      * @param {Number} endRow The index of the last row in the range
4542      */
4543     deselectRange : function(startRow, endRow, preventViewNotify){
4544         if(this.isLocked()){
4545             return;
4546         }
4547         for(var i = startRow; i <= endRow; i++){
4548             this.deselectRow(i, preventViewNotify);
4549         }
4550     },
4551
4552     /**
4553      * Selects a row.  Before selecting a row, checks if the selection model
4554      * {@link Ext.grid.AbstractSelectionModel#isLocked is locked} and fires the
4555      * {@link #beforerowselect} event.  If these checks are satisfied the row
4556      * will be selected and followed up by  firing the {@link #rowselect} and
4557      * {@link #selectionchange} events.
4558      * @param {Number} row The index of the row to select
4559      * @param {Boolean} keepExisting (optional) <tt>true</tt> to keep existing selections
4560      * @param {Boolean} preventViewNotify (optional) Specify <tt>true</tt> to
4561      * prevent notifying the view (disables updating the selected appearance)
4562      */
4563     selectRow : function(index, keepExisting, preventViewNotify){
4564         if(this.isLocked() || (index < 0 || index >= this.grid.store.getCount()) || (keepExisting && this.isSelected(index))){
4565             return;
4566         }
4567         var r = this.grid.store.getAt(index);
4568         if(r && this.fireEvent('beforerowselect', this, index, keepExisting, r) !== false){
4569             if(!keepExisting || this.singleSelect){
4570                 this.clearSelections();
4571             }
4572             this.selections.add(r);
4573             this.last = this.lastActive = index;
4574             if(!preventViewNotify){
4575                 this.grid.getView().onRowSelect(index);
4576             }
4577             this.fireEvent('rowselect', this, index, r);
4578             this.fireEvent('selectionchange', this);
4579         }
4580     },
4581
4582     /**
4583      * Deselects a row.  Before deselecting a row, checks if the selection model
4584      * {@link Ext.grid.AbstractSelectionModel#isLocked is locked}.
4585      * If this check is satisfied the row will be deselected and followed up by
4586      * firing the {@link #rowdeselect} and {@link #selectionchange} events.
4587      * @param {Number} row The index of the row to deselect
4588      * @param {Boolean} preventViewNotify (optional) Specify <tt>true</tt> to
4589      * prevent notifying the view (disables updating the selected appearance)
4590      */
4591     deselectRow : function(index, preventViewNotify){
4592         if(this.isLocked()){
4593             return;
4594         }
4595         if(this.last == index){
4596             this.last = false;
4597         }
4598         if(this.lastActive == index){
4599             this.lastActive = false;
4600         }
4601         var r = this.grid.store.getAt(index);
4602         if(r){
4603             this.selections.remove(r);
4604             if(!preventViewNotify){
4605                 this.grid.getView().onRowDeselect(index);
4606             }
4607             this.fireEvent('rowdeselect', this, index, r);
4608             this.fireEvent('selectionchange', this);
4609         }
4610     },
4611
4612     // private
4613     restoreLast : function(){
4614         if(this._last){
4615             this.last = this._last;
4616         }
4617     },
4618
4619     // private
4620     acceptsNav : function(row, col, cm){
4621         return !cm.isHidden(col) && cm.isCellEditable(col, row);
4622     },
4623
4624     // private
4625     onEditorKey : function(field, e){
4626         var k = e.getKey(), 
4627             newCell, 
4628             g = this.grid, 
4629             last = g.lastEdit,
4630             ed = g.activeEditor,
4631             ae, last, r, c;
4632         var shift = e.shiftKey;
4633         if(k == e.TAB){
4634             e.stopEvent();
4635             ed.completeEdit();
4636             if(shift){
4637                 newCell = g.walkCells(ed.row, ed.col-1, -1, this.acceptsNav, this);
4638             }else{
4639                 newCell = g.walkCells(ed.row, ed.col+1, 1, this.acceptsNav, this);
4640             }
4641         }else if(k == e.ENTER){
4642             if(this.moveEditorOnEnter !== false){
4643                 if(shift){
4644                     newCell = g.walkCells(last.row - 1, last.col, -1, this.acceptsNav, this);
4645                 }else{
4646                     newCell = g.walkCells(last.row + 1, last.col, 1, this.acceptsNav, this);
4647                 }
4648             }
4649         }
4650         if(newCell){
4651             r = newCell[0];
4652             c = newCell[1];
4653
4654             if(last.row != r){
4655                 this.selectRow(r); // *** highlight newly-selected cell and update selection
4656             }
4657
4658             if(g.isEditor && g.editing){ // *** handle tabbing while editorgrid is in edit mode
4659                 ae = g.activeEditor;
4660                 if(ae && ae.field.triggerBlur){
4661                     // *** if activeEditor is a TriggerField, explicitly call its triggerBlur() method
4662                     ae.field.triggerBlur();
4663                 }
4664             }
4665             g.startEditing(r, c);
4666         }
4667     },
4668     
4669     destroy : function(){
4670         if(this.rowNav){
4671             this.rowNav.disable();
4672             this.rowNav = null;
4673         }
4674         Ext.grid.RowSelectionModel.superclass.destroy.call(this);
4675     }
4676 });/**
4677  * @class Ext.grid.Column
4678  * <p>This class encapsulates column configuration data to be used in the initialization of a
4679  * {@link Ext.grid.ColumnModel ColumnModel}.</p>
4680  * <p>While subclasses are provided to render data in different ways, this class renders a passed
4681  * data field unchanged and is usually used for textual columns.</p>
4682  */
4683 Ext.grid.Column = Ext.extend(Object, {
4684     /**
4685      * @cfg {Boolean} editable Optional. Defaults to <tt>true</tt>, enabling the configured
4686      * <tt>{@link #editor}</tt>.  Set to <tt>false</tt> to initially disable editing on this column.
4687      * The initial configuration may be dynamically altered using
4688      * {@link Ext.grid.ColumnModel}.{@link Ext.grid.ColumnModel#setEditable setEditable()}.
4689      */
4690     /**
4691      * @cfg {String} id Optional. A name which identifies this column (defaults to the column's initial
4692      * ordinal position.) The <tt>id</tt> is used to create a CSS <b>class</b> name which is applied to all
4693      * table cells (including headers) in that column (in this context the <tt>id</tt> does not need to be
4694      * unique). The class name takes the form of <pre>x-grid3-td-<b>id</b></pre>
4695      * Header cells will also receive this class name, but will also have the class <pre>x-grid3-hd</pre>
4696      * So, to target header cells, use CSS selectors such as:<pre>.x-grid3-hd-row .x-grid3-td-<b>id</b></pre>
4697      * The {@link Ext.grid.GridPanel#autoExpandColumn} grid config option references the column via this
4698      * unique identifier.
4699      */
4700     /**
4701      * @cfg {String} header Optional. The header text to be used as innerHTML
4702      * (html tags are accepted) to display in the Grid view.  <b>Note</b>: to
4703      * have a clickable header with no text displayed use <tt>'&#160;'</tt>.
4704      */
4705     /**
4706      * @cfg {Boolean} groupable Optional. If the grid is being rendered by an {@link Ext.grid.GroupingView}, this option
4707      * may be used to disable the header menu item to group by the column selected. Defaults to <tt>true</tt>,
4708      * which enables the header menu group option.  Set to <tt>false</tt> to disable (but still show) the
4709      * group option in the header menu for the column. See also <code>{@link #groupName}</code>.
4710      */
4711     /**
4712      * @cfg {String} groupName Optional. If the grid is being rendered by an {@link Ext.grid.GroupingView}, this option
4713      * may be used to specify the text with which to prefix the group field value in the group header line.
4714      * See also {@link #groupRenderer} and
4715      * {@link Ext.grid.GroupingView}.{@link Ext.grid.GroupingView#showGroupName showGroupName}.
4716      */
4717     /**
4718      * @cfg {Function} groupRenderer <p>Optional. If the grid is being rendered by an {@link Ext.grid.GroupingView}, this option
4719      * may be used to specify the function used to format the grouping field value for display in the group
4720      * {@link #groupName header}.  If a <tt><b>groupRenderer</b></tt> is not specified, the configured
4721      * <tt><b>{@link #renderer}</b></tt> will be called; if a <tt><b>{@link #renderer}</b></tt> is also not specified
4722      * the new value of the group field will be used.</p>
4723      * <p>The called function (either the <tt><b>groupRenderer</b></tt> or <tt><b>{@link #renderer}</b></tt>) will be
4724      * passed the following parameters:
4725      * <div class="mdetail-params"><ul>
4726      * <li><b>v</b> : Object<p class="sub-desc">The new value of the group field.</p></li>
4727      * <li><b>unused</b> : undefined<p class="sub-desc">Unused parameter.</p></li>
4728      * <li><b>r</b> : Ext.data.Record<p class="sub-desc">The Record providing the data
4729      * for the row which caused group change.</p></li>
4730      * <li><b>rowIndex</b> : Number<p class="sub-desc">The row index of the Record which caused group change.</p></li>
4731      * <li><b>colIndex</b> : Number<p class="sub-desc">The column index of the group field.</p></li>
4732      * <li><b>ds</b> : Ext.data.Store<p class="sub-desc">The Store which is providing the data Model.</p></li>
4733      * </ul></div></p>
4734      * <p>The function should return a string value.</p>
4735      */
4736     /**
4737      * @cfg {String} emptyGroupText Optional. If the grid is being rendered by an {@link Ext.grid.GroupingView}, this option
4738      * may be used to specify the text to display when there is an empty group value. Defaults to the
4739      * {@link Ext.grid.GroupingView}.{@link Ext.grid.GroupingView#emptyGroupText emptyGroupText}.
4740      */
4741     /**
4742      * @cfg {String} dataIndex <p><b>Required</b>. The name of the field in the
4743      * grid's {@link Ext.data.Store}'s {@link Ext.data.Record} definition from
4744      * which to draw the column's value.</p>
4745      */
4746     /**
4747      * @cfg {Number} width
4748      * Optional. The initial width in pixels of the column.
4749      * The width of each column can also be affected if any of the following are configured:
4750      * <div class="mdetail-params"><ul>
4751      * <li>{@link Ext.grid.GridPanel}.<tt>{@link Ext.grid.GridPanel#autoExpandColumn autoExpandColumn}</tt></li>
4752      * <li>{@link Ext.grid.GridView}.<tt>{@link Ext.grid.GridView#forceFit forceFit}</tt>
4753      * <div class="sub-desc">
4754      * <p>By specifying <tt>forceFit:true</tt>, {@link #fixed non-fixed width} columns will be
4755      * re-proportioned (based on the relative initial widths) to fill the width of the grid so
4756      * that no horizontal scrollbar is shown.</p>
4757      * </div></li>
4758      * <li>{@link Ext.grid.GridView}.<tt>{@link Ext.grid.GridView#autoFill autoFill}</tt></li>
4759      * <li>{@link Ext.grid.GridPanel}.<tt>{@link Ext.grid.GridPanel#minColumnWidth minColumnWidth}</tt></li>
4760      * <br><p><b>Note</b>: when the width of each column is determined, a space on the right side
4761      * is reserved for the vertical scrollbar.  The
4762      * {@link Ext.grid.GridView}.<tt>{@link Ext.grid.GridView#scrollOffset scrollOffset}</tt>
4763      * can be modified to reduce or eliminate the reserved offset.</p>
4764      */
4765     /**
4766      * @cfg {Boolean} sortable Optional. <tt>true</tt> if sorting is to be allowed on this column.
4767      * Defaults to the value of the <code>{@link Ext.grid.ColumnModel#defaultSortable}</code> property.
4768      * Whether local/remote sorting is used is specified in <code>{@link Ext.data.Store#remoteSort}</code>.
4769      */
4770     /**
4771      * @cfg {Boolean} fixed Optional. <tt>true</tt> if the column width cannot be changed.  Defaults to <tt>false</tt>.
4772      */
4773     /**
4774      * @cfg {Boolean} resizable Optional. <tt>false</tt> to disable column resizing. Defaults to <tt>true</tt>.
4775      */
4776     /**
4777      * @cfg {Boolean} menuDisabled Optional. <tt>true</tt> to disable the column menu. Defaults to <tt>false</tt>.
4778      */
4779     /**
4780      * @cfg {Boolean} hidden
4781      * Optional. <tt>true</tt> to initially hide this column. Defaults to <tt>false</tt>.
4782      * A hidden column {@link Ext.grid.GridPanel#enableColumnHide may be shown via the header row menu}.
4783      * If a column is never to be shown, simply do not include this column in the Column Model at all.
4784      */
4785     /**
4786      * @cfg {String} tooltip Optional. A text string to use as the column header's tooltip.  If Quicktips
4787      * are enabled, this value will be used as the text of the quick tip, otherwise it will be set as the
4788      * header's HTML title attribute. Defaults to ''.
4789      */
4790     /**
4791      * @cfg {Mixed} renderer
4792      * <p>For an alternative to specifying a renderer see <code>{@link #xtype}</code></p>
4793      * <p>Optional. A renderer is an 'interceptor' method which can be used transform data (value,
4794      * appearance, etc.) before it is rendered). This may be specified in either of three ways:
4795      * <div class="mdetail-params"><ul>
4796      * <li>A renderer function used to return HTML markup for a cell given the cell's data value.</li>
4797      * <li>A string which references a property name of the {@link Ext.util.Format} class which
4798      * provides a renderer function.</li>
4799      * <li>An object specifying both the renderer function, and its execution scope (<tt><b>this</b></tt>
4800      * reference) e.g.:<pre style="margin-left:1.2em"><code>
4801 {
4802     fn: this.gridRenderer,
4803     scope: this
4804 }
4805 </code></pre></li></ul></div>
4806      * If not specified, the default renderer uses the raw data value.</p>
4807      * <p>For information about the renderer function (passed parameters, etc.), see
4808      * {@link Ext.grid.ColumnModel#setRenderer}. An example of specifying renderer function inline:</p><pre><code>
4809 var companyColumn = {
4810    header: 'Company Name',
4811    dataIndex: 'company',
4812    renderer: function(value, metaData, record, rowIndex, colIndex, store) {
4813       // provide the logic depending on business rules
4814       // name of your own choosing to manipulate the cell depending upon
4815       // the data in the underlying Record object.
4816       if (value == 'whatever') {
4817           //metaData.css : String : A CSS class name to add to the TD element of the cell.
4818           //metaData.attr : String : An html attribute definition string to apply to
4819           //                         the data container element within the table
4820           //                         cell (e.g. 'style="color:red;"').
4821           metaData.css = 'name-of-css-class-you-will-define';
4822       }
4823       return value;
4824    }
4825 }
4826      * </code></pre>
4827      * See also {@link #scope}.
4828      */
4829     /**
4830      * @cfg {String} xtype Optional. A String which references a predefined {@link Ext.grid.Column} subclass
4831      * type which is preconfigured with an appropriate <code>{@link #renderer}</code> to be easily
4832      * configured into a ColumnModel. The predefined {@link Ext.grid.Column} subclass types are:
4833      * <div class="mdetail-params"><ul>
4834      * <li><b><tt>gridcolumn</tt></b> : {@link Ext.grid.Column} (<b>Default</b>)<p class="sub-desc"></p></li>
4835      * <li><b><tt>booleancolumn</tt></b> : {@link Ext.grid.BooleanColumn}<p class="sub-desc"></p></li>
4836      * <li><b><tt>numbercolumn</tt></b> : {@link Ext.grid.NumberColumn}<p class="sub-desc"></p></li>
4837      * <li><b><tt>datecolumn</tt></b> : {@link Ext.grid.DateColumn}<p class="sub-desc"></p></li>
4838      * <li><b><tt>templatecolumn</tt></b> : {@link Ext.grid.TemplateColumn}<p class="sub-desc"></p></li>
4839      * </ul></div>
4840      * <p>Configuration properties for the specified <code>xtype</code> may be specified with
4841      * the Column configuration properties, for example:</p>
4842      * <pre><code>
4843 var grid = new Ext.grid.GridPanel({
4844     ...
4845     columns: [{
4846         header: 'Last Updated',
4847         dataIndex: 'lastChange',
4848         width: 85,
4849         sortable: true,
4850         //renderer: Ext.util.Format.dateRenderer('m/d/Y'),
4851         xtype: 'datecolumn', // use xtype instead of renderer
4852         format: 'M/d/Y' // configuration property for {@link Ext.grid.DateColumn}
4853     }, {
4854         ...
4855     }]
4856 });
4857      * </code></pre>
4858      */
4859     /**
4860      * @cfg {Object} scope Optional. The scope (<tt><b>this</b></tt> reference) in which to execute the
4861      * renderer.  Defaults to the Column configuration object.
4862      */
4863     /**
4864      * @cfg {String} align Optional. Set the CSS text-align property of the column.  Defaults to undefined.
4865      */
4866     /**
4867      * @cfg {String} css Optional. An inline style definition string which is applied to all table cells in the column
4868      * (excluding headers). Defaults to undefined.
4869      */
4870     /**
4871      * @cfg {Boolean} hideable Optional. Specify as <tt>false</tt> to prevent the user from hiding this column
4872      * (defaults to true).  To disallow column hiding globally for all columns in the grid, use
4873      * {@link Ext.grid.GridPanel#enableColumnHide} instead.
4874      */
4875     /**
4876      * @cfg {Ext.form.Field} editor Optional. The {@link Ext.form.Field} to use when editing values in this column
4877      * if editing is supported by the grid. See <tt>{@link #editable}</tt> also.
4878      */
4879
4880     /**
4881      * @private
4882      * @cfg {Boolean} isColumn
4883      * Used by ColumnModel setConfig method to avoid reprocessing a Column
4884      * if <code>isColumn</code> is not set ColumnModel will recreate a new Ext.grid.Column
4885      * Defaults to true.
4886      */
4887     isColumn : true,
4888
4889     constructor : function(config){
4890         Ext.apply(this, config);
4891
4892         if(Ext.isString(this.renderer)){
4893             this.renderer = Ext.util.Format[this.renderer];
4894         }else if(Ext.isObject(this.renderer)){
4895             this.scope = this.renderer.scope;
4896             this.renderer = this.renderer.fn;
4897         }
4898         if(!this.scope){
4899             this.scope = this;
4900         }
4901
4902         var ed = this.editor;
4903         delete this.editor;
4904         this.setEditor(ed);
4905     },
4906
4907     /**
4908      * Optional. A function which returns displayable data when passed the following parameters:
4909      * <div class="mdetail-params"><ul>
4910      * <li><b>value</b> : Object<p class="sub-desc">The data value for the cell.</p></li>
4911      * <li><b>metadata</b> : Object<p class="sub-desc">An object in which you may set the following attributes:<ul>
4912      * <li><b>css</b> : String<p class="sub-desc">A CSS class name to add to the cell's TD element.</p></li>
4913      * <li><b>attr</b> : String<p class="sub-desc">An HTML attribute definition string to apply to the data container
4914      * element <i>within</i> the table cell (e.g. 'style="color:red;"').</p></li></ul></p></li>
4915      * <li><b>record</b> : Ext.data.record<p class="sub-desc">The {@link Ext.data.Record} from which the data was
4916      * extracted.</p></li>
4917      * <li><b>rowIndex</b> : Number<p class="sub-desc">Row index</p></li>
4918      * <li><b>colIndex</b> : Number<p class="sub-desc">Column index</p></li>
4919      * <li><b>store</b> : Ext.data.Store<p class="sub-desc">The {@link Ext.data.Store} object from which the Record
4920      * was extracted.</p></li>
4921      * </ul></div>
4922      * @property renderer
4923      * @type Function
4924      */
4925     renderer : function(value){
4926         if(Ext.isString(value) && value.length < 1){
4927             return '&#160;';
4928         }
4929         return value;
4930     },
4931
4932     // private
4933     getEditor: function(rowIndex){
4934         return this.editable !== false ? this.editor : null;
4935     },
4936
4937     /**
4938      * Sets a new editor for this column.
4939      * @param {Ext.Editor/Ext.form.Field} editor The editor to set
4940      */
4941     setEditor : function(editor){
4942         var ed = this.editor;
4943         if(ed){
4944             if(ed.gridEditor){
4945                 ed.gridEditor.destroy();
4946                 delete ed.gridEditor;
4947             }else{
4948                 ed.destroy();
4949             }
4950         }
4951         this.editor = null;
4952         if(editor){
4953             //not an instance, create it
4954             if(!editor.isXType){
4955                 editor = Ext.create(editor, 'textfield');
4956             }
4957             this.editor = editor;
4958         }
4959     },
4960
4961     /**
4962      * Returns the {@link Ext.Editor editor} defined for this column that was created to wrap the {@link Ext.form.Field Field}
4963      * used to edit the cell.
4964      * @param {Number} rowIndex The row index
4965      * @return {Ext.Editor}
4966      */
4967     getCellEditor: function(rowIndex){
4968         var ed = this.getEditor(rowIndex);
4969         if(ed){
4970             if(!ed.startEdit){
4971                 if(!ed.gridEditor){
4972                     ed.gridEditor = new Ext.grid.GridEditor(ed);
4973                 }
4974                 ed = ed.gridEditor;
4975             }
4976         }
4977         return ed;
4978     }
4979 });
4980
4981 /**
4982  * @class Ext.grid.BooleanColumn
4983  * @extends Ext.grid.Column
4984  * <p>A Column definition class which renders boolean data fields.  See the {@link Ext.grid.Column#xtype xtype}
4985  * config option of {@link Ext.grid.Column} for more details.</p>
4986  */
4987 Ext.grid.BooleanColumn = Ext.extend(Ext.grid.Column, {
4988     /**
4989      * @cfg {String} trueText
4990      * The string returned by the renderer when the column value is not falsey (defaults to <tt>'true'</tt>).
4991      */
4992     trueText: 'true',
4993     /**
4994      * @cfg {String} falseText
4995      * The string returned by the renderer when the column value is falsey (but not undefined) (defaults to
4996      * <tt>'false'</tt>).
4997      */
4998     falseText: 'false',
4999     /**
5000      * @cfg {String} undefinedText
5001      * The string returned by the renderer when the column value is undefined (defaults to <tt>'&#160;'</tt>).
5002      */
5003     undefinedText: '&#160;',
5004
5005     constructor: function(cfg){
5006         Ext.grid.BooleanColumn.superclass.constructor.call(this, cfg);
5007         var t = this.trueText, f = this.falseText, u = this.undefinedText;
5008         this.renderer = function(v){
5009             if(v === undefined){
5010                 return u;
5011             }
5012             if(!v || v === 'false'){
5013                 return f;
5014             }
5015             return t;
5016         };
5017     }
5018 });
5019
5020 /**
5021  * @class Ext.grid.NumberColumn
5022  * @extends Ext.grid.Column
5023  * <p>A Column definition class which renders a numeric data field according to a {@link #format} string.  See the
5024  * {@link Ext.grid.Column#xtype xtype} config option of {@link Ext.grid.Column} for more details.</p>
5025  */
5026 Ext.grid.NumberColumn = Ext.extend(Ext.grid.Column, {
5027     /**
5028      * @cfg {String} format
5029      * A formatting string as used by {@link Ext.util.Format#number} to format a numeric value for this Column
5030      * (defaults to <tt>'0,000.00'</tt>).
5031      */
5032     format : '0,000.00',
5033     constructor: function(cfg){
5034         Ext.grid.NumberColumn.superclass.constructor.call(this, cfg);
5035         this.renderer = Ext.util.Format.numberRenderer(this.format);
5036     }
5037 });
5038
5039 /**
5040  * @class Ext.grid.DateColumn
5041  * @extends Ext.grid.Column
5042  * <p>A Column definition class which renders a passed date according to the default locale, or a configured
5043  * {@link #format}. See the {@link Ext.grid.Column#xtype xtype} config option of {@link Ext.grid.Column}
5044  * for more details.</p>
5045  */
5046 Ext.grid.DateColumn = Ext.extend(Ext.grid.Column, {
5047     /**
5048      * @cfg {String} format
5049      * A formatting string as used by {@link Date#format} to format a Date for this Column
5050      * (defaults to <tt>'m/d/Y'</tt>).
5051      */
5052     format : 'm/d/Y',
5053     constructor: function(cfg){
5054         Ext.grid.DateColumn.superclass.constructor.call(this, cfg);
5055         this.renderer = Ext.util.Format.dateRenderer(this.format);
5056     }
5057 });
5058
5059 /**
5060  * @class Ext.grid.TemplateColumn
5061  * @extends Ext.grid.Column
5062  * <p>A Column definition class which renders a value by processing a {@link Ext.data.Record Record}'s
5063  * {@link Ext.data.Record#data data} using a {@link #tpl configured} {@link Ext.XTemplate XTemplate}.
5064  * See the {@link Ext.grid.Column#xtype xtype} config option of {@link Ext.grid.Column} for more
5065  * details.</p>
5066  */
5067 Ext.grid.TemplateColumn = Ext.extend(Ext.grid.Column, {
5068     /**
5069      * @cfg {String/XTemplate} tpl
5070      * An {@link Ext.XTemplate XTemplate}, or an XTemplate <i>definition string</i> to use to process a
5071      * {@link Ext.data.Record Record}'s {@link Ext.data.Record#data data} to produce a column's rendered value.
5072      */
5073     constructor: function(cfg){
5074         Ext.grid.TemplateColumn.superclass.constructor.call(this, cfg);
5075         var tpl = (!Ext.isPrimitive(this.tpl) && this.tpl.compile) ? this.tpl : new Ext.XTemplate(this.tpl);
5076         this.renderer = function(value, p, r){
5077             return tpl.apply(r.data);
5078         };
5079         this.tpl = tpl;
5080     }
5081 });
5082
5083 /*
5084  * @property types
5085  * @type Object
5086  * @member Ext.grid.Column
5087  * @static
5088  * <p>An object containing predefined Column classes keyed by a mnemonic code which may be referenced
5089  * by the {@link Ext.grid.ColumnModel#xtype xtype} config option of ColumnModel.</p>
5090  * <p>This contains the following properties</p><div class="mdesc-details"><ul>
5091  * <li>gridcolumn : <b>{@link Ext.grid.Column Column constructor}</b></li>
5092  * <li>booleancolumn : <b>{@link Ext.grid.BooleanColumn BooleanColumn constructor}</b></li>
5093  * <li>numbercolumn : <b>{@link Ext.grid.NumberColumn NumberColumn constructor}</b></li>
5094  * <li>datecolumn : <b>{@link Ext.grid.DateColumn DateColumn constructor}</b></li>
5095  * <li>templatecolumn : <b>{@link Ext.grid.TemplateColumn TemplateColumn constructor}</b></li>
5096  * </ul></div>
5097  */
5098 Ext.grid.Column.types = {
5099     gridcolumn : Ext.grid.Column,
5100     booleancolumn: Ext.grid.BooleanColumn,
5101     numbercolumn: Ext.grid.NumberColumn,
5102     datecolumn: Ext.grid.DateColumn,
5103     templatecolumn: Ext.grid.TemplateColumn
5104 };/**
5105  * @class Ext.grid.RowNumberer
5106  * This is a utility class that can be passed into a {@link Ext.grid.ColumnModel} as a column config that provides
5107  * an automatic row numbering column.
5108  * <br>Usage:<br>
5109  <pre><code>
5110  // This is a typical column config with the first column providing row numbers
5111  var colModel = new Ext.grid.ColumnModel([
5112     new Ext.grid.RowNumberer(),
5113     {header: "Name", width: 80, sortable: true},
5114     {header: "Code", width: 50, sortable: true},
5115     {header: "Description", width: 200, sortable: true}
5116  ]);
5117  </code></pre>
5118  * @constructor
5119  * @param {Object} config The configuration options
5120  */
5121 Ext.grid.RowNumberer = Ext.extend(Object, {
5122     /**
5123      * @cfg {String} header Any valid text or HTML fragment to display in the header cell for the row
5124      * number column (defaults to '').
5125      */
5126     header: "",
5127     /**
5128      * @cfg {Number} width The default width in pixels of the row number column (defaults to 23).
5129      */
5130     width: 23,
5131     /**
5132      * @cfg {Boolean} sortable True if the row number column is sortable (defaults to false).
5133      * @hide
5134      */
5135     sortable: false,
5136     
5137     constructor : function(config){
5138         Ext.apply(this, config);
5139         if(this.rowspan){
5140             this.renderer = this.renderer.createDelegate(this);
5141         }
5142     },
5143
5144     // private
5145     fixed:true,
5146     hideable: false,
5147     menuDisabled:true,
5148     dataIndex: '',
5149     id: 'numberer',
5150     rowspan: undefined,
5151
5152     // private
5153     renderer : function(v, p, record, rowIndex){
5154         if(this.rowspan){
5155             p.cellAttr = 'rowspan="'+this.rowspan+'"';
5156         }
5157         return rowIndex+1;
5158     }
5159 });/**
5160  * @class Ext.grid.CheckboxSelectionModel
5161  * @extends Ext.grid.RowSelectionModel
5162  * A custom selection model that renders a column of checkboxes that can be toggled to select or deselect rows.
5163  * @constructor
5164  * @param {Object} config The configuration options
5165  */
5166 Ext.grid.CheckboxSelectionModel = Ext.extend(Ext.grid.RowSelectionModel, {
5167
5168     /**
5169      * @cfg {Boolean} checkOnly <tt>true</tt> if rows can only be selected by clicking on the
5170      * checkbox column (defaults to <tt>false</tt>).
5171      */
5172     /**
5173      * @cfg {String} header Any valid text or HTML fragment to display in the header cell for the
5174      * checkbox column.  Defaults to:<pre><code>
5175      * '&lt;div class="x-grid3-hd-checker">&#38;#160;&lt;/div>'</tt>
5176      * </code></pre>
5177      * The default CSS class of <tt>'x-grid3-hd-checker'</tt> displays a checkbox in the header
5178      * and provides support for automatic check all/none behavior on header click. This string
5179      * can be replaced by any valid HTML fragment, including a simple text string (e.g.,
5180      * <tt>'Select Rows'</tt>), but the automatic check all/none behavior will only work if the
5181      * <tt>'x-grid3-hd-checker'</tt> class is supplied.
5182      */
5183     header : '<div class="x-grid3-hd-checker">&#160;</div>',
5184     /**
5185      * @cfg {Number} width The default width in pixels of the checkbox column (defaults to <tt>20</tt>).
5186      */
5187     width : 20,
5188     /**
5189      * @cfg {Boolean} sortable <tt>true</tt> if the checkbox column is sortable (defaults to
5190      * <tt>false</tt>).
5191      */
5192     sortable : false,
5193
5194     // private
5195     menuDisabled : true,
5196     fixed : true,
5197     hideable: false,
5198     dataIndex : '',
5199     id : 'checker',
5200
5201     constructor : function(){
5202         Ext.grid.CheckboxSelectionModel.superclass.constructor.apply(this, arguments);
5203
5204         if(this.checkOnly){
5205             this.handleMouseDown = Ext.emptyFn;
5206         }
5207     },
5208
5209     // private
5210     initEvents : function(){
5211         Ext.grid.CheckboxSelectionModel.superclass.initEvents.call(this);
5212         this.grid.on('render', function(){
5213             var view = this.grid.getView();
5214             view.mainBody.on('mousedown', this.onMouseDown, this);
5215             Ext.fly(view.innerHd).on('mousedown', this.onHdMouseDown, this);
5216
5217         }, this);
5218     },
5219
5220     // If handleMouseDown was called from another event (enableDragDrop), set a flag so
5221     // onMouseDown does not process it a second time
5222     handleMouseDown : function() {
5223         Ext.grid.CheckboxSelectionModel.superclass.handleMouseDown.apply(this, arguments);
5224         this.mouseHandled = true;
5225     },
5226
5227     // private
5228     onMouseDown : function(e, t){
5229         if(e.button === 0 && t.className == 'x-grid3-row-checker'){ // Only fire if left-click
5230             e.stopEvent();
5231             var row = e.getTarget('.x-grid3-row');
5232
5233             // mouseHandled flag check for a duplicate selection (handleMouseDown) call
5234             if(!this.mouseHandled && row){
5235                 var index = row.rowIndex;
5236                 if(this.isSelected(index)){
5237                     this.deselectRow(index);
5238                 }else{
5239                     this.selectRow(index, true);
5240                     this.grid.getView().focusRow(index);
5241                 }
5242             }
5243         }
5244         this.mouseHandled = false;
5245     },
5246
5247     // private
5248     onHdMouseDown : function(e, t){
5249         if(t.className == 'x-grid3-hd-checker'){
5250             e.stopEvent();
5251             var hd = Ext.fly(t.parentNode);
5252             var isChecked = hd.hasClass('x-grid3-hd-checker-on');
5253             if(isChecked){
5254                 hd.removeClass('x-grid3-hd-checker-on');
5255                 this.clearSelections();
5256             }else{
5257                 hd.addClass('x-grid3-hd-checker-on');
5258                 this.selectAll();
5259             }
5260         }
5261     },
5262
5263     // private
5264     renderer : function(v, p, record){
5265         return '<div class="x-grid3-row-checker">&#160;</div>';
5266     }
5267 });