X-Git-Url: http://git.ithinksw.org/extjs.git/blobdiff_plain/6746dc89c47ed01b165cc1152533605f97eb8e8d..f562e4c6e5fac7bcb445985b99acbea4d706e6f0:/src/grid/Lockable.js diff --git a/src/grid/Lockable.js b/src/grid/Lockable.js index f836c7a8..4d7b66a9 100644 --- a/src/grid/Lockable.js +++ b/src/grid/Lockable.js @@ -20,24 +20,25 @@ If you are unsure which license is appropriate for your use, please contact the * 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 @@ -45,14 +46,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 @@ -64,14 +65,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; @@ -83,17 +86,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() { @@ -131,9 +134,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. @@ -145,59 +148,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, @@ -206,39 +229,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 @@ -250,60 +276,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."); } // - 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) { @@ -311,7 +348,7 @@ Ext.define('Ext.grid.Lockable', { delete me.spacerEl; } }, - + // cache the heights of all locked rows and sync rowheights onLockedGridAfterRefresh: function() { var me = this, @@ -320,16 +357,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, @@ -338,28 +375,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() { @@ -396,9 +433,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 { @@ -409,55 +446,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); @@ -476,7 +514,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 @@ -491,18 +529,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 { @@ -510,35 +549,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 @@ -546,7 +588,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 @@ -554,7 +596,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 @@ -574,30 +616,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; @@ -607,6 +716,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(); + } } });