X-Git-Url: http://git.ithinksw.org/extjs.git/blobdiff_plain/7a654f8d43fdb43d78b63d90528bed6e86b608cc..f562e4c6e5fac7bcb445985b99acbea4d706e6f0:/src/grid/Lockable.js diff --git a/src/grid/Lockable.js b/src/grid/Lockable.js index 88d7bffe..4d7b66a9 100644 --- a/src/grid/Lockable.js +++ b/src/grid/Lockable.js @@ -1,3 +1,17 @@ +/* + +This file is part of Ext JS 4 + +Copyright (c) 2011 Sencha Inc + +Contact: http://www.sencha.com/contact + +GNU General Public License Usage +This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html. + +If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact. + +*/ /** * @class Ext.grid.Lockable * @private @@ -6,24 +20,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 @@ -31,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 @@ -50,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; @@ -69,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() { @@ -117,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. @@ -131,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, @@ -192,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 @@ -236,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) { @@ -297,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, @@ -306,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, @@ -324,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() { @@ -382,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 { @@ -395,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); @@ -462,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 @@ -477,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 { @@ -496,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 @@ -532,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 @@ -540,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 @@ -560,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; @@ -593,5 +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(); + } } }); +