hangoutsslackgmailskypefacebook-workplaceoutlookemailmicrosoft-teamsdiscordmessengercustom-servicesmacoslinuxwindowsinboxwhatsappicloudtweetdeckhipchattelegram
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1284 lines
48 KiB
1284 lines
48 KiB
/** |
|
* @private |
|
* |
|
* Lockable is a private mixin which injects lockable behavior into any |
|
* TablePanel subclass such as GridPanel or TreePanel. TablePanel will |
|
* automatically inject the Ext.grid.locking.Lockable mixin in when one of the |
|
* these conditions are met: |
|
* |
|
* - The TablePanel has the lockable configuration set to true |
|
* - One of the columns in the TablePanel has locked set to true/false |
|
* |
|
* Each TablePanel subclass must register an alias. It should have an array |
|
* of configurations to copy to the 2 separate tablepanels that will be generated |
|
* to note what configurations should be copied. These are named normalCfgCopy and |
|
* lockedCfgCopy respectively. |
|
* |
|
* Configurations which are specified in this class will be available on any grid or |
|
* tree which is using the lockable functionality. |
|
* |
|
* By default the two grids, "locked" and "normal" will be arranged using an {@link Ext.layout.container.HBox hbox} |
|
* layout. If the lockable grid is configured with `{@link #split split:true}`, a vertical splitter |
|
* will be placed between the two grids to resize them. |
|
* |
|
* It is possible to override the layout of the lockable grid, or example, you may wish to |
|
* use a border layout and have one of the grids collapsible. |
|
*/ |
|
Ext.define('Ext.grid.locking.Lockable', { |
|
alternateClassName: 'Ext.grid.Lockable', |
|
|
|
requires: [ |
|
'Ext.grid.locking.View', |
|
'Ext.grid.header.Container', |
|
'Ext.grid.locking.HeaderContainer', |
|
'Ext.view.Table' |
|
], |
|
|
|
/** |
|
* @cfg {Boolean} syncRowHeight Synchronize rowHeight between the normal and |
|
* locked grid view. This is turned on by default. If your grid is guaranteed |
|
* to have rows of all the same height, you should set this to false to |
|
* optimize performance. |
|
*/ |
|
syncRowHeight: true, |
|
|
|
/** |
|
* @cfg {String} subGridXType The xtype of the subgrid to specify. If this is |
|
* not specified lockable will determine the subgrid xtype to create by the |
|
* following rule. Use the superclasses xtype if the superclass is NOT |
|
* tablepanel, otherwise use the xtype itself. |
|
*/ |
|
|
|
/** |
|
* @cfg {Object} lockedViewConfig A view configuration to be applied to the |
|
* locked side of the grid. Any conflicting configurations between lockedViewConfig |
|
* and viewConfig will be overwritten by the lockedViewConfig. |
|
*/ |
|
|
|
/** |
|
* @cfg {Object} normalViewConfig A view configuration to be applied to the |
|
* normal/unlocked side of the grid. Any conflicting configurations between normalViewConfig |
|
* and viewConfig will be overwritten by the normalViewConfig. |
|
*/ |
|
|
|
headerCounter: 0, |
|
|
|
/** |
|
* @cfg {Number} scrollDelta |
|
* Number of pixels to scroll when scrolling the locked section with mousewheel. |
|
*/ |
|
scrollDelta: 40, |
|
|
|
/** |
|
* @cfg {Object} lockedGridConfig |
|
* Any special configuration options for the locked part of the grid |
|
*/ |
|
|
|
/** |
|
* @cfg {Object} normalGridConfig |
|
* Any special configuration options for the normal part of the grid |
|
*/ |
|
|
|
/** |
|
* @cfg {Boolean} [split=false] |
|
* Configure as true to place a resizing {@link Ext.resizer.Splitter splitter} between the locked |
|
* and unlocked columns. |
|
*/ |
|
|
|
/** |
|
* @cfg {Object} [layout] |
|
* By default, a lockable grid uses an {@link Ext.layout.container.HBox HBox} layout to arrange |
|
* the two grids (possibly separated by a splitter). |
|
* |
|
* Using this config it is possible to specify a different layout to arrange the two grids. |
|
*/ |
|
|
|
lockedGridCls: Ext.baseCSSPrefix + 'grid-inner-locked', |
|
normalGridCls: Ext.baseCSSPrefix + 'grid-inner-normal', |
|
|
|
// i8n text |
|
//<locale> |
|
unlockText: 'Unlock', |
|
//</locale> |
|
//<locale> |
|
lockText: 'Lock', |
|
//</locale> |
|
|
|
// Required for the Lockable Mixin. These are the configurations which will be copied to the |
|
// normal and locked sub tablepanels |
|
bothCfgCopy: [ |
|
'invalidateScrollerOnRefresh', |
|
'hideHeaders', |
|
'enableColumnHide', |
|
'enableColumnMove', |
|
'enableColumnResize', |
|
'sortableColumns', |
|
'multiColumnSort', |
|
'columnLines', |
|
'rowLines', |
|
'variableRowHeight', |
|
'numFromEdge', |
|
'trailingBufferZone', |
|
'leadingBufferZone', |
|
'scrollToLoadBuffer' |
|
], |
|
normalCfgCopy: [ |
|
'verticalScroller', |
|
'verticalScrollDock', |
|
'verticalScrollerType', |
|
'scroll' |
|
], |
|
lockedCfgCopy: [], |
|
|
|
/** |
|
* @event processcolumns |
|
* Fires when the configured (or **reconfigured**) column set is split into two depending on the {@link Ext.grid.column.Column#locked locked} flag. |
|
* @param {Ext.grid.column.Column[]} lockedColumns The locked columns. |
|
* @param {Ext.grid.column.Column[]} normalColumns The normal columns. |
|
*/ |
|
|
|
/** |
|
* @event lockcolumn |
|
* Fires when a column is locked. |
|
* @param {Ext.grid.Panel} this The gridpanel. |
|
* @param {Ext.grid.column.Column} column The column being locked. |
|
*/ |
|
|
|
/** |
|
* @event unlockcolumn |
|
* Fires when a column is unlocked. |
|
* @param {Ext.grid.Panel} this The gridpanel. |
|
* @param {Ext.grid.column.Column} column The column being unlocked. |
|
*/ |
|
|
|
determineXTypeToCreate: function(lockedSide) { |
|
var me = this, |
|
typeToCreate, |
|
xtypes, xtypesLn, xtype, superxtype; |
|
|
|
if (me.subGridXType) { |
|
typeToCreate = me.subGridXType; |
|
} else { |
|
// Treeness only moves down into the locked side. |
|
// The normal side is always just a grid |
|
if (!lockedSide) { |
|
return 'gridpanel'; |
|
} |
|
xtypes = this.getXTypes().split('/'); |
|
xtypesLn = xtypes.length; |
|
xtype = xtypes[xtypesLn - 1]; |
|
superxtype = xtypes[xtypesLn - 2]; |
|
|
|
if (superxtype !== 'tablepanel') { |
|
typeToCreate = superxtype; |
|
} else { |
|
typeToCreate = xtype; |
|
} |
|
} |
|
|
|
return typeToCreate; |
|
}, |
|
|
|
// injectLockable will be invoked before initComponent's parent class implementation |
|
// is called, so throughout this method this. are configurations |
|
injectLockable: function() { |
|
// The child grids are focusable, not this one |
|
this.focusable = false; |
|
|
|
// ensure lockable is set to true in the TablePanel |
|
this.lockable = true; |
|
// Instruct the TablePanel it already has a view and not to create one. |
|
// We are going to aggregate 2 copies of whatever TablePanel we are using |
|
this.hasView = true; |
|
|
|
var me = this, |
|
scrollbarSize = Ext.getScrollbarSize(), |
|
scrollbarWidth = scrollbarSize.width, |
|
store = me.store = Ext.StoreManager.lookup(me.store), |
|
lockedViewConfig = me.lockedViewConfig, |
|
normalViewConfig = me.normalViewConfig, |
|
Obj = Ext.Object, |
|
|
|
// Hash of {lockedFeatures:[],normalFeatures:[]} |
|
allFeatures, |
|
|
|
// Hash of {topPlugins:[],lockedPlugins:[],normalPlugins:[]} |
|
allPlugins, |
|
|
|
lockedGrid, |
|
normalGrid, |
|
i, |
|
columns, |
|
lockedHeaderCt, |
|
normalHeaderCt, |
|
listeners, |
|
viewConfig = me.viewConfig, |
|
// When setting the loadMask value, the viewConfig wins if it is defined. |
|
loadMaskCfg = viewConfig && viewConfig.loadMask, |
|
loadMask = (loadMaskCfg !== undefined) ? loadMaskCfg : me.loadMask, |
|
bufferedRenderer = me.bufferedRenderer, |
|
clipVertLockedScrollbar = scrollbarWidth > 0 && Ext.supports.touchScroll !== 2; |
|
|
|
allFeatures = me.constructLockableFeatures(); |
|
|
|
// This is just a "shell" Panel which acts as a Container for the two grids and must not use the features |
|
me.features = null; |
|
|
|
// Distribute plugins to whichever Component needs them |
|
allPlugins = me.constructLockablePlugins(); |
|
me.plugins = allPlugins.topPlugins; |
|
|
|
lockedGrid = { |
|
id: me.id + '-locked', |
|
isLocked: true, |
|
bufferedRenderer: bufferedRenderer, |
|
ownerGrid: me, |
|
ownerLockable: me, |
|
xtype: me.determineXTypeToCreate(true), |
|
store: store, |
|
// if the browser's scrollbars take up space we always reserve space for the |
|
// vertical scrollbar on the locked side. This allows us to hide the vertical |
|
// scrollbar by clipping it using the locked grid's body element. |
|
reserveScrollbar: clipVertLockedScrollbar, |
|
scrollable: { |
|
indicators: { |
|
x: true, |
|
y: false |
|
} |
|
}, |
|
scrollerOwner: false, |
|
// Lockable does NOT support animations for Tree |
|
// Because the right side is just a grid, and the grid view doen't animate bulk insertions/removals |
|
animate: false, |
|
border: false, |
|
cls: me.lockedGridCls, |
|
|
|
// Usually a layout in one side necessitates the laying out of the other side even if each is fully |
|
// managed in both dimensions, and is therefore a layout root. |
|
// The only situation that we do *not* want layouts to escape into the owning lockable assembly |
|
// is when using a border layout and any of the border regions is floated from a collapsed state. |
|
isLayoutRoot: function() { |
|
return this.floatedFromCollapse || me.normalGrid.floatedFromCollapse; |
|
}, |
|
features: allFeatures.lockedFeatures, |
|
plugins: allPlugins.lockedPlugins |
|
}; |
|
|
|
normalGrid = { |
|
id: me.id + '-normal', |
|
isLocked: false, |
|
bufferedRenderer: bufferedRenderer, |
|
ownerGrid: me, |
|
ownerLockable: me, |
|
xtype: me.determineXTypeToCreate(), |
|
store: store, |
|
// Pass down our reserveScrollbar to the normal side: |
|
reserveScrollbar: me.reserveScrollbar, |
|
scrollerOwner: false, |
|
border: false, |
|
cls: me.normalGridCls, |
|
|
|
// As described above, isolate layouts when floated out from a collapsed border region. |
|
isLayoutRoot: function() { |
|
return this.floatedFromCollapse || me.lockedGrid.floatedFromCollapse; |
|
}, |
|
features: allFeatures.normalFeatures, |
|
plugins: allPlugins.normalPlugins |
|
}; |
|
|
|
me.addCls(Ext.baseCSSPrefix + 'grid-locked'); |
|
|
|
// Copy appropriate configurations to the respective aggregated tablepanel instances. |
|
// Pass 4th param true to NOT exclude those settings on our prototype. |
|
// Delete them from the master tablepanel. |
|
Ext.copyTo(normalGrid, me, me.bothCfgCopy, true); |
|
Ext.copyTo(lockedGrid, me, me.bothCfgCopy, true); |
|
Ext.copyTo(normalGrid, me, me.normalCfgCopy, true); |
|
Ext.copyTo(lockedGrid, me, me.lockedCfgCopy, true); |
|
|
|
Ext.apply(normalGrid, me.normalGridConfig); |
|
Ext.apply(lockedGrid, me.lockedGridConfig); |
|
|
|
for (i = 0; i < me.normalCfgCopy.length; i++) { |
|
delete me[me.normalCfgCopy[i]]; |
|
} |
|
|
|
for (i = 0; i < me.lockedCfgCopy.length; i++) { |
|
delete me[me.lockedCfgCopy[i]]; |
|
} |
|
|
|
me.addStateEvents(['lockcolumn', 'unlockcolumn']); |
|
|
|
columns = me.processColumns(me.columns || [], lockedGrid); |
|
|
|
lockedGrid.columns = columns.locked; |
|
|
|
// If no locked columns, hide the locked grid |
|
if (!lockedGrid.columns.items.length) { |
|
lockedGrid.hidden = true; |
|
} |
|
normalGrid.columns = columns.normal; |
|
|
|
if (!normalGrid.columns.items.length) { |
|
normalGrid.hidden = true; |
|
} |
|
|
|
// normal grid should flex the rest of the width |
|
normalGrid.flex = 1; |
|
// Chain view configs to avoid mutating user's config |
|
lockedGrid.viewConfig = lockedViewConfig = (lockedViewConfig ? Obj.chain(lockedViewConfig) : {}); |
|
normalGrid.viewConfig = normalViewConfig = (normalViewConfig ? Obj.chain(normalViewConfig) : {}); |
|
lockedViewConfig.loadingUseMsg = false; |
|
lockedViewConfig.loadMask = false; |
|
if (clipVertLockedScrollbar) { |
|
lockedViewConfig.margin = '0 -' + scrollbarWidth + ' 0 0'; |
|
} |
|
|
|
normalViewConfig.loadMask = false; |
|
|
|
//<debug> |
|
if (viewConfig && viewConfig.id) { |
|
Ext.log.warn('id specified on Lockable viewConfig, it will be shared between both views: "' + viewConfig.id + '"'); |
|
} |
|
//</debug> |
|
|
|
Ext.applyIf(lockedViewConfig, viewConfig); |
|
Ext.applyIf(normalViewConfig, viewConfig); |
|
|
|
// Allow developer to configure the layout. |
|
// Instantiate the layout so its type can be ascertained. |
|
if (!me.initialConfig.layout) { |
|
me.layout = { |
|
type: 'hbox', |
|
align: 'stretch' |
|
}; |
|
} |
|
me.getLayout(); |
|
|
|
// Sanity check the split config. |
|
// Only allowed to insert a splitter between the two grids if it's a box layout |
|
if (me.layout.type === 'border') { |
|
if (me.split) { |
|
lockedGrid.split = true; |
|
} |
|
if (!lockedGrid.region) { |
|
lockedGrid.region = 'west'; |
|
} |
|
if (!normalGrid.region) { |
|
normalGrid.region = 'center'; |
|
} |
|
me.addCls(Ext.baseCSSPrefix + 'grid-locked-split'); |
|
} |
|
if (!(me.layout instanceof Ext.layout.container.Box)) { |
|
me.split = false; |
|
} |
|
|
|
// The LockingView is a pseudo view which owns the two grids. |
|
// It listens for store events and relays the calls into each view bracketed by a layout suspension. |
|
me.view = new Ext.grid.locking.View({ |
|
loadMask: loadMask, |
|
locked: lockedGrid, |
|
normal: normalGrid, |
|
ownerGrid: me |
|
}); |
|
|
|
// after creating the locking view we now have Grid instances for both locked and |
|
// unlocked sides |
|
lockedGrid = me.lockedGrid; |
|
normalGrid = me.normalGrid; |
|
|
|
// make the locked/unlocked sides mirror each other's vertical scroll positions. |
|
normalGrid.getView().getScrollable().addPartner(lockedGrid.getView().getScrollable(), 'y'); |
|
|
|
// Extract the instantiated views from the locking View. |
|
// The locking View injects lockingGrid and normalGrid into this lockable panel. |
|
// This is because during constraction, it must be possible for descendant components |
|
// to navigate up to the owning lockable panel and then down into either side. |
|
|
|
// If there are system scrollbars, we have to monitor the mousewheel and fake a scroll |
|
// Also we need to postprocess the border width because of inline border setting styles. |
|
// The locked grid needs a bottom border to match with any scrollbar present in the normal grid . |
|
// Keep locked section's bottom border width synched |
|
if (scrollbarSize.height && Ext.supports.touchScroll !== 2) { |
|
lockedGrid.on({ |
|
afterlayout: me.afterLockedViewLayout, |
|
scope: me |
|
}); |
|
|
|
// Ensure the overflow flags have been calculated from the various overflow configs |
|
lockedGrid.getView().getOverflowStyle(); |
|
} |
|
|
|
lockedHeaderCt = lockedGrid.headerCt; |
|
normalHeaderCt = normalGrid.headerCt; |
|
|
|
if (clipVertLockedScrollbar) { |
|
// if we are clipping the locked vertical scrollbar, we do not want the |
|
// headerCt to reserve room for one |
|
lockedHeaderCt.reserveScrollbar = false; |
|
} |
|
|
|
// The top grid, and the LockingView both need to have a headerCt which is usable. |
|
// It is part of their private API that framework code uses when dealing with a grid or grid view |
|
me.headerCt = me.view.headerCt = new Ext.grid.locking.HeaderContainer(me); |
|
|
|
lockedHeaderCt.lockedCt = true; |
|
lockedHeaderCt.lockableInjected = true; |
|
normalHeaderCt.lockableInjected = true; |
|
|
|
lockedHeaderCt.on({ |
|
add: me.delaySyncLockedWidth, |
|
remove: me.delaySyncLockedWidth, |
|
columnshow: me.delaySyncLockedWidth, |
|
columnhide: me.delaySyncLockedWidth, |
|
sortchange: me.onLockedHeaderSortChange, |
|
columnresize: me.delaySyncLockedWidth, |
|
scope: me |
|
}); |
|
|
|
normalHeaderCt.on({ |
|
add: me.delaySyncLockedWidth, |
|
remove: me.delaySyncLockedWidth, |
|
columnshow: me.delaySyncLockedWidth, |
|
columnhide: me.delaySyncLockedWidth, |
|
sortchange: me.onNormalHeaderSortChange, |
|
scope: me |
|
}); |
|
|
|
me.modifyHeaderCt(); |
|
me.items = [lockedGrid]; |
|
if (me.split) { |
|
me.addCls(Ext.baseCSSPrefix + 'grid-locked-split'); |
|
me.items[1] = { |
|
xtype: 'splitter' |
|
}; |
|
} |
|
me.items.push(normalGrid); |
|
|
|
me.relayHeaderCtEvents(lockedHeaderCt); |
|
me.relayHeaderCtEvents(normalHeaderCt); |
|
|
|
// The top level Lockable container does not get bound to the store, so we need to programatically add the relayer so that |
|
// The filterchange state event is fired. |
|
me.storeRelayers = me.relayEvents(store, [ |
|
/** |
|
* @event filterchange |
|
* @inheritdoc Ext.data.Store#filterchange |
|
*/ |
|
'filterchange', |
|
/** |
|
* @event groupchange |
|
* @inheritdoc Ext.data.Store#groupchange |
|
*/ |
|
'groupchange' |
|
]); |
|
|
|
// Only need to relay from the normalGrid. Since it's created after the lockedGrid, |
|
// we can be confident to only listen to it. |
|
me.gridRelayers = me.relayEvents(normalGrid, [ |
|
/** |
|
* @event viewready |
|
* @inheritdoc Ext.panel.Table#viewready |
|
*/ |
|
'viewready' |
|
]); |
|
}, |
|
|
|
getLockingViewConfig: function(){ |
|
return { |
|
xclass: 'Ext.grid.locking.View', |
|
locked: this.lockedGrid, |
|
normal: this.normalGrid, |
|
panel: this |
|
}; |
|
}, |
|
|
|
processColumns: function(columns, lockedGrid) { |
|
// split apart normal and locked |
|
var me = this, |
|
i, |
|
len, |
|
column, |
|
cp = new Ext.grid.header.Container(), |
|
lockedHeaders = [], |
|
normalHeaders = [], |
|
lockedHeaderCt = { |
|
itemId: 'lockedHeaderCt', |
|
stretchMaxPartner: '^^>>#normalHeaderCt', |
|
items: lockedHeaders |
|
}, |
|
normalHeaderCt = { |
|
itemId: 'normalHeaderCt', |
|
stretchMaxPartner: '^^>>#lockedHeaderCt', |
|
items: normalHeaders |
|
}, |
|
result = { |
|
lockedWidth: lockedGrid.width || 0, |
|
locked: lockedHeaderCt, |
|
normal: normalHeaderCt |
|
}, |
|
shrinkWrapLocked = !(lockedGrid.width || lockedGrid.flex), |
|
copy; |
|
|
|
// Only save the initial configuration, since a width will be stamped on |
|
// after we sync the width. |
|
if (!me.hasOwnProperty('shrinkWrapLocked')) { |
|
me.shrinkWrapLocked = shrinkWrapLocked; |
|
} |
|
|
|
// In case they specified a config object with items... |
|
if (Ext.isObject(columns)) { |
|
Ext.applyIf(lockedHeaderCt, columns); |
|
Ext.applyIf(normalHeaderCt, columns); |
|
copy = Ext.apply({}, columns); |
|
delete copy.items; |
|
Ext.apply(cp, copy); |
|
columns = columns.items; |
|
} |
|
|
|
// Treat the column header as though we're just creating an instance, since this |
|
// doesn't follow the normal column creation pattern |
|
cp.constructing = true; |
|
for (i = 0, len = columns.length; i < len; ++i) { |
|
column = columns[i]; |
|
|
|
// Use the HeaderContainer object to correctly configure and create the column. |
|
// MUST instantiate now because the locked or autoLock config which we read here might be in the prototype. |
|
// MUST use a Container instance so that defaults from an object columns config get applied. |
|
if (!column.isComponent) { |
|
column = cp.applyDefaults(column); |
|
column.initOwnerCt = cp; |
|
column = cp.lookupComponent(column); |
|
delete column.initOwnerCt; |
|
} |
|
|
|
// mark the column as processed so that the locked attribute does not |
|
// trigger the locked subgrid to try to become a split lockable grid itself. |
|
column.processed = true; |
|
if (column.locked || column.autoLock) { |
|
// If the locked grid has not been configured with a width, we must |
|
// Calculate a width from the total width of locked columns |
|
if (shrinkWrapLocked && !column.hidden) { |
|
result.lockedWidth += me.getColumnWidth(column) || cp.defaultWidth; |
|
} |
|
lockedHeaders.push(column); |
|
} else { |
|
normalHeaders.push(column); |
|
} |
|
if (!column.headerId) { |
|
column.headerId = (column.initialConfig || column).id || ('h' + (++me.headerCounter)); |
|
} |
|
} |
|
me.fireEvent('processcolumns', me, lockedHeaders, normalHeaders); |
|
cp.destroy(); |
|
|
|
// If grid has not been configured with a width it must shrinkwrap columns with no horiontal scroll |
|
// TODO: Use shrinkWrapDock on the locked grid when it works. |
|
if (shrinkWrapLocked) { |
|
lockedGrid.width = result.lockedWidth; |
|
} |
|
return result; |
|
}, |
|
|
|
// Used when calculating total locked column width in processColumns |
|
// Use shrinkwrapping of child columns if no top level width. |
|
getColumnWidth: function(column) { |
|
var result = column.width || 0, |
|
subcols, len, i; |
|
|
|
// <debug> |
|
if (column.flex) { |
|
Ext.Error.raise("Locked columns in an unsized locked side do NOT support a flex width. You must set a width on the " + column.text + "column."); |
|
} |
|
// </debug> |
|
if (!result && column.isGroupHeader) { |
|
subcols = column.items.items; |
|
len = subcols.length; |
|
for (i = 0; i < len; i++) { |
|
result += this.getColumnWidth(subcols[i]); |
|
} |
|
} |
|
return result; |
|
}, |
|
|
|
// Due to automatic component border setting using inline style, to create the scrollbar-replacing |
|
// bottom border, we have to postprocess the locked view *after* render. |
|
// A tall bottom border takes the place of a horiz scrollbar if the opposite side has a horiz scrollbar. |
|
// When we can use overflow-x: scroll to create a matching scrollbar, we do this instead. |
|
afterLockedViewLayout: function() { |
|
var me = this, |
|
lockedGrid = me.lockedGrid, |
|
normalGrid = me.normalGrid, |
|
lockedView = lockedGrid.getView(), |
|
normalView = normalGrid.getView(), |
|
spacerHeight = Ext.getScrollbarSize().height, |
|
lockedViewHorizScrollBar = (lockedView.scrollFlags.x && lockedGrid.headerCt.tooNarrow ? spacerHeight : 0), |
|
normalViewHorizScrollBar = (normalView.scrollFlags.x && normalGrid.headerCt.tooNarrow ? spacerHeight : 0), |
|
normalScroller = normalView.getScrollable(), |
|
lockedScroller = lockedView.getScrollable(); |
|
|
|
if (lockedViewHorizScrollBar !== normalViewHorizScrollBar) { |
|
if (lockedViewHorizScrollBar) { |
|
normalScroller.setX('scroll'); |
|
lockedScroller.setX(true); |
|
} else { |
|
lockedScroller.setX('scroll'); |
|
normalScroller.setX(true); |
|
} |
|
} else { |
|
lockedScroller.setX(normalViewHorizScrollBar ? 'scroll' : true); |
|
normalScroller.setX(true); |
|
} |
|
|
|
}, |
|
|
|
ensureLockedVisible: function() { |
|
this.lockedGrid.ensureVisible.apply(this.lockedGrid, arguments); |
|
this.normalGrid.ensureVisible.apply(this.normalGrid, arguments); |
|
}, |
|
|
|
onLockedViewMouseWheel: function(e) { |
|
var me = this, |
|
deltaY = -me.scrollDelta * e.getWheelDeltas().y, |
|
lockedView = me.lockedGrid.getView(), |
|
lockedViewElDom = lockedView.el.dom, |
|
scrollTop, verticalCanScrollDown, verticalCanScrollUp; |
|
|
|
if (!me.ignoreMousewheel) { |
|
if (lockedViewElDom) { |
|
scrollTop = lockedView.getScrollY(); |
|
verticalCanScrollDown = scrollTop !== lockedViewElDom.scrollHeight - lockedViewElDom.clientHeight; |
|
verticalCanScrollUp = scrollTop !== 0; |
|
} |
|
|
|
if ((deltaY < 0 && verticalCanScrollUp) || (deltaY > 0 && verticalCanScrollDown)) { |
|
e.stopEvent(); |
|
|
|
// Inhibit processing of any scroll events we *may* cause here. |
|
// Some OSs do not fire a scroll event when we set the scrollTop of an overflow:hidden element, |
|
// so we invoke the scroll handler programatically below. |
|
scrollTop += deltaY; |
|
lockedView.setScrollY(scrollTop); |
|
me.normalGrid.getView().setScrollY(scrollTop); |
|
|
|
// Invoke the scroll event handler programatically to sync everything. |
|
me.onNormalViewScroll(); |
|
} |
|
} |
|
}, |
|
|
|
onLockedViewScroll: function() { |
|
var me = this, |
|
lockedView = me.lockedGrid.getView(), |
|
normalView = me.normalGrid.getView(), |
|
lockedScrollTop = lockedView.getScrollY(), |
|
normalScrollTop = normalView.getScrollY(), |
|
normalTable, |
|
lockedTable; |
|
|
|
if (normalScrollTop !== lockedScrollTop) { |
|
normalView.setScrollY(lockedScrollTop); |
|
|
|
// For buffered views, the absolute position is important as well as scrollTop |
|
if (normalView.bufferedRenderer) { |
|
lockedTable = lockedView.body.dom; |
|
normalTable = normalView.body.dom; |
|
normalTable.style.position = 'absolute'; |
|
normalTable.style.top = lockedTable.style.top; |
|
} |
|
} |
|
}, |
|
|
|
onNormalViewScroll: function() { |
|
var me = this, |
|
lockedView = me.lockedGrid.getView(), |
|
normalView = me.normalGrid.getView(), |
|
lockedScrollTop = lockedView.getScrollY(), |
|
normalScrollTop = normalView.getScrollY(), |
|
lockedRowContainer; |
|
|
|
if (normalScrollTop !== lockedScrollTop) { |
|
lockedView.setScrollY(normalScrollTop); |
|
|
|
// For buffered views, the absolute position is important as well as scrollTop |
|
if (normalView.bufferedRenderer) { |
|
lockedRowContainer = lockedView.body; |
|
|
|
// If we have attached the Fly to a DOM (will not have happened if all locked columns are hidden) |
|
if (lockedRowContainer.dom) { |
|
lockedRowContainer.dom.style.position = 'absolute'; |
|
lockedRowContainer.translate(null, normalView.bufferedRenderer.bodyTop); |
|
} |
|
} |
|
} |
|
}, |
|
/** |
|
* Synchronizes the row heights between the locked and non locked portion of the grid for each |
|
* row. If one row is smaller than the other, the height will be increased to match the larger one. |
|
*/ |
|
syncRowHeights: function() { |
|
// This is now called on animationFrame. It may have been destroyed in the interval. |
|
if (!this.isDestroyed) { |
|
var me = this, |
|
normalView = me.normalGrid.getView(), |
|
lockedView = me.lockedGrid.getView(), |
|
// These will reset any forced height styles from the last sync |
|
normalSync = normalView.syncRowHeightBegin(), |
|
lockedSync = lockedView.syncRowHeightBegin(), |
|
scrollTop; |
|
|
|
// Now bulk measure everything |
|
normalView.syncRowHeightMeasure(normalSync); |
|
lockedView.syncRowHeightMeasure(lockedSync); |
|
|
|
// Now write out all the explicit heights we need to sync up |
|
normalView.syncRowHeightFinish(normalSync, lockedSync); |
|
lockedView.syncRowHeightFinish(lockedSync, normalSync); |
|
|
|
// Synchronize the scrollTop positions of the two views |
|
scrollTop = normalView.getScrollY(); |
|
lockedView.setScrollY(scrollTop); |
|
} |
|
}, |
|
|
|
// inject Lock and Unlock text |
|
// Hide/show Lock/Unlock options |
|
modifyHeaderCt: function() { |
|
var me = this; |
|
me.lockedGrid.headerCt.getMenuItems = me.getMenuItems(me.lockedGrid.headerCt.getMenuItems, true); |
|
me.normalGrid.headerCt.getMenuItems = me.getMenuItems(me.normalGrid.headerCt.getMenuItems, false); |
|
me.lockedGrid.headerCt.showMenuBy = Ext.Function.createInterceptor(me.lockedGrid.headerCt.showMenuBy, me.showMenuBy); |
|
me.normalGrid.headerCt.showMenuBy = Ext.Function.createInterceptor(me.normalGrid.headerCt.showMenuBy, me.showMenuBy); |
|
}, |
|
|
|
onUnlockMenuClick: function() { |
|
this.unlock(); |
|
}, |
|
|
|
onLockMenuClick: function() { |
|
this.lock(); |
|
}, |
|
|
|
showMenuBy: function(clickEvent, t, header) { |
|
var menu = this.getMenu(), |
|
unlockItem = menu.down('#unlockItem'), |
|
lockItem = menu.down('#lockItem'), |
|
sep = unlockItem.prev(); |
|
|
|
if (header.lockable === false) { |
|
sep.hide(); |
|
unlockItem.hide(); |
|
lockItem.hide(); |
|
} else { |
|
sep.show(); |
|
unlockItem.show(); |
|
lockItem.show(); |
|
if (!unlockItem.initialConfig.disabled) { |
|
unlockItem.setDisabled(header.lockable === false); |
|
} |
|
if (!lockItem.initialConfig.disabled) { |
|
lockItem.setDisabled(!header.isLockable()); |
|
} |
|
} |
|
}, |
|
|
|
getMenuItems: function(getMenuItems, locked) { |
|
var me = this, |
|
unlockText = me.unlockText, |
|
lockText = me.lockText, |
|
unlockCls = Ext.baseCSSPrefix + 'hmenu-unlock', |
|
lockCls = Ext.baseCSSPrefix + 'hmenu-lock', |
|
unlockHandler = me.onUnlockMenuClick.bind(me), |
|
lockHandler = me.onLockMenuClick.bind(me); |
|
|
|
// runs in the scope of headerCt |
|
return function() { |
|
|
|
// We cannot use the method from HeaderContainer's prototype here |
|
// because other plugins or features may already have injected an implementation |
|
var o = getMenuItems.call(this); |
|
o.push('-', { |
|
itemId: 'unlockItem', |
|
iconCls: unlockCls, |
|
text: unlockText, |
|
handler: unlockHandler, |
|
disabled: !locked |
|
}); |
|
o.push({ |
|
itemId: 'lockItem', |
|
iconCls: lockCls, |
|
text: lockText, |
|
handler: lockHandler, |
|
disabled: locked |
|
}); |
|
return o; |
|
}; |
|
}, |
|
|
|
//<debug> |
|
syncTaskDelay: 1, |
|
//</debug> |
|
|
|
delaySyncLockedWidth: function() { |
|
var me = this, |
|
task = me.syncLockedWidthTask; |
|
|
|
if (!task) { |
|
task = me.syncLockedWidthTask = new Ext.util.DelayedTask(me.syncLockedWidth, me); |
|
} |
|
|
|
// Only useful for unit testing so we don't have to mess around with timers |
|
//<debug> |
|
if (me.syncTaskDelay === 0) { |
|
me.syncLockedWidth(); |
|
} else { |
|
//</debug> |
|
task.delay(1); |
|
//<debug> |
|
} |
|
//</debug> |
|
}, |
|
|
|
/** |
|
* @private |
|
* Updates the overall view after columns have been resized, or moved from |
|
* the locked to unlocked side or vice-versa. |
|
* |
|
* If all columns are removed from either side, that side must be hidden, and the |
|
* sole remaining column owning grid then becomes *the* grid. It must flex to occupy the |
|
* whole of the locking view. And it must also allow scrolling. |
|
* |
|
* If columns are shared between the two sides, the *locked* grid shrinkwraps the |
|
* width of the visible locked columns while the normal grid flexes in what space remains. |
|
* |
|
* @return {Boolean} `true` if there are visible locked columns which need refreshing. |
|
* |
|
*/ |
|
syncLockedWidth: function() { |
|
var me = this, |
|
rendered = me.rendered, |
|
locked = me.lockedGrid, |
|
lockedView = locked.view, |
|
normal = me.normalGrid, |
|
lockedColCount = locked.getVisibleColumnManager().getColumns().length, |
|
normalColCount = normal.getVisibleColumnManager().getColumns().length, |
|
task = me.syncLockedWidthTask; |
|
|
|
// If we are called directly, veto any existing task. |
|
if (task) { |
|
task.cancel(); |
|
} |
|
|
|
Ext.suspendLayouts(); |
|
|
|
// If there are still visible normal columns, then the normal grid will flex |
|
// while we effectively shrinkwrap the width of the locked columns |
|
if (normalColCount) { |
|
normal.show(); |
|
if (lockedColCount) { |
|
|
|
// The locked grid shrinkwraps the total column width while the normal grid flexes in what remains |
|
// UNLESS it has been set to forceFit |
|
if (rendered && me.shrinkWrapLocked && !locked.headerCt.forceFit) { |
|
delete locked.flex; |
|
// Don't pass the purge flag here |
|
locked.setWidth(locked.headerCt.getTableWidth() + locked.el.getBorderWidth('lr')); |
|
} |
|
locked.addCls(me.lockedGridCls); |
|
locked.show(); |
|
if (me.split) { |
|
me.child('splitter').show(); |
|
} |
|
} else { |
|
// No visible locked columns: hide the locked grid |
|
// We also need to trigger a clearViewEl to clear out any |
|
// old dom nodes |
|
if (rendered) { |
|
locked.getView().clearViewEl(true); |
|
} |
|
locked.hide(); |
|
if (me.split) { |
|
me.child('splitter').hide(); |
|
} |
|
} |
|
|
|
// Only if there is going to be an upcoming layout to correct the horizontal scrollbar setting. |
|
if (Ext.supports.touchScroll !== 2 && Ext.Component.pendingLayouts) { |
|
// We may have previously set horizontal placeholder scrollbar on the locked |
|
// view to match the unlocked side. Undo this before continuing, so that |
|
// the horizontal scrollbar does not affect the layout of the columns by |
|
// possibly triggering a vertical scrollbar as well |
|
lockedView.getScrollable().setX(true); |
|
} |
|
|
|
// Ignore mousewheel events if the view is configured to scroll vertically |
|
if (rendered) { |
|
me.ignoreMousewheel = lockedView.scrollFlags.y; |
|
} |
|
} |
|
|
|
// There are no normal grid columns. The "locked" grid has to be *the* |
|
// grid, and cannot have a shrinkwrapped width, but must flex the entire width. |
|
else { |
|
normal.hide(); |
|
|
|
// When the normal grid is hidden, we no longer need the bottom border "scrollbar replacement" |
|
if (rendered) { |
|
lockedView.getEl().setStyle('border-bottom-width', '0'); |
|
} |
|
|
|
// The locked now becomes *the* grid and has to flex to occupy the full view width |
|
locked.flex = 1; |
|
delete locked.width; |
|
locked.removeCls(me.lockedGridCls); |
|
locked.show(); |
|
|
|
me.ignoreMousewheel = true; |
|
} |
|
Ext.resumeLayouts(true); |
|
return [lockedColCount, normalColCount]; |
|
}, |
|
|
|
onLockedHeaderSortChange: Ext.emptyFn, |
|
|
|
onNormalHeaderSortChange: Ext.emptyFn, |
|
|
|
// going from unlocked section to locked |
|
/** |
|
* Locks the activeHeader as determined by which menu is open OR a header |
|
* as specified. |
|
* @param {Ext.grid.column.Column} [header] Header to unlock from the locked section. |
|
* Defaults to the header which has the menu open currently. |
|
* @param {Number} [toIdx] The index to move the unlocked header to. |
|
* Defaults to appending as the last item. |
|
* @private |
|
*/ |
|
lock: function(activeHd, toIdx, toCt) { |
|
var me = this, |
|
normalGrid = me.normalGrid, |
|
lockedGrid = me.lockedGrid, |
|
normalView = normalGrid.view, |
|
lockedView = lockedGrid.view, |
|
normalHCt = normalGrid.headerCt, |
|
refreshFlags, |
|
ownerCt, |
|
hadFocus; |
|
|
|
activeHd = activeHd || normalHCt.getMenu().activeHeader; |
|
hadFocus = activeHd.hasFocus; |
|
toCt = toCt || lockedGrid.headerCt; |
|
ownerCt = activeHd.ownerCt; |
|
|
|
// isLockable will test for making the locked side too wide |
|
if (!activeHd.isLockable()) { |
|
return; |
|
} |
|
|
|
// if column was previously flexed, get/set current width |
|
// and remove the flex |
|
if (activeHd.flex) { |
|
activeHd.width = activeHd.getWidth(); |
|
activeHd.flex = null; |
|
} |
|
|
|
Ext.suspendLayouts(); |
|
|
|
// If hidden, we need to show it now or the locked headerCt's .gridVisibleColumns may be out of sync as |
|
// headers are only added to a visible manager if they are not explicity hidden or hierarchically hidden. |
|
if (lockedGrid.hidden) { |
|
lockedGrid.show(); |
|
} |
|
|
|
// We decide which views to refresh. Do not let the grids do it in response to column changes |
|
normalView.blockRefresh = lockedView.blockRefresh = true; |
|
ownerCt.remove(activeHd, false); |
|
activeHd.locked = true; |
|
|
|
// Flag to the locked column add listener to do nothing |
|
if (Ext.isDefined(toIdx)) { |
|
toCt.insert(toIdx, activeHd); |
|
} else { |
|
toCt.add(activeHd); |
|
} |
|
normalView.blockRefresh = lockedView.blockRefresh = false; |
|
|
|
refreshFlags = me.syncLockedWidth(); |
|
if (refreshFlags[0]) { |
|
lockedGrid.getView().refreshView(); |
|
} |
|
if (refreshFlags[1]) { |
|
normalGrid.getView().refreshView(); |
|
} |
|
Ext.resumeLayouts(true); |
|
|
|
if (hadFocus) { |
|
activeHd.focus(); |
|
} |
|
me.fireEvent('lockcolumn', me, activeHd); |
|
}, |
|
|
|
// going from locked section to unlocked |
|
/** |
|
* Unlocks the activeHeader as determined by which menu is open OR a header |
|
* as specified. |
|
* @param {Ext.grid.column.Column} [header] Header to unlock from the locked section. |
|
* Defaults to the header which has the menu open currently. |
|
* @param {Number} [toIdx=0] The index to move the unlocked header to. |
|
* @private |
|
*/ |
|
unlock: function(activeHd, toIdx, toCt) { |
|
var me = this, |
|
normalGrid = me.normalGrid, |
|
lockedGrid = me.lockedGrid, |
|
normalView = normalGrid.view, |
|
lockedView = lockedGrid.view, |
|
lockedHCt = lockedGrid.headerCt, |
|
refreshFlags, |
|
hadFocus; |
|
|
|
// Unlocking; user expectation is that the unlocked column is inserted at the beginning. |
|
if (!Ext.isDefined(toIdx)) { |
|
toIdx = 0; |
|
} |
|
activeHd = activeHd || lockedHCt.getMenu().activeHeader; |
|
hadFocus = activeHd.hasFocus; |
|
toCt = toCt || normalGrid.headerCt; |
|
|
|
Ext.suspendLayouts(); |
|
|
|
// We decide which views to refresh. Do not let the grids do it in response to column changes |
|
normalView.blockRefresh = lockedView.blockRefresh = true; |
|
activeHd.ownerCt.remove(activeHd, false); |
|
activeHd.locked = false; |
|
toCt.insert(toIdx, activeHd); |
|
normalView.blockRefresh = lockedView.blockRefresh = false; |
|
|
|
// syncLockedWidth returns visible column counts for both grids. |
|
// only refresh what needs refreshing |
|
refreshFlags = me.syncLockedWidth(); |
|
|
|
if (refreshFlags[0]) { |
|
lockedGrid.getView().refreshView(); |
|
} |
|
if (refreshFlags[1]) { |
|
normalGrid.getView().refreshView(); |
|
} |
|
Ext.resumeLayouts(true); |
|
|
|
if (hadFocus) { |
|
activeHd.focus(); |
|
} |
|
me.fireEvent('unlockcolumn', me, activeHd); |
|
}, |
|
|
|
// we want to totally override the reconfigure behaviour here, since we're creating 2 sub-grids |
|
reconfigureLockable: function(store, columns) { |
|
var me = this, |
|
oldStore = me.store, |
|
lockedGrid = me.lockedGrid, |
|
normalGrid = me.normalGrid, |
|
view; |
|
|
|
// Note that we need to process the store first in case one or more passed columns (if there are any) |
|
// have active gridfilters with values which would filter the currently-bound store. |
|
if (store && store !== oldStore) { |
|
store = Ext.data.StoreManager.lookup(store); |
|
me.store = store; |
|
lockedGrid.view.blockRefresh = normalGrid.view.blockRefresh = true; |
|
|
|
lockedGrid.bindStore(store); |
|
|
|
// Subsidiary views have their bindStore changed because they must not |
|
// bind listeners themselves. This view listens and relays calls to each view. |
|
// BUT the dataSource and store properties must be set |
|
view = lockedGrid.view; |
|
view.store = store; |
|
|
|
// If the dataSource being used by the View is *not* a FeatureStore |
|
// (a modified view of the base Store injected by a Feature) |
|
// Then we promote the store to be the dataSource. |
|
// If it was a FeatureStore, then it must not be changed. A FeatureStore is mutated |
|
// by the Feature to respond to changes in the underlying Store. |
|
if (!view.dataSource.isFeatureStore) { |
|
view.dataSource = store; |
|
} |
|
if (view.bufferedRenderer) { |
|
view.bufferedRenderer.bindStore(store); |
|
} |
|
|
|
normalGrid.bindStore(store); |
|
view = normalGrid.view; |
|
view.store = store; |
|
|
|
// If the dataSource being used by the View is *not* a FeatureStore |
|
// (a modified view of the base Store injected by a Feature) |
|
// Then we promote the store to be the dataSource. |
|
// If it was a FeatureStore, then it must not be changed. A FeatureStore is mutated |
|
// by the Feature to respond to changes in the underlying Store. |
|
if (!view.dataSource.isFeatureStore) { |
|
view.dataSource = store; |
|
} |
|
if (view.bufferedRenderer) { |
|
view.bufferedRenderer.bindStore(store); |
|
} |
|
me.view.store = store; |
|
me.view.bindStore(normalGrid.view.dataSource, false, 'dataSource'); |
|
lockedGrid.view.blockRefresh = normalGrid.view.blockRefresh = false; |
|
} |
|
|
|
if (columns) { |
|
// Both grids must not react to the headers being changed (See panel/Table#onHeadersChanged) |
|
lockedGrid.reconfiguring = normalGrid.reconfiguring = true; |
|
lockedGrid.headerCt.removeAll(); |
|
normalGrid.headerCt.removeAll(); |
|
|
|
columns = me.processColumns(columns, lockedGrid); |
|
|
|
// Flag to the locked column add listener to do nothing |
|
lockedGrid.headerCt.add(columns.locked.items); |
|
normalGrid.headerCt.add(columns.normal.items); |
|
|
|
lockedGrid.reconfiguring = normalGrid.reconfiguring = false; |
|
|
|
// Ensure locked grid is set up correctly with correct width and bottom border, |
|
// and that both grids' visibility and scrollability status is correct |
|
me.syncLockedWidth(); |
|
} |
|
|
|
me.refreshCounter = lockedGrid.view.refreshCounter; |
|
}, |
|
|
|
afterReconfigureLockable: function() { |
|
var lockedView = this.lockedGrid.getView(); |
|
|
|
// If the counter hasn't changed since where we saved it previously, we haven't refreshed, |
|
// so kick it off now. |
|
if (this.refreshCounter === lockedView.refreshCounter) { |
|
this.view.refresh(); |
|
} |
|
}, |
|
|
|
constructLockableFeatures: function() { |
|
var features = this.features, |
|
feature, |
|
featureClone, |
|
lockedFeatures, |
|
normalFeatures, |
|
i = 0, len; |
|
|
|
if (features) { |
|
if (!Ext.isArray(features)) { |
|
features = [ features ]; |
|
} |
|
lockedFeatures = []; |
|
normalFeatures = []; |
|
len = features.length; |
|
for (; i < len; i++) { |
|
feature = features[i]; |
|
if (!feature.isFeature) { |
|
feature = Ext.create('feature.' + feature.ftype, feature); |
|
} |
|
switch (feature.lockableScope) { |
|
case 'locked': |
|
lockedFeatures.push(feature); |
|
break; |
|
case 'normal': |
|
normalFeatures.push(feature); |
|
break; |
|
default: |
|
feature.lockableScope = 'both'; |
|
lockedFeatures.push(feature); |
|
normalFeatures.push(featureClone = feature.clone()); |
|
|
|
// When cloned to either side, each gets a "lockingPartner" reference to the other |
|
featureClone.lockingPartner = feature; |
|
feature.lockingPartner = featureClone; |
|
} |
|
} |
|
} |
|
return { |
|
normalFeatures: normalFeatures, |
|
lockedFeatures: lockedFeatures |
|
}; |
|
}, |
|
|
|
constructLockablePlugins: function() { |
|
var plugins = this.plugins, |
|
plugin, |
|
normalPlugin, |
|
lockedPlugin, |
|
topPlugins, |
|
lockedPlugins, |
|
normalPlugins, |
|
i = 0, len, |
|
lockableScope, |
|
pluginCls; |
|
|
|
if (plugins) { |
|
if (!Ext.isArray(plugins)) { |
|
plugins = [ plugins ]; |
|
} |
|
topPlugins = []; |
|
lockedPlugins = []; |
|
normalPlugins = []; |
|
len = plugins.length; |
|
for (; i < len; i++) { |
|
|
|
plugin = plugins[i]; |
|
|
|
// Plugin will most likely already have been instantiated by the Component constructor |
|
if (plugin.init) { |
|
lockableScope = plugin.lockableScope; |
|
} |
|
// If not, it's because of late addition through a subclass's initComponent implementation, so we |
|
// must ascertain the lockableScope directly from the class. |
|
else { |
|
pluginCls = plugin.ptype ? Ext.ClassManager.getByAlias(('plugin.' + plugin.ptype)) : Ext.ClassManager.get(plugin.xclass); |
|
lockableScope = pluginCls.prototype.lockableScope; |
|
} |
|
|
|
switch (lockableScope) { |
|
case 'both': |
|
lockedPlugins.push(lockedPlugin = plugin.clonePlugin()); |
|
normalPlugins.push(normalPlugin = plugin.clonePlugin()); |
|
|
|
// When cloned to both sides, each gets a "lockingPartner" reference to the other |
|
lockedPlugin.lockingPartner = normalPlugin; |
|
normalPlugin.lockingPartner = lockedPlugin; |
|
|
|
// If the plugin has to be propagated down to both, a new plugin config object must be given to that side |
|
// and this plugin must be destroyed. |
|
Ext.destroy(plugin); |
|
break; |
|
case 'locked': |
|
lockedPlugins.push(plugin); |
|
break; |
|
case 'normal': |
|
normalPlugins.push(plugin); |
|
break; |
|
default: |
|
topPlugins.push(plugin); |
|
} |
|
} |
|
} |
|
return { |
|
topPlugins: topPlugins, |
|
normalPlugins: normalPlugins, |
|
lockedPlugins: lockedPlugins |
|
}; |
|
}, |
|
|
|
destroyLockable: function(){ |
|
// The locking view isn't a "real" view, so we need to destroy it manually |
|
var me = this, |
|
task = me.syncLockedWidthTask; |
|
|
|
if (task) { |
|
task.cancel(); |
|
me.syncLockedWidthTask = null; |
|
} |
|
Ext.destroy(me.view, me.headerCt); |
|
} |
|
}, function() { |
|
this.borrow(Ext.Component, ['constructPlugin']); |
|
});
|
|
|