4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
5 <title>The source code</title>
6 <link href="../prettify/prettify.css" type="text/css" rel="stylesheet" />
7 <script type="text/javascript" src="../prettify/prettify.js"></script>
8 <style type="text/css">
9 .highlight { display: block; background-color: #ddd; }
11 <script type="text/javascript">
12 function highlight() {
13 document.getElementById(location.hash.replace(/#/, "")).className = "highlight";
17 <body onload="prettyPrint(); highlight();">
18 <pre class="prettyprint lang-js"><span id='Ext-grid-Lockable'>/**
19 </span> * @class Ext.grid.Lockable
22 * Lockable is a private mixin which injects lockable behavior into any
23 * TablePanel subclass such as GridPanel or TreePanel. TablePanel will
24 * automatically inject the Ext.grid.Lockable mixin in when one of the
25 * these conditions are met:
26 * - The TablePanel has the lockable configuration set to true
27 * - One of the columns in the TablePanel has locked set to true/false
29 * Each TablePanel subclass *must* register an alias. It should have an array
30 * of configurations to copy to the 2 separate tablepanel's that will be generated
31 * to note what configurations should be copied. These are named normalCfgCopy and
32 * lockedCfgCopy respectively.
34 * Columns which are locked must specify a fixed width. They do *NOT* support a
37 * Configurations which are specified in this class will be available on any grid or
38 * tree which is using the lockable functionality.
40 Ext.define('Ext.grid.Lockable', {
42 requires: ['Ext.grid.LockingView'],
44 <span id='Ext-grid-Lockable-cfg-syncRowHeight'> /**
45 </span> * @cfg {Boolean} syncRowHeight Synchronize rowHeight between the normal and
46 * locked grid view. This is turned on by default. If your grid is guaranteed
47 * to have rows of all the same height, you should set this to false to
48 * optimize performance.
52 <span id='Ext-grid-Lockable-cfg-subGridXType'> /**
53 </span> * @cfg {String} subGridXType The xtype of the subgrid to specify. If this is
54 * not specified lockable will determine the subgrid xtype to create by the
55 * following rule. Use the superclasses xtype if the superclass is NOT
56 * tablepanel, otherwise use the xtype itself.
59 <span id='Ext-grid-Lockable-cfg-lockedViewConfig'> /**
60 </span> * @cfg {Object} lockedViewConfig A view configuration to be applied to the
61 * locked side of the grid. Any conflicting configurations between lockedViewConfig
62 * and viewConfig will be overwritten by the lockedViewConfig.
65 <span id='Ext-grid-Lockable-cfg-normalViewConfig'> /**
66 </span> * @cfg {Object} normalViewConfig A view configuration to be applied to the
67 * normal/unlocked side of the grid. Any conflicting configurations between normalViewConfig
68 * and viewConfig will be overwritten by the normalViewConfig.
71 // private variable to track whether or not the spacer is hidden/visible
78 determineXTypeToCreate: function() {
82 if (me.subGridXType) {
83 typeToCreate = me.subGridXType;
85 var xtypes = this.getXTypes().split('/'),
86 xtypesLn = xtypes.length,
87 xtype = xtypes[xtypesLn - 1],
88 superxtype = xtypes[xtypesLn - 2];
90 if (superxtype !== 'tablepanel') {
91 typeToCreate = superxtype;
100 // injectLockable will be invoked before initComponent's parent class implementation
101 // is called, so throughout this method this. are configurations
102 injectLockable: function() {
103 // ensure lockable is set to true in the TablePanel
104 this.lockable = true;
105 // Instruct the TablePanel it already has a view and not to create one.
106 // We are going to aggregate 2 copies of whatever TablePanel we are using
110 // xtype of this class, 'treepanel' or 'gridpanel'
111 // (Note: this makes it a requirement that any subclass that wants to use lockable functionality needs to register an
113 xtype = me.determineXTypeToCreate(),
114 // share the selection model
115 selModel = me.getSelectionModel(),
118 // Lockable does NOT support animations for Tree
119 enableAnimations: false,
121 scrollerOwner: false,
124 cls: Ext.baseCSSPrefix + 'grid-inner-locked'
128 enableAnimations: false,
129 scrollerOwner: false,
138 me.addCls(Ext.baseCSSPrefix + 'grid-locked');
140 // copy appropriate configurations to the respective
141 // aggregated tablepanel instances and then delete them
142 // from the master tablepanel.
143 Ext.copyTo(normalGrid, me, me.normalCfgCopy);
144 Ext.copyTo(lockedGrid, me, me.lockedCfgCopy);
145 for (; i < me.normalCfgCopy.length; i++) {
146 delete me[me.normalCfgCopy[i]];
148 for (i = 0; i < me.lockedCfgCopy.length; i++) {
149 delete me[me.lockedCfgCopy[i]];
152 me.lockedHeights = [];
153 me.normalHeights = [];
155 columns = me.processColumns(me.columns);
157 lockedGrid.width = columns.lockedWidth;
158 lockedGrid.columns = columns.locked;
159 normalGrid.columns = columns.normal;
161 me.store = Ext.StoreManager.lookup(me.store);
162 lockedGrid.store = me.store;
163 normalGrid.store = me.store;
165 // normal grid should flex the rest of the width
167 lockedGrid.viewConfig = me.lockedViewConfig || {};
168 lockedGrid.viewConfig.loadingUseMsg = false;
169 normalGrid.viewConfig = me.normalViewConfig || {};
171 Ext.applyIf(lockedGrid.viewConfig, me.viewConfig);
172 Ext.applyIf(normalGrid.viewConfig, me.viewConfig);
174 me.normalGrid = Ext.ComponentManager.create(normalGrid);
175 me.lockedGrid = Ext.ComponentManager.create(lockedGrid);
177 me.view = Ext.create('Ext.grid.LockingView', {
178 locked: me.lockedGrid,
179 normal: me.normalGrid,
183 if (me.syncRowHeight) {
184 me.lockedGrid.getView().on({
185 refresh: me.onLockedGridAfterRefresh,
186 itemupdate: me.onLockedGridAfterUpdate,
190 me.normalGrid.getView().on({
191 refresh: me.onNormalGridAfterRefresh,
192 itemupdate: me.onNormalGridAfterUpdate,
197 lockedHeaderCt = me.lockedGrid.headerCt;
198 normalHeaderCt = me.normalGrid.headerCt;
200 lockedHeaderCt.lockedCt = true;
201 lockedHeaderCt.lockableInjected = true;
202 normalHeaderCt.lockableInjected = true;
205 columnshow: me.onLockedHeaderShow,
206 columnhide: me.onLockedHeaderHide,
207 columnmove: me.onLockedHeaderMove,
208 sortchange: me.onLockedHeaderSortChange,
209 columnresize: me.onLockedHeaderResize,
214 columnmove: me.onNormalHeaderMove,
215 sortchange: me.onNormalHeaderSortChange,
220 scrollershow: me.onScrollerShow,
221 scrollerhide: me.onScrollerHide,
225 me.lockedGrid.on('afterlayout', me.onLockedGridAfterLayout, me, {single: true});
228 me.items = [me.lockedGrid, me.normalGrid];
236 processColumns: function(columns){
237 // split apart normal and lockedWidths
239 len = columns.length,
245 for (; i < len; ++i) {
247 // mark the column as processed so that the locked attribute does not
248 // trigger trying to aggregate the columns again.
249 column.processed = true;
253 Ext.Error.raise("Columns which are locked do NOT support a flex width. You must set a width on the " + columns[i].text + "column.");
256 lockedWidth += column.width;
257 lockedHeaders.push(column);
259 normalHeaders.push(column);
263 lockedWidth: lockedWidth,
264 locked: lockedHeaders,
265 normal: normalHeaders
269 // create a new spacer after the table is refreshed
270 onLockedGridAfterLayout: function() {
272 lockedView = me.lockedGrid.getView();
274 refresh: me.createSpacer,
275 beforerefresh: me.destroySpacer,
280 // trigger a pseudo refresh on the normal side
281 onLockedHeaderMove: function() {
282 if (this.syncRowHeight) {
283 this.onNormalGridAfterRefresh();
287 // trigger a pseudo refresh on the locked side
288 onNormalHeaderMove: function() {
289 if (this.syncRowHeight) {
290 this.onLockedGridAfterRefresh();
294 // create a spacer in lockedsection and store a reference
295 // TODO: Should destroy before refreshing content
296 createSpacer: function() {
298 // This affects scrolling all the way to the bottom of a locked grid
299 // additional test, sort a column and make sure it synchronizes
300 w = Ext.getScrollBarWidth() + (Ext.isIE ? 2 : 0),
301 view = me.lockedGrid.getView(),
304 me.spacerEl = Ext.core.DomHelper.append(el, {
305 cls: me.spacerHidden ? (Ext.baseCSSPrefix + 'hidden') : '',
306 style: 'height: ' + w + 'px;'
310 destroySpacer: function() {
313 me.spacerEl.destroy();
318 // cache the heights of all locked rows and sync rowheights
319 onLockedGridAfterRefresh: function() {
321 view = me.lockedGrid.getView(),
323 rowEls = el.query(view.getItemSelector()),
327 // reset heights each time.
328 me.lockedHeights = [];
330 for (; i < ln; i++) {
331 me.lockedHeights[i] = rowEls[i].clientHeight;
336 // cache the heights of all normal rows and sync rowheights
337 onNormalGridAfterRefresh: function() {
339 view = me.normalGrid.getView(),
341 rowEls = el.query(view.getItemSelector()),
345 // reset heights each time.
346 me.normalHeights = [];
348 for (; i < ln; i++) {
349 me.normalHeights[i] = rowEls[i].clientHeight;
354 // rows can get bigger/smaller
355 onLockedGridAfterUpdate: function(record, index, node) {
356 this.lockedHeights[index] = node.clientHeight;
357 this.syncRowHeights();
360 // rows can get bigger/smaller
361 onNormalGridAfterUpdate: function(record, index, node) {
362 this.normalHeights[index] = node.clientHeight;
363 this.syncRowHeights();
366 // match the rowheights to the biggest rowheight on either
368 syncRowHeights: function() {
370 lockedHeights = me.lockedHeights,
371 normalHeights = me.normalHeights,
373 ln = lockedHeights.length,
375 lockedView, normalView,
376 lockedRowEls, normalRowEls,
377 vertScroller = me.getVerticalScroller(),
380 // ensure there are an equal num of locked and normal
381 // rows before synchronization
382 if (lockedHeights.length && normalHeights.length) {
383 lockedView = me.lockedGrid.getView();
384 normalView = me.normalGrid.getView();
385 lockedRowEls = lockedView.el.query(lockedView.getItemSelector());
386 normalRowEls = normalView.el.query(normalView.getItemSelector());
388 // loop thru all of the heights and sync to the other side
389 for (; i < ln; i++) {
390 // ensure both are numbers
391 if (!isNaN(lockedHeights[i]) && !isNaN(normalHeights[i])) {
392 if (lockedHeights[i] > normalHeights[i]) {
393 Ext.fly(normalRowEls[i]).setHeight(lockedHeights[i]);
394 } else if (lockedHeights[i] < normalHeights[i]) {
395 Ext.fly(lockedRowEls[i]).setHeight(normalHeights[i]);
400 // invalidate the scroller and sync the scrollers
401 me.normalGrid.invalidateScroller();
403 // synchronize the view with the scroller, if we have a virtualScrollTop
404 // then the user is using a PagingScroller
405 if (vertScroller && vertScroller.setViewScrollTop) {
406 vertScroller.setViewScrollTop(me.virtualScrollTop);
408 // We don't use setScrollTop here because if the scrollTop is
409 // set to the exact same value some browsers won't fire the scroll
410 // event. Instead, we directly set the scrollTop.
411 scrollTop = normalView.el.dom.scrollTop;
412 normalView.el.dom.scrollTop = scrollTop;
413 lockedView.el.dom.scrollTop = scrollTop;
417 me.lockedHeights = [];
418 me.normalHeights = [];
422 // track when scroller is shown
423 onScrollerShow: function(scroller, direction) {
424 if (direction === 'horizontal') {
425 this.spacerHidden = false;
426 this.spacerEl.removeCls(Ext.baseCSSPrefix + 'hidden');
430 // track when scroller is hidden
431 onScrollerHide: function(scroller, direction) {
432 if (direction === 'horizontal') {
433 this.spacerHidden = true;
434 this.spacerEl.addCls(Ext.baseCSSPrefix + 'hidden');
439 // inject Lock and Unlock text
440 modifyHeaderCt: function() {
442 me.lockedGrid.headerCt.getMenuItems = me.getMenuItems(true);
443 me.normalGrid.headerCt.getMenuItems = me.getMenuItems(false);
446 onUnlockMenuClick: function() {
450 onLockMenuClick: function() {
454 getMenuItems: function(locked) {
456 unlockText = me.unlockText,
457 lockText = me.lockText,
458 // TODO: Refactor to use Ext.baseCSSPrefix
459 unlockCls = 'xg-hmenu-unlock',
460 lockCls = 'xg-hmenu-lock',
461 unlockHandler = Ext.Function.bind(me.onUnlockMenuClick, me),
462 lockHandler = Ext.Function.bind(me.onLockMenuClick, me);
464 // runs in the scope of headerCt
466 var o = Ext.grid.header.Container.prototype.getMenuItems.call(this);
470 handler: unlockHandler,
476 handler: lockHandler,
483 // going from unlocked section to locked
484 <span id='Ext-grid-Lockable-method-lock'> /**
485 </span> * Locks the activeHeader as determined by which menu is open OR a header
487 * @param {Ext.grid.column.Column} header (Optional) Header to unlock from the locked section. Defaults to the header which has the menu open currently.
488 * @param {Number} toIdx (Optional) The index to move the unlocked header to. Defaults to appending as the last item.
491 lock: function(activeHd, toIdx) {
493 normalGrid = me.normalGrid,
494 lockedGrid = me.lockedGrid,
495 normalHCt = normalGrid.headerCt,
496 lockedHCt = lockedGrid.headerCt;
498 activeHd = activeHd || normalHCt.getMenu().activeHeader;
500 // if column was previously flexed, get/set current width
501 // and remove the flex
503 activeHd.width = activeHd.getWidth();
504 delete activeHd.flex;
507 normalHCt.remove(activeHd, false);
508 lockedHCt.suspendLayout = true;
509 if (Ext.isDefined(toIdx)) {
510 lockedHCt.insert(toIdx, activeHd);
512 lockedHCt.add(activeHd);
514 lockedHCt.suspendLayout = false;
515 me.syncLockedSection();
518 syncLockedSection: function() {
520 me.syncLockedWidth();
521 me.lockedGrid.getView().refresh();
522 me.normalGrid.getView().refresh();
525 // adjust the locked section to the width of its respective
527 syncLockedWidth: function() {
529 width = me.lockedGrid.headerCt.getFullWidth(true);
530 me.lockedGrid.setWidth(width);
533 onLockedHeaderResize: function() {
534 this.syncLockedWidth();
537 onLockedHeaderHide: function() {
538 this.syncLockedWidth();
541 onLockedHeaderShow: function() {
542 this.syncLockedWidth();
545 onLockedHeaderSortChange: function(headerCt, header, sortState) {
547 // no real header, and silence the event so we dont get into an
549 this.normalGrid.headerCt.clearOtherSortStates(null, true);
553 onNormalHeaderSortChange: function(headerCt, header, sortState) {
555 // no real header, and silence the event so we dont get into an
557 this.lockedGrid.headerCt.clearOtherSortStates(null, true);
561 // going from locked section to unlocked
562 <span id='Ext-grid-Lockable-method-unlock'> /**
563 </span> * Unlocks the activeHeader as determined by which menu is open OR a header
565 * @param {Ext.grid.column.Column} header (Optional) Header to unlock from the locked section. Defaults to the header which has the menu open currently.
566 * @param {Number} toIdx (Optional) The index to move the unlocked header to. Defaults to 0.
569 unlock: function(activeHd, toIdx) {
571 normalGrid = me.normalGrid,
572 lockedGrid = me.lockedGrid,
573 normalHCt = normalGrid.headerCt,
574 lockedHCt = lockedGrid.headerCt;
576 if (!Ext.isDefined(toIdx)) {
579 activeHd = activeHd || lockedHCt.getMenu().activeHeader;
581 lockedHCt.remove(activeHd, false);
582 me.syncLockedWidth();
583 me.lockedGrid.getView().refresh();
584 normalHCt.insert(toIdx, activeHd);
585 me.normalGrid.getView().refresh();
588 // we want to totally override the reconfigure behaviour here, since we're creating 2 sub-grids
589 reconfigureLockable: function(store, columns) {
591 lockedGrid = me.lockedGrid,
592 normalGrid = me.normalGrid;
595 lockedGrid.headerCt.removeAll();
596 normalGrid.headerCt.removeAll();
598 columns = me.processColumns(columns);
599 lockedGrid.setWidth(columns.lockedWidth);
600 lockedGrid.headerCt.add(columns.locked);
601 normalGrid.headerCt.add(columns.normal);
605 store = Ext.data.StoreManager.lookup(store);
607 lockedGrid.bindStore(store);
608 normalGrid.bindStore(store);
610 lockedGrid.getView().refresh();
611 normalGrid.getView().refresh();