The source code
-
-
+
+
@@ -23,24 +23,25 @@
* TablePanel subclass such as GridPanel or TreePanel. TablePanel will
* automatically inject the Ext.grid.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
+ * - 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 tablepanel's that will be generated
* to note what configurations should be copied. These are named normalCfgCopy and
* lockedCfgCopy respectively.
*
- * Columns which are locked must specify a fixed width. They do *NOT* support a
+ * Columns which are locked must specify a fixed width. They do NOT support a
* flex width.
*
* Configurations which are specified in this class will be available on any grid or
* tree which is using the lockable functionality.
*/
Ext.define('Ext.grid.Lockable', {
-
+
requires: ['Ext.grid.LockingView'],
-
+
/**
* @cfg {Boolean} syncRowHeight Synchronize rowHeight between the normal and
* locked grid view. This is turned on by default. If your grid is guaranteed
@@ -48,14 +49,14 @@ Ext.define('Ext.grid.Lockable', {
* 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
@@ -67,14 +68,16 @@ Ext.define('Ext.grid.Lockable', {
* normal/unlocked side of the grid. Any conflicting configurations between normalViewConfig
* and viewConfig will be overwritten by the normalViewConfig.
*/
-
+
// private variable to track whether or not the spacer is hidden/visible
spacerHidden: true,
-
+
+ headerCounter: 0,
+
// i8n text
unlockText: 'Unlock',
lockText: 'Lock',
-
+
determineXTypeToCreate: function() {
var me = this,
typeToCreate;
@@ -86,17 +89,17 @@ Ext.define('Ext.grid.Lockable', {
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() {
@@ -134,9 +137,9 @@ Ext.define('Ext.grid.Lockable', {
columns,
lockedHeaderCt,
normalHeaderCt;
-
+
me.addCls(Ext.baseCSSPrefix + 'grid-locked');
-
+
// copy appropriate configurations to the respective
// aggregated tablepanel instances and then delete them
// from the master tablepanel.
@@ -148,59 +151,79 @@ Ext.define('Ext.grid.Lockable', {
for (i = 0; i < me.lockedCfgCopy.length; i++) {
delete me[me.lockedCfgCopy[i]];
}
-
+
+ me.addEvents(
+ /**
+ * @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.
+ */
+ 'lockcolumn',
+
+ /**
+ * @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.
+ */
+ 'unlockcolumn'
+ );
+
+ me.addStateEvents(['lockcolumn', 'unlockcolumn']);
+
me.lockedHeights = [];
me.normalHeights = [];
-
+
columns = me.processColumns(me.columns);
- lockedGrid.width = columns.lockedWidth;
+ lockedGrid.width = columns.lockedWidth + Ext.num(selModel.headerWidth, 0);
lockedGrid.columns = columns.locked;
normalGrid.columns = columns.normal;
-
+
me.store = Ext.StoreManager.lookup(me.store);
lockedGrid.store = me.store;
normalGrid.store = me.store;
-
+
// normal grid should flex the rest of the width
normalGrid.flex = 1;
lockedGrid.viewConfig = me.lockedViewConfig || {};
lockedGrid.viewConfig.loadingUseMsg = false;
normalGrid.viewConfig = me.normalViewConfig || {};
-
+
Ext.applyIf(lockedGrid.viewConfig, me.viewConfig);
Ext.applyIf(normalGrid.viewConfig, me.viewConfig);
-
+
me.normalGrid = Ext.ComponentManager.create(normalGrid);
me.lockedGrid = Ext.ComponentManager.create(lockedGrid);
-
+
me.view = Ext.create('Ext.grid.LockingView', {
locked: me.lockedGrid,
normal: me.normalGrid,
- panel: me
+ panel: me
});
-
+
if (me.syncRowHeight) {
me.lockedGrid.getView().on({
refresh: me.onLockedGridAfterRefresh,
itemupdate: me.onLockedGridAfterUpdate,
scope: me
});
-
+
me.normalGrid.getView().on({
refresh: me.onNormalGridAfterRefresh,
itemupdate: me.onNormalGridAfterUpdate,
scope: me
});
}
-
+
lockedHeaderCt = me.lockedGrid.headerCt;
normalHeaderCt = me.normalGrid.headerCt;
-
+
lockedHeaderCt.lockedCt = true;
lockedHeaderCt.lockableInjected = true;
normalHeaderCt.lockableInjected = true;
-
+
lockedHeaderCt.on({
columnshow: me.onLockedHeaderShow,
columnhide: me.onLockedHeaderHide,
@@ -209,39 +232,42 @@ Ext.define('Ext.grid.Lockable', {
columnresize: me.onLockedHeaderResize,
scope: me
});
-
+
normalHeaderCt.on({
columnmove: me.onNormalHeaderMove,
sortchange: me.onNormalHeaderSortChange,
scope: me
});
-
+
me.normalGrid.on({
scrollershow: me.onScrollerShow,
scrollerhide: me.onScrollerHide,
scope: me
});
-
+
me.lockedGrid.on('afterlayout', me.onLockedGridAfterLayout, me, {single: true});
-
+
me.modifyHeaderCt();
me.items = [me.lockedGrid, me.normalGrid];
+ me.relayHeaderCtEvents(lockedHeaderCt);
+ me.relayHeaderCtEvents(normalHeaderCt);
+
me.layout = {
type: 'hbox',
align: 'stretch'
};
},
-
+
processColumns: function(columns){
// split apart normal and lockedWidths
var i = 0,
len = columns.length,
- lockedWidth = 0,
+ lockedWidth = 1,
lockedHeaders = [],
normalHeaders = [],
column;
-
+
for (; i < len; ++i) {
column = columns[i];
// mark the column as processed so that the locked attribute does not
@@ -253,60 +279,71 @@ Ext.define('Ext.grid.Lockable', {
Ext.Error.raise("Columns which are locked do NOT support a flex width. You must set a width on the " + columns[i].text + "column.");
}
// </debug>
- lockedWidth += column.width;
+ if (!column.hidden) {
+ lockedWidth += column.width || Ext.grid.header.Container.prototype.defaultWidth;
+ }
lockedHeaders.push(column);
} else {
normalHeaders.push(column);
}
+ if (!column.headerId) {
+ column.headerId = (column.initialConfig || column).id || ('L' + (++this.headerCounter));
+ }
}
return {
lockedWidth: lockedWidth,
locked: lockedHeaders,
- normal: normalHeaders
+ normal: normalHeaders
};
},
-
+
// create a new spacer after the table is refreshed
onLockedGridAfterLayout: function() {
var me = this,
lockedView = me.lockedGrid.getView();
lockedView.on({
- refresh: me.createSpacer,
beforerefresh: me.destroySpacer,
scope: me
});
},
-
+
// trigger a pseudo refresh on the normal side
onLockedHeaderMove: function() {
if (this.syncRowHeight) {
this.onNormalGridAfterRefresh();
}
},
-
+
// trigger a pseudo refresh on the locked side
onNormalHeaderMove: function() {
if (this.syncRowHeight) {
this.onLockedGridAfterRefresh();
}
},
-
+
// create a spacer in lockedsection and store a reference
// TODO: Should destroy before refreshing content
- createSpacer: function() {
+ getSpacerEl: function() {
var me = this,
+ w,
+ view,
+ el;
+
+ if (!me.spacerEl) {
// This affects scrolling all the way to the bottom of a locked grid
// additional test, sort a column and make sure it synchronizes
- w = Ext.getScrollBarWidth() + (Ext.isIE ? 2 : 0),
- view = me.lockedGrid.getView(),
+ w = Ext.getScrollBarWidth() + (Ext.isIE ? 2 : 0);
+ view = me.lockedGrid.getView();
el = view.el;
- me.spacerEl = Ext.core.DomHelper.append(el, {
- cls: me.spacerHidden ? (Ext.baseCSSPrefix + 'hidden') : '',
- style: 'height: ' + w + 'px;'
- }, true);
+ me.spacerEl = Ext.DomHelper.append(el, {
+ cls: me.spacerHidden ? (Ext.baseCSSPrefix + 'hidden') : '',
+ style: 'height: ' + w + 'px;'
+ }, true);
+ }
+ return me.spacerEl;
},
-
+
destroySpacer: function() {
var me = this;
if (me.spacerEl) {
@@ -314,7 +351,7 @@ Ext.define('Ext.grid.Lockable', {
delete me.spacerEl;
}
},
-
+
// cache the heights of all locked rows and sync rowheights
onLockedGridAfterRefresh: function() {
var me = this,
@@ -323,16 +360,16 @@ Ext.define('Ext.grid.Lockable', {
rowEls = el.query(view.getItemSelector()),
ln = rowEls.length,
i = 0;
-
+
// reset heights each time.
me.lockedHeights = [];
-
+
for (; i < ln; i++) {
me.lockedHeights[i] = rowEls[i].clientHeight;
}
me.syncRowHeights();
},
-
+
// cache the heights of all normal rows and sync rowheights
onNormalGridAfterRefresh: function() {
var me = this,
@@ -341,28 +378,28 @@ Ext.define('Ext.grid.Lockable', {
rowEls = el.query(view.getItemSelector()),
ln = rowEls.length,
i = 0;
-
+
// reset heights each time.
me.normalHeights = [];
-
+
for (; i < ln; i++) {
me.normalHeights[i] = rowEls[i].clientHeight;
}
me.syncRowHeights();
},
-
+
// rows can get bigger/smaller
onLockedGridAfterUpdate: function(record, index, node) {
this.lockedHeights[index] = node.clientHeight;
this.syncRowHeights();
},
-
+
// rows can get bigger/smaller
onNormalGridAfterUpdate: function(record, index, node) {
this.normalHeights[index] = node.clientHeight;
this.syncRowHeights();
},
-
+
// match the rowheights to the biggest rowheight on either
// side
syncRowHeights: function() {
@@ -399,9 +436,9 @@ Ext.define('Ext.grid.Lockable', {
// invalidate the scroller and sync the scrollers
me.normalGrid.invalidateScroller();
-
+
// synchronize the view with the scroller, if we have a virtualScrollTop
- // then the user is using a PagingScroller
+ // then the user is using a PagingScroller
if (vertScroller && vertScroller.setViewScrollTop) {
vertScroller.setViewScrollTop(me.virtualScrollTop);
} else {
@@ -412,55 +449,56 @@ Ext.define('Ext.grid.Lockable', {
normalView.el.dom.scrollTop = scrollTop;
lockedView.el.dom.scrollTop = scrollTop;
}
-
+
// reset the heights
me.lockedHeights = [];
me.normalHeights = [];
}
},
-
+
// track when scroller is shown
onScrollerShow: function(scroller, direction) {
if (direction === 'horizontal') {
this.spacerHidden = false;
- this.spacerEl.removeCls(Ext.baseCSSPrefix + 'hidden');
+ this.getSpacerEl().removeCls(Ext.baseCSSPrefix + 'hidden');
}
},
-
+
// track when scroller is hidden
onScrollerHide: function(scroller, direction) {
if (direction === 'horizontal') {
this.spacerHidden = true;
- this.spacerEl.addCls(Ext.baseCSSPrefix + 'hidden');
+ if (this.spacerEl) {
+ this.spacerEl.addCls(Ext.baseCSSPrefix + 'hidden');
+ }
}
},
-
+
// inject Lock and Unlock text
modifyHeaderCt: function() {
var me = this;
me.lockedGrid.headerCt.getMenuItems = me.getMenuItems(true);
me.normalGrid.headerCt.getMenuItems = me.getMenuItems(false);
},
-
+
onUnlockMenuClick: function() {
this.unlock();
},
-
+
onLockMenuClick: function() {
this.lock();
},
-
+
getMenuItems: function(locked) {
var me = this,
unlockText = me.unlockText,
lockText = me.lockText,
- // TODO: Refactor to use Ext.baseCSSPrefix
- unlockCls = 'xg-hmenu-unlock',
- lockCls = 'xg-hmenu-lock',
+ unlockCls = Ext.baseCSSPrefix + 'hmenu-unlock',
+ lockCls = Ext.baseCSSPrefix + 'hmenu-lock',
unlockHandler = Ext.Function.bind(me.onUnlockMenuClick, me),
lockHandler = Ext.Function.bind(me.onLockMenuClick, me);
-
+
// runs in the scope of headerCt
return function() {
var o = Ext.grid.header.Container.prototype.getMenuItems.call(this);
@@ -479,7 +517,7 @@ Ext.define('Ext.grid.Lockable', {
return o;
};
},
-
+
// going from unlocked section to locked
/**
* Locks the activeHeader as determined by which menu is open OR a header
@@ -494,18 +532,19 @@ Ext.define('Ext.grid.Lockable', {
lockedGrid = me.lockedGrid,
normalHCt = normalGrid.headerCt,
lockedHCt = lockedGrid.headerCt;
-
+
activeHd = activeHd || normalHCt.getMenu().activeHeader;
-
+
// if column was previously flexed, get/set current width
// and remove the flex
if (activeHd.flex) {
activeHd.width = activeHd.getWidth();
delete activeHd.flex;
}
-
+
normalHCt.remove(activeHd, false);
lockedHCt.suspendLayout = true;
+ activeHd.locked = true;
if (Ext.isDefined(toIdx)) {
lockedHCt.insert(toIdx, activeHd);
} else {
@@ -513,35 +552,38 @@ Ext.define('Ext.grid.Lockable', {
}
lockedHCt.suspendLayout = false;
me.syncLockedSection();
+
+ me.fireEvent('lockcolumn', me, activeHd);
},
-
+
syncLockedSection: function() {
var me = this;
me.syncLockedWidth();
me.lockedGrid.getView().refresh();
me.normalGrid.getView().refresh();
},
-
+
// adjust the locked section to the width of its respective
// headerCt
syncLockedWidth: function() {
var me = this,
width = me.lockedGrid.headerCt.getFullWidth(true);
- me.lockedGrid.setWidth(width);
+ me.lockedGrid.setWidth(width+1); // +1 for border pixel
+ me.doComponentLayout();
},
-
+
onLockedHeaderResize: function() {
this.syncLockedWidth();
},
-
+
onLockedHeaderHide: function() {
this.syncLockedWidth();
},
-
+
onLockedHeaderShow: function() {
this.syncLockedWidth();
},
-
+
onLockedHeaderSortChange: function(headerCt, header, sortState) {
if (sortState) {
// no real header, and silence the event so we dont get into an
@@ -549,7 +591,7 @@ Ext.define('Ext.grid.Lockable', {
this.normalGrid.headerCt.clearOtherSortStates(null, true);
}
},
-
+
onNormalHeaderSortChange: function(headerCt, header, sortState) {
if (sortState) {
// no real header, and silence the event so we dont get into an
@@ -557,7 +599,7 @@ Ext.define('Ext.grid.Lockable', {
this.lockedGrid.headerCt.clearOtherSortStates(null, true);
}
},
-
+
// going from locked section to unlocked
/**
* Unlocks the activeHeader as determined by which menu is open OR a header
@@ -577,30 +619,97 @@ Ext.define('Ext.grid.Lockable', {
toIdx = 0;
}
activeHd = activeHd || lockedHCt.getMenu().activeHeader;
-
+
lockedHCt.remove(activeHd, false);
me.syncLockedWidth();
me.lockedGrid.getView().refresh();
+ activeHd.locked = false;
normalHCt.insert(toIdx, activeHd);
me.normalGrid.getView().refresh();
+
+ me.fireEvent('unlockcolumn', me, activeHd);
+ },
+
+ applyColumnsState: function (columns) {
+ var me = this,
+ lockedGrid = me.lockedGrid,
+ lockedHeaderCt = lockedGrid.headerCt,
+ normalHeaderCt = me.normalGrid.headerCt,
+ lockedCols = lockedHeaderCt.items,
+ normalCols = normalHeaderCt.items,
+ existing,
+ locked = [],
+ normal = [],
+ lockedDefault,
+ lockedWidth = 1;
+
+ Ext.each(columns, function (col) {
+ function matches (item) {
+ return item.headerId == col.id;
+ }
+
+ lockedDefault = true;
+ if (!(existing = lockedCols.findBy(matches))) {
+ existing = normalCols.findBy(matches);
+ lockedDefault = false;
+ }
+
+ if (existing) {
+ if (existing.applyColumnState) {
+ existing.applyColumnState(col);
+ }
+ if (!Ext.isDefined(existing.locked)) {
+ existing.locked = lockedDefault;
+ }
+ if (existing.locked) {
+ locked.push(existing);
+ if (!existing.hidden && Ext.isNumber(existing.width)) {
+ lockedWidth += existing.width;
+ }
+ } else {
+ normal.push(existing);
+ }
+ }
+ });
+
+ // state and config must have the same columns (compare counts for now):
+ if (locked.length + normal.length == lockedCols.getCount() + normalCols.getCount()) {
+ lockedHeaderCt.removeAll(false);
+ normalHeaderCt.removeAll(false);
+
+ lockedHeaderCt.add(locked);
+ normalHeaderCt.add(normal);
+
+ lockedGrid.setWidth(lockedWidth);
+ }
},
-
+
+ getColumnsState: function () {
+ var me = this,
+ locked = me.lockedGrid.headerCt.getColumnsState(),
+ normal = me.normalGrid.headerCt.getColumnsState();
+
+ return locked.concat(normal);
+ },
+
// we want to totally override the reconfigure behaviour here, since we're creating 2 sub-grids
reconfigureLockable: function(store, columns) {
var me = this,
lockedGrid = me.lockedGrid,
normalGrid = me.normalGrid;
-
+
if (columns) {
+ lockedGrid.headerCt.suspendLayout = true;
+ normalGrid.headerCt.suspendLayout = true;
lockedGrid.headerCt.removeAll();
normalGrid.headerCt.removeAll();
-
+
columns = me.processColumns(columns);
lockedGrid.setWidth(columns.lockedWidth);
lockedGrid.headerCt.add(columns.locked);
normalGrid.headerCt.add(columns.normal);
}
-
+
if (store) {
store = Ext.data.StoreManager.lookup(store);
me.store = store;
@@ -610,6 +719,13 @@ Ext.define('Ext.grid.Lockable', {
lockedGrid.getView().refresh();
normalGrid.getView().refresh();
}
+
+ if (columns) {
+ lockedGrid.headerCt.suspendLayout = false;
+ normalGrid.headerCt.suspendLayout = false;
+ lockedGrid.headerCt.forceComponentLayout();
+ normalGrid.headerCt.forceComponentLayout();
+ }
}
});