Upgrade to ExtJS 4.0.7 - Released 10/19/2011
[extjs.git] / docs / source / Lockable.html
index c02bb7b..9f728c2 100644 (file)
@@ -3,8 +3,8 @@
 <head>
   <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
   <title>The source code</title>
-  <link href="../prettify/prettify.css" type="text/css" rel="stylesheet" />
-  <script type="text/javascript" src="../prettify/prettify.js"></script>
+  <link href="../resources/prettify/prettify.css" type="text/css" rel="stylesheet" />
+  <script type="text/javascript" src="../resources/prettify/prettify.js"></script>
   <style type="text/css">
     .highlight { display: block; background-color: #ddd; }
   </style>
  * TablePanel subclass such as GridPanel or TreePanel. TablePanel will
  * automatically inject the Ext.grid.Lockable mixin in when one of the
  * these conditions are met:
- * - The TablePanel has the lockable configuration set to true
- * - One of the columns in the TablePanel has locked set to true/false
  *
- * Each TablePanel subclass *must* register an alias. It should have an array
+ *  - The TablePanel has the lockable configuration set to true
+ *  - One of the columns in the TablePanel has locked set to true/false
+ *
+ * Each TablePanel subclass must register an alias. It should have an array
  * of configurations to copy to the 2 separate tablepanel's that will be generated
  * to note what configurations should be copied. These are named normalCfgCopy and
  * lockedCfgCopy respectively.
  *
- * Columns which are locked must specify a fixed width. They do *NOT* support a
+ * Columns which are locked must specify a fixed width. They do NOT support a
  * flex width.
  *
  * Configurations which are specified in this class will be available on any grid or
  * tree which is using the lockable functionality.
  */
 Ext.define('Ext.grid.Lockable', {
-    
+
     requires: ['Ext.grid.LockingView'],
-    
+
 <span id='Ext-grid-Lockable-cfg-syncRowHeight'>    /**
 </span>     * @cfg {Boolean} syncRowHeight Synchronize rowHeight between the normal and
      * locked grid view. This is turned on by default. If your grid is guaranteed
@@ -48,14 +49,14 @@ Ext.define('Ext.grid.Lockable', {
      * optimize performance.
      */
     syncRowHeight: true,
-    
+
 <span id='Ext-grid-Lockable-cfg-subGridXType'>    /**
 </span>     * @cfg {String} subGridXType The xtype of the subgrid to specify. If this is
      * not specified lockable will determine the subgrid xtype to create by the
      * following rule. Use the superclasses xtype if the superclass is NOT
      * tablepanel, otherwise use the xtype itself.
      */
-    
+
 <span id='Ext-grid-Lockable-cfg-lockedViewConfig'>    /**
 </span>     * @cfg {Object} lockedViewConfig A view configuration to be applied to the
      * locked side of the grid. Any conflicting configurations between lockedViewConfig
@@ -67,14 +68,16 @@ Ext.define('Ext.grid.Lockable', {
      * normal/unlocked side of the grid. Any conflicting configurations between normalViewConfig
      * and viewConfig will be overwritten by the normalViewConfig.
      */
-    
+
     // private variable to track whether or not the spacer is hidden/visible
     spacerHidden: true,
-    
+
+    headerCounter: 0,
+
     // i8n text
     unlockText: 'Unlock',
     lockText: 'Lock',
-    
+
     determineXTypeToCreate: function() {
         var me = this,
             typeToCreate;
@@ -86,17 +89,17 @@ Ext.define('Ext.grid.Lockable', {
                 xtypesLn   = xtypes.length,
                 xtype      = xtypes[xtypesLn - 1],
                 superxtype = xtypes[xtypesLn - 2];
-                
+
             if (superxtype !== 'tablepanel') {
                 typeToCreate = superxtype;
             } else {
                 typeToCreate = xtype;
             }
         }
-        
+
         return typeToCreate;
     },
-    
+
     // injectLockable will be invoked before initComponent's parent class implementation
     // is called, so throughout this method this. are configurations
     injectLockable: function() {
@@ -134,9 +137,9 @@ Ext.define('Ext.grid.Lockable', {
             columns,
             lockedHeaderCt,
             normalHeaderCt;
-        
+
         me.addCls(Ext.baseCSSPrefix + 'grid-locked');
-        
+
         // copy appropriate configurations to the respective
         // aggregated tablepanel instances and then delete them
         // from the master tablepanel.
@@ -148,59 +151,79 @@ Ext.define('Ext.grid.Lockable', {
         for (i = 0; i &lt; me.lockedCfgCopy.length; i++) {
             delete me[me.lockedCfgCopy[i]];
         }
-        
+
+        me.addEvents(
+<span id='Ext-grid-Lockable-event-lockcolumn'>            /**
+</span>             * @event lockcolumn
+             * Fires when a column is locked.
+             * @param {Ext.grid.Panel} this The gridpanel.
+             * @param {Ext.grid.column.Column} column The column being locked.
+             */
+            'lockcolumn',
+
+<span id='Ext-grid-Lockable-event-unlockcolumn'>            /**
+</span>             * @event unlockcolumn
+             * Fires when a column is unlocked.
+             * @param {Ext.grid.Panel} this The gridpanel.
+             * @param {Ext.grid.column.Column} column The column being unlocked.
+             */
+            'unlockcolumn'
+        );
+
+        me.addStateEvents(['lockcolumn', 'unlockcolumn']);
+
         me.lockedHeights = [];
         me.normalHeights = [];
-        
+
         columns = me.processColumns(me.columns);
 
-        lockedGrid.width = columns.lockedWidth;
+        lockedGrid.width = columns.lockedWidth + Ext.num(selModel.headerWidth, 0);
         lockedGrid.columns = columns.locked;
         normalGrid.columns = columns.normal;
-        
+
         me.store = Ext.StoreManager.lookup(me.store);
         lockedGrid.store = me.store;
         normalGrid.store = me.store;
-        
+
         // normal grid should flex the rest of the width
         normalGrid.flex = 1;
         lockedGrid.viewConfig = me.lockedViewConfig || {};
         lockedGrid.viewConfig.loadingUseMsg = false;
         normalGrid.viewConfig = me.normalViewConfig || {};
-        
+
         Ext.applyIf(lockedGrid.viewConfig, me.viewConfig);
         Ext.applyIf(normalGrid.viewConfig, me.viewConfig);
-        
+
         me.normalGrid = Ext.ComponentManager.create(normalGrid);
         me.lockedGrid = Ext.ComponentManager.create(lockedGrid);
-        
+
         me.view = Ext.create('Ext.grid.LockingView', {
             locked: me.lockedGrid,
             normal: me.normalGrid,
-            panel: me    
+            panel: me
         });
-        
+
         if (me.syncRowHeight) {
             me.lockedGrid.getView().on({
                 refresh: me.onLockedGridAfterRefresh,
                 itemupdate: me.onLockedGridAfterUpdate,
                 scope: me
             });
-            
+
             me.normalGrid.getView().on({
                 refresh: me.onNormalGridAfterRefresh,
                 itemupdate: me.onNormalGridAfterUpdate,
                 scope: me
             });
         }
-        
+
         lockedHeaderCt = me.lockedGrid.headerCt;
         normalHeaderCt = me.normalGrid.headerCt;
-        
+
         lockedHeaderCt.lockedCt = true;
         lockedHeaderCt.lockableInjected = true;
         normalHeaderCt.lockableInjected = true;
-        
+
         lockedHeaderCt.on({
             columnshow: me.onLockedHeaderShow,
             columnhide: me.onLockedHeaderHide,
@@ -209,39 +232,42 @@ Ext.define('Ext.grid.Lockable', {
             columnresize: me.onLockedHeaderResize,
             scope: me
         });
-        
+
         normalHeaderCt.on({
             columnmove: me.onNormalHeaderMove,
             sortchange: me.onNormalHeaderSortChange,
             scope: me
         });
-        
+
         me.normalGrid.on({
             scrollershow: me.onScrollerShow,
             scrollerhide: me.onScrollerHide,
             scope: me
         });
-        
+
         me.lockedGrid.on('afterlayout', me.onLockedGridAfterLayout, me, {single: true});
-        
+
         me.modifyHeaderCt();
         me.items = [me.lockedGrid, me.normalGrid];
 
+        me.relayHeaderCtEvents(lockedHeaderCt);
+        me.relayHeaderCtEvents(normalHeaderCt);
+
         me.layout = {
             type: 'hbox',
             align: 'stretch'
         };
     },
-    
+
     processColumns: function(columns){
         // split apart normal and lockedWidths
         var i = 0,
             len = columns.length,
-            lockedWidth = 0,
+            lockedWidth = 1,
             lockedHeaders = [],
             normalHeaders = [],
             column;
-            
+
         for (; i &lt; len; ++i) {
             column = columns[i];
             // mark the column as processed so that the locked attribute does not
@@ -253,60 +279,71 @@ Ext.define('Ext.grid.Lockable', {
                     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;);
                 }
                 // &lt;/debug&gt;
-                lockedWidth += column.width;
+                if (!column.hidden) {
+                    lockedWidth += column.width || Ext.grid.header.Container.prototype.defaultWidth;
+                }
                 lockedHeaders.push(column);
             } else {
                 normalHeaders.push(column);
             }
+            if (!column.headerId) {
+                column.headerId = (column.initialConfig || column).id || ('L' + (++this.headerCounter));
+            }
         }
         return {
             lockedWidth: lockedWidth,
             locked: lockedHeaders,
-            normal: normalHeaders    
+            normal: normalHeaders
         };
     },
-    
+
     // create a new spacer after the table is refreshed
     onLockedGridAfterLayout: function() {
         var me         = this,
             lockedView = me.lockedGrid.getView();
         lockedView.on({
-            refresh: me.createSpacer,
             beforerefresh: me.destroySpacer,
             scope: me
         });
     },
-    
+
     // trigger a pseudo refresh on the normal side
     onLockedHeaderMove: function() {
         if (this.syncRowHeight) {
             this.onNormalGridAfterRefresh();
         }
     },
-    
+
     // trigger a pseudo refresh on the locked side
     onNormalHeaderMove: function() {
         if (this.syncRowHeight) {
             this.onLockedGridAfterRefresh();
         }
     },
-    
+
     // create a spacer in lockedsection and store a reference
     // TODO: Should destroy before refreshing content
-    createSpacer: function() {
+    getSpacerEl: function() {
         var me   = this,
+            w,
+            view,
+            el;
+
+        if (!me.spacerEl) {
             // This affects scrolling all the way to the bottom of a locked grid
             // additional test, sort a column and make sure it synchronizes
-            w    = Ext.getScrollBarWidth() + (Ext.isIE ? 2 : 0),
-            view = me.lockedGrid.getView(),
+            w    = Ext.getScrollBarWidth() + (Ext.isIE ? 2 : 0);
+            view = me.lockedGrid.getView();
             el   = view.el;
 
-        me.spacerEl = Ext.core.DomHelper.append(el, {
-            cls: me.spacerHidden ? (Ext.baseCSSPrefix + 'hidden') : '',
-            style: 'height: ' + w + 'px;'
-        }, true);
+            me.spacerEl = Ext.DomHelper.append(el, {
+                cls: me.spacerHidden ? (Ext.baseCSSPrefix + 'hidden') : '',
+                style: 'height: ' + w + 'px;'
+            }, true);
+        }
+        return me.spacerEl;
     },
-    
+
     destroySpacer: function() {
         var me = this;
         if (me.spacerEl) {
@@ -314,7 +351,7 @@ Ext.define('Ext.grid.Lockable', {
             delete me.spacerEl;
         }
     },
-    
+
     // cache the heights of all locked rows and sync rowheights
     onLockedGridAfterRefresh: function() {
         var me     = this,
@@ -323,16 +360,16 @@ Ext.define('Ext.grid.Lockable', {
             rowEls = el.query(view.getItemSelector()),
             ln     = rowEls.length,
             i = 0;
-            
+
         // reset heights each time.
         me.lockedHeights = [];
-        
+
         for (; i &lt; ln; i++) {
             me.lockedHeights[i] = rowEls[i].clientHeight;
         }
         me.syncRowHeights();
     },
-    
+
     // cache the heights of all normal rows and sync rowheights
     onNormalGridAfterRefresh: function() {
         var me     = this,
@@ -341,28 +378,28 @@ Ext.define('Ext.grid.Lockable', {
             rowEls = el.query(view.getItemSelector()),
             ln     = rowEls.length,
             i = 0;
-            
+
         // reset heights each time.
         me.normalHeights = [];
-        
+
         for (; i &lt; ln; i++) {
             me.normalHeights[i] = rowEls[i].clientHeight;
         }
         me.syncRowHeights();
     },
-    
+
     // rows can get bigger/smaller
     onLockedGridAfterUpdate: function(record, index, node) {
         this.lockedHeights[index] = node.clientHeight;
         this.syncRowHeights();
     },
-    
+
     // rows can get bigger/smaller
     onNormalGridAfterUpdate: function(record, index, node) {
         this.normalHeights[index] = node.clientHeight;
         this.syncRowHeights();
     },
-    
+
     // match the rowheights to the biggest rowheight on either
     // side
     syncRowHeights: function() {
@@ -399,9 +436,9 @@ Ext.define('Ext.grid.Lockable', {
 
             // invalidate the scroller and sync the scrollers
             me.normalGrid.invalidateScroller();
-            
+
             // synchronize the view with the scroller, if we have a virtualScrollTop
-            // then the user is using a PagingScroller 
+            // then the user is using a PagingScroller
             if (vertScroller &amp;&amp; vertScroller.setViewScrollTop) {
                 vertScroller.setViewScrollTop(me.virtualScrollTop);
             } else {
@@ -412,55 +449,56 @@ Ext.define('Ext.grid.Lockable', {
                 normalView.el.dom.scrollTop = scrollTop;
                 lockedView.el.dom.scrollTop = scrollTop;
             }
-            
+
             // reset the heights
             me.lockedHeights = [];
             me.normalHeights = [];
         }
     },
-    
+
     // track when scroller is shown
     onScrollerShow: function(scroller, direction) {
         if (direction === 'horizontal') {
             this.spacerHidden = false;
-            this.spacerEl.removeCls(Ext.baseCSSPrefix + 'hidden');
+            this.getSpacerEl().removeCls(Ext.baseCSSPrefix + 'hidden');
         }
     },
-    
+
     // track when scroller is hidden
     onScrollerHide: function(scroller, direction) {
         if (direction === 'horizontal') {
             this.spacerHidden = true;
-            this.spacerEl.addCls(Ext.baseCSSPrefix + 'hidden');
+            if (this.spacerEl) {
+                this.spacerEl.addCls(Ext.baseCSSPrefix + 'hidden');
+            }
         }
     },
 
-    
+
     // inject Lock and Unlock text
     modifyHeaderCt: function() {
         var me = this;
         me.lockedGrid.headerCt.getMenuItems = me.getMenuItems(true);
         me.normalGrid.headerCt.getMenuItems = me.getMenuItems(false);
     },
-    
+
     onUnlockMenuClick: function() {
         this.unlock();
     },
-    
+
     onLockMenuClick: function() {
         this.lock();
     },
-    
+
     getMenuItems: function(locked) {
         var me            = this,
             unlockText    = me.unlockText,
             lockText      = me.lockText,
-            // TODO: Refactor to use Ext.baseCSSPrefix
-            unlockCls     = 'xg-hmenu-unlock',
-            lockCls       = 'xg-hmenu-lock',
+            unlockCls     = Ext.baseCSSPrefix + 'hmenu-unlock',
+            lockCls       = Ext.baseCSSPrefix + 'hmenu-lock',
             unlockHandler = Ext.Function.bind(me.onUnlockMenuClick, me),
             lockHandler   = Ext.Function.bind(me.onLockMenuClick, me);
-        
+
         // runs in the scope of headerCt
         return function() {
             var o = Ext.grid.header.Container.prototype.getMenuItems.call(this);
@@ -479,7 +517,7 @@ Ext.define('Ext.grid.Lockable', {
             return o;
         };
     },
-    
+
     // going from unlocked section to locked
 <span id='Ext-grid-Lockable-method-lock'>    /**
 </span>     * Locks the activeHeader as determined by which menu is open OR a header
@@ -494,18 +532,19 @@ Ext.define('Ext.grid.Lockable', {
             lockedGrid = me.lockedGrid,
             normalHCt  = normalGrid.headerCt,
             lockedHCt  = lockedGrid.headerCt;
-            
+
         activeHd = activeHd || normalHCt.getMenu().activeHeader;
-        
+
         // if column was previously flexed, get/set current width
         // and remove the flex
         if (activeHd.flex) {
             activeHd.width = activeHd.getWidth();
             delete activeHd.flex;
         }
-        
+
         normalHCt.remove(activeHd, false);
         lockedHCt.suspendLayout = true;
+        activeHd.locked = true;
         if (Ext.isDefined(toIdx)) {
             lockedHCt.insert(toIdx, activeHd);
         } else {
@@ -513,35 +552,38 @@ Ext.define('Ext.grid.Lockable', {
         }
         lockedHCt.suspendLayout = false;
         me.syncLockedSection();
+
+        me.fireEvent('lockcolumn', me, activeHd);
     },
-    
+
     syncLockedSection: function() {
         var me = this;
         me.syncLockedWidth();
         me.lockedGrid.getView().refresh();
         me.normalGrid.getView().refresh();
     },
-    
+
     // adjust the locked section to the width of its respective
     // headerCt
     syncLockedWidth: function() {
         var me = this,
             width = me.lockedGrid.headerCt.getFullWidth(true);
-        me.lockedGrid.setWidth(width);
+        me.lockedGrid.setWidth(width+1); // +1 for border pixel
+        me.doComponentLayout();
     },
-    
+
     onLockedHeaderResize: function() {
         this.syncLockedWidth();
     },
-    
+
     onLockedHeaderHide: function() {
         this.syncLockedWidth();
     },
-    
+
     onLockedHeaderShow: function() {
         this.syncLockedWidth();
     },
-    
+
     onLockedHeaderSortChange: function(headerCt, header, sortState) {
         if (sortState) {
             // no real header, and silence the event so we dont get into an
@@ -549,7 +591,7 @@ Ext.define('Ext.grid.Lockable', {
             this.normalGrid.headerCt.clearOtherSortStates(null, true);
         }
     },
-    
+
     onNormalHeaderSortChange: function(headerCt, header, sortState) {
         if (sortState) {
             // no real header, and silence the event so we dont get into an
@@ -557,7 +599,7 @@ Ext.define('Ext.grid.Lockable', {
             this.lockedGrid.headerCt.clearOtherSortStates(null, true);
         }
     },
-    
+
     // going from locked section to unlocked
 <span id='Ext-grid-Lockable-method-unlock'>    /**
 </span>     * Unlocks the activeHeader as determined by which menu is open OR a header
@@ -577,30 +619,97 @@ Ext.define('Ext.grid.Lockable', {
             toIdx = 0;
         }
         activeHd = activeHd || lockedHCt.getMenu().activeHeader;
-        
+
         lockedHCt.remove(activeHd, false);
         me.syncLockedWidth();
         me.lockedGrid.getView().refresh();
+        activeHd.locked = false;
         normalHCt.insert(toIdx, activeHd);
         me.normalGrid.getView().refresh();
+
+        me.fireEvent('unlockcolumn', me, activeHd);
+    },
+
+    applyColumnsState: function (columns) {
+        var me = this,
+            lockedGrid = me.lockedGrid,
+            lockedHeaderCt = lockedGrid.headerCt,
+            normalHeaderCt = me.normalGrid.headerCt,
+            lockedCols = lockedHeaderCt.items,
+            normalCols = normalHeaderCt.items,
+            existing,
+            locked = [],
+            normal = [],
+            lockedDefault,
+            lockedWidth = 1;
+
+        Ext.each(columns, function (col) {
+            function matches (item) {
+                return item.headerId == col.id;
+            }
+
+            lockedDefault = true;
+            if (!(existing = lockedCols.findBy(matches))) {
+                existing = normalCols.findBy(matches);
+                lockedDefault = false;
+            }
+
+            if (existing) {
+                if (existing.applyColumnState) {
+                    existing.applyColumnState(col);
+                }
+                if (!Ext.isDefined(existing.locked)) {
+                    existing.locked = lockedDefault;
+                }
+                if (existing.locked) {
+                    locked.push(existing);
+                    if (!existing.hidden &amp;&amp; Ext.isNumber(existing.width)) {
+                        lockedWidth += existing.width;
+                    }
+                } else {
+                    normal.push(existing);
+                }
+            }
+        });
+
+        // state and config must have the same columns (compare counts for now):
+        if (locked.length + normal.length == lockedCols.getCount() + normalCols.getCount()) {
+            lockedHeaderCt.removeAll(false);
+            normalHeaderCt.removeAll(false);
+
+            lockedHeaderCt.add(locked);
+            normalHeaderCt.add(normal);
+
+            lockedGrid.setWidth(lockedWidth);
+        }
     },
-    
+
+    getColumnsState: function () {
+        var me = this,
+            locked = me.lockedGrid.headerCt.getColumnsState(),
+            normal = me.normalGrid.headerCt.getColumnsState();
+
+        return locked.concat(normal);
+    },
+
     // we want to totally override the reconfigure behaviour here, since we're creating 2 sub-grids
     reconfigureLockable: function(store, columns) {
         var me = this,
             lockedGrid = me.lockedGrid,
             normalGrid = me.normalGrid;
-        
+
         if (columns) {
+            lockedGrid.headerCt.suspendLayout = true;
+            normalGrid.headerCt.suspendLayout = true;
             lockedGrid.headerCt.removeAll();
             normalGrid.headerCt.removeAll();
-            
+
             columns = me.processColumns(columns);
             lockedGrid.setWidth(columns.lockedWidth);
             lockedGrid.headerCt.add(columns.locked);
             normalGrid.headerCt.add(columns.normal);
         }
-        
+
         if (store) {
             store = Ext.data.StoreManager.lookup(store);
             me.store = store;
@@ -610,6 +719,13 @@ Ext.define('Ext.grid.Lockable', {
             lockedGrid.getView().refresh();
             normalGrid.getView().refresh();
         }
+
+        if (columns) {
+            lockedGrid.headerCt.suspendLayout = false;
+            normalGrid.headerCt.suspendLayout = false;
+            lockedGrid.headerCt.forceComponentLayout();
+            normalGrid.headerCt.forceComponentLayout();
+        }
     }
 });
 </pre>