Upgrade to ExtJS 4.0.7 - Released 10/19/2011
[extjs.git] / docs / source / Lockable.html
1 <!DOCTYPE html>
2 <html>
3 <head>
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; }
10   </style>
11   <script type="text/javascript">
12     function highlight() {
13       document.getElementById(location.hash.replace(/#/, "")).className = "highlight";
14     }
15   </script>
16 </head>
17 <body onload="prettyPrint(); highlight();">
18   <pre class="prettyprint lang-js"><span id='Ext-grid-Lockable'>/**
19 </span> * @class Ext.grid.Lockable
20  * @private
21  *
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  *
27  *  - The TablePanel has the lockable configuration set to true
28  *  - One of the columns in the TablePanel has locked set to true/false
29  *
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.
34  *
35  * Columns which are locked must specify a fixed width. They do NOT support a
36  * flex width.
37  *
38  * Configurations which are specified in this class will be available on any grid or
39  * tree which is using the lockable functionality.
40  */
41 Ext.define('Ext.grid.Lockable', {
42
43     requires: ['Ext.grid.LockingView'],
44
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.
50      */
51     syncRowHeight: true,
52
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.
58      */
59
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.
64      */
65
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.
70      */
71
72     // private variable to track whether or not the spacer is hidden/visible
73     spacerHidden: true,
74
75     headerCounter: 0,
76
77     // i8n text
78     unlockText: 'Unlock',
79     lockText: 'Lock',
80
81     determineXTypeToCreate: function() {
82         var me = this,
83             typeToCreate;
84
85         if (me.subGridXType) {
86             typeToCreate = me.subGridXType;
87         } else {
88             var xtypes     = this.getXTypes().split('/'),
89                 xtypesLn   = xtypes.length,
90                 xtype      = xtypes[xtypesLn - 1],
91                 superxtype = xtypes[xtypesLn - 2];
92
93             if (superxtype !== 'tablepanel') {
94                 typeToCreate = superxtype;
95             } else {
96                 typeToCreate = xtype;
97             }
98         }
99
100         return typeToCreate;
101     },
102
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
110         this.hasView = true;
111
112         var me = this,
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
115             // alias.)
116             xtype = me.determineXTypeToCreate(),
117             // share the selection model
118             selModel = me.getSelectionModel(),
119             lockedGrid = {
120                 xtype: xtype,
121                 // Lockable does NOT support animations for Tree
122                 enableAnimations: false,
123                 scroll: false,
124                 scrollerOwner: false,
125                 selModel: selModel,
126                 border: false,
127                 cls: Ext.baseCSSPrefix + 'grid-inner-locked'
128             },
129             normalGrid = {
130                 xtype: xtype,
131                 enableAnimations: false,
132                 scrollerOwner: false,
133                 selModel: selModel,
134                 border: false
135             },
136             i = 0,
137             columns,
138             lockedHeaderCt,
139             normalHeaderCt;
140
141         me.addCls(Ext.baseCSSPrefix + 'grid-locked');
142
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 &lt; me.normalCfgCopy.length; i++) {
149             delete me[me.normalCfgCopy[i]];
150         }
151         for (i = 0; i &lt; me.lockedCfgCopy.length; i++) {
152             delete me[me.lockedCfgCopy[i]];
153         }
154
155         me.addEvents(
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.
161              */
162             'lockcolumn',
163
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.
169              */
170             'unlockcolumn'
171         );
172
173         me.addStateEvents(['lockcolumn', 'unlockcolumn']);
174
175         me.lockedHeights = [];
176         me.normalHeights = [];
177
178         columns = me.processColumns(me.columns);
179
180         lockedGrid.width = columns.lockedWidth + Ext.num(selModel.headerWidth, 0);
181         lockedGrid.columns = columns.locked;
182         normalGrid.columns = columns.normal;
183
184         me.store = Ext.StoreManager.lookup(me.store);
185         lockedGrid.store = me.store;
186         normalGrid.store = me.store;
187
188         // normal grid should flex the rest of the width
189         normalGrid.flex = 1;
190         lockedGrid.viewConfig = me.lockedViewConfig || {};
191         lockedGrid.viewConfig.loadingUseMsg = false;
192         normalGrid.viewConfig = me.normalViewConfig || {};
193
194         Ext.applyIf(lockedGrid.viewConfig, me.viewConfig);
195         Ext.applyIf(normalGrid.viewConfig, me.viewConfig);
196
197         me.normalGrid = Ext.ComponentManager.create(normalGrid);
198         me.lockedGrid = Ext.ComponentManager.create(lockedGrid);
199
200         me.view = Ext.create('Ext.grid.LockingView', {
201             locked: me.lockedGrid,
202             normal: me.normalGrid,
203             panel: me
204         });
205
206         if (me.syncRowHeight) {
207             me.lockedGrid.getView().on({
208                 refresh: me.onLockedGridAfterRefresh,
209                 itemupdate: me.onLockedGridAfterUpdate,
210                 scope: me
211             });
212
213             me.normalGrid.getView().on({
214                 refresh: me.onNormalGridAfterRefresh,
215                 itemupdate: me.onNormalGridAfterUpdate,
216                 scope: me
217             });
218         }
219
220         lockedHeaderCt = me.lockedGrid.headerCt;
221         normalHeaderCt = me.normalGrid.headerCt;
222
223         lockedHeaderCt.lockedCt = true;
224         lockedHeaderCt.lockableInjected = true;
225         normalHeaderCt.lockableInjected = true;
226
227         lockedHeaderCt.on({
228             columnshow: me.onLockedHeaderShow,
229             columnhide: me.onLockedHeaderHide,
230             columnmove: me.onLockedHeaderMove,
231             sortchange: me.onLockedHeaderSortChange,
232             columnresize: me.onLockedHeaderResize,
233             scope: me
234         });
235
236         normalHeaderCt.on({
237             columnmove: me.onNormalHeaderMove,
238             sortchange: me.onNormalHeaderSortChange,
239             scope: me
240         });
241
242         me.normalGrid.on({
243             scrollershow: me.onScrollerShow,
244             scrollerhide: me.onScrollerHide,
245             scope: me
246         });
247
248         me.lockedGrid.on('afterlayout', me.onLockedGridAfterLayout, me, {single: true});
249
250         me.modifyHeaderCt();
251         me.items = [me.lockedGrid, me.normalGrid];
252
253         me.relayHeaderCtEvents(lockedHeaderCt);
254         me.relayHeaderCtEvents(normalHeaderCt);
255
256         me.layout = {
257             type: 'hbox',
258             align: 'stretch'
259         };
260     },
261
262     processColumns: function(columns){
263         // split apart normal and lockedWidths
264         var i = 0,
265             len = columns.length,
266             lockedWidth = 1,
267             lockedHeaders = [],
268             normalHeaders = [],
269             column;
270
271         for (; i &lt; len; ++i) {
272             column = columns[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;
276             if (column.locked) {
277                 // &lt;debug&gt;
278                 if (column.flex) {
279                     Ext.Error.raise(&quot;Columns which are locked do NOT support a flex width. You must set a width on the &quot; + columns[i].text + &quot;column.&quot;);
280                 }
281                 // &lt;/debug&gt;
282                 if (!column.hidden) {
283                     lockedWidth += column.width || Ext.grid.header.Container.prototype.defaultWidth;
284                 }
285                 lockedHeaders.push(column);
286             } else {
287                 normalHeaders.push(column);
288             }
289             if (!column.headerId) {
290                 column.headerId = (column.initialConfig || column).id || ('L' + (++this.headerCounter));
291             }
292         }
293         return {
294             lockedWidth: lockedWidth,
295             locked: lockedHeaders,
296             normal: normalHeaders
297         };
298     },
299
300     // create a new spacer after the table is refreshed
301     onLockedGridAfterLayout: function() {
302         var me         = this,
303             lockedView = me.lockedGrid.getView();
304         lockedView.on({
305             beforerefresh: me.destroySpacer,
306             scope: me
307         });
308     },
309
310     // trigger a pseudo refresh on the normal side
311     onLockedHeaderMove: function() {
312         if (this.syncRowHeight) {
313             this.onNormalGridAfterRefresh();
314         }
315     },
316
317     // trigger a pseudo refresh on the locked side
318     onNormalHeaderMove: function() {
319         if (this.syncRowHeight) {
320             this.onLockedGridAfterRefresh();
321         }
322     },
323
324     // create a spacer in lockedsection and store a reference
325     // TODO: Should destroy before refreshing content
326     getSpacerEl: function() {
327         var me   = this,
328             w,
329             view,
330             el;
331
332         if (!me.spacerEl) {
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();
337             el   = view.el;
338
339             me.spacerEl = Ext.DomHelper.append(el, {
340                 cls: me.spacerHidden ? (Ext.baseCSSPrefix + 'hidden') : '',
341                 style: 'height: ' + w + 'px;'
342             }, true);
343         }
344         return me.spacerEl;
345     },
346
347     destroySpacer: function() {
348         var me = this;
349         if (me.spacerEl) {
350             me.spacerEl.destroy();
351             delete me.spacerEl;
352         }
353     },
354
355     // cache the heights of all locked rows and sync rowheights
356     onLockedGridAfterRefresh: function() {
357         var me     = this,
358             view   = me.lockedGrid.getView(),
359             el     = view.el,
360             rowEls = el.query(view.getItemSelector()),
361             ln     = rowEls.length,
362             i = 0;
363
364         // reset heights each time.
365         me.lockedHeights = [];
366
367         for (; i &lt; ln; i++) {
368             me.lockedHeights[i] = rowEls[i].clientHeight;
369         }
370         me.syncRowHeights();
371     },
372
373     // cache the heights of all normal rows and sync rowheights
374     onNormalGridAfterRefresh: function() {
375         var me     = this,
376             view   = me.normalGrid.getView(),
377             el     = view.el,
378             rowEls = el.query(view.getItemSelector()),
379             ln     = rowEls.length,
380             i = 0;
381
382         // reset heights each time.
383         me.normalHeights = [];
384
385         for (; i &lt; ln; i++) {
386             me.normalHeights[i] = rowEls[i].clientHeight;
387         }
388         me.syncRowHeights();
389     },
390
391     // rows can get bigger/smaller
392     onLockedGridAfterUpdate: function(record, index, node) {
393         this.lockedHeights[index] = node.clientHeight;
394         this.syncRowHeights();
395     },
396
397     // rows can get bigger/smaller
398     onNormalGridAfterUpdate: function(record, index, node) {
399         this.normalHeights[index] = node.clientHeight;
400         this.syncRowHeights();
401     },
402
403     // match the rowheights to the biggest rowheight on either
404     // side
405     syncRowHeights: function() {
406         var me = this,
407             lockedHeights = me.lockedHeights,
408             normalHeights = me.normalHeights,
409             calcHeights   = [],
410             ln = lockedHeights.length,
411             i  = 0,
412             lockedView, normalView,
413             lockedRowEls, normalRowEls,
414             vertScroller = me.getVerticalScroller(),
415             scrollTop;
416
417         // ensure there are an equal num of locked and normal
418         // rows before synchronization
419         if (lockedHeights.length &amp;&amp; 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());
424
425             // loop thru all of the heights and sync to the other side
426             for (; i &lt; ln; i++) {
427                 // ensure both are numbers
428                 if (!isNaN(lockedHeights[i]) &amp;&amp; !isNaN(normalHeights[i])) {
429                     if (lockedHeights[i] &gt; normalHeights[i]) {
430                         Ext.fly(normalRowEls[i]).setHeight(lockedHeights[i]);
431                     } else if (lockedHeights[i] &lt; normalHeights[i]) {
432                         Ext.fly(lockedRowEls[i]).setHeight(normalHeights[i]);
433                     }
434                 }
435             }
436
437             // invalidate the scroller and sync the scrollers
438             me.normalGrid.invalidateScroller();
439
440             // synchronize the view with the scroller, if we have a virtualScrollTop
441             // then the user is using a PagingScroller
442             if (vertScroller &amp;&amp; vertScroller.setViewScrollTop) {
443                 vertScroller.setViewScrollTop(me.virtualScrollTop);
444             } else {
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;
451             }
452
453             // reset the heights
454             me.lockedHeights = [];
455             me.normalHeights = [];
456         }
457     },
458
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');
464         }
465     },
466
467     // track when scroller is hidden
468     onScrollerHide: function(scroller, direction) {
469         if (direction === 'horizontal') {
470             this.spacerHidden = true;
471             if (this.spacerEl) {
472                 this.spacerEl.addCls(Ext.baseCSSPrefix + 'hidden');
473             }
474         }
475     },
476
477
478     // inject Lock and Unlock text
479     modifyHeaderCt: function() {
480         var me = this;
481         me.lockedGrid.headerCt.getMenuItems = me.getMenuItems(true);
482         me.normalGrid.headerCt.getMenuItems = me.getMenuItems(false);
483     },
484
485     onUnlockMenuClick: function() {
486         this.unlock();
487     },
488
489     onLockMenuClick: function() {
490         this.lock();
491     },
492
493     getMenuItems: function(locked) {
494         var me            = this,
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);
501
502         // runs in the scope of headerCt
503         return function() {
504             var o = Ext.grid.header.Container.prototype.getMenuItems.call(this);
505             o.push('-',{
506                 cls: unlockCls,
507                 text: unlockText,
508                 handler: unlockHandler,
509                 disabled: !locked
510             });
511             o.push({
512                 cls: lockCls,
513                 text: lockText,
514                 handler: lockHandler,
515                 disabled: locked
516             });
517             return o;
518         };
519     },
520
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
524      * as specified.
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.
527      * @private
528      */
529     lock: function(activeHd, toIdx) {
530         var me         = this,
531             normalGrid = me.normalGrid,
532             lockedGrid = me.lockedGrid,
533             normalHCt  = normalGrid.headerCt,
534             lockedHCt  = lockedGrid.headerCt;
535
536         activeHd = activeHd || normalHCt.getMenu().activeHeader;
537
538         // if column was previously flexed, get/set current width
539         // and remove the flex
540         if (activeHd.flex) {
541             activeHd.width = activeHd.getWidth();
542             delete activeHd.flex;
543         }
544
545         normalHCt.remove(activeHd, false);
546         lockedHCt.suspendLayout = true;
547         activeHd.locked = true;
548         if (Ext.isDefined(toIdx)) {
549             lockedHCt.insert(toIdx, activeHd);
550         } else {
551             lockedHCt.add(activeHd);
552         }
553         lockedHCt.suspendLayout = false;
554         me.syncLockedSection();
555
556         me.fireEvent('lockcolumn', me, activeHd);
557     },
558
559     syncLockedSection: function() {
560         var me = this;
561         me.syncLockedWidth();
562         me.lockedGrid.getView().refresh();
563         me.normalGrid.getView().refresh();
564     },
565
566     // adjust the locked section to the width of its respective
567     // headerCt
568     syncLockedWidth: function() {
569         var me = this,
570             width = me.lockedGrid.headerCt.getFullWidth(true);
571         me.lockedGrid.setWidth(width+1); // +1 for border pixel
572         me.doComponentLayout();
573     },
574
575     onLockedHeaderResize: function() {
576         this.syncLockedWidth();
577     },
578
579     onLockedHeaderHide: function() {
580         this.syncLockedWidth();
581     },
582
583     onLockedHeaderShow: function() {
584         this.syncLockedWidth();
585     },
586
587     onLockedHeaderSortChange: function(headerCt, header, sortState) {
588         if (sortState) {
589             // no real header, and silence the event so we dont get into an
590             // infinite loop
591             this.normalGrid.headerCt.clearOtherSortStates(null, true);
592         }
593     },
594
595     onNormalHeaderSortChange: function(headerCt, header, sortState) {
596         if (sortState) {
597             // no real header, and silence the event so we dont get into an
598             // infinite loop
599             this.lockedGrid.headerCt.clearOtherSortStates(null, true);
600         }
601     },
602
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
606      * as specified.
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.
609      * @private
610      */
611     unlock: function(activeHd, toIdx) {
612         var me         = this,
613             normalGrid = me.normalGrid,
614             lockedGrid = me.lockedGrid,
615             normalHCt  = normalGrid.headerCt,
616             lockedHCt  = lockedGrid.headerCt;
617
618         if (!Ext.isDefined(toIdx)) {
619             toIdx = 0;
620         }
621         activeHd = activeHd || lockedHCt.getMenu().activeHeader;
622
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();
629
630         me.fireEvent('unlockcolumn', me, activeHd);
631     },
632
633     applyColumnsState: function (columns) {
634         var me = this,
635             lockedGrid = me.lockedGrid,
636             lockedHeaderCt = lockedGrid.headerCt,
637             normalHeaderCt = me.normalGrid.headerCt,
638             lockedCols = lockedHeaderCt.items,
639             normalCols = normalHeaderCt.items,
640             existing,
641             locked = [],
642             normal = [],
643             lockedDefault,
644             lockedWidth = 1;
645
646         Ext.each(columns, function (col) {
647             function matches (item) {
648                 return item.headerId == col.id;
649             }
650
651             lockedDefault = true;
652             if (!(existing = lockedCols.findBy(matches))) {
653                 existing = normalCols.findBy(matches);
654                 lockedDefault = false;
655             }
656
657             if (existing) {
658                 if (existing.applyColumnState) {
659                     existing.applyColumnState(col);
660                 }
661                 if (!Ext.isDefined(existing.locked)) {
662                     existing.locked = lockedDefault;
663                 }
664                 if (existing.locked) {
665                     locked.push(existing);
666                     if (!existing.hidden &amp;&amp; Ext.isNumber(existing.width)) {
667                         lockedWidth += existing.width;
668                     }
669                 } else {
670                     normal.push(existing);
671                 }
672             }
673         });
674
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);
679
680             lockedHeaderCt.add(locked);
681             normalHeaderCt.add(normal);
682
683             lockedGrid.setWidth(lockedWidth);
684         }
685     },
686
687     getColumnsState: function () {
688         var me = this,
689             locked = me.lockedGrid.headerCt.getColumnsState(),
690             normal = me.normalGrid.headerCt.getColumnsState();
691
692         return locked.concat(normal);
693     },
694
695     // we want to totally override the reconfigure behaviour here, since we're creating 2 sub-grids
696     reconfigureLockable: function(store, columns) {
697         var me = this,
698             lockedGrid = me.lockedGrid,
699             normalGrid = me.normalGrid;
700
701         if (columns) {
702             lockedGrid.headerCt.suspendLayout = true;
703             normalGrid.headerCt.suspendLayout = true;
704             lockedGrid.headerCt.removeAll();
705             normalGrid.headerCt.removeAll();
706
707             columns = me.processColumns(columns);
708             lockedGrid.setWidth(columns.lockedWidth);
709             lockedGrid.headerCt.add(columns.locked);
710             normalGrid.headerCt.add(columns.normal);
711         }
712
713         if (store) {
714             store = Ext.data.StoreManager.lookup(store);
715             me.store = store;
716             lockedGrid.bindStore(store);
717             normalGrid.bindStore(store);
718         } else {
719             lockedGrid.getView().refresh();
720             normalGrid.getView().refresh();
721         }
722
723         if (columns) {
724             lockedGrid.headerCt.suspendLayout = false;
725             normalGrid.headerCt.suspendLayout = false;
726             lockedGrid.headerCt.forceComponentLayout();
727             normalGrid.headerCt.forceComponentLayout();
728         }
729     }
730 });
731 </pre>
732 </body>
733 </html>