2 * @class Ext.grid.Lockable
5 * Lockable is a private mixin which injects lockable behavior into any
6 * TablePanel subclass such as GridPanel or TreePanel. TablePanel will
7 * automatically inject the Ext.grid.Lockable mixin in when one of the
8 * these conditions are met:
9 * - The TablePanel has the lockable configuration set to true
10 * - One of the columns in the TablePanel has locked set to true/false
12 * Each TablePanel subclass *must* register an alias. It should have an array
13 * of configurations to copy to the 2 separate tablepanel's that will be generated
14 * to note what configurations should be copied. These are named normalCfgCopy and
15 * lockedCfgCopy respectively.
17 * Columns which are locked must specify a fixed width. They do *NOT* support a
20 * Configurations which are specified in this class will be available on any grid or
21 * tree which is using the lockable functionality.
23 Ext.define('Ext.grid.Lockable', {
25 requires: ['Ext.grid.LockingView'],
28 * @cfg {Boolean} syncRowHeight Synchronize rowHeight between the normal and
29 * locked grid view. This is turned on by default. If your grid is guaranteed
30 * to have rows of all the same height, you should set this to false to
31 * optimize performance.
36 * @cfg {String} subGridXType The xtype of the subgrid to specify. If this is
37 * not specified lockable will determine the subgrid xtype to create by the
38 * following rule. Use the superclasses xtype if the superclass is NOT
39 * tablepanel, otherwise use the xtype itself.
43 * @cfg {Object} lockedViewConfig A view configuration to be applied to the
44 * locked side of the grid. Any conflicting configurations between lockedViewConfig
45 * and viewConfig will be overwritten by the lockedViewConfig.
49 * @cfg {Object} normalViewConfig A view configuration to be applied to the
50 * normal/unlocked side of the grid. Any conflicting configurations between normalViewConfig
51 * and viewConfig will be overwritten by the normalViewConfig.
54 // private variable to track whether or not the spacer is hidden/visible
61 determineXTypeToCreate: function() {
65 if (me.subGridXType) {
66 typeToCreate = me.subGridXType;
68 var xtypes = this.getXTypes().split('/'),
69 xtypesLn = xtypes.length,
70 xtype = xtypes[xtypesLn - 1],
71 superxtype = xtypes[xtypesLn - 2];
73 if (superxtype !== 'tablepanel') {
74 typeToCreate = superxtype;
83 // injectLockable will be invoked before initComponent's parent class implementation
84 // is called, so throughout this method this. are configurations
85 injectLockable: function() {
86 // ensure lockable is set to true in the TablePanel
88 // Instruct the TablePanel it already has a view and not to create one.
89 // We are going to aggregate 2 copies of whatever TablePanel we are using
93 // xtype of this class, 'treepanel' or 'gridpanel'
94 // (Note: this makes it a requirement that any subclass that wants to use lockable functionality needs to register an
96 xtype = me.determineXTypeToCreate(),
97 // share the selection model
98 selModel = me.getSelectionModel(),
101 // Lockable does NOT support animations for Tree
102 enableAnimations: false,
104 scrollerOwner: false,
107 cls: Ext.baseCSSPrefix + 'grid-inner-locked'
111 enableAnimations: false,
112 scrollerOwner: false,
121 me.addCls(Ext.baseCSSPrefix + 'grid-locked');
123 // copy appropriate configurations to the respective
124 // aggregated tablepanel instances and then delete them
125 // from the master tablepanel.
126 Ext.copyTo(normalGrid, me, me.normalCfgCopy);
127 Ext.copyTo(lockedGrid, me, me.lockedCfgCopy);
128 for (; i < me.normalCfgCopy.length; i++) {
129 delete me[me.normalCfgCopy[i]];
131 for (i = 0; i < me.lockedCfgCopy.length; i++) {
132 delete me[me.lockedCfgCopy[i]];
135 me.lockedHeights = [];
136 me.normalHeights = [];
138 columns = me.processColumns(me.columns);
140 lockedGrid.width = columns.lockedWidth;
141 lockedGrid.columns = columns.locked;
142 normalGrid.columns = columns.normal;
144 me.store = Ext.StoreManager.lookup(me.store);
145 lockedGrid.store = me.store;
146 normalGrid.store = me.store;
148 // normal grid should flex the rest of the width
150 lockedGrid.viewConfig = me.lockedViewConfig || {};
151 lockedGrid.viewConfig.loadingUseMsg = false;
152 normalGrid.viewConfig = me.normalViewConfig || {};
154 Ext.applyIf(lockedGrid.viewConfig, me.viewConfig);
155 Ext.applyIf(normalGrid.viewConfig, me.viewConfig);
157 me.normalGrid = Ext.ComponentManager.create(normalGrid);
158 me.lockedGrid = Ext.ComponentManager.create(lockedGrid);
160 me.view = Ext.create('Ext.grid.LockingView', {
161 locked: me.lockedGrid,
162 normal: me.normalGrid,
166 if (me.syncRowHeight) {
167 me.lockedGrid.getView().on({
168 refresh: me.onLockedGridAfterRefresh,
169 itemupdate: me.onLockedGridAfterUpdate,
173 me.normalGrid.getView().on({
174 refresh: me.onNormalGridAfterRefresh,
175 itemupdate: me.onNormalGridAfterUpdate,
180 lockedHeaderCt = me.lockedGrid.headerCt;
181 normalHeaderCt = me.normalGrid.headerCt;
183 lockedHeaderCt.lockedCt = true;
184 lockedHeaderCt.lockableInjected = true;
185 normalHeaderCt.lockableInjected = true;
188 columnshow: me.onLockedHeaderShow,
189 columnhide: me.onLockedHeaderHide,
190 columnmove: me.onLockedHeaderMove,
191 sortchange: me.onLockedHeaderSortChange,
192 columnresize: me.onLockedHeaderResize,
197 columnmove: me.onNormalHeaderMove,
198 sortchange: me.onNormalHeaderSortChange,
203 scrollershow: me.onScrollerShow,
204 scrollerhide: me.onScrollerHide,
208 me.lockedGrid.on('afterlayout', me.onLockedGridAfterLayout, me, {single: true});
211 me.items = [me.lockedGrid, me.normalGrid];
219 processColumns: function(columns){
220 // split apart normal and lockedWidths
222 len = columns.length,
228 for (; i < len; ++i) {
230 // mark the column as processed so that the locked attribute does not
231 // trigger trying to aggregate the columns again.
232 column.processed = true;
236 Ext.Error.raise("Columns which are locked do NOT support a flex width. You must set a width on the " + columns[i].text + "column.");
239 lockedWidth += column.width;
240 lockedHeaders.push(column);
242 normalHeaders.push(column);
246 lockedWidth: lockedWidth,
247 locked: lockedHeaders,
248 normal: normalHeaders
252 // create a new spacer after the table is refreshed
253 onLockedGridAfterLayout: function() {
255 lockedView = me.lockedGrid.getView();
257 refresh: me.createSpacer,
258 beforerefresh: me.destroySpacer,
263 // trigger a pseudo refresh on the normal side
264 onLockedHeaderMove: function() {
265 if (this.syncRowHeight) {
266 this.onNormalGridAfterRefresh();
270 // trigger a pseudo refresh on the locked side
271 onNormalHeaderMove: function() {
272 if (this.syncRowHeight) {
273 this.onLockedGridAfterRefresh();
277 // create a spacer in lockedsection and store a reference
278 // TODO: Should destroy before refreshing content
279 createSpacer: function() {
281 // This affects scrolling all the way to the bottom of a locked grid
282 // additional test, sort a column and make sure it synchronizes
283 w = Ext.getScrollBarWidth() + (Ext.isIE ? 2 : 0),
284 view = me.lockedGrid.getView(),
287 me.spacerEl = Ext.core.DomHelper.append(el, {
288 cls: me.spacerHidden ? (Ext.baseCSSPrefix + 'hidden') : '',
289 style: 'height: ' + w + 'px;'
293 destroySpacer: function() {
296 me.spacerEl.destroy();
301 // cache the heights of all locked rows and sync rowheights
302 onLockedGridAfterRefresh: function() {
304 view = me.lockedGrid.getView(),
306 rowEls = el.query(view.getItemSelector()),
310 // reset heights each time.
311 me.lockedHeights = [];
313 for (; i < ln; i++) {
314 me.lockedHeights[i] = rowEls[i].clientHeight;
319 // cache the heights of all normal rows and sync rowheights
320 onNormalGridAfterRefresh: function() {
322 view = me.normalGrid.getView(),
324 rowEls = el.query(view.getItemSelector()),
328 // reset heights each time.
329 me.normalHeights = [];
331 for (; i < ln; i++) {
332 me.normalHeights[i] = rowEls[i].clientHeight;
337 // rows can get bigger/smaller
338 onLockedGridAfterUpdate: function(record, index, node) {
339 this.lockedHeights[index] = node.clientHeight;
340 this.syncRowHeights();
343 // rows can get bigger/smaller
344 onNormalGridAfterUpdate: function(record, index, node) {
345 this.normalHeights[index] = node.clientHeight;
346 this.syncRowHeights();
349 // match the rowheights to the biggest rowheight on either
351 syncRowHeights: function() {
353 lockedHeights = me.lockedHeights,
354 normalHeights = me.normalHeights,
356 ln = lockedHeights.length,
358 lockedView, normalView,
359 lockedRowEls, normalRowEls,
360 vertScroller = me.getVerticalScroller(),
363 // ensure there are an equal num of locked and normal
364 // rows before synchronization
365 if (lockedHeights.length && normalHeights.length) {
366 lockedView = me.lockedGrid.getView();
367 normalView = me.normalGrid.getView();
368 lockedRowEls = lockedView.el.query(lockedView.getItemSelector());
369 normalRowEls = normalView.el.query(normalView.getItemSelector());
371 // loop thru all of the heights and sync to the other side
372 for (; i < ln; i++) {
373 // ensure both are numbers
374 if (!isNaN(lockedHeights[i]) && !isNaN(normalHeights[i])) {
375 if (lockedHeights[i] > normalHeights[i]) {
376 Ext.fly(normalRowEls[i]).setHeight(lockedHeights[i]);
377 } else if (lockedHeights[i] < normalHeights[i]) {
378 Ext.fly(lockedRowEls[i]).setHeight(normalHeights[i]);
383 // invalidate the scroller and sync the scrollers
384 me.normalGrid.invalidateScroller();
386 // synchronize the view with the scroller, if we have a virtualScrollTop
387 // then the user is using a PagingScroller
388 if (vertScroller && vertScroller.setViewScrollTop) {
389 vertScroller.setViewScrollTop(me.virtualScrollTop);
391 // We don't use setScrollTop here because if the scrollTop is
392 // set to the exact same value some browsers won't fire the scroll
393 // event. Instead, we directly set the scrollTop.
394 scrollTop = normalView.el.dom.scrollTop;
395 normalView.el.dom.scrollTop = scrollTop;
396 lockedView.el.dom.scrollTop = scrollTop;
400 me.lockedHeights = [];
401 me.normalHeights = [];
405 // track when scroller is shown
406 onScrollerShow: function(scroller, direction) {
407 if (direction === 'horizontal') {
408 this.spacerHidden = false;
409 this.spacerEl.removeCls(Ext.baseCSSPrefix + 'hidden');
413 // track when scroller is hidden
414 onScrollerHide: function(scroller, direction) {
415 if (direction === 'horizontal') {
416 this.spacerHidden = true;
417 this.spacerEl.addCls(Ext.baseCSSPrefix + 'hidden');
422 // inject Lock and Unlock text
423 modifyHeaderCt: function() {
425 me.lockedGrid.headerCt.getMenuItems = me.getMenuItems(true);
426 me.normalGrid.headerCt.getMenuItems = me.getMenuItems(false);
429 onUnlockMenuClick: function() {
433 onLockMenuClick: function() {
437 getMenuItems: function(locked) {
439 unlockText = me.unlockText,
440 lockText = me.lockText,
441 // TODO: Refactor to use Ext.baseCSSPrefix
442 unlockCls = 'xg-hmenu-unlock',
443 lockCls = 'xg-hmenu-lock',
444 unlockHandler = Ext.Function.bind(me.onUnlockMenuClick, me),
445 lockHandler = Ext.Function.bind(me.onLockMenuClick, me);
447 // runs in the scope of headerCt
449 var o = Ext.grid.header.Container.prototype.getMenuItems.call(this);
453 handler: unlockHandler,
459 handler: lockHandler,
466 // going from unlocked section to locked
468 * Locks the activeHeader as determined by which menu is open OR a header
470 * @param {Ext.grid.column.Column} header (Optional) Header to unlock from the locked section. Defaults to the header which has the menu open currently.
471 * @param {Number} toIdx (Optional) The index to move the unlocked header to. Defaults to appending as the last item.
474 lock: function(activeHd, toIdx) {
476 normalGrid = me.normalGrid,
477 lockedGrid = me.lockedGrid,
478 normalHCt = normalGrid.headerCt,
479 lockedHCt = lockedGrid.headerCt;
481 activeHd = activeHd || normalHCt.getMenu().activeHeader;
483 // if column was previously flexed, get/set current width
484 // and remove the flex
486 activeHd.width = activeHd.getWidth();
487 delete activeHd.flex;
490 normalHCt.remove(activeHd, false);
491 lockedHCt.suspendLayout = true;
492 if (Ext.isDefined(toIdx)) {
493 lockedHCt.insert(toIdx, activeHd);
495 lockedHCt.add(activeHd);
497 lockedHCt.suspendLayout = false;
498 me.syncLockedSection();
501 syncLockedSection: function() {
503 me.syncLockedWidth();
504 me.lockedGrid.getView().refresh();
505 me.normalGrid.getView().refresh();
508 // adjust the locked section to the width of its respective
510 syncLockedWidth: function() {
512 width = me.lockedGrid.headerCt.getFullWidth(true);
513 me.lockedGrid.setWidth(width);
516 onLockedHeaderResize: function() {
517 this.syncLockedWidth();
520 onLockedHeaderHide: function() {
521 this.syncLockedWidth();
524 onLockedHeaderShow: function() {
525 this.syncLockedWidth();
528 onLockedHeaderSortChange: function(headerCt, header, sortState) {
530 // no real header, and silence the event so we dont get into an
532 this.normalGrid.headerCt.clearOtherSortStates(null, true);
536 onNormalHeaderSortChange: function(headerCt, header, sortState) {
538 // no real header, and silence the event so we dont get into an
540 this.lockedGrid.headerCt.clearOtherSortStates(null, true);
544 // going from locked section to unlocked
546 * Unlocks the activeHeader as determined by which menu is open OR a header
548 * @param {Ext.grid.column.Column} header (Optional) Header to unlock from the locked section. Defaults to the header which has the menu open currently.
549 * @param {Number} toIdx (Optional) The index to move the unlocked header to. Defaults to 0.
552 unlock: function(activeHd, toIdx) {
554 normalGrid = me.normalGrid,
555 lockedGrid = me.lockedGrid,
556 normalHCt = normalGrid.headerCt,
557 lockedHCt = lockedGrid.headerCt;
559 if (!Ext.isDefined(toIdx)) {
562 activeHd = activeHd || lockedHCt.getMenu().activeHeader;
564 lockedHCt.remove(activeHd, false);
565 me.syncLockedWidth();
566 me.lockedGrid.getView().refresh();
567 normalHCt.insert(toIdx, activeHd);
568 me.normalGrid.getView().refresh();
571 // we want to totally override the reconfigure behaviour here, since we're creating 2 sub-grids
572 reconfigureLockable: function(store, columns) {
574 lockedGrid = me.lockedGrid,
575 normalGrid = me.normalGrid;
578 lockedGrid.headerCt.removeAll();
579 normalGrid.headerCt.removeAll();
581 columns = me.processColumns(columns);
582 lockedGrid.setWidth(columns.lockedWidth);
583 lockedGrid.headerCt.add(columns.locked);
584 normalGrid.headerCt.add(columns.normal);
588 store = Ext.data.StoreManager.lookup(store);
590 lockedGrid.bindStore(store);
591 normalGrid.bindStore(store);
593 lockedGrid.getView().refresh();
594 normalGrid.getView().refresh();