3 This file is part of Ext JS 4
5 Copyright (c) 2011 Sencha Inc
7 Contact: http://www.sencha.com/contact
9 GNU General Public License Usage
10 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.
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
16 * @class Ext.grid.Lockable
19 * Lockable is a private mixin which injects lockable behavior into any
20 * TablePanel subclass such as GridPanel or TreePanel. TablePanel will
21 * automatically inject the Ext.grid.Lockable mixin in when one of the
22 * these conditions are met:
23 * - The TablePanel has the lockable configuration set to true
24 * - One of the columns in the TablePanel has locked set to true/false
26 * Each TablePanel subclass *must* register an alias. It should have an array
27 * of configurations to copy to the 2 separate tablepanel's that will be generated
28 * to note what configurations should be copied. These are named normalCfgCopy and
29 * lockedCfgCopy respectively.
31 * Columns which are locked must specify a fixed width. They do *NOT* support a
34 * Configurations which are specified in this class will be available on any grid or
35 * tree which is using the lockable functionality.
37 Ext.define('Ext.grid.Lockable', {
39 requires: ['Ext.grid.LockingView'],
42 * @cfg {Boolean} syncRowHeight Synchronize rowHeight between the normal and
43 * locked grid view. This is turned on by default. If your grid is guaranteed
44 * to have rows of all the same height, you should set this to false to
45 * optimize performance.
50 * @cfg {String} subGridXType The xtype of the subgrid to specify. If this is
51 * not specified lockable will determine the subgrid xtype to create by the
52 * following rule. Use the superclasses xtype if the superclass is NOT
53 * tablepanel, otherwise use the xtype itself.
57 * @cfg {Object} lockedViewConfig A view configuration to be applied to the
58 * locked side of the grid. Any conflicting configurations between lockedViewConfig
59 * and viewConfig will be overwritten by the lockedViewConfig.
63 * @cfg {Object} normalViewConfig A view configuration to be applied to the
64 * normal/unlocked side of the grid. Any conflicting configurations between normalViewConfig
65 * and viewConfig will be overwritten by the normalViewConfig.
68 // private variable to track whether or not the spacer is hidden/visible
75 determineXTypeToCreate: function() {
79 if (me.subGridXType) {
80 typeToCreate = me.subGridXType;
82 var xtypes = this.getXTypes().split('/'),
83 xtypesLn = xtypes.length,
84 xtype = xtypes[xtypesLn - 1],
85 superxtype = xtypes[xtypesLn - 2];
87 if (superxtype !== 'tablepanel') {
88 typeToCreate = superxtype;
97 // injectLockable will be invoked before initComponent's parent class implementation
98 // is called, so throughout this method this. are configurations
99 injectLockable: function() {
100 // ensure lockable is set to true in the TablePanel
101 this.lockable = true;
102 // Instruct the TablePanel it already has a view and not to create one.
103 // We are going to aggregate 2 copies of whatever TablePanel we are using
107 // xtype of this class, 'treepanel' or 'gridpanel'
108 // (Note: this makes it a requirement that any subclass that wants to use lockable functionality needs to register an
110 xtype = me.determineXTypeToCreate(),
111 // share the selection model
112 selModel = me.getSelectionModel(),
115 // Lockable does NOT support animations for Tree
116 enableAnimations: false,
118 scrollerOwner: false,
121 cls: Ext.baseCSSPrefix + 'grid-inner-locked'
125 enableAnimations: false,
126 scrollerOwner: false,
135 me.addCls(Ext.baseCSSPrefix + 'grid-locked');
137 // copy appropriate configurations to the respective
138 // aggregated tablepanel instances and then delete them
139 // from the master tablepanel.
140 Ext.copyTo(normalGrid, me, me.normalCfgCopy);
141 Ext.copyTo(lockedGrid, me, me.lockedCfgCopy);
142 for (; i < me.normalCfgCopy.length; i++) {
143 delete me[me.normalCfgCopy[i]];
145 for (i = 0; i < me.lockedCfgCopy.length; i++) {
146 delete me[me.lockedCfgCopy[i]];
149 me.lockedHeights = [];
150 me.normalHeights = [];
152 columns = me.processColumns(me.columns);
154 lockedGrid.width = columns.lockedWidth;
155 lockedGrid.columns = columns.locked;
156 normalGrid.columns = columns.normal;
158 me.store = Ext.StoreManager.lookup(me.store);
159 lockedGrid.store = me.store;
160 normalGrid.store = me.store;
162 // normal grid should flex the rest of the width
164 lockedGrid.viewConfig = me.lockedViewConfig || {};
165 lockedGrid.viewConfig.loadingUseMsg = false;
166 normalGrid.viewConfig = me.normalViewConfig || {};
168 Ext.applyIf(lockedGrid.viewConfig, me.viewConfig);
169 Ext.applyIf(normalGrid.viewConfig, me.viewConfig);
171 me.normalGrid = Ext.ComponentManager.create(normalGrid);
172 me.lockedGrid = Ext.ComponentManager.create(lockedGrid);
174 me.view = Ext.create('Ext.grid.LockingView', {
175 locked: me.lockedGrid,
176 normal: me.normalGrid,
180 if (me.syncRowHeight) {
181 me.lockedGrid.getView().on({
182 refresh: me.onLockedGridAfterRefresh,
183 itemupdate: me.onLockedGridAfterUpdate,
187 me.normalGrid.getView().on({
188 refresh: me.onNormalGridAfterRefresh,
189 itemupdate: me.onNormalGridAfterUpdate,
194 lockedHeaderCt = me.lockedGrid.headerCt;
195 normalHeaderCt = me.normalGrid.headerCt;
197 lockedHeaderCt.lockedCt = true;
198 lockedHeaderCt.lockableInjected = true;
199 normalHeaderCt.lockableInjected = true;
202 columnshow: me.onLockedHeaderShow,
203 columnhide: me.onLockedHeaderHide,
204 columnmove: me.onLockedHeaderMove,
205 sortchange: me.onLockedHeaderSortChange,
206 columnresize: me.onLockedHeaderResize,
211 columnmove: me.onNormalHeaderMove,
212 sortchange: me.onNormalHeaderSortChange,
217 scrollershow: me.onScrollerShow,
218 scrollerhide: me.onScrollerHide,
222 me.lockedGrid.on('afterlayout', me.onLockedGridAfterLayout, me, {single: true});
225 me.items = [me.lockedGrid, me.normalGrid];
233 processColumns: function(columns){
234 // split apart normal and lockedWidths
236 len = columns.length,
242 for (; i < len; ++i) {
244 // mark the column as processed so that the locked attribute does not
245 // trigger trying to aggregate the columns again.
246 column.processed = true;
250 Ext.Error.raise("Columns which are locked do NOT support a flex width. You must set a width on the " + columns[i].text + "column.");
253 lockedWidth += column.width;
254 lockedHeaders.push(column);
256 normalHeaders.push(column);
260 lockedWidth: lockedWidth,
261 locked: lockedHeaders,
262 normal: normalHeaders
266 // create a new spacer after the table is refreshed
267 onLockedGridAfterLayout: function() {
269 lockedView = me.lockedGrid.getView();
271 refresh: me.createSpacer,
272 beforerefresh: me.destroySpacer,
277 // trigger a pseudo refresh on the normal side
278 onLockedHeaderMove: function() {
279 if (this.syncRowHeight) {
280 this.onNormalGridAfterRefresh();
284 // trigger a pseudo refresh on the locked side
285 onNormalHeaderMove: function() {
286 if (this.syncRowHeight) {
287 this.onLockedGridAfterRefresh();
291 // create a spacer in lockedsection and store a reference
292 // TODO: Should destroy before refreshing content
293 createSpacer: function() {
295 // This affects scrolling all the way to the bottom of a locked grid
296 // additional test, sort a column and make sure it synchronizes
297 w = Ext.getScrollBarWidth() + (Ext.isIE ? 2 : 0),
298 view = me.lockedGrid.getView(),
301 me.spacerEl = Ext.core.DomHelper.append(el, {
302 cls: me.spacerHidden ? (Ext.baseCSSPrefix + 'hidden') : '',
303 style: 'height: ' + w + 'px;'
307 destroySpacer: function() {
310 me.spacerEl.destroy();
315 // cache the heights of all locked rows and sync rowheights
316 onLockedGridAfterRefresh: function() {
318 view = me.lockedGrid.getView(),
320 rowEls = el.query(view.getItemSelector()),
324 // reset heights each time.
325 me.lockedHeights = [];
327 for (; i < ln; i++) {
328 me.lockedHeights[i] = rowEls[i].clientHeight;
333 // cache the heights of all normal rows and sync rowheights
334 onNormalGridAfterRefresh: function() {
336 view = me.normalGrid.getView(),
338 rowEls = el.query(view.getItemSelector()),
342 // reset heights each time.
343 me.normalHeights = [];
345 for (; i < ln; i++) {
346 me.normalHeights[i] = rowEls[i].clientHeight;
351 // rows can get bigger/smaller
352 onLockedGridAfterUpdate: function(record, index, node) {
353 this.lockedHeights[index] = node.clientHeight;
354 this.syncRowHeights();
357 // rows can get bigger/smaller
358 onNormalGridAfterUpdate: function(record, index, node) {
359 this.normalHeights[index] = node.clientHeight;
360 this.syncRowHeights();
363 // match the rowheights to the biggest rowheight on either
365 syncRowHeights: function() {
367 lockedHeights = me.lockedHeights,
368 normalHeights = me.normalHeights,
370 ln = lockedHeights.length,
372 lockedView, normalView,
373 lockedRowEls, normalRowEls,
374 vertScroller = me.getVerticalScroller(),
377 // ensure there are an equal num of locked and normal
378 // rows before synchronization
379 if (lockedHeights.length && normalHeights.length) {
380 lockedView = me.lockedGrid.getView();
381 normalView = me.normalGrid.getView();
382 lockedRowEls = lockedView.el.query(lockedView.getItemSelector());
383 normalRowEls = normalView.el.query(normalView.getItemSelector());
385 // loop thru all of the heights and sync to the other side
386 for (; i < ln; i++) {
387 // ensure both are numbers
388 if (!isNaN(lockedHeights[i]) && !isNaN(normalHeights[i])) {
389 if (lockedHeights[i] > normalHeights[i]) {
390 Ext.fly(normalRowEls[i]).setHeight(lockedHeights[i]);
391 } else if (lockedHeights[i] < normalHeights[i]) {
392 Ext.fly(lockedRowEls[i]).setHeight(normalHeights[i]);
397 // invalidate the scroller and sync the scrollers
398 me.normalGrid.invalidateScroller();
400 // synchronize the view with the scroller, if we have a virtualScrollTop
401 // then the user is using a PagingScroller
402 if (vertScroller && vertScroller.setViewScrollTop) {
403 vertScroller.setViewScrollTop(me.virtualScrollTop);
405 // We don't use setScrollTop here because if the scrollTop is
406 // set to the exact same value some browsers won't fire the scroll
407 // event. Instead, we directly set the scrollTop.
408 scrollTop = normalView.el.dom.scrollTop;
409 normalView.el.dom.scrollTop = scrollTop;
410 lockedView.el.dom.scrollTop = scrollTop;
414 me.lockedHeights = [];
415 me.normalHeights = [];
419 // track when scroller is shown
420 onScrollerShow: function(scroller, direction) {
421 if (direction === 'horizontal') {
422 this.spacerHidden = false;
423 this.spacerEl.removeCls(Ext.baseCSSPrefix + 'hidden');
427 // track when scroller is hidden
428 onScrollerHide: function(scroller, direction) {
429 if (direction === 'horizontal') {
430 this.spacerHidden = true;
431 this.spacerEl.addCls(Ext.baseCSSPrefix + 'hidden');
436 // inject Lock and Unlock text
437 modifyHeaderCt: function() {
439 me.lockedGrid.headerCt.getMenuItems = me.getMenuItems(true);
440 me.normalGrid.headerCt.getMenuItems = me.getMenuItems(false);
443 onUnlockMenuClick: function() {
447 onLockMenuClick: function() {
451 getMenuItems: function(locked) {
453 unlockText = me.unlockText,
454 lockText = me.lockText,
455 // TODO: Refactor to use Ext.baseCSSPrefix
456 unlockCls = 'xg-hmenu-unlock',
457 lockCls = 'xg-hmenu-lock',
458 unlockHandler = Ext.Function.bind(me.onUnlockMenuClick, me),
459 lockHandler = Ext.Function.bind(me.onLockMenuClick, me);
461 // runs in the scope of headerCt
463 var o = Ext.grid.header.Container.prototype.getMenuItems.call(this);
467 handler: unlockHandler,
473 handler: lockHandler,
480 // going from unlocked section to locked
482 * Locks the activeHeader as determined by which menu is open OR a header
484 * @param {Ext.grid.column.Column} header (Optional) Header to unlock from the locked section. Defaults to the header which has the menu open currently.
485 * @param {Number} toIdx (Optional) The index to move the unlocked header to. Defaults to appending as the last item.
488 lock: function(activeHd, toIdx) {
490 normalGrid = me.normalGrid,
491 lockedGrid = me.lockedGrid,
492 normalHCt = normalGrid.headerCt,
493 lockedHCt = lockedGrid.headerCt;
495 activeHd = activeHd || normalHCt.getMenu().activeHeader;
497 // if column was previously flexed, get/set current width
498 // and remove the flex
500 activeHd.width = activeHd.getWidth();
501 delete activeHd.flex;
504 normalHCt.remove(activeHd, false);
505 lockedHCt.suspendLayout = true;
506 if (Ext.isDefined(toIdx)) {
507 lockedHCt.insert(toIdx, activeHd);
509 lockedHCt.add(activeHd);
511 lockedHCt.suspendLayout = false;
512 me.syncLockedSection();
515 syncLockedSection: function() {
517 me.syncLockedWidth();
518 me.lockedGrid.getView().refresh();
519 me.normalGrid.getView().refresh();
522 // adjust the locked section to the width of its respective
524 syncLockedWidth: function() {
526 width = me.lockedGrid.headerCt.getFullWidth(true);
527 me.lockedGrid.setWidth(width);
530 onLockedHeaderResize: function() {
531 this.syncLockedWidth();
534 onLockedHeaderHide: function() {
535 this.syncLockedWidth();
538 onLockedHeaderShow: function() {
539 this.syncLockedWidth();
542 onLockedHeaderSortChange: function(headerCt, header, sortState) {
544 // no real header, and silence the event so we dont get into an
546 this.normalGrid.headerCt.clearOtherSortStates(null, true);
550 onNormalHeaderSortChange: function(headerCt, header, sortState) {
552 // no real header, and silence the event so we dont get into an
554 this.lockedGrid.headerCt.clearOtherSortStates(null, true);
558 // going from locked section to unlocked
560 * Unlocks the activeHeader as determined by which menu is open OR a header
562 * @param {Ext.grid.column.Column} header (Optional) Header to unlock from the locked section. Defaults to the header which has the menu open currently.
563 * @param {Number} toIdx (Optional) The index to move the unlocked header to. Defaults to 0.
566 unlock: function(activeHd, toIdx) {
568 normalGrid = me.normalGrid,
569 lockedGrid = me.lockedGrid,
570 normalHCt = normalGrid.headerCt,
571 lockedHCt = lockedGrid.headerCt;
573 if (!Ext.isDefined(toIdx)) {
576 activeHd = activeHd || lockedHCt.getMenu().activeHeader;
578 lockedHCt.remove(activeHd, false);
579 me.syncLockedWidth();
580 me.lockedGrid.getView().refresh();
581 normalHCt.insert(toIdx, activeHd);
582 me.normalGrid.getView().refresh();
585 // we want to totally override the reconfigure behaviour here, since we're creating 2 sub-grids
586 reconfigureLockable: function(store, columns) {
588 lockedGrid = me.lockedGrid,
589 normalGrid = me.normalGrid;
592 lockedGrid.headerCt.removeAll();
593 normalGrid.headerCt.removeAll();
595 columns = me.processColumns(columns);
596 lockedGrid.setWidth(columns.lockedWidth);
597 lockedGrid.headerCt.add(columns.locked);
598 normalGrid.headerCt.add(columns.normal);
602 store = Ext.data.StoreManager.lookup(store);
604 lockedGrid.bindStore(store);
605 normalGrid.bindStore(store);
607 lockedGrid.getView().refresh();
608 normalGrid.getView().refresh();