4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
5 <title>The source code</title>
6 <link href="../resources/prettify/prettify.css" type="text/css" rel="stylesheet" />
7 <script type="text/javascript" src="../resources/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:
27 * - The TablePanel has the lockable configuration set to true
28 * - One of the columns in the TablePanel has locked set to true/false
30 * Each TablePanel subclass must register an alias. It should have an array
31 * of configurations to copy to the 2 separate tablepanel's that will be generated
32 * to note what configurations should be copied. These are named normalCfgCopy and
33 * lockedCfgCopy respectively.
35 * Columns which are locked must specify a fixed width. They do NOT support a
38 * Configurations which are specified in this class will be available on any grid or
39 * tree which is using the lockable functionality.
41 Ext.define('Ext.grid.Lockable', {
43 requires: ['Ext.grid.LockingView'],
45 <span id='Ext-grid-Lockable-cfg-syncRowHeight'> /**
46 </span> * @cfg {Boolean} syncRowHeight Synchronize rowHeight between the normal and
47 * locked grid view. This is turned on by default. If your grid is guaranteed
48 * to have rows of all the same height, you should set this to false to
49 * optimize performance.
53 <span id='Ext-grid-Lockable-cfg-subGridXType'> /**
54 </span> * @cfg {String} subGridXType The xtype of the subgrid to specify. If this is
55 * not specified lockable will determine the subgrid xtype to create by the
56 * following rule. Use the superclasses xtype if the superclass is NOT
57 * tablepanel, otherwise use the xtype itself.
60 <span id='Ext-grid-Lockable-cfg-lockedViewConfig'> /**
61 </span> * @cfg {Object} lockedViewConfig A view configuration to be applied to the
62 * locked side of the grid. Any conflicting configurations between lockedViewConfig
63 * and viewConfig will be overwritten by the lockedViewConfig.
66 <span id='Ext-grid-Lockable-cfg-normalViewConfig'> /**
67 </span> * @cfg {Object} normalViewConfig A view configuration to be applied to the
68 * normal/unlocked side of the grid. Any conflicting configurations between normalViewConfig
69 * and viewConfig will be overwritten by the normalViewConfig.
72 // private variable to track whether or not the spacer is hidden/visible
81 determineXTypeToCreate: function() {
85 if (me.subGridXType) {
86 typeToCreate = me.subGridXType;
88 var xtypes = this.getXTypes().split('/'),
89 xtypesLn = xtypes.length,
90 xtype = xtypes[xtypesLn - 1],
91 superxtype = xtypes[xtypesLn - 2];
93 if (superxtype !== 'tablepanel') {
94 typeToCreate = superxtype;
103 // injectLockable will be invoked before initComponent's parent class implementation
104 // is called, so throughout this method this. are configurations
105 injectLockable: function() {
106 // ensure lockable is set to true in the TablePanel
107 this.lockable = true;
108 // Instruct the TablePanel it already has a view and not to create one.
109 // We are going to aggregate 2 copies of whatever TablePanel we are using
113 // xtype of this class, 'treepanel' or 'gridpanel'
114 // (Note: this makes it a requirement that any subclass that wants to use lockable functionality needs to register an
116 xtype = me.determineXTypeToCreate(),
117 // share the selection model
118 selModel = me.getSelectionModel(),
121 // Lockable does NOT support animations for Tree
122 enableAnimations: false,
124 scrollerOwner: false,
127 cls: Ext.baseCSSPrefix + 'grid-inner-locked'
131 enableAnimations: false,
132 scrollerOwner: false,
141 me.addCls(Ext.baseCSSPrefix + 'grid-locked');
143 // copy appropriate configurations to the respective
144 // aggregated tablepanel instances and then delete them
145 // from the master tablepanel.
146 Ext.copyTo(normalGrid, me, me.normalCfgCopy);
147 Ext.copyTo(lockedGrid, me, me.lockedCfgCopy);
148 for (; i < me.normalCfgCopy.length; i++) {
149 delete me[me.normalCfgCopy[i]];
151 for (i = 0; i < me.lockedCfgCopy.length; i++) {
152 delete me[me.lockedCfgCopy[i]];
156 <span id='Ext-grid-Lockable-event-lockcolumn'> /**
157 </span> * @event lockcolumn
158 * Fires when a column is locked.
159 * @param {Ext.grid.Panel} this The gridpanel.
160 * @param {Ext.grid.column.Column} column The column being locked.
164 <span id='Ext-grid-Lockable-event-unlockcolumn'> /**
165 </span> * @event unlockcolumn
166 * Fires when a column is unlocked.
167 * @param {Ext.grid.Panel} this The gridpanel.
168 * @param {Ext.grid.column.Column} column The column being unlocked.
173 me.addStateEvents(['lockcolumn', 'unlockcolumn']);
175 me.lockedHeights = [];
176 me.normalHeights = [];
178 columns = me.processColumns(me.columns);
180 lockedGrid.width = columns.lockedWidth + Ext.num(selModel.headerWidth, 0);
181 lockedGrid.columns = columns.locked;
182 normalGrid.columns = columns.normal;
184 me.store = Ext.StoreManager.lookup(me.store);
185 lockedGrid.store = me.store;
186 normalGrid.store = me.store;
188 // normal grid should flex the rest of the width
190 lockedGrid.viewConfig = me.lockedViewConfig || {};
191 lockedGrid.viewConfig.loadingUseMsg = false;
192 normalGrid.viewConfig = me.normalViewConfig || {};
194 Ext.applyIf(lockedGrid.viewConfig, me.viewConfig);
195 Ext.applyIf(normalGrid.viewConfig, me.viewConfig);
197 me.normalGrid = Ext.ComponentManager.create(normalGrid);
198 me.lockedGrid = Ext.ComponentManager.create(lockedGrid);
200 me.view = Ext.create('Ext.grid.LockingView', {
201 locked: me.lockedGrid,
202 normal: me.normalGrid,
206 if (me.syncRowHeight) {
207 me.lockedGrid.getView().on({
208 refresh: me.onLockedGridAfterRefresh,
209 itemupdate: me.onLockedGridAfterUpdate,
213 me.normalGrid.getView().on({
214 refresh: me.onNormalGridAfterRefresh,
215 itemupdate: me.onNormalGridAfterUpdate,
220 lockedHeaderCt = me.lockedGrid.headerCt;
221 normalHeaderCt = me.normalGrid.headerCt;
223 lockedHeaderCt.lockedCt = true;
224 lockedHeaderCt.lockableInjected = true;
225 normalHeaderCt.lockableInjected = true;
228 columnshow: me.onLockedHeaderShow,
229 columnhide: me.onLockedHeaderHide,
230 columnmove: me.onLockedHeaderMove,
231 sortchange: me.onLockedHeaderSortChange,
232 columnresize: me.onLockedHeaderResize,
237 columnmove: me.onNormalHeaderMove,
238 sortchange: me.onNormalHeaderSortChange,
243 scrollershow: me.onScrollerShow,
244 scrollerhide: me.onScrollerHide,
248 me.lockedGrid.on('afterlayout', me.onLockedGridAfterLayout, me, {single: true});
251 me.items = [me.lockedGrid, me.normalGrid];
253 me.relayHeaderCtEvents(lockedHeaderCt);
254 me.relayHeaderCtEvents(normalHeaderCt);
262 processColumns: function(columns){
263 // split apart normal and lockedWidths
265 len = columns.length,
271 for (; i < len; ++i) {
273 // mark the column as processed so that the locked attribute does not
274 // trigger trying to aggregate the columns again.
275 column.processed = true;
279 Ext.Error.raise("Columns which are locked do NOT support a flex width. You must set a width on the " + columns[i].text + "column.");
282 if (!column.hidden) {
283 lockedWidth += column.width || Ext.grid.header.Container.prototype.defaultWidth;
285 lockedHeaders.push(column);
287 normalHeaders.push(column);
289 if (!column.headerId) {
290 column.headerId = (column.initialConfig || column).id || ('L' + (++this.headerCounter));
294 lockedWidth: lockedWidth,
295 locked: lockedHeaders,
296 normal: normalHeaders
300 // create a new spacer after the table is refreshed
301 onLockedGridAfterLayout: function() {
303 lockedView = me.lockedGrid.getView();
305 beforerefresh: me.destroySpacer,
310 // trigger a pseudo refresh on the normal side
311 onLockedHeaderMove: function() {
312 if (this.syncRowHeight) {
313 this.onNormalGridAfterRefresh();
317 // trigger a pseudo refresh on the locked side
318 onNormalHeaderMove: function() {
319 if (this.syncRowHeight) {
320 this.onLockedGridAfterRefresh();
324 // create a spacer in lockedsection and store a reference
325 // TODO: Should destroy before refreshing content
326 getSpacerEl: function() {
333 // This affects scrolling all the way to the bottom of a locked grid
334 // additional test, sort a column and make sure it synchronizes
335 w = Ext.getScrollBarWidth() + (Ext.isIE ? 2 : 0);
336 view = me.lockedGrid.getView();
339 me.spacerEl = Ext.DomHelper.append(el, {
340 cls: me.spacerHidden ? (Ext.baseCSSPrefix + 'hidden') : '',
341 style: 'height: ' + w + 'px;'
347 destroySpacer: function() {
350 me.spacerEl.destroy();
355 // cache the heights of all locked rows and sync rowheights
356 onLockedGridAfterRefresh: function() {
358 view = me.lockedGrid.getView(),
360 rowEls = el.query(view.getItemSelector()),
364 // reset heights each time.
365 me.lockedHeights = [];
367 for (; i < ln; i++) {
368 me.lockedHeights[i] = rowEls[i].clientHeight;
373 // cache the heights of all normal rows and sync rowheights
374 onNormalGridAfterRefresh: function() {
376 view = me.normalGrid.getView(),
378 rowEls = el.query(view.getItemSelector()),
382 // reset heights each time.
383 me.normalHeights = [];
385 for (; i < ln; i++) {
386 me.normalHeights[i] = rowEls[i].clientHeight;
391 // rows can get bigger/smaller
392 onLockedGridAfterUpdate: function(record, index, node) {
393 this.lockedHeights[index] = node.clientHeight;
394 this.syncRowHeights();
397 // rows can get bigger/smaller
398 onNormalGridAfterUpdate: function(record, index, node) {
399 this.normalHeights[index] = node.clientHeight;
400 this.syncRowHeights();
403 // match the rowheights to the biggest rowheight on either
405 syncRowHeights: function() {
407 lockedHeights = me.lockedHeights,
408 normalHeights = me.normalHeights,
410 ln = lockedHeights.length,
412 lockedView, normalView,
413 lockedRowEls, normalRowEls,
414 vertScroller = me.getVerticalScroller(),
417 // ensure there are an equal num of locked and normal
418 // rows before synchronization
419 if (lockedHeights.length && normalHeights.length) {
420 lockedView = me.lockedGrid.getView();
421 normalView = me.normalGrid.getView();
422 lockedRowEls = lockedView.el.query(lockedView.getItemSelector());
423 normalRowEls = normalView.el.query(normalView.getItemSelector());
425 // loop thru all of the heights and sync to the other side
426 for (; i < ln; i++) {
427 // ensure both are numbers
428 if (!isNaN(lockedHeights[i]) && !isNaN(normalHeights[i])) {
429 if (lockedHeights[i] > normalHeights[i]) {
430 Ext.fly(normalRowEls[i]).setHeight(lockedHeights[i]);
431 } else if (lockedHeights[i] < normalHeights[i]) {
432 Ext.fly(lockedRowEls[i]).setHeight(normalHeights[i]);
437 // invalidate the scroller and sync the scrollers
438 me.normalGrid.invalidateScroller();
440 // synchronize the view with the scroller, if we have a virtualScrollTop
441 // then the user is using a PagingScroller
442 if (vertScroller && vertScroller.setViewScrollTop) {
443 vertScroller.setViewScrollTop(me.virtualScrollTop);
445 // We don't use setScrollTop here because if the scrollTop is
446 // set to the exact same value some browsers won't fire the scroll
447 // event. Instead, we directly set the scrollTop.
448 scrollTop = normalView.el.dom.scrollTop;
449 normalView.el.dom.scrollTop = scrollTop;
450 lockedView.el.dom.scrollTop = scrollTop;
454 me.lockedHeights = [];
455 me.normalHeights = [];
459 // track when scroller is shown
460 onScrollerShow: function(scroller, direction) {
461 if (direction === 'horizontal') {
462 this.spacerHidden = false;
463 this.getSpacerEl().removeCls(Ext.baseCSSPrefix + 'hidden');
467 // track when scroller is hidden
468 onScrollerHide: function(scroller, direction) {
469 if (direction === 'horizontal') {
470 this.spacerHidden = true;
472 this.spacerEl.addCls(Ext.baseCSSPrefix + 'hidden');
478 // inject Lock and Unlock text
479 modifyHeaderCt: function() {
481 me.lockedGrid.headerCt.getMenuItems = me.getMenuItems(true);
482 me.normalGrid.headerCt.getMenuItems = me.getMenuItems(false);
485 onUnlockMenuClick: function() {
489 onLockMenuClick: function() {
493 getMenuItems: function(locked) {
495 unlockText = me.unlockText,
496 lockText = me.lockText,
497 unlockCls = Ext.baseCSSPrefix + 'hmenu-unlock',
498 lockCls = Ext.baseCSSPrefix + 'hmenu-lock',
499 unlockHandler = Ext.Function.bind(me.onUnlockMenuClick, me),
500 lockHandler = Ext.Function.bind(me.onLockMenuClick, me);
502 // runs in the scope of headerCt
504 var o = Ext.grid.header.Container.prototype.getMenuItems.call(this);
508 handler: unlockHandler,
514 handler: lockHandler,
521 // going from unlocked section to locked
522 <span id='Ext-grid-Lockable-method-lock'> /**
523 </span> * Locks the activeHeader as determined by which menu is open OR a header
525 * @param {Ext.grid.column.Column} header (Optional) Header to unlock from the locked section. Defaults to the header which has the menu open currently.
526 * @param {Number} toIdx (Optional) The index to move the unlocked header to. Defaults to appending as the last item.
529 lock: function(activeHd, toIdx) {
531 normalGrid = me.normalGrid,
532 lockedGrid = me.lockedGrid,
533 normalHCt = normalGrid.headerCt,
534 lockedHCt = lockedGrid.headerCt;
536 activeHd = activeHd || normalHCt.getMenu().activeHeader;
538 // if column was previously flexed, get/set current width
539 // and remove the flex
541 activeHd.width = activeHd.getWidth();
542 delete activeHd.flex;
545 normalHCt.remove(activeHd, false);
546 lockedHCt.suspendLayout = true;
547 activeHd.locked = true;
548 if (Ext.isDefined(toIdx)) {
549 lockedHCt.insert(toIdx, activeHd);
551 lockedHCt.add(activeHd);
553 lockedHCt.suspendLayout = false;
554 me.syncLockedSection();
556 me.fireEvent('lockcolumn', me, activeHd);
559 syncLockedSection: function() {
561 me.syncLockedWidth();
562 me.lockedGrid.getView().refresh();
563 me.normalGrid.getView().refresh();
566 // adjust the locked section to the width of its respective
568 syncLockedWidth: function() {
570 width = me.lockedGrid.headerCt.getFullWidth(true);
571 me.lockedGrid.setWidth(width+1); // +1 for border pixel
572 me.doComponentLayout();
575 onLockedHeaderResize: function() {
576 this.syncLockedWidth();
579 onLockedHeaderHide: function() {
580 this.syncLockedWidth();
583 onLockedHeaderShow: function() {
584 this.syncLockedWidth();
587 onLockedHeaderSortChange: function(headerCt, header, sortState) {
589 // no real header, and silence the event so we dont get into an
591 this.normalGrid.headerCt.clearOtherSortStates(null, true);
595 onNormalHeaderSortChange: function(headerCt, header, sortState) {
597 // no real header, and silence the event so we dont get into an
599 this.lockedGrid.headerCt.clearOtherSortStates(null, true);
603 // going from locked section to unlocked
604 <span id='Ext-grid-Lockable-method-unlock'> /**
605 </span> * Unlocks the activeHeader as determined by which menu is open OR a header
607 * @param {Ext.grid.column.Column} header (Optional) Header to unlock from the locked section. Defaults to the header which has the menu open currently.
608 * @param {Number} toIdx (Optional) The index to move the unlocked header to. Defaults to 0.
611 unlock: function(activeHd, toIdx) {
613 normalGrid = me.normalGrid,
614 lockedGrid = me.lockedGrid,
615 normalHCt = normalGrid.headerCt,
616 lockedHCt = lockedGrid.headerCt;
618 if (!Ext.isDefined(toIdx)) {
621 activeHd = activeHd || lockedHCt.getMenu().activeHeader;
623 lockedHCt.remove(activeHd, false);
624 me.syncLockedWidth();
625 me.lockedGrid.getView().refresh();
626 activeHd.locked = false;
627 normalHCt.insert(toIdx, activeHd);
628 me.normalGrid.getView().refresh();
630 me.fireEvent('unlockcolumn', me, activeHd);
633 applyColumnsState: function (columns) {
635 lockedGrid = me.lockedGrid,
636 lockedHeaderCt = lockedGrid.headerCt,
637 normalHeaderCt = me.normalGrid.headerCt,
638 lockedCols = lockedHeaderCt.items,
639 normalCols = normalHeaderCt.items,
646 Ext.each(columns, function (col) {
647 function matches (item) {
648 return item.headerId == col.id;
651 lockedDefault = true;
652 if (!(existing = lockedCols.findBy(matches))) {
653 existing = normalCols.findBy(matches);
654 lockedDefault = false;
658 if (existing.applyColumnState) {
659 existing.applyColumnState(col);
661 if (!Ext.isDefined(existing.locked)) {
662 existing.locked = lockedDefault;
664 if (existing.locked) {
665 locked.push(existing);
666 if (!existing.hidden && Ext.isNumber(existing.width)) {
667 lockedWidth += existing.width;
670 normal.push(existing);
675 // state and config must have the same columns (compare counts for now):
676 if (locked.length + normal.length == lockedCols.getCount() + normalCols.getCount()) {
677 lockedHeaderCt.removeAll(false);
678 normalHeaderCt.removeAll(false);
680 lockedHeaderCt.add(locked);
681 normalHeaderCt.add(normal);
683 lockedGrid.setWidth(lockedWidth);
687 getColumnsState: function () {
689 locked = me.lockedGrid.headerCt.getColumnsState(),
690 normal = me.normalGrid.headerCt.getColumnsState();
692 return locked.concat(normal);
695 // we want to totally override the reconfigure behaviour here, since we're creating 2 sub-grids
696 reconfigureLockable: function(store, columns) {
698 lockedGrid = me.lockedGrid,
699 normalGrid = me.normalGrid;
702 lockedGrid.headerCt.suspendLayout = true;
703 normalGrid.headerCt.suspendLayout = true;
704 lockedGrid.headerCt.removeAll();
705 normalGrid.headerCt.removeAll();
707 columns = me.processColumns(columns);
708 lockedGrid.setWidth(columns.lockedWidth);
709 lockedGrid.headerCt.add(columns.locked);
710 normalGrid.headerCt.add(columns.normal);
714 store = Ext.data.StoreManager.lookup(store);
716 lockedGrid.bindStore(store);
717 normalGrid.bindStore(store);
719 lockedGrid.getView().refresh();
720 normalGrid.getView().refresh();
724 lockedGrid.headerCt.suspendLayout = false;
725 normalGrid.headerCt.suspendLayout = false;
726 lockedGrid.headerCt.forceComponentLayout();
727 normalGrid.headerCt.forceComponentLayout();