X-Git-Url: http://git.ithinksw.org/extjs.git/blobdiff_plain/3789b528d8dd8aad4558e38e22d775bcab1cbd36..f562e4c6e5fac7bcb445985b99acbea4d706e6f0:/docs/source/Table2.html diff --git a/docs/source/Table2.html b/docs/source/Table2.html index 02321306..ff0b2e25 100644 --- a/docs/source/Table2.html +++ b/docs/source/Table2.html @@ -3,8 +3,8 @@
/** - * @class Ext.panel.Table - * @extends Ext.panel.Panel - * @xtype tablepanel - * @private - * @author Nicolas Ferrero - * TablePanel is a private class and the basis of both TreePanel and GridPanel. + * @author Nicolas Ferrero + * + * TablePanel is the basis of both {@link Ext.tree.Panel TreePanel} and {@link Ext.grid.Panel GridPanel}. * * TablePanel aggregates: * @@ -30,7 +27,6 @@ * - a Store * - Scrollers * - Ext.grid.header.Container - * */ Ext.define('Ext.panel.Table', { extend: 'Ext.panel.Panel', @@ -44,58 +40,124 @@ Ext.define('Ext.panel.Table', { 'Ext.grid.Lockable' ], - cls: Ext.baseCSSPrefix + 'grid', + extraBaseCls: Ext.baseCSSPrefix + 'grid', extraBodyCls: Ext.baseCSSPrefix + 'grid-body', layout: 'fit', /** - * Boolean to indicate that a view has been injected into the panel. - * @property hasView + * @property {Boolean} hasView + * True to indicate that a view has been injected into the panel. */ hasView: false, // each panel should dictate what viewType and selType to use + /** + * @cfg {String} viewType + * An xtype of view to use. This is automatically set to 'gridview' by {@link Ext.grid.Panel Grid} + * and to 'treeview' by {@link Ext.tree.Panel Tree}. + */ viewType: null, + + /** + * @cfg {Object} viewConfig + * A config object that will be applied to the grid's UI view. Any of the config options available for + * {@link Ext.view.Table} can be specified here. This option is ignored if {@link #view} is specified. + */ + + /** + * @cfg {Ext.view.Table} view + * The {@link Ext.view.Table} used by the grid. Use {@link #viewConfig} to just supply some config options to + * view (instead of creating an entire View instance). + */ + + /** + * @cfg {String} selType + * An xtype of selection model to use. Defaults to 'rowmodel'. This is used to create selection model if just + * a config object or nothing at all given in {@link #selModel} config. + */ selType: 'rowmodel', + /** + * @cfg {Ext.selection.Model/Object} selModel + * A {@link Ext.selection.Model selection model} instance or config object. In latter case the {@link #selType} + * config option determines to which type of selection model this config is applied. + */ + + /** + * @cfg {Boolean} multiSelect + * True to enable 'MULTI' selection mode on selection model. See {@link Ext.selection.Model#mode}. + */ + + /** + * @cfg {Boolean} simpleSelect + * True to enable 'SIMPLE' selection mode on selection model. See {@link Ext.selection.Model#mode}. + */ + + /** + * @cfg {Ext.data.Store} store (required) + * The {@link Ext.data.Store Store} the grid should use as its data source. + */ + /** * @cfg {Number} scrollDelta * Number of pixels to scroll when scrolling with mousewheel. - * Defaults to 40. */ scrollDelta: 40, /** * @cfg {String/Boolean} scroll - * Valid values are 'both', 'horizontal' or 'vertical'. true implies 'both'. false implies 'none'. - * Defaults to true. + * Scrollers configuration. Valid values are 'both', 'horizontal' or 'vertical'. + * True implies 'both'. False implies 'none'. */ scroll: true, /** - * @cfg {Array} columns - * An array of {@link Ext.grid.column.Column column} definition objects which define all columns that appear in this grid. Each - * column definition provides the header text for the column, and a definition of where the data for that column comes from. + * @cfg {Ext.grid.column.Column[]} columns + * An array of {@link Ext.grid.column.Column column} definition objects which define all columns that appear in this + * grid. Each column definition provides the header text for the column, and a definition of where the data for that + * column comes from. */ /** * @cfg {Boolean} forceFit - * Specify as <code>true</code> to force the columns to fit into the available width. Headers are first sized according to configuration, whether that be - * a specific width, or flex. Then they are all proportionally changed in width so that the entire content width is used.. + * Ttrue to force the columns to fit into the available width. Headers are first sized according to configuration, + * whether that be a specific width, or flex. Then they are all proportionally changed in width so that the entire + * content width is used. + */ + + /** + * @cfg {Ext.grid.feature.Feature[]} features + * An array of grid Features to be added to this grid. See {@link Ext.grid.feature.Feature} for usage. */ /** - * @cfg {Boolean} hideHeaders - * Specify as <code>true</code> to hide the headers. + * @cfg {Boolean} [hideHeaders=false] + * True to hide column headers. */ + /** + * @cfg {Boolean} deferRowRender + * Defaults to true to enable deferred row rendering. + * + * This allows the View to execute a refresh quickly, with the expensive update of the row structure deferred so + * that layouts with GridPanels appear, and lay out more quickly. + */ + + deferRowRender: true, + /** * @cfg {Boolean} sortableColumns - * Defaults to true. Set to false to disable column sorting via clicking the - * header and via the Sorting menu items. + * False to disable column sorting via clicking the header and via the Sorting menu items. */ sortableColumns: true, + /** + * @cfg {Boolean} [enableLocking=false] + * True to enable locking support for this grid. Alternatively, locking will also be automatically + * enabled if any of the columns in the column configuration contain the locked config option. + */ + enableLocking: false, + verticalScrollDock: 'right', verticalScrollerType: 'gridscroller', @@ -107,19 +169,30 @@ Ext.define('Ext.panel.Table', { scrollerOwner: true, invalidateScrollerOnRefresh: true, - + + /** + * @cfg {Boolean} enableColumnMove + * False to disable column dragging within this grid. + */ enableColumnMove: true, + + /** + * @cfg {Boolean} enableColumnResize + * False to disable column resizing within this grid. + */ enableColumnResize: true, + /** + * @cfg {Boolean} enableColumnHide + * False to disable column hiding within this grid. + */ + enableColumnHide: true, initComponent: function() { //<debug> if (!this.viewType) { Ext.Error.raise("You must specify a viewType config."); } - if (!this.store) { - Ext.Error.raise("You must specify a store config"); - } if (this.headers) { Ext.Error.raise("The headers config is not supported. Please specify columns instead."); } @@ -134,14 +207,13 @@ Ext.define('Ext.panel.Table', { view, border = me.border; - // Set our determinScrollbars method to reference a buffered call to determinScrollbars which fires on a 30ms buffer. - me.determineScrollbars = Ext.Function.createBuffered(me.determineScrollbars, 30); - me.injectView = Ext.Function.createBuffered(me.injectView, 30); - if (me.hideHeaders) { border = false; } + // Look up the configured Store. If none configured, use the fieldless, empty Store defined in Ext.data.Store. + me.store = Ext.data.StoreManager.lookup(me.store || 'ext-empty-store'); + // The columns/colModel config may be either a fully instantiated HeaderContainer, or an array of Column definitions, or a config object of a HeaderContainer // Either way, we extract a columns property referencing an array of Column definitions. if (headerCtCfg instanceof Ext.grid.header.Container) { @@ -160,30 +232,42 @@ Ext.define('Ext.panel.Table', { sortable: me.sortableColumns, enableColumnMove: me.enableColumnMove, enableColumnResize: me.enableColumnResize, + enableColumnHide: me.enableColumnHide, border: border }); me.columns = headerCtCfg.items; // If any of the Column objects contain a locked property, and are not processed, this is a lockable TablePanel, a // special view will be injected by the Ext.grid.Lockable mixin, so no processing of . - if (Ext.ComponentQuery.query('{locked !== undefined}{processed != true}', me.columns).length) { + if (me.enableLocking || Ext.ComponentQuery.query('{locked !== undefined}{processed != true}', me.columns).length) { me.self.mixin('lockable', Ext.grid.Lockable); me.injectLockable(); } } - me.store = Ext.data.StoreManager.lookup(me.store); me.addEvents( + /** + * @event reconfigure + * Fires after a reconfigure. + * @param {Ext.panel.Table} this + */ + 'reconfigure', + /** + * @event viewready + * Fires when the grid view is available (use this for selecting a default row). + * @param {Ext.panel.Table} this + */ + 'viewready', /** * @event scrollerhide - * Fires when a scroller is hidden + * Fires when a scroller is hidden. * @param {Ext.grid.Scroller} scroller * @param {String} orientation Orientation, can be 'vertical' or 'horizontal' */ 'scrollerhide', /** * @event scrollershow - * Fires when a scroller is shown + * Fires when a scroller is shown. * @param {Ext.grid.Scroller} scroller * @param {String} orientation Orientation, can be 'vertical' or 'horizontal' */ @@ -192,6 +276,9 @@ Ext.define('Ext.panel.Table', { me.bodyCls = me.bodyCls || ''; me.bodyCls += (' ' + me.extraBodyCls); + + me.cls = me.cls || ''; + me.cls += (' ' + me.extraBaseCls); // autoScroll is not a valid configuration delete me.autoScroll; @@ -236,13 +323,7 @@ Ext.define('Ext.panel.Table', { } if (vertical) { - me.verticalScroller = me.verticalScroller || {}; - Ext.applyIf(me.verticalScroller, { - dock: me.verticalScrollDock, - xtype: me.verticalScrollerType, - store: me.store - }); - me.verticalScroller = Ext.ComponentManager.create(me.verticalScroller); + me.verticalScroller = Ext.ComponentManager.create(me.initVerticalScroller()); me.mon(me.verticalScroller, { bodyscroll: me.onVerticalScroll, scope: me @@ -250,21 +331,19 @@ Ext.define('Ext.panel.Table', { } if (horizontal) { - me.horizontalScroller = Ext.ComponentManager.create({ - xtype: 'gridscroller', - section: me, - dock: 'bottom', - store: me.store - }); + me.horizontalScroller = Ext.ComponentManager.create(me.initHorizontalScroller()); me.mon(me.horizontalScroller, { bodyscroll: me.onHorizontalScroll, scope: me }); } - me.headerCt.on('columnresize', me.onHeaderResize, me); - me.relayEvents(me.headerCt, ['columnresize', 'columnmove', 'columnhide', 'columnshow', 'sortchange']); + me.headerCt.on('resize', me.onHeaderResize, me); + me.relayHeaderCtEvents(me.headerCt); me.features = me.features || []; + if (!Ext.isArray(me.features)) { + me.features = [me.features]; + } me.dockedItems = me.dockedItems || []; me.dockedItems.unshift(me.headerCt); me.viewConfig = me.viewConfig || {}; @@ -274,273 +353,215 @@ Ext.define('Ext.panel.Table', { // getView converts viewConfig into a View instance view = me.getView(); - if (view) { - me.mon(view.store, { - load: me.onStoreLoad, - scope: me - }); - me.mon(view, { - refresh: { - fn: this.onViewRefresh, - scope: me, - buffer: 50 - }, - itemupdate: me.onViewItemUpdate, - scope: me - }); - this.relayEvents(view, [ - /** - * @event beforeitemmousedown - * Fires before the mousedown event on an item is processed. Returns false to cancel the default action. - * @param {Ext.view.View} this - * @param {Ext.data.Model} record The record that belongs to the item - * @param {HTMLElement} item The item's element - * @param {Number} index The item's index - * @param {Ext.EventObject} e The raw event object - */ - 'beforeitemmousedown', - /** - * @event beforeitemmouseup - * Fires before the mouseup event on an item is processed. Returns false to cancel the default action. - * @param {Ext.view.View} this - * @param {Ext.data.Model} record The record that belongs to the item - * @param {HTMLElement} item The item's element - * @param {Number} index The item's index - * @param {Ext.EventObject} e The raw event object - */ - 'beforeitemmouseup', - /** - * @event beforeitemmouseenter - * Fires before the mouseenter event on an item is processed. Returns false to cancel the default action. - * @param {Ext.view.View} this - * @param {Ext.data.Model} record The record that belongs to the item - * @param {HTMLElement} item The item's element - * @param {Number} index The item's index - * @param {Ext.EventObject} e The raw event object - */ - 'beforeitemmouseenter', - /** - * @event beforeitemmouseleave - * Fires before the mouseleave event on an item is processed. Returns false to cancel the default action. - * @param {Ext.view.View} this - * @param {Ext.data.Model} record The record that belongs to the item - * @param {HTMLElement} item The item's element - * @param {Number} index The item's index - * @param {Ext.EventObject} e The raw event object - */ - 'beforeitemmouseleave', - /** - * @event beforeitemclick - * Fires before the click event on an item is processed. Returns false to cancel the default action. - * @param {Ext.view.View} this - * @param {Ext.data.Model} record The record that belongs to the item - * @param {HTMLElement} item The item's element - * @param {Number} index The item's index - * @param {Ext.EventObject} e The raw event object - */ - 'beforeitemclick', - /** - * @event beforeitemdblclick - * Fires before the dblclick event on an item is processed. Returns false to cancel the default action. - * @param {Ext.view.View} this - * @param {Ext.data.Model} record The record that belongs to the item - * @param {HTMLElement} item The item's element - * @param {Number} index The item's index - * @param {Ext.EventObject} e The raw event object - */ - 'beforeitemdblclick', - * @event beforeitemcontextmenu - * Fires before the contextmenu event on an item is processed. Returns false to cancel the default action. - * @param {Ext.view.View} this - * @param {Ext.data.Model} record The record that belongs to the item - * @param {HTMLElement} item The item's element - * @param {Number} index The item's index - * @param {Ext.EventObject} e The raw event object - */ - 'beforeitemcontextmenu', - /** - * @event itemmousedown - * Fires when there is a mouse down on an item - * @param {Ext.view.View} this - * @param {Ext.data.Model} record The record that belongs to the item - * @param {HTMLElement} item The item's element - * @param {Number} index The item's index - * @param {Ext.EventObject} e The raw event object - */ - 'itemmousedown', - /** - * @event itemmouseup - * Fires when there is a mouse up on an item - * @param {Ext.view.View} this - * @param {Ext.data.Model} record The record that belongs to the item - * @param {HTMLElement} item The item's element - * @param {Number} index The item's index - * @param {Ext.EventObject} e The raw event object - */ - 'itemmouseup', - /** - * @event itemmouseenter - * Fires when the mouse enters an item. - * @param {Ext.view.View} this - * @param {Ext.data.Model} record The record that belongs to the item - * @param {HTMLElement} item The item's element - * @param {Number} index The item's index - * @param {Ext.EventObject} e The raw event object - */ - 'itemmouseenter', - /** - * @event itemmouseleave - * Fires when the mouse leaves an item. - * @param {Ext.view.View} this - * @param {Ext.data.Model} record The record that belongs to the item - * @param {HTMLElement} item The item's element - * @param {Number} index The item's index - * @param {Ext.EventObject} e The raw event object - */ - 'itemmouseleave', - /** - * @event itemclick - * Fires when an item is clicked. - * @param {Ext.view.View} this - * @param {Ext.data.Model} record The record that belongs to the item - * @param {HTMLElement} item The item's element - * @param {Number} index The item's index - * @param {Ext.EventObject} e The raw event object - */ - 'itemclick', - /** - * @event itemdblclick - * Fires when an item is double clicked. - * @param {Ext.view.View} this - * @param {Ext.data.Model} record The record that belongs to the item - * @param {HTMLElement} item The item's element - * @param {Number} index The item's index - * @param {Ext.EventObject} e The raw event object - */ - 'itemdblclick', - * @event itemcontextmenu - * Fires when an item is right clicked. - * @param {Ext.view.View} this - * @param {Ext.data.Model} record The record that belongs to the item - * @param {HTMLElement} item The item's element - * @param {Number} index The item's index - * @param {Ext.EventObject} e The raw event object - */ - 'itemcontextmenu', - /** - * @event beforecontainermousedown - * Fires before the mousedown event on the container is processed. Returns false to cancel the default action. - * @param {Ext.view.View} this - * @param {Ext.EventObject} e The raw event object - */ - 'beforecontainermousedown', - /** - * @event beforecontainermouseup - * Fires before the mouseup event on the container is processed. Returns false to cancel the default action. - * @param {Ext.view.View} this - * @param {Ext.EventObject} e The raw event object - */ - 'beforecontainermouseup', - /** - * @event beforecontainermouseover - * Fires before the mouseover event on the container is processed. Returns false to cancel the default action. - * @param {Ext.view.View} this - * @param {Ext.EventObject} e The raw event object - */ - 'beforecontainermouseover', - /** - * @event beforecontainermouseout - * Fires before the mouseout event on the container is processed. Returns false to cancel the default action. - * @param {Ext.view.View} this - * @param {Ext.EventObject} e The raw event object - */ - 'beforecontainermouseout', - /** - * @event beforecontainerclick - * Fires before the click event on the container is processed. Returns false to cancel the default action. - * @param {Ext.view.View} this - * @param {Ext.EventObject} e The raw event object - */ - 'beforecontainerclick', - /** - * @event beforecontainerdblclick - * Fires before the dblclick event on the container is processed. Returns false to cancel the default action. - * @param {Ext.view.View} this - * @param {Ext.EventObject} e The raw event object - */ - 'beforecontainerdblclick', - * @event beforecontainercontextmenu - * Fires before the contextmenu event on the container is processed. Returns false to cancel the default action. - * @param {Ext.view.View} this - * @param {Ext.EventObject} e The raw event object - */ - 'beforecontainercontextmenu', - /** - * @event containermouseup - * Fires when there is a mouse up on the container - * @param {Ext.view.View} this - * @param {Ext.EventObject} e The raw event object - */ - 'containermouseup', - /** - * @event containermouseover - * Fires when you move the mouse over the container. - * @param {Ext.view.View} this - * @param {Ext.EventObject} e The raw event object - */ - 'containermouseover', - /** - * @event containermouseout - * Fires when you move the mouse out of the container. - * @param {Ext.view.View} this - * @param {Ext.EventObject} e The raw event object - */ - 'containermouseout', - /** - * @event containerclick - * Fires when the container is clicked. - * @param {Ext.view.View} this - * @param {Ext.EventObject} e The raw event object - */ - 'containerclick', - /** - * @event containerdblclick - * Fires when the container is double clicked. - * @param {Ext.view.View} this - * @param {Ext.EventObject} e The raw event object - */ - 'containerdblclick', - * @event containercontextmenu - * Fires when the container is right clicked. - * @param {Ext.view.View} this - * @param {Ext.EventObject} e The raw event object - */ - 'containercontextmenu', - - /** - * @event selectionchange - * Fires when the selected nodes change. Relayed event from the underlying selection model. - * @param {Ext.view.View} this - * @param {Array} selections Array of the selected nodes - */ - 'selectionchange', - /** - * @event beforeselect - * Fires before a selection is made. If any handlers return false, the selection is cancelled. - * @param {Ext.view.View} this - * @param {HTMLElement} node The node to be selected - * @param {Array} selections Array of currently selected nodes - */ - 'beforeselect' - ]); - } + view.on({ + afterrender: function () { + // hijack the view el's scroll method + view.el.scroll = Ext.Function.bind(me.elScroll, me); + // We use to listen to document.body wheel events, but that's a + // little much. We scope just to the view now. + me.mon(view.el, { + mousewheel: me.onMouseWheel, + scope: me + }); + }, + single: true + }); + me.items = [view]; + me.hasView = true; + + me.mon(view.store, { + load: me.onStoreLoad, + scope: me + }); + me.mon(view, { + viewReady: me.onViewReady, + resize: me.onViewResize, + refresh: { + fn: me.onViewRefresh, + scope: me, + buffer: 50 + }, + scope: me + }); + this.relayEvents(view, [ + /** + * @event beforeitemmousedown + * @alias Ext.view.View#beforeitemmousedown + */ + 'beforeitemmousedown', + /** + * @event beforeitemmouseup + * @alias Ext.view.View#beforeitemmouseup + */ + 'beforeitemmouseup', + /** + * @event beforeitemmouseenter + * @alias Ext.view.View#beforeitemmouseenter + */ + 'beforeitemmouseenter', + /** + * @event beforeitemmouseleave + * @alias Ext.view.View#beforeitemmouseleave + */ + 'beforeitemmouseleave', + /** + * @event beforeitemclick + * @alias Ext.view.View#beforeitemclick + */ + 'beforeitemclick', + /** + * @event beforeitemdblclick + * @alias Ext.view.View#beforeitemdblclick + */ + 'beforeitemdblclick', + * @event beforeitemcontextmenu + * @alias Ext.view.View#beforeitemcontextmenu + */ + 'beforeitemcontextmenu', + /** + * @event itemmousedown + * @alias Ext.view.View#itemmousedown + */ + 'itemmousedown', + /** + * @event itemmouseup + * @alias Ext.view.View#itemmouseup + */ + 'itemmouseup', + /** + * @event itemmouseenter + * @alias Ext.view.View#itemmouseenter + */ + 'itemmouseenter', + /** + * @event itemmouseleave + * @alias Ext.view.View#itemmouseleave + */ + 'itemmouseleave', + /** + * @event itemclick + * @alias Ext.view.View#itemclick + */ + 'itemclick', + /** + * @event itemdblclick + * @alias Ext.view.View#itemdblclick + */ + 'itemdblclick', + * @event itemcontextmenu + * @alias Ext.view.View#itemcontextmenu + */ + 'itemcontextmenu', + /** + * @event beforecontainermousedown + * @alias Ext.view.View#beforecontainermousedown + */ + 'beforecontainermousedown', + /** + * @event beforecontainermouseup + * @alias Ext.view.View#beforecontainermouseup + */ + 'beforecontainermouseup', + /** + * @event beforecontainermouseover + * @alias Ext.view.View#beforecontainermouseover + */ + 'beforecontainermouseover', + /** + * @event beforecontainermouseout + * @alias Ext.view.View#beforecontainermouseout + */ + 'beforecontainermouseout', + /** + * @event beforecontainerclick + * @alias Ext.view.View#beforecontainerclick + */ + 'beforecontainerclick', + /** + * @event beforecontainerdblclick + * @alias Ext.view.View#beforecontainerdblclick + */ + 'beforecontainerdblclick', + * @event beforecontainercontextmenu + * @alias Ext.view.View#beforecontainercontextmenu + */ + 'beforecontainercontextmenu', + /** + * @event containermouseup + * @alias Ext.view.View#containermouseup + */ + 'containermouseup', + /** + * @event containermouseover + * @alias Ext.view.View#containermouseover + */ + 'containermouseover', + /** + * @event containermouseout + * @alias Ext.view.View#containermouseout + */ + 'containermouseout', + /** + * @event containerclick + * @alias Ext.view.View#containerclick + */ + 'containerclick', + /** + * @event containerdblclick + * @alias Ext.view.View#containerdblclick + */ + 'containerdblclick', + * @event containercontextmenu + * @alias Ext.view.View#containercontextmenu + */ + 'containercontextmenu', + /** + * @event selectionchange + * @alias Ext.selection.Model#selectionchange + */ + 'selectionchange', + /** + * @event beforeselect + * @alias Ext.selection.RowModel#beforeselect + */ + 'beforeselect', + /** + * @event select + * @alias Ext.selection.RowModel#select + */ + 'select', + /** + * @event beforedeselect + * @alias Ext.selection.RowModel#beforedeselect + */ + 'beforedeselect', + /** + * @event deselect + * @alias Ext.selection.RowModel#deselect + */ + 'deselect' + ]); } + me.callParent(arguments); }, + + onRender: function(){ + var vScroll = this.verticalScroller, + hScroll = this.horizontalScroller; + + if (vScroll) { + vScroll.ensureDimension(); + } + if (hScroll) { + hScroll.ensureDimension(); + } + this.callParent(arguments); + }, // state management initStateEvents: function(){ @@ -554,59 +575,98 @@ Ext.define('Ext.panel.Table', { this.callParent(); }, - getState: function(){ - var state = { - columns: [] - }, - sorter = this.store.sorters.first(); - - this.headerCt.items.each(function(header){ - state.columns.push({ - id: header.headerId, - width: header.flex ? undefined : header.width, - hidden: header.hidden, - sortable: header.sortable - }); + /** + * Returns the horizontal scroller config. + */ + initHorizontalScroller: function () { + var me = this, + ret = { + xtype: 'gridscroller', + dock: 'bottom', + section: me, + store: me.store + }; + + return ret; + }, + + /** + * Returns the vertical scroller config. + */ + initVerticalScroller: function () { + var me = this, + ret = me.verticalScroller || {}; + + Ext.applyIf(ret, { + xtype: me.verticalScrollerType, + dock: me.verticalScrollDock, + store: me.store }); + return ret; + }, + + relayHeaderCtEvents: function (headerCt) { + this.relayEvents(headerCt, [ + /** + * @event columnresize + * @alias Ext.grid.header.Container#columnresize + */ + 'columnresize', + /** + * @event columnmove + * @alias Ext.grid.header.Container#columnmove + */ + 'columnmove', + /** + * @event columnhide + * @alias Ext.grid.header.Container#columnhide + */ + 'columnhide', + /** + * @event columnshow + * @alias Ext.grid.header.Container#columnshow + */ + 'columnshow', + /** + * @event sortchange + * @alias Ext.grid.header.Container#sortchange + */ + 'sortchange' + ]); + }, + + getState: function(){ + var me = this, + state = me.callParent(), + sorter = me.store.sorters.first(); + + state.columns = (me.headerCt || me).getColumnsState(); + if (sorter) { state.sort = { property: sorter.property, direction: sorter.direction }; } + return state; }, applyState: function(state) { - var headers = state.columns, - length = headers ? headers.length : 0, - headerCt = this.headerCt, - items = headerCt.items, + var me = this, sorter = state.sort, - store = this.store, - i = 0, - index, - headerState, - header; + store = me.store, + columns = state.columns; - for (; i < length; ++i) { - headerState = headers[i]; - header = headerCt.down('gridcolumn[headerId=' + headerState.id + ']'); - index = items.indexOf(header); - if (i !== index) { - headerCt.moveHeader(index, i); - } - header.sortable = headerState.sortable; - if (Ext.isDefined(headerState.width)) { - delete header.flex; - if (header.rendered) { - header.setWidth(headerState.width); - } else { - header.minWidth = header.width = headerState.width; - } - } - header.hidden = headerState.hidden; + delete state.columns; + + // Ensure superclass has applied *its* state. + // AbstractComponent saves dimensions (and anchor/flex) plus collapsed state. + me.callParent(arguments); + + if (columns) { + (me.headerCt || me).applyColumnsState(columns); } if (sorter) { @@ -641,6 +701,7 @@ Ext.define('Ext.panel.Table', { if (!me.view) { sm = me.getSelectionModel(); me.view = me.createComponent(Ext.apply({}, me.viewConfig, { + deferInitialRefresh: me.deferRowRender, xtype: me.viewType, store: me.store, headerCt: me.headerCt, @@ -676,62 +737,33 @@ Ext.define('Ext.panel.Table', { if (direction === "up" || direction === "left") { distance = -distance; } - + if (direction === "down" || direction === "up") { scroller = me.getVerticalScroller(); - scroller.scrollByDeltaY(distance); + + //if the grid does not currently need a vertical scroller don't try to update it (EXTJSIV-3891) + if (scroller) { + scroller.scrollByDeltaY(distance); + } } else { scroller = me.getHorizontalScroller(); - scroller.scrollByDeltaX(distance); - } - }, - - afterLayout: function() { - this.callParent(arguments); - this.injectView(); - }, - - - /** - * @private - * Called after this Component has achieved its correct initial size, after all layouts have done their thing. - * This is so we can add the View only after the initial size is known. This method is buffered 30ms. - */ - injectView: function() { - if (!this.hasView && !this.collapsed) { - var me = this, - view = me.getView(); - - me.hasView = true; - me.add(view); - - // hijack the view el's scroll method - view.el.scroll = Ext.Function.bind(me.elScroll, me); - // We use to listen to document.body wheel events, but that's a - // little much. We scope just to the view now. - me.mon(view.el, { - mousewheel: me.onMouseWheel, - scope: me - }); - } - }, - - afterExpand: function() { - this.callParent(arguments); - if (!this.hasView) { - this.injectView(); + + //if the grid does not currently need a horizontal scroller don't try to update it (EXTJSIV-3891) + if (scroller) { + scroller.scrollByDeltaX(distance); + } } }, /** * @private - * Process UI events from the view. Propagate them to whatever internal Components need to process them + * Processes UI events from the view. Propagates them to whatever internal Components need to process them. * @param {String} type Event type, eg 'click' - * @param {TableView} view TableView Component - * @param {HtmlElement} cell Cell HtmlElement the event took place within + * @param {Ext.view.Table} view TableView Component + * @param {HTMLElement} cell Cell HtmlElement the event took place within * @param {Number} recordIndex Index of the associated Store Model (-1 if none) * @param {Number} cellIndex Cell index within the row - * @param {EventObject} e Original event + * @param {Ext.EventObject} e Original event */ processEvent: function(type, view, cell, recordIndex, cellIndex, e) { var me = this, @@ -744,70 +776,149 @@ Ext.define('Ext.panel.Table', { }, /** - * Request a recalculation of scrollbars and put them in if they are needed. + * Requests a recalculation of scrollbars and puts them in if they are needed. */ determineScrollbars: function() { + // Set a flag so that afterComponentLayout does not recurse back into here. + if (this.determineScrollbarsRunning) { + return; + } + this.determineScrollbarsRunning = true; var me = this, - viewElDom, - centerScrollWidth, - centerClientWidth, + view = me.view, + box, + tableEl, + scrollWidth, + clientWidth, scrollHeight, - clientHeight; - - if (!me.collapsed && me.view && me.view.el) { - viewElDom = me.view.el.dom; - //centerScrollWidth = viewElDom.scrollWidth; - centerScrollWidth = me.headerCt.getFullWidth(); - /** - * clientWidth often returns 0 in IE resulting in an - * infinity result, here we use offsetWidth bc there are - * no possible scrollbars and we don't care about margins - */ - centerClientWidth = viewElDom.offsetWidth; - if (me.verticalScroller && me.verticalScroller.el) { - scrollHeight = me.verticalScroller.getSizeCalculation().height; + clientHeight, + verticalScroller = me.verticalScroller, + horizontalScroller = me.horizontalScroller, + curScrollbars = (verticalScroller && verticalScroller.ownerCt === me ? 1 : 0) | + (horizontalScroller && horizontalScroller.ownerCt === me ? 2 : 0), + reqScrollbars = 0; // 1 = vertical, 2 = horizontal, 3 = both + + // If we are not collapsed, and the view has been rendered AND filled, then we can determine scrollbars + if (!me.collapsed && view && view.viewReady) { + + // Calculate maximum, *scrollbarless* space which the view has available. + // It will be the Fit Layout's calculated size, plus the widths of any currently shown scrollbars + box = view.el.getSize(); + + clientWidth = box.width + ((curScrollbars & 1) ? verticalScroller.width : 0); + clientHeight = box.height + ((curScrollbars & 2) ? horizontalScroller.height : 0); + + // Calculate the width of the scrolling block + // There will never be a horizontal scrollbar if all columns are flexed. + + scrollWidth = (me.headerCt.query('[flex]').length && !me.headerCt.layout.tooNarrow) ? 0 : me.headerCt.getFullWidth(); + + // Calculate the height of the scrolling block + if (verticalScroller && verticalScroller.el) { + scrollHeight = verticalScroller.getSizeCalculation().height; } else { - scrollHeight = viewElDom.scrollHeight; + tableEl = view.el.child('table', true); + scrollHeight = tableEl ? tableEl.offsetHeight : 0; } - clientHeight = viewElDom.clientHeight; + // View is too high. + // Definitely need a vertical scrollbar + if (scrollHeight > clientHeight) { + reqScrollbars = 1; - me.suspendLayout = true; - me.scrollbarChanged = false; - if (!me.collapsed && scrollHeight > clientHeight) { - me.showVerticalScroller(); - } else { - me.hideVerticalScroller(); + // But if scrollable block width goes into the zone required by the vertical scrollbar, we'll also need a horizontal + if (horizontalScroller && ((clientWidth - scrollWidth) < verticalScroller.width)) { + reqScrollbars = 3; + } } - if (!me.collapsed && centerScrollWidth > (centerClientWidth + Ext.getScrollBarWidth() - 2)) { - me.showHorizontalScroller(); - } else { - me.hideHorizontalScroller(); + // View height fits. But we stil may need a horizontal scrollbar, and this might necessitate a vertical one. + else { + // View is too wide. + // Definitely need a horizontal scrollbar + if (scrollWidth > clientWidth) { + reqScrollbars = 2; + + // But if scrollable block height goes into the zone required by the horizontal scrollbar, we'll also need a vertical + if (verticalScroller && ((clientHeight - scrollHeight) < horizontalScroller.height)) { + reqScrollbars = 3; + } + } } - me.suspendLayout = false; - if (me.scrollbarChanged) { + + // If scrollbar requirements have changed, change 'em... + if (reqScrollbars !== curScrollbars) { + + // Suspend component layout while we add/remove the docked scrollers + me.suspendLayout = true; + if (reqScrollbars & 1) { + me.showVerticalScroller(); + } else { + me.hideVerticalScroller(); + } + if (reqScrollbars & 2) { + me.showHorizontalScroller(); + } else { + me.hideHorizontalScroller(); + } + me.suspendLayout = false; + + // Lay out the Component. me.doComponentLayout(); + // Lay out me.items + me.getLayout().layout(); } } + delete me.determineScrollbarsRunning; + }, + + onViewResize: function() { + this.determineScrollbars(); + }, + + afterComponentLayout: function() { + this.callParent(arguments); + this.determineScrollbars(); + this.invalidateScroller(); }, onHeaderResize: function() { - if (this.view && this.view.rendered) { + if (!this.componentLayout.layoutBusy && this.view && this.view.rendered) { this.determineScrollbars(); this.invalidateScroller(); } }, + afterCollapse: function() { + var me = this; + if (me.verticalScroller) { + me.verticalScroller.saveScrollPos(); + } + if (me.horizontalScroller) { + me.horizontalScroller.saveScrollPos(); + } + me.callParent(arguments); + }, + + afterExpand: function() { + var me = this; + me.callParent(arguments); + if (me.verticalScroller) { + me.verticalScroller.restoreScrollPos(); + } + if (me.horizontalScroller) { + me.horizontalScroller.restoreScrollPos(); + } + }, + /** - * Hide the verticalScroller and remove the horizontalScrollerPresentCls. + * Hides the verticalScroller and removes the horizontalScrollerPresentCls. */ hideHorizontalScroller: function() { var me = this; if (me.horizontalScroller && me.horizontalScroller.ownerCt === me) { - me.scrollbarChanged = true; - me.verticalScroller.offsets.bottom = 0; + me.verticalScroller.setReservedSpace(0); me.removeDocked(me.horizontalScroller, false); me.removeCls(me.horizontalScrollerPresentCls); me.fireEvent('scrollerhide', me.horizontalScroller, 'horizontal'); @@ -816,16 +927,15 @@ Ext.define('Ext.panel.Table', { }, /** - * Show the horizontalScroller and add the horizontalScrollerPresentCls. + * Shows the horizontalScroller and add the horizontalScrollerPresentCls. */ showHorizontalScroller: function() { var me = this; if (me.verticalScroller) { - me.verticalScroller.offsets.bottom = Ext.getScrollBarWidth() - 2; + me.verticalScroller.setReservedSpace(Ext.getScrollbarSize().height - 1); } if (me.horizontalScroller && me.horizontalScroller.ownerCt !== me) { - me.scrollbarChanged = true; me.addDocked(me.horizontalScroller); me.addCls(me.horizontalScrollerPresentCls); me.fireEvent('scrollershow', me.horizontalScroller, 'horizontal'); @@ -833,19 +943,13 @@ Ext.define('Ext.panel.Table', { }, /** - * Hide the verticalScroller and remove the verticalScrollerPresentCls. + * Hides the verticalScroller and removes the verticalScrollerPresentCls. */ hideVerticalScroller: function() { - var me = this, - headerCt = me.headerCt; + var me = this; - // only trigger a layout when reserveOffset is changing - if (headerCt && headerCt.layout.reserveOffset) { - headerCt.layout.reserveOffset = false; - headerCt.doLayout(); - } + me.setHeaderReserveOffset(false); if (me.verticalScroller && me.verticalScroller.ownerCt === me) { - me.scrollbarChanged = true; me.removeDocked(me.verticalScroller, false); me.removeCls(me.verticalScrollerPresentCls); me.fireEvent('scrollerhide', me.verticalScroller, 'vertical'); @@ -853,28 +957,34 @@ Ext.define('Ext.panel.Table', { }, /** - * Show the verticalScroller and add the verticalScrollerPresentCls. + * Shows the verticalScroller and adds the verticalScrollerPresentCls. */ showVerticalScroller: function() { - var me = this, - headerCt = me.headerCt; + var me = this; - // only trigger a layout when reserveOffset is changing - if (headerCt && !headerCt.layout.reserveOffset) { - headerCt.layout.reserveOffset = true; - headerCt.doLayout(); - } + me.setHeaderReserveOffset(true); if (me.verticalScroller && me.verticalScroller.ownerCt !== me) { - me.scrollbarChanged = true; me.addDocked(me.verticalScroller); me.addCls(me.verticalScrollerPresentCls); me.fireEvent('scrollershow', me.verticalScroller, 'vertical'); } }, + setHeaderReserveOffset: function (reserveOffset) { + var headerCt = this.headerCt, + layout = headerCt.layout; + + // only trigger a layout when reserveOffset is changing + if (layout && layout.reserveOffset !== reserveOffset) { + layout.reserveOffset = reserveOffset; + if (!this.suspendLayout) { + headerCt.doLayout(); + } + } + }, + /** - * Invalides scrollers that are present and forces a recalculation. - * (Not related to showing/hiding the scrollers) + * Invalides scrollers that are present and forces a recalculation. (Not related to showing/hiding the scrollers) */ invalidateScroller: function() { var me = this, @@ -913,11 +1023,12 @@ Ext.define('Ext.panel.Table', { onMouseWheel: function(e) { var me = this, - browserEvent = e.browserEvent, vertScroller = me.getVerticalScroller(), horizScroller = me.getHorizontalScroller(), - scrollDelta = me.scrollDelta, - deltaY, deltaX, + scrollDelta = -me.scrollDelta, + deltas = e.getWheelDeltas(), + deltaX = scrollDelta * deltas.x, + deltaY = scrollDelta * deltas.y, vertScrollerEl, horizScrollerEl, vertScrollerElDom, horizScrollerElDom, horizontalCanScrollLeft, horizontalCanScrollRight, @@ -925,7 +1036,7 @@ Ext.define('Ext.panel.Table', { // calculate whether or not both scrollbars can scroll right/left and up/down if (horizScroller) { - horizScrollerEl = horizScroller.el; + horizScrollerEl = horizScroller.scrollEl; if (horizScrollerEl) { horizScrollerElDom = horizScrollerEl.dom; horizontalCanScrollRight = horizScrollerElDom.scrollLeft !== horizScrollerElDom.scrollWidth - horizScrollerElDom.clientWidth; @@ -933,7 +1044,7 @@ Ext.define('Ext.panel.Table', { } } if (vertScroller) { - vertScrollerEl = vertScroller.el; + vertScrollerEl = vertScroller.scrollEl; if (vertScrollerEl) { vertScrollerElDom = vertScrollerEl.dom; verticalCanScrollDown = vertScrollerElDom.scrollTop !== vertScrollerElDom.scrollHeight - vertScrollerElDom.clientHeight; @@ -941,19 +1052,6 @@ Ext.define('Ext.panel.Table', { } } - // Webkit Horizontal Axis - if (browserEvent.wheelDeltaX || browserEvent.wheelDeltaY) { - deltaX = -browserEvent.wheelDeltaX / 120 * scrollDelta / 3; - deltaY = -browserEvent.wheelDeltaY / 120 * scrollDelta / 3; - } else { - // Gecko Horizontal Axis - if (browserEvent.axis && browserEvent.axis === 1) { - deltaX = -(scrollDelta * e.getWheelDelta()) / 3; - } else { - deltaY = -(scrollDelta * e.getWheelDelta() / 3); - } - } - if (horizScroller) { if ((deltaX < 0 && horizontalCanScrollLeft) || (deltaX > 0 && horizontalCanScrollRight)) { e.stopEvent(); @@ -963,82 +1061,43 @@ Ext.define('Ext.panel.Table', { if (vertScroller) { if ((deltaY < 0 && verticalCanScrollUp) || (deltaY > 0 && verticalCanScrollDown)) { e.stopEvent(); - vertScroller.scrollByDeltaY(deltaY); + vertScroller.scrollByDeltaY(deltaY); } } }, - /** + /** * @private - * Determine and invalidate scrollers on view refresh + * Fires the TablePanel's viewready event when the view declares that its internal DOM is ready */ - onViewRefresh: function() { - if (Ext.isIE) { - this.syncCellHeight(); - } - this.determineScrollbars(); - if (this.invalidateScrollerOnRefresh) { - this.invalidateScroller(); - } - }, - - onViewItemUpdate: function(record, index, tr) { - if (Ext.isIE) { - this.syncCellHeight([tr]); + onViewReady: function() { + var me = this; + me.fireEvent('viewready', me); + if (me.deferRowRender) { + me.determineScrollbars(); + me.invalidateScroller(); } }, - // BrowserBug: IE will not stretch the td to fit the height of the entire - // tr, so manually sync cellheights on refresh and when an item has been - // updated. - syncCellHeight: function(trs) { - var me = this, - i = 0, - tds, - j, tdsLn, - tr, td, - trsLn, - rowHeights = [], - cellHeights, - cellClsSelector = ('.' + Ext.baseCSSPrefix + 'grid-cell'); - - trs = trs || me.view.getNodes(); - - trsLn = trs.length; - // Reading loop - for (; i < trsLn; i++) { - tr = trs[i]; - tds = Ext.fly(tr).query(cellClsSelector); - tdsLn = tds.length; - cellHeights = []; - for (j = 0; j < tdsLn; j++) { - td = tds[j]; - cellHeights.push(td.clientHeight); - } - rowHeights.push(Ext.Array.max(cellHeights)); - } + /** + * @private + * Determines and invalidates scrollers on view refresh + */ + onViewRefresh: function() { + var me = this; - // Setting loop - for (i = 0; i < trsLn; i++) { - tr = trs[i]; - tdsLn = tr.childNodes.length; - for (j = 0; j < tdsLn; j++) { - td = Ext.fly(tr.childNodes[j]); - if (rowHeights[i]) { - if (td.is(cellClsSelector)) { - td.setHeight(rowHeights[i]); - } else { - td.down(cellClsSelector).setHeight(rowHeights[i]); - } - } - + // Refresh *during* render must be ignored. + if (!me.rendering) { + this.determineScrollbars(); + if (this.invalidateScrollerOnRefresh) { + this.invalidateScroller(); } } }, /** * Sets the scrollTop of the TablePanel. - * @param {Number} deltaY + * @param {Number} top */ setScrollTop: function(top) { var me = this, @@ -1049,7 +1108,6 @@ Ext.define('Ext.panel.Table', { if (verticalScroller) { verticalScroller.setScrollTop(top); } - }, getScrollerOwner: function() { @@ -1065,32 +1123,34 @@ Ext.define('Ext.panel.Table', { * @param {Number} deltaY */ scrollByDeltaY: function(deltaY) { - var rootCmp = this.getScrollerOwner(), - scrollerRight; - scrollerRight = rootCmp.down('gridscroller[dock=' + this.verticalScrollDock + ']'); - if (scrollerRight) { - scrollerRight.scrollByDeltaY(deltaY); + var verticalScroller = this.getVerticalScroller(); + + if (verticalScroller) { + verticalScroller.scrollByDeltaY(deltaY); } }, - /** * Scrolls the TablePanel by deltaX - * @param {Number} deltaY + * @param {Number} deltaX */ scrollByDeltaX: function(deltaX) { - this.horizontalScroller.scrollByDeltaX(deltaX); + var horizontalScroller = this.getHorizontalScroller(); + + if (horizontalScroller) { + horizontalScroller.scrollByDeltaX(deltaX); + } }, /** - * Get left hand side marker for header resizing. + * Gets left hand side marker for header resizing. * @private */ getLhsMarker: function() { var me = this; if (!me.lhsMarker) { - me.lhsMarker = Ext.core.DomHelper.append(me.el, { + me.lhsMarker = Ext.DomHelper.append(me.el, { cls: Ext.baseCSSPrefix + 'grid-resize-marker' }, true); } @@ -1098,14 +1158,14 @@ Ext.define('Ext.panel.Table', { }, /** - * Get right hand side marker for header resizing. + * Gets right hand side marker for header resizing. * @private */ getRhsMarker: function() { var me = this; if (!me.rhsMarker) { - me.rhsMarker = Ext.core.DomHelper.append(me.el, { + me.rhsMarker = Ext.DomHelper.append(me.el, { cls: Ext.baseCSSPrefix + 'grid-resize-marker' }, true); } @@ -1113,8 +1173,7 @@ Ext.define('Ext.panel.Table', { }, /** - * Returns the selection model being used and creates it via the configuration - * if it has not been created already. + * Returns the selection model being used and creates it via the configuration if it has not been created already. * @return {Ext.selection.Model} selModel */ getSelectionModel: function(){ @@ -1141,7 +1200,9 @@ Ext.define('Ext.panel.Table', { } if (!this.selModel.hasRelaySetup) { - this.relayEvents(this.selModel, ['selectionchange', 'select', 'deselect']); + this.relayEvents(this.selModel, [ + 'selectionchange', 'beforeselect', 'beforedeselect', 'select', 'deselect' + ]); this.selModel.hasRelaySetup = true; } @@ -1167,21 +1228,9 @@ Ext.define('Ext.panel.Table', { onHorizontalScroll: function(event, target) { var owner = this.getScrollerOwner(), items = owner.query('tableview'), - i = 0, - len = items.length, - center, - centerEl, - centerScrollWidth, - centerClientWidth, - width; - - center = items[1] || items[0]; - centerEl = center.el.dom; - centerScrollWidth = centerEl.scrollWidth; - centerClientWidth = centerEl.offsetWidth; - width = this.horizontalScroller.getWidth(); - - centerEl.scrollLeft = target.scrollLeft; + center = items[1] || items[0]; + + center.el.dom.scrollLeft = target.scrollLeft; this.headerCt.el.dom.scrollLeft = target.scrollLeft; }, @@ -1197,25 +1246,45 @@ Ext.define('Ext.panel.Table', { me.store = store; me.getView().bindStore(store); }, + + beforeDestroy: function(){ + // may be some duplication here since the horizontal and vertical + // scroller may be part of the docked items, but we need to clean + // them up in case they aren't visible. + Ext.destroy(this.horizontalScroller, this.verticalScroller); + this.callParent(); + }, + /** + * Reconfigures the table with a new store/columns. Either the store or the columns can be ommitted if you don't wish + * to change them. + * @param {Ext.data.Store} store (Optional) The new store. + * @param {Object[]} columns (Optional) An array of column configs + */ reconfigure: function(store, columns) { - var me = this; + var me = this, + headerCt = me.headerCt; if (me.lockable) { me.reconfigureLockable(store, columns); - return; - } - - if (columns) { - me.headerCt.removeAll(); - me.headerCt.add(columns); - } - if (store) { - store = Ext.StoreManager.lookup(store); - me.bindStore(store); } else { - me.getView().refresh(); + if (columns) { + headerCt.suspendLayout = true; + headerCt.removeAll(); + headerCt.add(columns); + } + if (store) { + store = Ext.StoreManager.lookup(store); + me.bindStore(store); + } else { + me.getView().refresh(); + } + if (columns) { + headerCt.suspendLayout = false; + me.forceComponentLayout(); + } } + me.fireEvent('reconfigure', me); } });