/**
* This class is the base class for both {@link Ext.tree.Panel TreePanel} and
* {@link Ext.grid.Panel GridPanel}.
*
* TablePanel aggregates:
*
* - a Selection Model
* - a View
* - a Store
* - Ext.grid.header.Container
*/
Ext.define('Ext.panel.Table', {
extend: 'Ext.panel.Panel',
alias: 'widget.tablepanel',
requires: [
'Ext.layout.container.Fit'
],
uses: [
'Ext.selection.RowModel',
'Ext.selection.CellModel',
'Ext.selection.CheckboxModel',
'Ext.grid.plugin.BufferedRenderer',
'Ext.grid.header.Container',
'Ext.grid.locking.Lockable',
'Ext.grid.NavigationModel'
],
extraBodyCls: Ext.baseCSSPrefix + 'grid-body',
defaultBindProperty: 'store',
layout: 'fit',
ariaRole: 'grid',
config: {
selection: null
},
publishes: ['selection'],
twoWayBindable: ['selection'],
/**
* @cfg [autoLoad=false]
* Use `true` to load the store as soon as this component is fully constructed. It is
* best to initiate the store load this way to allow this component and potentially
* its plugins (such as `{@link Ext.grid.filters.Filters}` to be ready to load.
*/
autoLoad: false,
/**
* @cfg {Boolean} [variableRowHeight=false]
* @deprecated 5.0.0 Use {@link Ext.grid.column.Column#variableRowHeight} instead.
* Configure as `true` if the row heights are not all the same height as the first row.
*/
variableRowHeight: false,
/**
* @cfg {Number}
* This configures the zone which causes new rows to be appended to the view. As soon as the edge
* of the rendered grid is this number of rows from the edge of the viewport, the view is moved.
*/
numFromEdge: 2,
/**
* @cfg {Number}
* TableViews are buffer rendered in 5.x which means that only the visible subset of data rows
* are rendered into the DOM. These are removed and added as scrolling demands.
*
* This configures the number of extra rows to render on the trailing side of scrolling
* **outside the {@link #numFromEdge}** buffer as scrolling proceeds.
*/
trailingBufferZone: 10,
/**
* @cfg {Number}
* TableViews are buffer rendered in 5.x which means that only the visible subset of data rows
* are rendered into the DOM. These are removed and added as scrolling demands.
*
* This configures the number of extra rows to render on the leading side of scrolling
* **outside the {@link #numFromEdge}** buffer as scrolling proceeds.
*/
leadingBufferZone: 20,
/**
* @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}.
* @protected
*/
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. 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=false]
* True to enable 'MULTI' selection mode on selection model.
* @deprecated 4.1.1 Use {@link Ext.selection.Model#mode} 'MULTI' instead.
*/
/**
* @cfg {Boolean} [simpleSelect=false]
* True to enable 'SIMPLE' selection mode on selection model.
* @deprecated 4.1.1 Use {@link Ext.selection.Model#mode} 'SIMPLE' instead.
*/
/**
* @cfg {Ext.data.Store} store (required)
* The {@link Ext.data.Store Store} the grid should use as its data source.
*/
/**
* @cfg {String/Boolean} scroll
* Scrollers configuration. Valid values are 'both', 'horizontal' or 'vertical'.
* True implies 'both'. False implies 'none'.
*/
scroll: true,
/**
* @cfg {Boolean} [reserveScrollbar=false]
* Set this to true to **always** leave a scrollbar sized space at the end of the grid content when
* fitting content into the width of the grid.
*
* If the grid's record count fluctuates enough to hide and show the scrollbar regularly, this setting
* avoids the multiple layouts associated with switching from scrollbar present to scrollbar not present.
*/
/**
* @cfg {Ext.grid.column.Column[]/Object} columns (required)
* 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.
*
* This can also be a configuration object for a {@link Ext.grid.header.Container HeaderContainer} which may override
* certain default configurations if necessary. For example, the special layout may be overridden to use a simpler
* layout, or one can set default values shared by all columns:
*
* columns: {
* items: [
* {
* text: "Column A",
* dataIndex: "field_A"
* },{
* text: "Column B",
* dataIndex: "field_B"
* },
* ...
* ],
* defaults: {
* flex: 1
* }
* }
*/
/**
* @cfg {Boolean} forceFit
* True 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. For more accurate control, it is more optimal to specify a flex setting on the columns
* that are to be stretched & explicit widths on columns that are not.
*/
/**
* @cfg {Ext.grid.feature.Feature[]/Object[]/Ext.enums.Feature[]} features
* An array of grid Features to be added to this grid. Can also be just a single feature instead of array.
*
* Features config behaves much like {@link #plugins}.
* A feature can be added by either directly referencing the instance:
*
* features: [Ext.create('Ext.grid.feature.GroupingSummary', {groupHeaderTpl: 'Subject: {name}'})],
*
* By using config object with ftype:
*
* features: [{ftype: 'groupingsummary', groupHeaderTpl: 'Subject: {name}'}],
*
* Or with just a ftype:
*
* features: ['grouping', 'groupingsummary'],
*
* See {@link Ext.enums.Feature} for list of all ftypes.
*/
* @cfg {Boolean} [hideHeaders=false]
* True to hide column headers.
*/
/**
* @cfg {Boolean} [deferRowRender=false]
* Configure as `true` to enable deferred row rendering.
*
* This allows the View to execute a refresh quickly, with the update of the row structure deferred so
* that layouts with GridPanels appear, and lay out more quickly.
*/
deferRowRender: false,
/**
* @cfg {Boolean} [sortableColumns=true]
* False to disable column sorting via clicking the header and via the Sorting menu items.
*/
sortableColumns: true,
/**
* @cfg {Boolean} [multiColumnSort=false]
* Configure as `true` to have columns remember their sorted state after other columns have been clicked upon to sort.
*
* As subsequent columns are clicked upon, they become the new primary sort key.
*
* The maximum number of sorters allowed in a Store is configurable via its underlying data collection. See {@link Ext.util.Collection#multiSortLimit}
*/
multiColumnSort: false,
/**
* @cfg {Boolean} [enableLocking=false]
* Configure as `true` to enable locking support for this grid. Alternatively, locking will also be automatically
* enabled if any of the columns in the {@link #columns columns} configuration contain a {@link Ext.grid.column.Column#locked locked} config option.
*
* A locking grid is processed in a special way. The configuration options are cloned and *two* grids are created to be the locked (left) side
* and the normal (right) side. This Panel becomes merely a {@link Ext.container.Container container} which arranges both in an {@link Ext.layout.container.HBox HBox} layout.
*
* {@link #plugins Plugins} may be targeted at either locked, or unlocked grid, or, both, in which case the plugin is cloned and used on both sides.
*
* Plugins may also be targeted at the containing locking Panel.
*
* This is configured by specifying a `lockableScope` property in your plugin which may have the following values:
*
* * `"both"` (the default) - The plugin is added to both grids
* * `"top"` - The plugin is added to the containing Panel
* * `"locked"` - The plugin is added to the locked (left) grid
* * `"normal"` - The plugin is added to the normal (right) grid
*
* If `both` is specified, then each copy of the plugin gains a property `lockingPartner` which references its sibling on the other side so that they
* can synchronize operations is necessary.
*
* {@link #features Features} may also be configured with `lockableScope` and may target the locked grid, the normal grid or both grids. Features
* also get a `lockingPartner` reference injected.
*/
enableLocking: false,
// private property used to determine where to go down to find views
// this is here to support locking.
scrollerOwner: true,
/**
* @cfg {Boolean} [enableColumnMove=true]
* False to disable column dragging within this grid.
*/
enableColumnMove: true,
/**
* @cfg {Boolean} [sealedColumns=false]
* True to constrain column dragging so that a column cannot be dragged in or out of it's
* current group. Only relevant while {@link #enableColumnMove} is enabled.
*/
sealedColumns: false,
/**
* @cfg {Boolean} [enableColumnResize=true]
* False to disable column resizing within this grid.
*/
enableColumnResize: true,
/**
* @cfg {Boolean} [enableColumnHide=true]
* False to disable column hiding within this grid.
*/
/**
* @cfg {Boolean} columnLines Adds column line styling
*/
/**
* @cfg {Boolean} [rowLines=true] Adds row line styling
*/
rowLines: true,
/**
* @cfg {Boolean} [disableSelection=false]
* True to disable selection model.
*/
/**
* @cfg {String} emptyText Default text (HTML tags are accepted) to display in the
* Panel body when the Store is empty. When specified, and the Store is empty, the
* text will be rendered inside a DIV with the CSS class "x-grid-empty". The emptyText
* will not display until the first load of the associated store by default. If you
* want the text to be displayed prior to the first store load use the
* {@link Ext.view.Table#deferEmptyText deferEmptyText} config in the {@link #viewConfig} config.
*/
/**
* @cfg {Boolean} [allowDeselect=false]
* True to allow deselecting a record. This config is forwarded to {@link Ext.selection.Model#allowDeselect}.
*/
/**
* @cfg {Boolean} [bufferedRenderer=true]
* Buffered rendering is enabled by default.
*
* Configure as `false` to disable buffered rendering. See {@link #Ext.grid.plugin.BufferedRenderer}.
*
* @since 5.0.0
*/
bufferedRenderer: true,
/**
* @property {Boolean} optimizedColumnMove
* If you are writing a grid plugin or a {Ext.grid.feature.Feature Feature} which creates a column-based structure which
* needs a view refresh when columns are moved, then set this property in the grid.
*
* An example is the built in {@link Ext.grid.feature.AbstractSummary Summary} Feature. This creates summary rows, and the
* summary columns must be in the same order as the data columns. This plugin sets the `optimizedColumnMove` to `false.
*/
/**
* @property {Ext.view.Table} ownerGrid
* A reference to the top-level owning grid component.
*
* This is a reference to this GridPanel if this GridPanel is not part of a locked grid arrangement.
* @readonly
* @private
* @since 5.0.0
*/
ownerGrid: null,
colLinesCls: Ext.baseCSSPrefix + 'grid-with-col-lines',
rowLinesCls: Ext.baseCSSPrefix + 'grid-with-row-lines',
noRowLinesCls: Ext.baseCSSPrefix + 'grid-no-row-lines',
resizeMarkerCls: Ext.baseCSSPrefix + 'grid-resize-marker',
emptyCls: Ext.baseCSSPrefix + 'grid-empty',
focusable: true,
tabIndex: 0,
/**
* @event viewready
* Fires when the grid view is available (use this for selecting a default row).
* @param {Ext.panel.Table} this
*/
constructor: function (config) {
var me = this,
store;
me.ownerGrid = (config && config.ownerGrid) || me;
me.callParent([config]);
store = this.store;
// Any further changes become stateful.
store.trackStateChanges = true;
if (me.autoLoad) {
store.unblockLoad();
store.load();
}
},
initComponent: function() {
//<debug>
if (this.verticalScroller) {
Ext.Error.raise("The verticalScroller config is not supported.");
}
if (!this.viewType) {
Ext.Error.raise("You must specify a viewType config.");
}
if (this.headers) {
Ext.Error.raise("The headers config is not supported. Please specify columns instead.");
}
//</debug>
var me = this,
headerCtCfg = me.columns || me.colModel || [],
view,
i, len,
bufferedRenderer,
// Look up the configured Store. If none configured, use the fieldless, empty Store defined in Ext.data.Store.
store = me.store = Ext.data.StoreManager.lookup(me.store || 'ext-empty-store'),
columns;
me.enableLocking = me.enableLocking || me.hasLockedColumns(headerCtCfg);
// Block store loads during construction or initialization of plugins!
if (me.autoLoad) {
me.store.blockLoad();
}
// Construct the plugins now rather than in the constructor of AbstractComponent because the component may have a subclass
// that has overridden initComponent and defined plugins in it. For plugins like RowExpander that rely upon a grid feature,
// this is a problem because the view needs to know about all its features before it's constructed. Constructing the plugins
// now ensures that plugins defined in the instance config or in initComponent are all constructed before the view.
// See EXTJSIV-11927.
//
// Note that any components that do not inherit from this class will still have their plugins constructed in
// AbstractComponent:initComponent.
if (me.plugins) {
me.plugins = me.constructPlugins();
}
// Add the row/column line classes to the body element so that the settings are not inherited by docked grids (https://sencha.jira.com/browse/EXTJSIV-9263).
if (me.columnLines) {
me.addBodyCls(me.colLinesCls);
}
me.addBodyCls(me.rowLines ? me.rowLinesCls : me.noRowLinesCls);
me.addBodyCls(me.extraBodyCls);
// 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.locking.Lockable mixin, so no processing of .
if (me.enableLocking) {
me.self.mixin('lockable', Ext.grid.locking.Lockable);
me.injectLockable();
}
// Not lockable - create the HeaderContainer
else {
// It's a fully instantiated HeaderContainer
if (headerCtCfg.isRootHeader) {
me.headerCt = headerCtCfg;
me.headerCt.forceFit = !!me.forceFit;
// If it's an instance then the column managers were already created and bound to the headerCt.
me.columnManager = headerCtCfg.columnManager;
me.visibleColumnManager = headerCtCfg.visibleColumnManager;
}
// It's an array of Column definitions, or a config object of a HeaderContainer
else {
if (Ext.isArray(headerCtCfg)) {
headerCtCfg = {
items: headerCtCfg
};
}
Ext.apply(headerCtCfg, {
grid: me,
forceFit: me.forceFit,
sortable: me.sortableColumns,
enableColumnMove: me.enableColumnMove,
enableColumnResize: me.enableColumnResize,
columnLines: me.columnLines,
sealed: me.sealedColumns
});
if (me.hideHeaders) {
headerCtCfg.height = 0;
// don't set the hidden property, we still need these to layout
headerCtCfg.hiddenHeaders = true;
}
if (Ext.isDefined(me.enableColumnHide)) {
headerCtCfg.enableColumnHide = me.enableColumnHide;
}
me.headerCt = new Ext.grid.header.Container(headerCtCfg);
}
}
// Maintain backward compatibiliy by providing the initial leaf column set as a property.
me.columns = columns = me.headerCt.getGridColumns();
me.scrollTask = new Ext.util.DelayedTask(me.syncHorizontalScroll, me);
me.cls = (me.cls || '') + (' ' + me.extraBaseCls);
// autoScroll is not a valid configuration
delete me.autoScroll;
bufferedRenderer = me.plugins && Ext.Array.findBy(me.plugins, function(p) {
return p.isBufferedRenderer;
});
// If we find one in the plugins, just use that.
if (bufferedRenderer) {
me.bufferedRenderer = bufferedRenderer;
}
// If this TablePanel is lockable (Either configured lockable, or any of the defined columns has a 'locked' property)
// then a special lockable view containing 2 side-by-side grids will have been injected so we do not need to set up any UI.
if (!me.hasView) {
// If the Store is paging blocks of the dataset in, then it can only be sorted remotely.
if (store.isBufferedStore && !store.remoteSort) {
for (i = 0, len = columns.length; i < len; i++) {
columns[i].sortable = false;
}
}
if (me.hideHeaders) {
me.headerCt.addCls(me.hiddenHeaderCtCls);
me.addCls(me.hiddenHeaderCls);
}
me.relayHeaderCtEvents(me.headerCt);
me.features = me.features || [];
if (!Ext.isArray(me.features)) {
me.features = [me.features];
}
me.dockedItems = [].concat(me.dockedItems || []);
me.dockedItems.unshift(me.headerCt);
me.viewConfig = me.viewConfig || {};
// AbstractDataView will look up a Store configured as an object
// getView converts viewConfig into a View instance
view = me.getView();
me.items = [view];
me.hasView = true;
// Add a listener to synchronize the horizontal scroll position of the headers
// with the table view's element... Unless we are not showing headers!
if (!me.hideHeaders) {
view.on({
scroll: me.onHorizontalScroll,
scope: me,
onFrame: !!Ext.global.requestAnimationFrame
});
}
// Attach this Panel to the Store
me.bindStore(store, true);
me.mon(view, {
viewready: me.onViewReady,
refresh: me.onRestoreHorzScroll,
scope: me
});
}
// Relay events from the View whether it be a LockingView, or a regular GridView
me.relayEvents(me.view, [
/**
* @event beforeitemmousedown
* @inheritdoc Ext.view.View#beforeitemmousedown
*/
'beforeitemmousedown',
/**
* @event beforeitemmouseup
* @inheritdoc Ext.view.View#beforeitemmouseup
*/
'beforeitemmouseup',
/**
* @event beforeitemmouseenter
* @inheritdoc Ext.view.View#beforeitemmouseenter
*/
'beforeitemmouseenter',
/**
* @event beforeitemmouseleave
* @inheritdoc Ext.view.View#beforeitemmouseleave
*/
'beforeitemmouseleave',
/**
* @event beforeitemclick
* @inheritdoc Ext.view.View#beforeitemclick
*/
'beforeitemclick',
/**
* @event beforeitemdblclick
* @inheritdoc Ext.view.View#beforeitemdblclick
*/
'beforeitemdblclick',
* @event beforeitemcontextmenu
* @inheritdoc Ext.view.View#beforeitemcontextmenu
*/
'beforeitemcontextmenu',
/**
* @event itemmousedown
* @inheritdoc Ext.view.View#itemmousedown
*/
'itemmousedown',
/**
* @event itemmouseup
* @inheritdoc Ext.view.View#itemmouseup
*/
'itemmouseup',
/**
* @event itemmouseenter
* @inheritdoc Ext.view.View#itemmouseenter
*/
'itemmouseenter',
/**
* @event itemmouseleave
* @inheritdoc Ext.view.View#itemmouseleave
*/
'itemmouseleave',
/**
* @event itemclick
* @inheritdoc Ext.view.View#itemclick
*/
'itemclick',
/**
* @event itemdblclick
* @inheritdoc Ext.view.View#itemdblclick
*/
'itemdblclick',
* @event itemcontextmenu
* @inheritdoc Ext.view.View#itemcontextmenu
*/
'itemcontextmenu',
/**
* @event beforecellclick
* @inheritdoc Ext.view.Table#beforecellclick
*/
'beforecellclick',
/**
* @event cellclick
* @inheritdoc Ext.view.Table#cellclick
*/
'cellclick',
/**
* @event beforecelldblclick
* @inheritdoc Ext.view.Table#beforecelldblclick
*/
'beforecelldblclick',
/**
* @event celldblclick
* @inheritdoc Ext.view.Table#celldblclick
*/
'celldblclick',
* @event beforecellcontextmenu
* @inheritdoc Ext.view.Table#beforecellcontextmenu
*/
'beforecellcontextmenu',
* @event cellcontextmenu
* @inheritdoc Ext.view.Table#cellcontextmenu
*/
'cellcontextmenu',
/**
* @event beforecellmousedown
* @inheritdoc Ext.view.Table#beforecellmousedown
*/
'beforecellmousedown',
/**
* @event cellmousedown
* @inheritdoc Ext.view.Table#cellmousedown
*/
'cellmousedown',
/**
* @event beforecellmouseup
* @inheritdoc Ext.view.Table#beforecellmouseup
*/
'beforecellmouseup',
/**
* @event cellmouseup
* @inheritdoc Ext.view.Table#cellmouseup
*/
'cellmouseup',
/**
* @event beforecellkeydown
* @inheritdoc Ext.view.Table#beforecellkeydown
*/
'beforecellkeydown',
/**
* @event cellkeydown
* @inheritdoc Ext.view.Table#cellkeydown
*/
'cellkeydown',
/**
* @event rowclick
* @inheritdoc Ext.view.Table#rowclick
*/
'rowclick',
/**
* @event rowdblclick
* @inheritdoc Ext.view.Table#rowdblclick
*/
'rowdblclick',
* @event rowcontextmenu
* @inheritdoc Ext.view.Table#rowcontextmenu
*/
'rowcontextmenu',
/**
* @event rowmousedown
* @inheritdoc Ext.view.Table#rowmousedown
*/
'rowmousedown',
/**
* @event rowmouseup
* @inheritdoc Ext.view.Table#rowmouseup
*/
'rowmouseup',
/**
* @event rowkeydown
* @inheritdoc Ext.view.Table#rowkeydown
*/
'rowkeydown',
/**
* @event beforeitemkeydown
* @inheritdoc Ext.view.Table#beforeitemkeydown
*/
'beforeitemkeydown',
/**
* @event itemkeydown
* @inheritdoc Ext.view.Table#itemkeydown
*/
'itemkeydown',
/**
* @event beforecontainermousedown
* @inheritdoc Ext.view.View#beforecontainermousedown
*/
'beforecontainermousedown',
/**
* @event beforecontainermouseup
* @inheritdoc Ext.view.View#beforecontainermouseup
*/
'beforecontainermouseup',
/**
* @event beforecontainermouseover
* @inheritdoc Ext.view.View#beforecontainermouseover
*/
'beforecontainermouseover',
/**
* @event beforecontainermouseout
* @inheritdoc Ext.view.View#beforecontainermouseout
*/
'beforecontainermouseout',
/**
* @event beforecontainerclick
* @inheritdoc Ext.view.View#beforecontainerclick
*/
'beforecontainerclick',
/**
* @event beforecontainerdblclick
* @inheritdoc Ext.view.View#beforecontainerdblclick
*/
'beforecontainerdblclick',
* @event beforecontainercontextmenu
* @inheritdoc Ext.view.View#beforecontainercontextmenu
*/
'beforecontainercontextmenu',
/**
* @event beforecontainerkeydown
* @inheritdoc Ext.view.View#beforecontainerkeydown
*/
'beforecontainerkeydown',
/**
* @event containermouseup
* @inheritdoc Ext.view.View#containermouseup
*/
'containermouseup',
/**
* @event containermousedown
* @inheritdoc Ext.view.View#containermousedown
*/
'containermousedown',
/**
* @event containermouseover
* @inheritdoc Ext.view.View#containermouseover
*/
'containermouseover',
/**
* @event containermouseout
* @inheritdoc Ext.view.View#containermouseout
*/
'containermouseout',
/**
* @event containerclick
* @inheritdoc Ext.view.View#containerclick
*/
'containerclick',
/**
* @event containerdblclick
* @inheritdoc Ext.view.View#containerdblclick
*/
'containerdblclick',
* @event containercontextmenu
* @inheritdoc Ext.view.View#containercontextmenu
*/
'containercontextmenu',
/**
* @event containerkeydown
* @inheritdoc Ext.view.View#containerkeydown
*/
'containerkeydown',
/**
* @event selectionchange
* @inheritdoc Ext.selection.Model#selectionchange
*/
'selectionchange',
/**
* @event beforeselect
* @inheritdoc Ext.selection.RowModel#beforeselect
*/
'beforeselect',
/**
* @event select
* @inheritdoc Ext.selection.RowModel#select
*/
'select',
/**
* @event beforedeselect
* @inheritdoc Ext.selection.RowModel#beforedeselect
*/
'beforedeselect',
/**
* @event deselect
* @inheritdoc Ext.selection.RowModel#deselect
*/
'deselect'
]);
me.callParent(arguments);
me.addStateEvents(['columnresize', 'columnmove', 'columnhide', 'columnshow', 'sortchange', 'filterchange', 'groupchange']);
},
beforeRender: function() {
var me = this,
bufferedRenderer = me.bufferedRenderer;
// Don't create a buffered renderer for a locked grid.
if (!me.lockable) {
// If we're auto heighting, we can't buffered render, so don't create it
if (bufferedRenderer && me.getSizeModel().height.auto) {
//<debug>
if (bufferedRenderer.isBufferedRenderer) {
Ext.Error.raise('Cannot use buffered rendering with auto height');
}
//</debug>
me.bufferedRenderer = bufferedRenderer = false;
}
if (bufferedRenderer && !bufferedRenderer.isBufferedRenderer) {
// Create a BufferedRenderer as a plugin if we have not already configured with one.
bufferedRenderer = {
xclass: 'Ext.grid.plugin.BufferedRenderer'
};
Ext.copyTo(bufferedRenderer, me, 'variableRowHeight,numFromEdge,trailingBufferZone,leadingBufferZone,scrollToLoadBuffer');
me.bufferedRenderer = me.addPlugin(bufferedRenderer);
}
}
me.callParent(arguments);
},
initFocusableEvents: function() {
var me = this,
view = me.getView();
me.callParent();
me.focusEnterLeaveListeners = view.getFocusEl().on({
focusenter: me.onFocusEnter,
focusleave: me.onFocusLeave,
scope: me,
destroyable: true
});
},
onFocus: function(e) {
this.callParent([e]);
// Focusing the main el delegates focus to a descendant cell.
this.handleFocusEnter(e);
},
onFocusEnter: function(e) {
this.handleFocusEnter(e);
},
handleFocusEnter: function(e) {
var me = this,
view = me.getView(),
targetView,
navigationModel = view.getNavigationModel(),
lastFocused,
focusPosition,
br = view.bufferedRenderer,
firstRecord;
if (!me.containsFocus) {
lastFocused = focusPosition = view.getLastFocused();
// Default to the first cell if the NavigationModel has never focused anything
if (!focusPosition) {
targetView = view.isLockingView ? (view.lockedGrid.isVisible() ? view.lockedView : view.normalView) : view;
firstRecord = view.dataSource.getAt(br ? br.getFirstVisibleRowIndex() : 0);
// A non-row producing record like a collapsed placeholder.
// We cannot focus these yet.
if (!firstRecord.isNonData) {
focusPosition = new Ext.grid.CellContext(targetView).setPosition({
row: firstRecord,
column: 0
});
}
}
// Not a descendant which we allow to carry focus. Blur it.
if (!focusPosition) {
e.stopEvent();
e.getTarget().blur();
return;
}
navigationModel.setPosition(focusPosition, null, e, null, !!lastFocused);
// We now contain focus is that was successful
me.containsFocus = !!navigationModel.getPosition();
}
if (me.containsFocus) {
this.getView().el.dom.setAttribute('tabindex', '-1');
}
},
onFocusLeave: function(e) {
var view = this.getView();
// Ignore this event if we do not actually contain focus.
// CellEditors are rendered into the view's encapculating element,
// So focusleave will fire when they are programatically blurred.
// We will not have focus at that point.
if (this.containsFocus) {
// Blur the focused cell
view.getNavigationModel().setPosition(null, null, e, null, true);
this.containsFocus = false;
view.focusEl = view.el;
view.focusEl.dom.setAttribute('tabindex', 0);
}
},
// Private. Determine if there are any columns with a locked configuration option
hasLockedColumns: function(columns) {
var i,
len,
column;
// Fully instantiated HeaderContainer
if (columns.isRootHeader) {
columns = columns.items.items;
}
// Config object with items
else if (Ext.isObject(columns)) {
columns = columns.items;
}
for (i = 0, len = columns.length; i < len; i++) {
column = columns[i];
if (!column.processed && column.locked) {
return true;
}
}
},
this.relayEvents(headerCt, [
/**
* @event columnresize
* @inheritdoc Ext.grid.header.Container#columnresize
*/
'columnresize',
/**
* @event columnmove
* @inheritdoc Ext.grid.header.Container#columnmove
*/
'columnmove',
/**
* @event columnhide
* @inheritdoc Ext.grid.header.Container#columnhide
*/
'columnhide',
/**
* @event columnshow
* @inheritdoc Ext.grid.header.Container#columnshow
*/
'columnshow',
/**
* @event columnschanged
* @inheritdoc Ext.grid.header.Container#columnschanged
*/
'columnschanged',
/**
* @event sortchange
* @inheritdoc Ext.grid.header.Container#sortchange
*/
'sortchange',
* @event headerclick
* @inheritdoc Ext.grid.header.Container#headerclick
*/
'headerclick',
* @event headercontextmenu
* @inheritdoc Ext.grid.header.Container#headercontextmenu
*/
'headercontextmenu',
* @event headertriggerclick
* @inheritdoc Ext.grid.header.Container#headertriggerclick
*/
'headertriggerclick'
]);
},
getState: function(){
var me = this,
state = me.callParent(),
storeState = me.store.getState();
state = me.addPropertyToState(state, 'columns', me.headerCt.getColumnsState());
if (storeState) {
state.storeState = storeState;
}
return state;
},
applyState: function (state) {
var me = this,
sorter = state.sort,
storeState = state.storeState,
store = me.store,
columns = state.columns;
delete state.columns;
// Ensure superclass has applied *its* state.
// Component saves dimensions (and anchor/flex) plus collapsed state.
me.callParent(arguments);
if (columns) {
me.headerCt.applyColumnsState(columns);
}
// Old stored sort state. Deprecated and will die out.
if (sorter) {
if (store.remoteSort) {
// Pass false to prevent a sort from occurring.
store.sort({
property: sorter.property,
direction: sorter.direction,
root: sorter.root
}, null, false);
} else {
store.sort(sorter.property, sorter.direction);
}
}
// New storeState which encapsulates groupers, sorters and filters.
else if (storeState) {
store.applyState(storeState);
}
},
/**
* Returns the store associated with this Panel.
* @return {Ext.data.Store} The store
*/
getStore: function(){
return this.store;
},
/**
* Gets the view for this panel.
* @return {Ext.view.Table}
*/
getView: function() {
var me = this,
sm,
viewConfig;
if (!me.view) {
sm = me.getSelectionModel();
viewConfig = Ext.apply({
// TableView injects the view reference into this grid so that we have a reference as early as possible
// and Features need a reference to the grid.
// For these reasons, we configure a reference to this grid into the View
grid: me,
ownerGrid: me.ownerGrid,
deferInitialRefresh: me.deferRowRender,
variableRowHeight: me.variableRowHeight,
preserveScrollOnRefresh: true,
trackOver: me.trackMouseOver !== false,
throttledUpdate: me.throttledUpdate === true,
scroll: me.scroll,
xtype: me.viewType,
store: me.store,
headerCt: me.headerCt,
columnLines: me.columnLines,
rowLines: me.rowLines,
selModel: sm,
navigationModel: 'grid',
features: me.features,
panel: me,
emptyText: me.emptyText || ''
}, me.viewConfig);
// Reconcile conflicting scroll requests in the grid's scroll configuration and viewConfig's scroll configuration.
// If the grid has scroll:'vertical', and the viewConfig has scroll"horizontal', the outcome must be scroll: 'both'
if (me.scroll && me.viewConfig.scroll && me.scroll !== me.viewConfig.scroll) {
viewConfig.scroll = 'both';
}
Ext.widget(viewConfig);
// Normalize the application of the markup wrapping the emptyText config.
// `emptyText` can now be defined on the grid as well as on its viewConfig, and this led to the emptyText not
// having the wrapping markup when it was defined in the viewConfig. It should be backwards compatible.
// Note that in the unlikely event that emptyText is defined on both the grid config and the viewConfig that the viewConfig wins.
if (me.view.emptyText) {
me.view.emptyText = '<div class="' + me.emptyCls + '">' + me.view.emptyText + '</div>';
}
// TableView's custom component layout, Ext.view.TableLayout requires a reference to the headerCt because it depends on the headerCt doing its work.
me.view.getComponentLayout().headerCt = me.headerCt;
me.mon(me.view, {
uievent: me.processEvent,
scope: me
});
sm.view = me.view;
me.headerCt.view = me.view;
// Plugins and features may need to access the view as soon as it is created.
if (me.hasListeners.viewcreated) {
me.fireEvent('viewcreated', me, me.view);
}
}
return me.view;
},
getColumnManager: function() {
return this.columnManager;
},
getVisibleColumnManager: function() {
return this.visibleColumnManager;
},
getTopLevelColumnManager: function() {
return this.ownerGrid.getColumnManager();
},
getTopLevelVisibleColumnManager: function() {
return this.ownerGrid.getVisibleColumnManager();
},
/**
* @private
* autoScroll is never valid for all classes which extend TablePanel.
*/
setAutoScroll: Ext.emptyFn,
/**
* @private
* Processes UI events from the view. Propagates them to whatever internal Components need to process them.
* @param {String} type Event type, eg 'click'
* @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 {Ext.event.Event} e Original event
*/
processEvent: function(type, view, cell, recordIndex, cellIndex, e, record, row) {
var header = e.position.column;
if (header) {
return header.processEvent.apply(header, arguments);
}
},
scrollByDeltaY: function(yDelta, animate) {
this.getView().scrollBy(0, yDelta, animate);
},
scrollByDeltaX: function(xDelta, animate) {
this.getView().scrollBy(xDelta, 0, animate);
},
afterCollapse: function() {
this.saveScrollPos();
this.callParent(arguments);
},
afterExpand: function() {
this.callParent(arguments);
this.restoreScrollPos();
},
saveScrollPos: Ext.emptyFn,
restoreScrollPos: Ext.emptyFn,
// Touch scroll manager needs to know about the new width
if (this.view.scrollManager) {
this.view.scrollManager.refresh();
}
},
onHeaderMove: function(headerCt, header, colsToMove, fromIdx, toIdx) {
var me = this;
// If there are Features or Plugins which create DOM which must match column order, they set the optimizedColumnMove flag to false.
// In this case we must refresh the view on column move.
if (me.optimizedColumnMove === false) {
me.view.refreshView();
}
// Simplest case for default DOM structure is just to swap the columns round in the view.
else {
me.view.moveColumn(fromIdx, toIdx, colsToMove);
}
me.delayScroll();
},
onHeaderHide: function(headerCt, header) {
if (this.view.refreshCounter) {
this.view.refreshView();
}
},
if (this.view.refreshCounter) {
this.view.refreshView();
}
},
onHeadersChanged: function(headerCt, header) {
var me = this;
if (me.rendered && !me.reconfiguring) {
me.view.refreshView();
me.delayScroll();
}
},
delayScroll: function(){
var target = this.view;
if (target) {
// Do not cause a layout by reading scrollX now.
// It must be read from the target when the task finally executes.
this.scrollTask.delay(10, null, null, [target]);
}
},
/**
* @private
* Fires the TablePanel's viewready event when the view declares that its internal DOM is ready
*/
onViewReady: function() {
this.fireEvent('viewready', this);
},
/**
* @private
* Tracks when things happen to the view and preserves the horizontal scroll position.
*/
onRestoreHorzScroll: function() {
var me = this,
x = me.scrollXPos;
if (x) {
// We need to restore the body scroll position here
me.syncHorizontalScroll(me, true);
}
},
getScrollerOwner: function() {
var rootCmp = this;
if (!this.scrollerOwner) {
rootCmp = this.up('[scrollerOwner]');
}
return rootCmp;
},
/**
* Gets left hand side marker for header resizing.
* @private
*/
getLhsMarker: function() {
var me = this;
return me.lhsMarker || (me.lhsMarker = Ext.DomHelper.append(me.el, {
role: 'presentation',
cls: me.resizeMarkerCls
}, true));
},
/**
* Gets right hand side marker for header resizing.
* @private
*/
getRhsMarker: function() {
var me = this;
return me.rhsMarker || (me.rhsMarker = Ext.DomHelper.append(me.el, {
role: 'presentation',
cls: me.resizeMarkerCls
}, true));
},
/**
* Returns the grid's selection. See `{@link Ext.selection.Model#getSelection}`.
* @inheritdoc Ext.selection.Model#getSelection
*/
getSelection: function () {
return this.getSelectionModel().getSelection();
},
updateSelection: function(selection) {
var me = this,
sm;
if (!me.ignoreNextSelection) {
me.ignoreNextSelection = true;
sm = me.getSelectionModel();
if (selection) {
sm.select(selection);
} else {
sm.deselectAll();
}
me.ignoreNextSelection = false;
}
},
updateBindSelection: function(selModel, selection) {
var me = this,
selected = null;
if (!me.ignoreNextSelection) {
me.ignoreNextSelection = true;
if (selection.length) {
selected = selModel.getLastSelected();
me.hasHadSelection = true;
}
if (me.hasHadSelection) {
me.setSelection(selected);
}
me.ignoreNextSelection = false;
}
},
getNavigationModel: function() {
return this.getView().getNavigationModel();
},
/**
* 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(){
var me = this,
selModel = me.selModel,
applyMode, mode, type;
if (!selModel) {
selModel = {};
// no config, set our own mode
applyMode = true;
}
if (!selModel.events) {
// only config provided, set our mode if one doesn't exist on the config
type = selModel.selType || me.selType;
applyMode = !selModel.mode;
selModel = me.selModel = Ext.create('selection.' + type, selModel);
}
if (me.simpleSelect) {
mode = 'SIMPLE';
} else if (me.multiSelect) {
mode = 'MULTI';
}
Ext.applyIf(selModel, {
allowDeselect: me.allowDeselect
});
if (mode && applyMode) {
selModel.setSelectionMode(mode);
}
if (!selModel.hasRelaySetup) {
me.relayEvents(selModel, [
'selectionchange', 'beforeselect', 'beforedeselect', 'select', 'deselect'
]);
selModel.hasRelaySetup = true;
if (selModel.isRowModel) {
selModel.on('selectionchange', me.updateBindSelection, me);
}
}
// lock the selection model if user
// has disabled selection
if (me.disableSelection) {
selModel.locked = true;
}
return selModel;
},
getScrollTarget: function(){
var owner = this.getScrollerOwner(),
items = owner.query('tableview');
return items[1] || items[0];
},
onHorizontalScroll: function(view) {
this.syncHorizontalScroll(view);
},
syncHorizontalScroll: function(target, setBody) {
var me = this,
x = me.view.getScrollX(),
scrollTarget;
setBody = setBody === true;
// Only set the horizontal scroll if we've changed position,
// so that we don't set this on vertical scrolls
if (me.rendered && (setBody || x !== me.scrollXPos)) {
// Only set the body position if we're reacting to a refresh, otherwise
// we just need to set the header.
if (setBody) {
scrollTarget = me.getScrollTarget();
scrollTarget.setScrollX(x);
}
me.headerCt.setScrollX(x);
me.scrollXPos = x;
}
},
// template method meant to be overriden
onStoreLoad: Ext.emptyFn,
getEditorParent: function() {
return this.body;
},
bindStore: function(store, initial) {
var me = this,
view = me.getView(),
bufferedRenderer = me.bufferedRenderer;
// Normally, this method will always be called with a valid store (because there is a symmetric
// .unbindStore method), but there are cases where this method will be called and passed a null
// value, i.e., a panel is used as a pickerfield. See EXTJS-13089.
if (store) {
// Bind to store immediately because subsequent processing looks for grid's store property
me.store = store;
// If we're in a reconfigure (we already have a BufferedRenderer which is bound to our old store),
// rebind the BufferedRenderer
if (bufferedRenderer && bufferedRenderer.isBufferedRenderer && bufferedRenderer.store) {
bufferedRenderer.bindStore(store);
}
if (view.store !== store) {
// If coming from a reconfigure, we need to set the actual store property on the view. Setting the
// store will then also set the dataSource.
//
// Note that if it's a grid feature then this is sorted out in view.bindStore(), and it's own
// implementation of .bindStore() will be called.
view.bindStore(store, false);
}
me.mon(store, {
load: me.onStoreLoad,
scope: me
});
me.storeRelayers = me.relayEvents(store, [
/**
* @event filterchange
* @inheritdoc Ext.data.Store#filterchange
*/
'filterchange',
/**
* @event groupchange
* @inheritdoc Ext.data.Store#groupchange
*/
'groupchange'
]);
} else {
me.unbindStore();
}
},
unbindStore: function() {
var me = this,
store = me.store,
view;
if (store) {
me.store = null;
me.mun(store, {
load: me.onStoreLoad,
scope: me
});
Ext.destroy(me.storeRelayers);
view = me.view;
if (view.store) {
view.bindStore(null);
}
}
},
setColumns: function(columns) {
// If being reconfigured from zero columns to zero columns, skip operation.
// This can happen if columns are being set from a binding and the initial value
// of the bound data in the ViewModel is []
if (columns.length || this.getColumnManager().getColumns().length) {
this.reconfigure(undefined, columns);
}
},
setStore: function (store) {
this.reconfigure(store);
},
/**
* @method reconfigure
* Reconfigures the grid / tree with a new store/columns. Either the store or the
* columns can be omitted if you don't wish to change them.
*
* The {@link #enableLocking} config should be set to `true` before the reconfigure
* method is executed if locked columns are intended to be used.
*
* @param {Ext.data.Store} [store] The new store. You can pass `null` if no new store.
* @param {Object[]} [columns] An array of column configs
*/
reconfigure: function(store, columns) {
var me = this,
oldStore = me.store,
headerCt = me.headerCt,
oldColumns = headerCt ? headerCt.items.getRange() : me.columns;
// Allow optional store argument to be fully omitted, and the columns argument to be solo
if (arguments.length === 1 && Ext.isArray(store)) {
columns = store;
store = null;
}
// Make copy in case the beforereconfigure listener mutates it.
if (columns) {
columns = Ext.Array.slice(columns);
}
me.reconfiguring = true;
me.fireEvent('beforereconfigure', me, store, columns, oldStore, oldColumns);
if (me.lockable) {
me.reconfigureLockable(store, columns);
} else {
Ext.suspendLayouts();
if (columns) {
// new columns, delete scroll pos
delete me.scrollXPos;
headerCt.removeAll();
headerCt.add(columns);
}
// The following test compares the result of an assignment of the store var with the oldStore var
// This saves a large amount of code.
if (store && (store = Ext.StoreManager.lookup(store)) !== oldStore) {
me.unbindStore();
me.bindStore(store);
} else {
me.getView().refreshView();
}
Ext.resumeLayouts(true);
}
me.fireEvent('reconfigure', me, store, columns, oldStore, oldColumns);
delete me.reconfiguring;
},
beforeDestroy: function(){
var task = this.scrollTask;
if (task) {
task.cancel();
this.scrollTask = null;
}
Ext.destroy(this.focusEnterLeaveListeners);
this.callParent();
},
onDestroy: function(){
var me = this;
if (me.lockable) {
me.destroyLockable();
}
me.unbindStore();
me.callParent();
me.columns = me.storeRelayers = me.columnManager = me.visibleColumnManager = null;
},
destroy: function() {
// Clear out references here because other things (plugins/features) may need to know about them during destruction
var me = this;
me.callParent();
if (me.isDestroyed) {
me.view = me.selModel = me.headerCt = null;
}
},
privates: {
getFocusEl: function() {
return this.getView().getFocusEl();
}
}
});