Upgrade to ExtJS 4.0.7 - Released 10/19/2011
[extjs.git] / src / panel / Table.js
1 /*
2
3 This file is part of Ext JS 4
4
5 Copyright (c) 2011 Sencha Inc
6
7 Contact:  http://www.sencha.com/contact
8
9 GNU General Public License Usage
10 This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file.  Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
11
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
13
14 */
15 /**
16  * @author Nicolas Ferrero
17  *
18  * TablePanel is the basis of both {@link Ext.tree.Panel TreePanel} and {@link Ext.grid.Panel GridPanel}.
19  *
20  * TablePanel aggregates:
21  *
22  *  - a Selection Model
23  *  - a View
24  *  - a Store
25  *  - Scrollers
26  *  - Ext.grid.header.Container
27  */
28 Ext.define('Ext.panel.Table', {
29     extend: 'Ext.panel.Panel',
30
31     alias: 'widget.tablepanel',
32
33     uses: [
34         'Ext.selection.RowModel',
35         'Ext.grid.Scroller',
36         'Ext.grid.header.Container',
37         'Ext.grid.Lockable'
38     ],
39
40     extraBaseCls: Ext.baseCSSPrefix + 'grid',
41     extraBodyCls: Ext.baseCSSPrefix + 'grid-body',
42
43     layout: 'fit',
44     /**
45      * @property {Boolean} hasView
46      * True to indicate that a view has been injected into the panel.
47      */
48     hasView: false,
49
50     // each panel should dictate what viewType and selType to use
51     /**
52      * @cfg {String} viewType
53      * An xtype of view to use. This is automatically set to 'gridview' by {@link Ext.grid.Panel Grid}
54      * and to 'treeview' by {@link Ext.tree.Panel Tree}.
55      */
56     viewType: null,
57
58     /**
59      * @cfg {Object} viewConfig
60      * A config object that will be applied to the grid's UI view. Any of the config options available for
61      * {@link Ext.view.Table} can be specified here. This option is ignored if {@link #view} is specified.
62      */
63
64     /**
65      * @cfg {Ext.view.Table} view
66      * The {@link Ext.view.Table} used by the grid. Use {@link #viewConfig} to just supply some config options to
67      * view (instead of creating an entire View instance).
68      */
69
70     /**
71      * @cfg {String} selType
72      * An xtype of selection model to use. Defaults to 'rowmodel'. This is used to create selection model if just
73      * a config object or nothing at all given in {@link #selModel} config.
74      */
75     selType: 'rowmodel',
76
77     /**
78      * @cfg {Ext.selection.Model/Object} selModel
79      * A {@link Ext.selection.Model selection model} instance or config object.  In latter case the {@link #selType}
80      * config option determines to which type of selection model this config is applied.
81      */
82
83     /**
84      * @cfg {Boolean} multiSelect
85      * True to enable 'MULTI' selection mode on selection model. See {@link Ext.selection.Model#mode}.
86      */
87
88     /**
89      * @cfg {Boolean} simpleSelect
90      * True to enable 'SIMPLE' selection mode on selection model. See {@link Ext.selection.Model#mode}.
91      */
92
93     /**
94      * @cfg {Ext.data.Store} store (required)
95      * The {@link Ext.data.Store Store} the grid should use as its data source.
96      */
97
98     /**
99      * @cfg {Number} scrollDelta
100      * Number of pixels to scroll when scrolling with mousewheel.
101      */
102     scrollDelta: 40,
103
104     /**
105      * @cfg {String/Boolean} scroll
106      * Scrollers configuration. Valid values are 'both', 'horizontal' or 'vertical'.
107      * True implies 'both'. False implies 'none'.
108      */
109     scroll: true,
110
111     /**
112      * @cfg {Ext.grid.column.Column[]} columns
113      * An array of {@link Ext.grid.column.Column column} definition objects which define all columns that appear in this
114      * grid. Each column definition provides the header text for the column, and a definition of where the data for that
115      * column comes from.
116      */
117
118     /**
119      * @cfg {Boolean} forceFit
120      * Ttrue to force the columns to fit into the available width. Headers are first sized according to configuration,
121      * whether that be a specific width, or flex. Then they are all proportionally changed in width so that the entire
122      * content width is used.
123      */
124
125     /**
126      * @cfg {Ext.grid.feature.Feature[]} features
127      * An array of grid Features to be added to this grid. See {@link Ext.grid.feature.Feature} for usage.
128      */
129
130     /**
131      * @cfg {Boolean} [hideHeaders=false]
132      * True to hide column headers.
133      */
134
135     /**
136      * @cfg {Boolean} deferRowRender
137      * Defaults to true to enable deferred row rendering.
138      *
139      * This allows the View to execute a refresh quickly, with the expensive update of the row structure deferred so
140      * that layouts with GridPanels appear, and lay out more quickly.
141      */
142
143      deferRowRender: true,
144      
145     /**
146      * @cfg {Boolean} sortableColumns
147      * False to disable column sorting via clicking the header and via the Sorting menu items.
148      */
149     sortableColumns: true,
150
151     /**
152      * @cfg {Boolean} [enableLocking=false]
153      * True to enable locking support for this grid. Alternatively, locking will also be automatically
154      * enabled if any of the columns in the column configuration contain the locked config option.
155      */
156     enableLocking: false,
157
158     verticalScrollDock: 'right',
159     verticalScrollerType: 'gridscroller',
160
161     horizontalScrollerPresentCls: Ext.baseCSSPrefix + 'horizontal-scroller-present',
162     verticalScrollerPresentCls: Ext.baseCSSPrefix + 'vertical-scroller-present',
163
164     // private property used to determine where to go down to find views
165     // this is here to support locking.
166     scrollerOwner: true,
167
168     invalidateScrollerOnRefresh: true,
169
170     /**
171      * @cfg {Boolean} enableColumnMove
172      * False to disable column dragging within this grid.
173      */
174     enableColumnMove: true,
175
176     /**
177      * @cfg {Boolean} enableColumnResize
178      * False to disable column resizing within this grid.
179      */
180     enableColumnResize: true,
181
182     /**
183      * @cfg {Boolean} enableColumnHide
184      * False to disable column hiding within this grid.
185      */
186     enableColumnHide: true,
187
188     initComponent: function() {
189         //<debug>
190         if (!this.viewType) {
191             Ext.Error.raise("You must specify a viewType config.");
192         }
193         if (this.headers) {
194             Ext.Error.raise("The headers config is not supported. Please specify columns instead.");
195         }
196         //</debug>
197
198         var me          = this,
199             scroll      = me.scroll,
200             vertical    = false,
201             horizontal  = false,
202             headerCtCfg = me.columns || me.colModel,
203             i           = 0,
204             view,
205             border = me.border;
206
207         if (me.hideHeaders) {
208             border = false;
209         }
210
211         // Look up the configured Store. If none configured, use the fieldless, empty Store defined in Ext.data.Store.
212         me.store = Ext.data.StoreManager.lookup(me.store || 'ext-empty-store');
213
214         // The columns/colModel config may be either a fully instantiated HeaderContainer, or an array of Column definitions, or a config object of a HeaderContainer
215         // Either way, we extract a columns property referencing an array of Column definitions.
216         if (headerCtCfg instanceof Ext.grid.header.Container) {
217             me.headerCt = headerCtCfg;
218             me.headerCt.border = border;
219             me.columns = me.headerCt.items.items;
220         } else {
221             if (Ext.isArray(headerCtCfg)) {
222                 headerCtCfg = {
223                     items: headerCtCfg,
224                     border: border
225                 };
226             }
227             Ext.apply(headerCtCfg, {
228                 forceFit: me.forceFit,
229                 sortable: me.sortableColumns,
230                 enableColumnMove: me.enableColumnMove,
231                 enableColumnResize: me.enableColumnResize,
232                 enableColumnHide: me.enableColumnHide,
233                 border:  border
234             });
235             me.columns = headerCtCfg.items;
236
237              // If any of the Column objects contain a locked property, and are not processed, this is a lockable TablePanel, a
238              // special view will be injected by the Ext.grid.Lockable mixin, so no processing of .
239              if (me.enableLocking || Ext.ComponentQuery.query('{locked !== undefined}{processed != true}', me.columns).length) {
240                  me.self.mixin('lockable', Ext.grid.Lockable);
241                  me.injectLockable();
242              }
243         }
244
245         me.addEvents(
246             /**
247              * @event reconfigure
248              * Fires after a reconfigure.
249              * @param {Ext.panel.Table} this
250              */
251             'reconfigure',
252             /**
253              * @event viewready
254              * Fires when the grid view is available (use this for selecting a default row).
255              * @param {Ext.panel.Table} this
256              */
257             'viewready',
258             /**
259              * @event scrollerhide
260              * Fires when a scroller is hidden.
261              * @param {Ext.grid.Scroller} scroller
262              * @param {String} orientation Orientation, can be 'vertical' or 'horizontal'
263              */
264             'scrollerhide',
265             /**
266              * @event scrollershow
267              * Fires when a scroller is shown.
268              * @param {Ext.grid.Scroller} scroller
269              * @param {String} orientation Orientation, can be 'vertical' or 'horizontal'
270              */
271             'scrollershow'
272         );
273
274         me.bodyCls = me.bodyCls || '';
275         me.bodyCls += (' ' + me.extraBodyCls);
276         
277         me.cls = me.cls || '';
278         me.cls += (' ' + me.extraBaseCls);
279
280         // autoScroll is not a valid configuration
281         delete me.autoScroll;
282
283         // If this TablePanel is lockable (Either configured lockable, or any of the defined columns has a 'locked' property)
284         // than a special lockable view containing 2 side-by-side grids will have been injected so we do not need to set up any UI.
285         if (!me.hasView) {
286
287             // If we were not configured with a ready-made headerCt (either by direct config with a headerCt property, or by passing
288             // a HeaderContainer instance as the 'columns' property, then go ahead and create one from the config object created above.
289             if (!me.headerCt) {
290                 me.headerCt = Ext.create('Ext.grid.header.Container', headerCtCfg);
291             }
292
293             // Extract the array of Column objects
294             me.columns = me.headerCt.items.items;
295
296             if (me.hideHeaders) {
297                 me.headerCt.height = 0;
298                 me.headerCt.border = false;
299                 me.headerCt.addCls(Ext.baseCSSPrefix + 'grid-header-ct-hidden');
300                 me.addCls(Ext.baseCSSPrefix + 'grid-header-hidden');
301                 // IE Quirks Mode fix
302                 // If hidden configuration option was used, several layout calculations will be bypassed.
303                 if (Ext.isIEQuirks) {
304                     me.headerCt.style = {
305                         display: 'none'
306                     };
307                 }
308             }
309
310             // turn both on.
311             if (scroll === true || scroll === 'both') {
312                 vertical = horizontal = true;
313             } else if (scroll === 'horizontal') {
314                 horizontal = true;
315             } else if (scroll === 'vertical') {
316                 vertical = true;
317             // All other values become 'none' or false.
318             } else {
319                 me.headerCt.availableSpaceOffset = 0;
320             }
321
322             if (vertical) {
323                 me.verticalScroller = Ext.ComponentManager.create(me.initVerticalScroller());
324                 me.mon(me.verticalScroller, {
325                     bodyscroll: me.onVerticalScroll,
326                     scope: me
327                 });
328             }
329
330             if (horizontal) {
331                 me.horizontalScroller = Ext.ComponentManager.create(me.initHorizontalScroller());
332                 me.mon(me.horizontalScroller, {
333                     bodyscroll: me.onHorizontalScroll,
334                     scope: me
335                 });
336             }
337
338             me.headerCt.on('resize', me.onHeaderResize, me);
339             me.relayHeaderCtEvents(me.headerCt);
340             me.features = me.features || [];
341             if (!Ext.isArray(me.features)) {
342                 me.features = [me.features];
343             }
344             me.dockedItems = me.dockedItems || [];
345             me.dockedItems.unshift(me.headerCt);
346             me.viewConfig = me.viewConfig || {};
347             me.viewConfig.invalidateScrollerOnRefresh = me.invalidateScrollerOnRefresh;
348
349             // AbstractDataView will look up a Store configured as an object
350             // getView converts viewConfig into a View instance
351             view = me.getView();
352
353             view.on({
354                 afterrender: function () {
355                     // hijack the view el's scroll method
356                     view.el.scroll = Ext.Function.bind(me.elScroll, me);
357                     // We use to listen to document.body wheel events, but that's a
358                     // little much. We scope just to the view now.
359                     me.mon(view.el, {
360                         mousewheel: me.onMouseWheel,
361                         scope: me
362                     });
363                 },
364                 single: true
365             });
366             me.items = [view];
367             me.hasView = true;
368
369             me.mon(view.store, {
370                 load: me.onStoreLoad,
371                 scope: me
372             });
373             me.mon(view, {
374                 viewReady: me.onViewReady,
375                 resize: me.onViewResize,
376                 refresh: {
377                     fn: me.onViewRefresh,
378                     scope: me,
379                     buffer: 50
380                 },
381                 scope: me
382             });
383             this.relayEvents(view, [
384                 /**
385                  * @event beforeitemmousedown
386                  * @alias Ext.view.View#beforeitemmousedown
387                  */
388                 'beforeitemmousedown',
389                 /**
390                  * @event beforeitemmouseup
391                  * @alias Ext.view.View#beforeitemmouseup
392                  */
393                 'beforeitemmouseup',
394                 /**
395                  * @event beforeitemmouseenter
396                  * @alias Ext.view.View#beforeitemmouseenter
397                  */
398                 'beforeitemmouseenter',
399                 /**
400                  * @event beforeitemmouseleave
401                  * @alias Ext.view.View#beforeitemmouseleave
402                  */
403                 'beforeitemmouseleave',
404                 /**
405                  * @event beforeitemclick
406                  * @alias Ext.view.View#beforeitemclick
407                  */
408                 'beforeitemclick',
409                 /**
410                  * @event beforeitemdblclick
411                  * @alias Ext.view.View#beforeitemdblclick
412                  */
413                 'beforeitemdblclick',
414                 /**
415                  * @event beforeitemcontextmenu
416                  * @alias Ext.view.View#beforeitemcontextmenu
417                  */
418                 'beforeitemcontextmenu',
419                 /**
420                  * @event itemmousedown
421                  * @alias Ext.view.View#itemmousedown
422                  */
423                 'itemmousedown',
424                 /**
425                  * @event itemmouseup
426                  * @alias Ext.view.View#itemmouseup
427                  */
428                 'itemmouseup',
429                 /**
430                  * @event itemmouseenter
431                  * @alias Ext.view.View#itemmouseenter
432                  */
433                 'itemmouseenter',
434                 /**
435                  * @event itemmouseleave
436                  * @alias Ext.view.View#itemmouseleave
437                  */
438                 'itemmouseleave',
439                 /**
440                  * @event itemclick
441                  * @alias Ext.view.View#itemclick
442                  */
443                 'itemclick',
444                 /**
445                  * @event itemdblclick
446                  * @alias Ext.view.View#itemdblclick
447                  */
448                 'itemdblclick',
449                 /**
450                  * @event itemcontextmenu
451                  * @alias Ext.view.View#itemcontextmenu
452                  */
453                 'itemcontextmenu',
454                 /**
455                  * @event beforecontainermousedown
456                  * @alias Ext.view.View#beforecontainermousedown
457                  */
458                 'beforecontainermousedown',
459                 /**
460                  * @event beforecontainermouseup
461                  * @alias Ext.view.View#beforecontainermouseup
462                  */
463                 'beforecontainermouseup',
464                 /**
465                  * @event beforecontainermouseover
466                  * @alias Ext.view.View#beforecontainermouseover
467                  */
468                 'beforecontainermouseover',
469                 /**
470                  * @event beforecontainermouseout
471                  * @alias Ext.view.View#beforecontainermouseout
472                  */
473                 'beforecontainermouseout',
474                 /**
475                  * @event beforecontainerclick
476                  * @alias Ext.view.View#beforecontainerclick
477                  */
478                 'beforecontainerclick',
479                 /**
480                  * @event beforecontainerdblclick
481                  * @alias Ext.view.View#beforecontainerdblclick
482                  */
483                 'beforecontainerdblclick',
484                 /**
485                  * @event beforecontainercontextmenu
486                  * @alias Ext.view.View#beforecontainercontextmenu
487                  */
488                 'beforecontainercontextmenu',
489                 /**
490                  * @event containermouseup
491                  * @alias Ext.view.View#containermouseup
492                  */
493                 'containermouseup',
494                 /**
495                  * @event containermouseover
496                  * @alias Ext.view.View#containermouseover
497                  */
498                 'containermouseover',
499                 /**
500                  * @event containermouseout
501                  * @alias Ext.view.View#containermouseout
502                  */
503                 'containermouseout',
504                 /**
505                  * @event containerclick
506                  * @alias Ext.view.View#containerclick
507                  */
508                 'containerclick',
509                 /**
510                  * @event containerdblclick
511                  * @alias Ext.view.View#containerdblclick
512                  */
513                 'containerdblclick',
514                 /**
515                  * @event containercontextmenu
516                  * @alias Ext.view.View#containercontextmenu
517                  */
518                 'containercontextmenu',
519                 /**
520                  * @event selectionchange
521                  * @alias Ext.selection.Model#selectionchange
522                  */
523                 'selectionchange',
524                 /**
525                  * @event beforeselect
526                  * @alias Ext.selection.RowModel#beforeselect
527                  */
528                 'beforeselect',
529                 /**
530                  * @event select
531                  * @alias Ext.selection.RowModel#select
532                  */
533                 'select',
534                 /**
535                  * @event beforedeselect
536                  * @alias Ext.selection.RowModel#beforedeselect
537                  */
538                 'beforedeselect',
539                 /**
540                  * @event deselect
541                  * @alias Ext.selection.RowModel#deselect
542                  */
543                 'deselect'
544             ]);
545         }
546
547         me.callParent(arguments);
548     },
549     
550     onRender: function(){
551         var vScroll = this.verticalScroller,
552             hScroll = this.horizontalScroller;
553
554         if (vScroll) {
555             vScroll.ensureDimension();
556         }
557         if (hScroll) {
558             hScroll.ensureDimension();
559         }
560         this.callParent(arguments);    
561     },
562
563     // state management
564     initStateEvents: function(){
565         var events = this.stateEvents;
566         // push on stateEvents if they don't exist
567         Ext.each(['columnresize', 'columnmove', 'columnhide', 'columnshow', 'sortchange'], function(event){
568             if (Ext.Array.indexOf(events, event)) {
569                 events.push(event);
570             }
571         });
572         this.callParent();
573     },
574
575     /**
576      * Returns the horizontal scroller config.
577      */
578     initHorizontalScroller: function () {
579         var me = this,
580             ret = {
581                 xtype: 'gridscroller',
582                 dock: 'bottom',
583                 section: me,
584                 store: me.store
585             };
586
587         return ret;
588     },
589
590     /**
591      * Returns the vertical scroller config.
592      */
593     initVerticalScroller: function () {
594         var me = this,
595             ret = me.verticalScroller || {};
596
597         Ext.applyIf(ret, {
598             xtype: me.verticalScrollerType,
599             dock: me.verticalScrollDock,
600             store: me.store
601         });
602
603         return ret;
604     },
605
606     relayHeaderCtEvents: function (headerCt) {
607         this.relayEvents(headerCt, [
608             /**
609              * @event columnresize
610              * @alias Ext.grid.header.Container#columnresize
611              */
612             'columnresize',
613             /**
614              * @event columnmove
615              * @alias Ext.grid.header.Container#columnmove
616              */
617             'columnmove',
618             /**
619              * @event columnhide
620              * @alias Ext.grid.header.Container#columnhide
621              */
622             'columnhide',
623             /**
624              * @event columnshow
625              * @alias Ext.grid.header.Container#columnshow
626              */
627             'columnshow',
628             /**
629              * @event sortchange
630              * @alias Ext.grid.header.Container#sortchange
631              */
632             'sortchange'
633         ]);
634     },
635
636     getState: function(){
637         var me = this,
638             state = me.callParent(),
639             sorter = me.store.sorters.first();
640
641         state.columns = (me.headerCt || me).getColumnsState();
642
643         if (sorter) {
644             state.sort = {
645                 property: sorter.property,
646                 direction: sorter.direction
647             };
648         }
649
650         return state;
651     },
652
653     applyState: function(state) {
654         var me = this,
655             sorter = state.sort,
656             store = me.store,
657             columns = state.columns;
658
659         delete state.columns;
660
661         // Ensure superclass has applied *its* state.
662         // AbstractComponent saves dimensions (and anchor/flex) plus collapsed state.
663         me.callParent(arguments);
664
665         if (columns) {
666             (me.headerCt || me).applyColumnsState(columns);
667         }
668
669         if (sorter) {
670             if (store.remoteSort) {
671                 store.sorters.add(Ext.create('Ext.util.Sorter', {
672                     property: sorter.property,
673                     direction: sorter.direction
674                 }));
675             }
676             else {
677                 store.sort(sorter.property, sorter.direction);
678             }
679         }
680     },
681
682     /**
683      * Returns the store associated with this Panel.
684      * @return {Ext.data.Store} The store
685      */
686     getStore: function(){
687         return this.store;
688     },
689
690     /**
691      * Gets the view for this panel.
692      * @return {Ext.view.Table}
693      */
694     getView: function() {
695         var me = this,
696             sm;
697
698         if (!me.view) {
699             sm = me.getSelectionModel();
700             me.view = me.createComponent(Ext.apply({}, me.viewConfig, {
701                 deferInitialRefresh: me.deferRowRender,
702                 xtype: me.viewType,
703                 store: me.store,
704                 headerCt: me.headerCt,
705                 selModel: sm,
706                 features: me.features,
707                 panel: me
708             }));
709             me.mon(me.view, {
710                 uievent: me.processEvent,
711                 scope: me
712             });
713             sm.view = me.view;
714             me.headerCt.view = me.view;
715             me.relayEvents(me.view, ['cellclick', 'celldblclick']);
716         }
717         return me.view;
718     },
719
720     /**
721      * @private
722      * @override
723      * autoScroll is never valid for all classes which extend TablePanel.
724      */
725     setAutoScroll: Ext.emptyFn,
726
727     // This method hijacks Ext.view.Table's el scroll method.
728     // This enables us to keep the virtualized scrollbars in sync
729     // with the view. It currently does NOT support animation.
730     elScroll: function(direction, distance, animate) {
731         var me = this,
732             scroller;
733
734         if (direction === "up" || direction === "left") {
735             distance = -distance;
736         }
737         
738         if (direction === "down" || direction === "up") {
739             scroller = me.getVerticalScroller();
740             
741             //if the grid does not currently need a vertical scroller don't try to update it (EXTJSIV-3891)
742             if (scroller) {
743                 scroller.scrollByDeltaY(distance);
744             }
745         } else {
746             scroller = me.getHorizontalScroller();
747             
748             //if the grid does not currently need a horizontal scroller don't try to update it (EXTJSIV-3891)
749             if (scroller) {
750                 scroller.scrollByDeltaX(distance);
751             }
752         }
753     },
754
755     /**
756      * @private
757      * Processes UI events from the view. Propagates them to whatever internal Components need to process them.
758      * @param {String} type Event type, eg 'click'
759      * @param {Ext.view.Table} view TableView Component
760      * @param {HTMLElement} cell Cell HtmlElement the event took place within
761      * @param {Number} recordIndex Index of the associated Store Model (-1 if none)
762      * @param {Number} cellIndex Cell index within the row
763      * @param {Ext.EventObject} e Original event
764      */
765     processEvent: function(type, view, cell, recordIndex, cellIndex, e) {
766         var me = this,
767             header;
768
769         if (cellIndex !== -1) {
770             header = me.headerCt.getGridColumns()[cellIndex];
771             return header.processEvent.apply(header, arguments);
772         }
773     },
774
775     /**
776      * Requests a recalculation of scrollbars and puts them in if they are needed.
777      */
778     determineScrollbars: function() {
779         // Set a flag so that afterComponentLayout does not recurse back into here.
780         if (this.determineScrollbarsRunning) {
781             return;
782         }
783         this.determineScrollbarsRunning = true;
784         var me = this,
785             view = me.view,
786             box,
787             tableEl,
788             scrollWidth,
789             clientWidth,
790             scrollHeight,
791             clientHeight,
792             verticalScroller = me.verticalScroller,
793             horizontalScroller = me.horizontalScroller,
794             curScrollbars = (verticalScroller   && verticalScroller.ownerCt === me ? 1 : 0) |
795                             (horizontalScroller && horizontalScroller.ownerCt === me ? 2 : 0),
796             reqScrollbars = 0; // 1 = vertical, 2 = horizontal, 3 = both
797
798         // If we are not collapsed, and the view has been rendered AND filled, then we can determine scrollbars
799         if (!me.collapsed && view && view.viewReady) {
800
801             // Calculate maximum, *scrollbarless* space which the view has available.
802             // It will be the Fit Layout's calculated size, plus the widths of any currently shown scrollbars
803             box = view.el.getSize();
804
805             clientWidth  = box.width  + ((curScrollbars & 1) ? verticalScroller.width : 0);
806             clientHeight = box.height + ((curScrollbars & 2) ? horizontalScroller.height : 0);
807
808             // Calculate the width of the scrolling block
809             // There will never be a horizontal scrollbar if all columns are flexed.
810
811             scrollWidth = (me.headerCt.query('[flex]').length && !me.headerCt.layout.tooNarrow) ? 0 : me.headerCt.getFullWidth();
812
813             // Calculate the height of the scrolling block
814             if (verticalScroller && verticalScroller.el) {
815                 scrollHeight = verticalScroller.getSizeCalculation().height;
816             } else {
817                 tableEl = view.el.child('table', true);
818                 scrollHeight = tableEl ? tableEl.offsetHeight : 0;
819             }
820
821             // View is too high.
822             // Definitely need a vertical scrollbar
823             if (scrollHeight > clientHeight) {
824                 reqScrollbars = 1;
825
826                 // But if scrollable block width goes into the zone required by the vertical scrollbar, we'll also need a horizontal
827                 if (horizontalScroller && ((clientWidth - scrollWidth) < verticalScroller.width)) {
828                     reqScrollbars = 3;
829                 }
830             }
831
832             // View height fits. But we stil may need a horizontal scrollbar, and this might necessitate a vertical one.
833             else {
834                 // View is too wide.
835                 // Definitely need a horizontal scrollbar
836                 if (scrollWidth > clientWidth) {
837                     reqScrollbars = 2;
838
839                     // But if scrollable block height goes into the zone required by the horizontal scrollbar, we'll also need a vertical
840                     if (verticalScroller && ((clientHeight - scrollHeight) < horizontalScroller.height)) {
841                         reqScrollbars = 3;
842                     }
843                 }
844             }
845
846             // If scrollbar requirements have changed, change 'em...
847             if (reqScrollbars !== curScrollbars) {
848
849                 // Suspend component layout while we add/remove the docked scrollers
850                 me.suspendLayout = true;
851                 if (reqScrollbars & 1) {
852                     me.showVerticalScroller();
853                 } else {
854                     me.hideVerticalScroller();
855                 }
856                 if (reqScrollbars & 2) {
857                     me.showHorizontalScroller();
858                 } else {
859                     me.hideHorizontalScroller();
860                 }
861                 me.suspendLayout = false;
862
863                 // Lay out the Component.
864                 me.doComponentLayout();
865                 // Lay out me.items
866                 me.getLayout().layout();
867             }
868         }
869         delete me.determineScrollbarsRunning;
870     },
871
872     onViewResize: function() {
873         this.determineScrollbars();
874     },
875
876     afterComponentLayout: function() {
877         this.callParent(arguments);
878         this.determineScrollbars();
879         this.invalidateScroller();
880     },
881
882     onHeaderResize: function() {
883         if (!this.componentLayout.layoutBusy && this.view && this.view.rendered) {
884             this.determineScrollbars();
885             this.invalidateScroller();
886         }
887     },
888
889     afterCollapse: function() {
890         var me = this;
891         if (me.verticalScroller) {
892             me.verticalScroller.saveScrollPos();
893         }
894         if (me.horizontalScroller) {
895             me.horizontalScroller.saveScrollPos();
896         }
897         me.callParent(arguments);
898     },
899
900     afterExpand: function() {
901         var me = this;
902         me.callParent(arguments);
903         if (me.verticalScroller) {
904             me.verticalScroller.restoreScrollPos();
905         }
906         if (me.horizontalScroller) {
907             me.horizontalScroller.restoreScrollPos();
908         }
909     },
910
911     /**
912      * Hides the verticalScroller and removes the horizontalScrollerPresentCls.
913      */
914     hideHorizontalScroller: function() {
915         var me = this;
916
917         if (me.horizontalScroller && me.horizontalScroller.ownerCt === me) {
918             me.verticalScroller.setReservedSpace(0);
919             me.removeDocked(me.horizontalScroller, false);
920             me.removeCls(me.horizontalScrollerPresentCls);
921             me.fireEvent('scrollerhide', me.horizontalScroller, 'horizontal');
922         }
923
924     },
925
926     /**
927      * Shows the horizontalScroller and add the horizontalScrollerPresentCls.
928      */
929     showHorizontalScroller: function() {
930         var me = this;
931
932         if (me.verticalScroller) {
933             me.verticalScroller.setReservedSpace(Ext.getScrollbarSize().height - 1);
934         }
935         if (me.horizontalScroller && me.horizontalScroller.ownerCt !== me) {
936             me.addDocked(me.horizontalScroller);
937             me.addCls(me.horizontalScrollerPresentCls);
938             me.fireEvent('scrollershow', me.horizontalScroller, 'horizontal');
939         }
940     },
941
942     /**
943      * Hides the verticalScroller and removes the verticalScrollerPresentCls.
944      */
945     hideVerticalScroller: function() {
946         var me = this;
947
948         me.setHeaderReserveOffset(false);
949         if (me.verticalScroller && me.verticalScroller.ownerCt === me) {
950             me.removeDocked(me.verticalScroller, false);
951             me.removeCls(me.verticalScrollerPresentCls);
952             me.fireEvent('scrollerhide', me.verticalScroller, 'vertical');
953         }
954     },
955
956     /**
957      * Shows the verticalScroller and adds the verticalScrollerPresentCls.
958      */
959     showVerticalScroller: function() {
960         var me = this;
961
962         me.setHeaderReserveOffset(true);
963         if (me.verticalScroller && me.verticalScroller.ownerCt !== me) {
964             me.addDocked(me.verticalScroller);
965             me.addCls(me.verticalScrollerPresentCls);
966             me.fireEvent('scrollershow', me.verticalScroller, 'vertical');
967         }
968     },
969
970     setHeaderReserveOffset: function (reserveOffset) {
971         var headerCt = this.headerCt,
972             layout = headerCt.layout;
973
974         // only trigger a layout when reserveOffset is changing
975         if (layout && layout.reserveOffset !== reserveOffset) {
976             layout.reserveOffset = reserveOffset;
977             if (!this.suspendLayout) {
978                 headerCt.doLayout();
979             }
980         }
981     },
982
983     /**
984      * Invalides scrollers that are present and forces a recalculation. (Not related to showing/hiding the scrollers)
985      */
986     invalidateScroller: function() {
987         var me = this,
988             vScroll = me.verticalScroller,
989             hScroll = me.horizontalScroller;
990
991         if (vScroll) {
992             vScroll.invalidate();
993         }
994         if (hScroll) {
995             hScroll.invalidate();
996         }
997     },
998
999     // refresh the view when a header moves
1000     onHeaderMove: function(headerCt, header, fromIdx, toIdx) {
1001         this.view.refresh();
1002     },
1003
1004     // Section onHeaderHide is invoked after view.
1005     onHeaderHide: function(headerCt, header) {
1006         this.invalidateScroller();
1007     },
1008
1009     onHeaderShow: function(headerCt, header) {
1010         this.invalidateScroller();
1011     },
1012
1013     getVerticalScroller: function() {
1014         return this.getScrollerOwner().down('gridscroller[dock=' + this.verticalScrollDock + ']');
1015     },
1016
1017     getHorizontalScroller: function() {
1018         return this.getScrollerOwner().down('gridscroller[dock=bottom]');
1019     },
1020
1021     onMouseWheel: function(e) {
1022         var me = this,
1023             vertScroller = me.getVerticalScroller(),
1024             horizScroller = me.getHorizontalScroller(),
1025             scrollDelta = -me.scrollDelta,
1026             deltas = e.getWheelDeltas(),
1027             deltaX = scrollDelta * deltas.x,
1028             deltaY = scrollDelta * deltas.y,
1029             vertScrollerEl, horizScrollerEl,
1030             vertScrollerElDom, horizScrollerElDom,
1031             horizontalCanScrollLeft, horizontalCanScrollRight,
1032             verticalCanScrollDown, verticalCanScrollUp;
1033
1034         // calculate whether or not both scrollbars can scroll right/left and up/down
1035         if (horizScroller) {
1036             horizScrollerEl = horizScroller.scrollEl;
1037             if (horizScrollerEl) {
1038                 horizScrollerElDom = horizScrollerEl.dom;
1039                 horizontalCanScrollRight = horizScrollerElDom.scrollLeft !== horizScrollerElDom.scrollWidth - horizScrollerElDom.clientWidth;
1040                 horizontalCanScrollLeft  = horizScrollerElDom.scrollLeft !== 0;
1041             }
1042         }
1043         if (vertScroller) {
1044             vertScrollerEl = vertScroller.scrollEl;
1045             if (vertScrollerEl) {
1046                 vertScrollerElDom = vertScrollerEl.dom;
1047                 verticalCanScrollDown = vertScrollerElDom.scrollTop !== vertScrollerElDom.scrollHeight - vertScrollerElDom.clientHeight;
1048                 verticalCanScrollUp   = vertScrollerElDom.scrollTop !== 0;
1049             }
1050         }
1051
1052         if (horizScroller) {
1053             if ((deltaX < 0 && horizontalCanScrollLeft) || (deltaX > 0 && horizontalCanScrollRight)) {
1054                 e.stopEvent();
1055                 horizScroller.scrollByDeltaX(deltaX);
1056             }
1057         }
1058         if (vertScroller) {
1059             if ((deltaY < 0 && verticalCanScrollUp) || (deltaY > 0 && verticalCanScrollDown)) {
1060                 e.stopEvent();
1061                 vertScroller.scrollByDeltaY(deltaY);
1062             }
1063         }
1064     },
1065
1066     /**
1067      * @private
1068      * Fires the TablePanel's viewready event when the view declares that its internal DOM is ready
1069      */
1070     onViewReady: function() {
1071         var me = this;
1072         me.fireEvent('viewready', me);
1073         if (me.deferRowRender) {
1074             me.determineScrollbars();
1075             me.invalidateScroller();
1076         }
1077     },
1078
1079     /**
1080      * @private
1081      * Determines and invalidates scrollers on view refresh
1082      */
1083     onViewRefresh: function() {
1084         var me = this;
1085
1086         // Refresh *during* render must be ignored.
1087         if (!me.rendering) {
1088             this.determineScrollbars();
1089             if (this.invalidateScrollerOnRefresh) {
1090                 this.invalidateScroller();
1091             }
1092         }
1093     },
1094
1095     /**
1096      * Sets the scrollTop of the TablePanel.
1097      * @param {Number} top
1098      */
1099     setScrollTop: function(top) {
1100         var me               = this,
1101             rootCmp          = me.getScrollerOwner(),
1102             verticalScroller = me.getVerticalScroller();
1103
1104         rootCmp.virtualScrollTop = top;
1105         if (verticalScroller) {
1106             verticalScroller.setScrollTop(top);
1107         }
1108     },
1109
1110     getScrollerOwner: function() {
1111         var rootCmp = this;
1112         if (!this.scrollerOwner) {
1113             rootCmp = this.up('[scrollerOwner]');
1114         }
1115         return rootCmp;
1116     },
1117
1118     /**
1119      * Scrolls the TablePanel by deltaY
1120      * @param {Number} deltaY
1121      */
1122     scrollByDeltaY: function(deltaY) {
1123         var verticalScroller = this.getVerticalScroller();
1124
1125         if (verticalScroller) {
1126             verticalScroller.scrollByDeltaY(deltaY);
1127         }
1128     },
1129
1130     /**
1131      * Scrolls the TablePanel by deltaX
1132      * @param {Number} deltaX
1133      */
1134     scrollByDeltaX: function(deltaX) {
1135         var horizontalScroller = this.getHorizontalScroller();
1136
1137         if (horizontalScroller) {
1138             horizontalScroller.scrollByDeltaX(deltaX);
1139         }
1140     },
1141
1142     /**
1143      * Gets left hand side marker for header resizing.
1144      * @private
1145      */
1146     getLhsMarker: function() {
1147         var me = this;
1148
1149         if (!me.lhsMarker) {
1150             me.lhsMarker = Ext.DomHelper.append(me.el, {
1151                 cls: Ext.baseCSSPrefix + 'grid-resize-marker'
1152             }, true);
1153         }
1154         return me.lhsMarker;
1155     },
1156
1157     /**
1158      * Gets right hand side marker for header resizing.
1159      * @private
1160      */
1161     getRhsMarker: function() {
1162         var me = this;
1163
1164         if (!me.rhsMarker) {
1165             me.rhsMarker = Ext.DomHelper.append(me.el, {
1166                 cls: Ext.baseCSSPrefix + 'grid-resize-marker'
1167             }, true);
1168         }
1169         return me.rhsMarker;
1170     },
1171
1172     /**
1173      * Returns the selection model being used and creates it via the configuration if it has not been created already.
1174      * @return {Ext.selection.Model} selModel
1175      */
1176     getSelectionModel: function(){
1177         if (!this.selModel) {
1178             this.selModel = {};
1179         }
1180
1181         var mode = 'SINGLE',
1182             type;
1183         if (this.simpleSelect) {
1184             mode = 'SIMPLE';
1185         } else if (this.multiSelect) {
1186             mode = 'MULTI';
1187         }
1188
1189         Ext.applyIf(this.selModel, {
1190             allowDeselect: this.allowDeselect,
1191             mode: mode
1192         });
1193
1194         if (!this.selModel.events) {
1195             type = this.selModel.selType || this.selType;
1196             this.selModel = Ext.create('selection.' + type, this.selModel);
1197         }
1198
1199         if (!this.selModel.hasRelaySetup) {
1200             this.relayEvents(this.selModel, [
1201                 'selectionchange', 'beforeselect', 'beforedeselect', 'select', 'deselect'
1202             ]);
1203             this.selModel.hasRelaySetup = true;
1204         }
1205
1206         // lock the selection model if user
1207         // has disabled selection
1208         if (this.disableSelection) {
1209             this.selModel.locked = true;
1210         }
1211         return this.selModel;
1212     },
1213
1214     onVerticalScroll: function(event, target) {
1215         var owner = this.getScrollerOwner(),
1216             items = owner.query('tableview'),
1217             i = 0,
1218             len = items.length;
1219
1220         for (; i < len; i++) {
1221             items[i].el.dom.scrollTop = target.scrollTop;
1222         }
1223     },
1224
1225     onHorizontalScroll: function(event, target) {
1226         var owner = this.getScrollerOwner(),
1227             items = owner.query('tableview'),
1228             center = items[1] || items[0];
1229
1230         center.el.dom.scrollLeft = target.scrollLeft;
1231         this.headerCt.el.dom.scrollLeft = target.scrollLeft;
1232     },
1233
1234     // template method meant to be overriden
1235     onStoreLoad: Ext.emptyFn,
1236
1237     getEditorParent: function() {
1238         return this.body;
1239     },
1240
1241     bindStore: function(store) {
1242         var me = this;
1243         me.store = store;
1244         me.getView().bindStore(store);
1245     },
1246     
1247     beforeDestroy: function(){
1248         // may be some duplication here since the horizontal and vertical
1249         // scroller may be part of the docked items, but we need to clean
1250         // them up in case they aren't visible.
1251         Ext.destroy(this.horizontalScroller, this.verticalScroller);
1252         this.callParent();
1253     },
1254
1255     /**
1256      * Reconfigures the table with a new store/columns. Either the store or the columns can be ommitted if you don't wish
1257      * to change them.
1258      * @param {Ext.data.Store} store (Optional) The new store.
1259      * @param {Object[]} columns (Optional) An array of column configs
1260      */
1261     reconfigure: function(store, columns) {
1262         var me = this,
1263             headerCt = me.headerCt;
1264
1265         if (me.lockable) {
1266             me.reconfigureLockable(store, columns);
1267         } else {
1268             if (columns) {
1269                 headerCt.suspendLayout = true;
1270                 headerCt.removeAll();
1271                 headerCt.add(columns);
1272             }
1273             if (store) {
1274                 store = Ext.StoreManager.lookup(store);
1275                 me.bindStore(store);
1276             } else {
1277                 me.getView().refresh();
1278             }
1279             if (columns) {
1280                 headerCt.suspendLayout = false;
1281                 me.forceComponentLayout();
1282             }
1283         }
1284         me.fireEvent('reconfigure', me);
1285     }
1286 });