X-Git-Url: http://git.ithinksw.org/extjs.git/blobdiff_plain/6746dc89c47ed01b165cc1152533605f97eb8e8d..HEAD:/docs/source/Container2.html?ds=inline diff --git a/docs/source/Container2.html b/docs/source/Container2.html index 2240c5f8..d54c25b1 100644 --- a/docs/source/Container2.html +++ b/docs/source/Container2.html @@ -3,8 +3,8 @@ The source code - - + + @@ -15,186 +15,900 @@ -
/**
- * @class Ext.container.Container
- * @extends Ext.container.AbstractContainer
- * <p>Base class for any {@link Ext.Component} that may contain other Components. Containers handle the
- * basic behavior of containing items, namely adding, inserting and removing items.</p>
+  
/**
+ * @class Ext.grid.header.Container
+ * @extends Ext.container.Container
  *
- * <p>The most commonly used Container classes are {@link Ext.panel.Panel}, {@link Ext.window.Window} and {@link Ext.tab.Panel}.
- * If you do not need the capabilities offered by the aforementioned classes you can create a lightweight
- * Container to be encapsulated by an HTML element to your specifications by using the
- * <code><b>{@link Ext.Component#autoEl autoEl}</b></code> config option.</p>
- *
- * {@img Ext.Container/Ext.Container.png Ext.Container component} 
- * <p>The code below illustrates how to explicitly create a Container:<pre><code>
-// explicitly create a Container
-Ext.create('Ext.container.Container', {
-    layout: {
-        type: 'hbox'
-    },
-    width: 400,
-    renderTo: Ext.getBody(),
-    border: 1,
-    style: {borderColor:'#000000', borderStyle:'solid', borderWidth:'1px'},
-    defaults: {
-        labelWidth: 80,
-        // implicitly create Container by specifying xtype
-        xtype: 'datefield',
-        flex: 1,
-        style: {
-            padding: '10px'
-        }
-    },
-    items: [{
-        xtype: 'datefield',
-        name: 'startDate',
-        fieldLabel: 'Start date'
-    },{
-        xtype: 'datefield',
-        name: 'endDate',
-        fieldLabel: 'End date'
-    }]
-});
-</code></pre></p>
- *
- * <p><u><b>Layout</b></u></p>
- * <p>Container classes delegate the rendering of child Components to a layout
- * manager class which must be configured into the Container using the
- * <code><b>{@link #layout}</b></code> configuration property.</p>
- * <p>When either specifying child <code>{@link #items}</code> of a Container,
- * or dynamically {@link #add adding} Components to a Container, remember to
- * consider how you wish the Container to arrange those child elements, and
- * whether those child elements need to be sized using one of Ext's built-in
- * <b><code>{@link #layout}</code></b> schemes. By default, Containers use the
- * {@link Ext.layout.container.Auto Auto} scheme which only
- * renders child components, appending them one after the other inside the
- * Container, and <b>does not apply any sizing</b> at all.</p>
- * <p>A common mistake is when a developer neglects to specify a
- * <b><code>{@link #layout}</code></b> (e.g. widgets like GridPanels or
- * TreePanels are added to Containers for which no <code><b>{@link #layout}</b></code>
- * has been specified). If a Container is left to use the default
- * {Ext.layout.container.Auto Auto} scheme, none of its
- * child components will be resized, or changed in any way when the Container
- * is resized.</p>
- * <p>Certain layout managers allow dynamic addition of child components.
- * Those that do include {@link Ext.layout.container.Card},
- * {@link Ext.layout.container.Anchor}, {@link Ext.layout.container.VBox}, {@link Ext.layout.container.HBox}, and
- * {@link Ext.layout.container.Table}. For example:<pre><code>
-//  Create the GridPanel.
-var myNewGrid = new Ext.grid.Panel({
-    store: myStore,
-    headers: myHeaders,
-    title: 'Results', // the title becomes the title of the tab
-});
-
-myTabPanel.add(myNewGrid); // {@link Ext.tab.Panel} implicitly uses {@link Ext.layout.container.Card Card}
-myTabPanel.{@link Ext.tab.Panel#setActiveTab setActiveTab}(myNewGrid);
- * </code></pre></p>
- * <p>The example above adds a newly created GridPanel to a TabPanel. Note that
- * a TabPanel uses {@link Ext.layout.container.Card} as its layout manager which
- * means all its child items are sized to {@link Ext.layout.container.Fit fit}
- * exactly into its client area.
- * <p><b><u>Overnesting is a common problem</u></b>.
- * An example of overnesting occurs when a GridPanel is added to a TabPanel
- * by wrapping the GridPanel <i>inside</i> a wrapping Panel (that has no
- * <code><b>{@link #layout}</b></code> specified) and then add that wrapping Panel
- * to the TabPanel. The point to realize is that a GridPanel <b>is</b> a
- * Component which can be added directly to a Container. If the wrapping Panel
- * has no <code><b>{@link #layout}</b></code> configuration, then the overnested
- * GridPanel will not be sized as expected.<p>
- *
- * <p><u><b>Adding via remote configuration</b></u></p>
- *
- * <p>A server side script can be used to add Components which are generated dynamically on the server.
- * An example of adding a GridPanel to a TabPanel where the GridPanel is generated by the server
- * based on certain parameters:
- * </p><pre><code>
-// execute an Ajax request to invoke server side script:
-Ext.Ajax.request({
-    url: 'gen-invoice-grid.php',
-    // send additional parameters to instruct server script
-    params: {
-        startDate: Ext.getCmp('start-date').getValue(),
-        endDate: Ext.getCmp('end-date').getValue()
-    },
-    // process the response object to add it to the TabPanel:
-    success: function(xhr) {
-        var newComponent = eval(xhr.responseText); // see discussion below
-        myTabPanel.add(newComponent); // add the component to the TabPanel
-        myTabPanel.setActiveTab(newComponent);
-    },
-    failure: function() {
-        Ext.Msg.alert("Grid create failed", "Server communication failure");
-    }
-});
-</code></pre>
- * <p>The server script needs to return a JSON representation of a configuration object, which, when decoded
- * will return a config object with an {@link Ext.Component#xtype xtype}. The server might return the following
- * JSON:</p><pre><code>
-{
-    "xtype": 'grid',
-    "title": 'Invoice Report',
-    "store": {
-        "model": 'Invoice',
-        "proxy": {
-            "type": 'ajax',
-            "url": 'get-invoice-data.php',
-            "reader": {
-                "type": 'json'
-                "record": 'transaction',
-                "idProperty": 'id',
-                "totalRecords": 'total'
-            })
-        },
-        "autoLoad": {
-            "params": {
-                "startDate": '01/01/2008',
-                "endDate": '01/31/2008'
-            }
-        }
-    },
-    "headers": [
-        {"header": "Customer", "width": 250, "dataIndex": 'customer', "sortable": true},
-        {"header": "Invoice Number", "width": 120, "dataIndex": 'invNo', "sortable": true},
-        {"header": "Invoice Date", "width": 100, "dataIndex": 'date', "renderer": Ext.util.Format.dateRenderer('M d, y'), "sortable": true},
-        {"header": "Value", "width": 120, "dataIndex": 'value', "renderer": 'usMoney', "sortable": true}
-    ]
-}
-</code></pre>
- * <p>When the above code fragment is passed through the <code>eval</code> function in the success handler
- * of the Ajax request, the result will be a config object which, when added to a Container, will cause instantiation
- * of a GridPanel. <b>Be sure that the Container is configured with a layout which sizes and positions the child items to your requirements.</b></p>
- * <p>Note: since the code above is <i>generated</i> by a server script, the <code>autoLoad</code> params for
- * the Store, the user's preferred date format, the metadata to allow generation of the Model layout, and the ColumnModel
- * can all be generated into the code since these are all known on the server.</p>
+ * Container which holds headers and is docked at the top or bottom of a TablePanel.
+ * The HeaderContainer drives resizing/moving/hiding of columns within the TableView.
+ * As headers are hidden, moved or resized the headercontainer is responsible for
+ * triggering changes within the view.
  */
-Ext.define('Ext.container.Container', {
-    extend: 'Ext.container.AbstractContainer',
-    alias: 'widget.container',
-    alternateClassName: 'Ext.Container',
-
-    /**
-     * Return the immediate child Component in which the passed element is located.
-     * @param el The element to test.
-     * @return {Component} The child item which contains the passed element.
-     */
-    getChildByElement: function(el) {
-        var item,
-            itemEl,
+Ext.define('Ext.grid.header.Container', {
+    extend: 'Ext.container.Container',
+    uses: [
+        'Ext.grid.ColumnLayout',
+        'Ext.grid.column.Column',
+        'Ext.menu.Menu',
+        'Ext.menu.CheckItem',
+        'Ext.menu.Separator',
+        'Ext.grid.plugin.HeaderResizer',
+        'Ext.grid.plugin.HeaderReorderer'
+    ],
+    border: true,
+
+    alias: 'widget.headercontainer',
+
+    baseCls: Ext.baseCSSPrefix + 'grid-header-ct',
+    dock: 'top',
+
+    /**
+     * @cfg {Number} weight
+     * HeaderContainer overrides the default weight of 0 for all docked items to 100.
+     * This is so that it has more priority over things like toolbars.
+     */
+    weight: 100,
+    defaultType: 'gridcolumn',
+    /**
+     * @cfg {Number} defaultWidth
+     * Width of the header if no width or flex is specified. Defaults to 100.
+     */
+    defaultWidth: 100,
+
+
+    sortAscText: 'Sort Ascending',
+    sortDescText: 'Sort Descending',
+    sortClearText: 'Clear Sort',
+    columnsText: 'Columns',
+
+    lastHeaderCls: Ext.baseCSSPrefix + 'column-header-last',
+    firstHeaderCls: Ext.baseCSSPrefix + 'column-header-first',
+    headerOpenCls: Ext.baseCSSPrefix + 'column-header-open',
+
+    // private; will probably be removed by 4.0
+    triStateSort: false,
+
+    ddLock: false,
+
+    dragging: false,
+
+    /**
+     * <code>true</code> if this HeaderContainer is in fact a group header which contains sub headers.
+     * @type Boolean
+     * @property isGroupHeader
+     */
+
+    /**
+     * @cfg {Boolean} sortable
+     * Provides the default sortable state for all Headers within this HeaderContainer.
+     * Also turns on or off the menus in the HeaderContainer. Note that the menu is
+     * shared across every header and therefore turning it off will remove the menu
+     * items for every header.
+     */
+    sortable: true,
+
+    initComponent: function() {
+        var me = this;
+
+        me.headerCounter = 0;
+        me.plugins = me.plugins || [];
+
+        // TODO: Pass in configurations to turn on/off dynamic
+        //       resizing and disable resizing all together
+
+        // Only set up a Resizer and Reorderer for the topmost HeaderContainer.
+        // Nested Group Headers are themselves HeaderContainers
+        if (!me.isHeader) {
+            me.resizer   = Ext.create('Ext.grid.plugin.HeaderResizer');
+            me.reorderer = Ext.create('Ext.grid.plugin.HeaderReorderer');
+            if (!me.enableColumnResize) {
+                me.resizer.disable();
+            }
+            if (!me.enableColumnMove) {
+                me.reorderer.disable();
+            }
+            me.plugins.push(me.reorderer, me.resizer);
+        }
+
+        // Base headers do not need a box layout
+        if (me.isHeader && !me.items) {
+            me.layout = 'auto';
+        }
+        // HeaderContainer and Group header needs a gridcolumn layout.
+        else {
+            me.layout = {
+                type: 'gridcolumn',
+                availableSpaceOffset: me.availableSpaceOffset,
+                align: 'stretchmax',
+                resetStretch: true
+            };
+        }
+        me.defaults = me.defaults || {};
+        Ext.applyIf(me.defaults, {
+            width: me.defaultWidth,
+            triStateSort: me.triStateSort,
+            sortable: me.sortable
+        });
+        me.callParent();
+        me.addEvents(
+            /**
+             * @event columnresize
+             * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
+             * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
+             * @param {Number} width
+             */
+            'columnresize',
+
+            /**
+             * @event headerclick
+             * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
+             * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
+             * @param {Ext.EventObject} e
+             * @param {HTMLElement} t
+             */
+            'headerclick',
+
+            /**
+             * @event headertriggerclick
+             * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
+             * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
+             * @param {Ext.EventObject} e
+             * @param {HTMLElement} t
+             */
+            'headertriggerclick',
+
+            /**
+             * @event columnmove
+             * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
+             * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
+             * @param {Number} fromIdx
+             * @param {Number} toIdx
+             */
+            'columnmove',
+            /**
+             * @event columnhide
+             * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
+             * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
+             */
+            'columnhide',
+            /**
+             * @event columnshow
+             * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
+             * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
+             */
+            'columnshow',
+            /**
+             * @event sortchange
+             * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
+             * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
+             * @param {String} direction
+             */
+            'sortchange',
+            /**
+             * @event menucreate
+             * Fired immediately after the column header menu is created.
+             * @param {Ext.grid.header.Container} ct This instance
+             * @param {Ext.menu.Menu} menu The Menu that was created
+             */
+            'menucreate'
+        );
+    },
+
+    onDestroy: function() {
+        Ext.destroy(this.resizer, this.reorderer);
+        this.callParent();
+    },
+    
+    applyDefaults: function(config){
+        /*
+         * Ensure header.Container defaults don't get applied to a RowNumberer 
+         * if an xtype is supplied. This isn't an ideal solution however it's 
+         * much more likely that a RowNumberer with no options will be created, 
+         * wanting to use the defaults specified on the class as opposed to 
+         * those setup on the Container.
+         */
+        if (config && !config.isComponent && config.xtype == 'rownumberer') {
+            return config;
+        }
+        return this.callParent([config]);
+    },
+
+    applyColumnsState: function(columns) {
+        if (!columns || !columns.length) {
+            return;
+        }
+
+        var me = this,
+            i = 0,
+            index,
+            col;
+
+        Ext.each(columns, function (columnState) {
+            col = me.down('gridcolumn[headerId=' + columnState.id + ']');
+            if (col) {
+                index = me.items.indexOf(col);
+                if (i !== index) {
+                    me.moveHeader(index, i);
+                }
+
+                if (col.applyColumnState) {
+                    col.applyColumnState(columnState);
+                }
+                ++i;
+            }
+        });
+    },
+
+    getColumnsState: function () {
+        var me = this,
+            columns = [],
+            state;
+
+        me.items.each(function (col) {
+            state = col.getColumnState && col.getColumnState();
+            if (state) {
+                columns.push(state);
+            }
+        });
+
+        return columns;
+    },
+
+    // Invalidate column cache on add
+    // We cannot refresh the View on every add because this method is called
+    // when the HeaderDropZone moves Headers around, that will also refresh the view
+    onAdd: function(c) {
+        var me = this;
+        if (!c.headerId) {
+            c.headerId = c.initialConfig.id || ('h' + (++me.headerCounter));
+        }
+        //<debug warn>
+        if (Ext.global.console && Ext.global.console.warn) {
+            if (!me._usedIDs) me._usedIDs = {};
+            if (me._usedIDs[c.headerId]) {
+                Ext.global.console.warn(this.$className, 'attempted to reuse an existing id', c.headerId);
+            }
+            me._usedIDs[c.headerId] = true;
+        }
+        //</debug>
+        me.callParent(arguments);
+        me.purgeCache();
+    },
+
+    // Invalidate column cache on remove
+    // We cannot refresh the View on every remove because this method is called
+    // when the HeaderDropZone moves Headers around, that will also refresh the view
+    onRemove: function(c) {
+        var me = this;
+        me.callParent(arguments);
+        me.purgeCache();
+    },
+
+    afterRender: function() {
+        this.callParent();
+        var store   = this.up('[store]').store,
+            sorters = store.sorters,
+            first   = sorters.first(),
+            hd;
+
+        if (first) {
+            hd = this.down('gridcolumn[dataIndex=' + first.property  +']');
+            if (hd) {
+                hd.setSortState(first.direction, false, true);
+            }
+        }
+    },
+
+    afterLayout: function() {
+        if (!this.isHeader) {
+            var me = this,
+                topHeaders = me.query('>gridcolumn:not([hidden])'),
+                viewEl,
+                firstHeaderEl,
+                lastHeaderEl;
+
+            me.callParent(arguments);
+
+            if (topHeaders.length) {
+                firstHeaderEl = topHeaders[0].el;
+                if (firstHeaderEl !== me.pastFirstHeaderEl) {
+                    if (me.pastFirstHeaderEl) {
+                        me.pastFirstHeaderEl.removeCls(me.firstHeaderCls);
+                    }
+                    firstHeaderEl.addCls(me.firstHeaderCls);
+                    me.pastFirstHeaderEl = firstHeaderEl;
+                }
+
+                lastHeaderEl = topHeaders[topHeaders.length - 1].el;
+                if (lastHeaderEl !== me.pastLastHeaderEl) {
+                    if (me.pastLastHeaderEl) {
+                        me.pastLastHeaderEl.removeCls(me.lastHeaderCls);
+                    }
+                    lastHeaderEl.addCls(me.lastHeaderCls);
+                    me.pastLastHeaderEl = lastHeaderEl;
+                }
+            }
+        }
+
+    },
+
+    onHeaderShow: function(header, preventLayout) {
+        // Pass up to the GridSection
+        var me = this,
+            gridSection = me.ownerCt,
+            menu = me.getMenu(),
+            topItems, topItemsVisible,
+            colCheckItem,
+            itemToEnable,
+            len, i;
+
+        if (menu) {
+
+            colCheckItem = menu.down('menucheckitem[headerId=' + header.id + ']');
+            if (colCheckItem) {
+                colCheckItem.setChecked(true, true);
+            }
+
+            // There's more than one header visible, and we've disabled some checked items... re-enable them
+            topItems = menu.query('#columnItem>menucheckitem[checked]');
+            topItemsVisible = topItems.length;
+            if ((me.getVisibleGridColumns().length > 1) && me.disabledMenuItems && me.disabledMenuItems.length) {
+                if (topItemsVisible == 1) {
+                    Ext.Array.remove(me.disabledMenuItems, topItems[0]);
+                }
+                for (i = 0, len = me.disabledMenuItems.length; i < len; i++) {
+                    itemToEnable = me.disabledMenuItems[i];
+                    if (!itemToEnable.isDestroyed) {
+                        itemToEnable[itemToEnable.menu ? 'enableCheckChange' : 'enable']();
+                    }
+                }
+                if (topItemsVisible == 1) {
+                    me.disabledMenuItems = topItems;
+                } else {
+                    me.disabledMenuItems = [];
+                }
+            }
+        }
+
+        // Only update the grid UI when we are notified about base level Header shows;
+        // Group header shows just cause a layout of the HeaderContainer
+        if (!header.isGroupHeader) {
+            if (me.view) {
+                me.view.onHeaderShow(me, header, true);
+            }
+            if (gridSection) {
+                gridSection.onHeaderShow(me, header);
+            }
+        }
+        me.fireEvent('columnshow', me, header);
+
+        // The header's own hide suppresses cascading layouts, so lay the headers out now
+        if (preventLayout !== true) {
+            me.doLayout();
+        }
+    },
+
+    doComponentLayout: function(){
+        var me = this;
+        if (me.view && me.view.saveScrollState) {
+            me.view.saveScrollState();
+        }
+        me.callParent(arguments);
+        if (me.view && me.view.restoreScrollState) {
+            me.view.restoreScrollState();
+        }
+    },
+
+    onHeaderHide: function(header, suppressLayout) {
+        // Pass up to the GridSection
+        var me = this,
+            gridSection = me.ownerCt,
+            menu = me.getMenu(),
+            colCheckItem;
+
+        if (menu) {
+
+            // If the header was hidden programmatically, sync the Menu state
+            colCheckItem = menu.down('menucheckitem[headerId=' + header.id + ']');
+            if (colCheckItem) {
+                colCheckItem.setChecked(false, true);
+            }
+            me.setDisabledItems();
+        }
+
+        // Only update the UI when we are notified about base level Header hides;
+        if (!header.isGroupHeader) {
+            if (me.view) {
+                me.view.onHeaderHide(me, header, true);
+            }
+            if (gridSection) {
+                gridSection.onHeaderHide(me, header);
+            }
+
+            // The header's own hide suppresses cascading layouts, so lay the headers out now
+            if (!suppressLayout) {
+                me.doLayout();
+            }
+        }
+        me.fireEvent('columnhide', me, header);
+    },
+
+    setDisabledItems: function(){
+        var me = this,
+            menu = me.getMenu(),
+            i = 0,
+            len,
+            itemsToDisable,
+            itemToDisable;
+
+        // Find what to disable. If only one top level item remaining checked, we have to disable stuff.
+        itemsToDisable = menu.query('#columnItem>menucheckitem[checked]');
+        if ((itemsToDisable.length === 1)) {
+            if (!me.disabledMenuItems) {
+                me.disabledMenuItems = [];
+            }
+
+            // If down to only one column visible, also disable any descendant checkitems
+            if ((me.getVisibleGridColumns().length === 1) && itemsToDisable[0].menu) {
+                itemsToDisable = itemsToDisable.concat(itemsToDisable[0].menu.query('menucheckitem[checked]'));
+            }
+
+            len = itemsToDisable.length;
+            // Disable any further unchecking at any level.
+            for (i = 0; i < len; i++) {
+                itemToDisable = itemsToDisable[i];
+                if (!Ext.Array.contains(me.disabledMenuItems, itemToDisable)) {
+
+                    // If we only want to disable check change: it might be a disabled item, so enable it prior to
+                    // setting its correct disablement level.
+                    itemToDisable.disabled = false;
+                    itemToDisable[itemToDisable.menu ? 'disableCheckChange' : 'disable']();
+                    me.disabledMenuItems.push(itemToDisable);
+                }
+            }
+        }
+    },
+
+    /**
+     * Temporarily lock the headerCt. This makes it so that clicking on headers
+     * don't trigger actions like sorting or opening of the header menu. This is
+     * done because extraneous events may be fired on the headers after interacting
+     * with a drag drop operation.
+     * @private
+     */
+    tempLock: function() {
+        this.ddLock = true;
+        Ext.Function.defer(function() {
+            this.ddLock = false;
+        }, 200, this);
+    },
+
+    onHeaderResize: function(header, w, suppressFocus) {
+        this.tempLock();
+        if (this.view && this.view.rendered) {
+            this.view.onHeaderResize(header, w, suppressFocus);
+        }
+    },
+
+    onHeaderClick: function(header, e, t) {
+        this.fireEvent("headerclick", this, header, e, t);
+    },
+
+    onHeaderTriggerClick: function(header, e, t) {
+        // generate and cache menu, provide ability to cancel/etc
+        if (this.fireEvent("headertriggerclick", this, header, e, t) !== false) {
+            this.showMenuBy(t, header);
+        }
+    },
+
+    showMenuBy: function(t, header) {
+        var menu = this.getMenu(),
+            ascItem  = menu.down('#ascItem'),
+            descItem = menu.down('#descItem'),
+            sortableMth;
+
+        menu.activeHeader = menu.ownerCt = header;
+        menu.setFloatParent(header);
+        // TODO: remove coupling to Header's titleContainer el
+        header.titleContainer.addCls(this.headerOpenCls);
+
+        // enable or disable asc & desc menu items based on header being sortable
+        sortableMth = header.sortable ? 'enable' : 'disable';
+        if (ascItem) {
+            ascItem[sortableMth]();
+        }
+        if (descItem) {
+            descItem[sortableMth]();
+        }
+        menu.showBy(t);
+    },
+
+    // remove the trigger open class when the menu is hidden
+    onMenuDeactivate: function() {
+        var menu = this.getMenu();
+        // TODO: remove coupling to Header's titleContainer el
+        menu.activeHeader.titleContainer.removeCls(this.headerOpenCls);
+    },
+
+    moveHeader: function(fromIdx, toIdx) {
+
+        // An automatically expiring lock
+        this.tempLock();
+        this.onHeaderMoved(this.move(fromIdx, toIdx), fromIdx, toIdx);
+    },
+
+    purgeCache: function() {
+        var me = this;
+        // Delete column cache - column order has changed.
+        delete me.gridDataColumns;
+        delete me.hideableColumns;
+
+        // Menu changes when columns are moved. It will be recreated.
+        if (me.menu) {
+            me.menu.destroy();
+            delete me.menu;
+        }
+    },
+
+    onHeaderMoved: function(header, fromIdx, toIdx) {
+        var me = this,
+            gridSection = me.ownerCt;
+
+        if (gridSection && gridSection.onHeaderMove) {
+            gridSection.onHeaderMove(me, header, fromIdx, toIdx);
+        }
+        me.fireEvent("columnmove", me, header, fromIdx, toIdx);
+    },
+
+    /**
+     * Gets the menu (and will create it if it doesn't already exist)
+     * @private
+     */
+    getMenu: function() {
+        var me = this;
+
+        if (!me.menu) {
+            me.menu = Ext.create('Ext.menu.Menu', {
+                hideOnParentHide: false,  // Persists when owning ColumnHeader is hidden
+                items: me.getMenuItems(),
+                listeners: {
+                    deactivate: me.onMenuDeactivate,
+                    scope: me
+                }
+            });
+            me.setDisabledItems();
+            me.fireEvent('menucreate', me, me.menu);
+        }
+        return me.menu;
+    },
+
+    /**
+     * Returns an array of menu items to be placed into the shared menu
+     * across all headers in this header container.
+     * @returns {Array} menuItems
+     */
+    getMenuItems: function() {
+        var me = this,
+            menuItems = [],
+            hideableColumns = me.enableColumnHide ? me.getColumnMenu(me) : null;
+
+        if (me.sortable) {
+            menuItems = [{
+                itemId: 'ascItem',
+                text: me.sortAscText,
+                cls: Ext.baseCSSPrefix + 'hmenu-sort-asc',
+                handler: me.onSortAscClick,
+                scope: me
+            },{
+                itemId: 'descItem',
+                text: me.sortDescText,
+                cls: Ext.baseCSSPrefix + 'hmenu-sort-desc',
+                handler: me.onSortDescClick,
+                scope: me
+            }];
+        }
+        if (hideableColumns && hideableColumns.length) {
+            menuItems.push('-', {
+                itemId: 'columnItem',
+                text: me.columnsText,
+                cls: Ext.baseCSSPrefix + 'cols-icon',
+                menu: hideableColumns
+            });
+        }
+        return menuItems;
+    },
+
+    // sort asc when clicking on item in menu
+    onSortAscClick: function() {
+        var menu = this.getMenu(),
+            activeHeader = menu.activeHeader;
+
+        activeHeader.setSortState('ASC');
+    },
+
+    // sort desc when clicking on item in menu
+    onSortDescClick: function() {
+        var menu = this.getMenu(),
+            activeHeader = menu.activeHeader;
+
+        activeHeader.setSortState('DESC');
+    },
+
+    /**
+     * Returns an array of menu CheckItems corresponding to all immediate children of the passed Container which have been configured as hideable.
+     */
+    getColumnMenu: function(headerContainer) {
+        var menuItems = [],
             i = 0,
-            it = this.items.items,
-            ln = it.length;
+            item,
+            items = headerContainer.query('>gridcolumn[hideable]'),
+            itemsLn = items.length,
+            menuItem;
 
-        el = Ext.getDom(el);
-        for (; i < ln; i++) {
-            item = it[i];
-            itemEl = item.getEl();
-            if ((itemEl.dom === el) || itemEl.contains(el)) {
-                return item;
+        for (; i < itemsLn; i++) {
+            item = items[i];
+            menuItem = Ext.create('Ext.menu.CheckItem', {
+                text: item.text,
+                checked: !item.hidden,
+                hideOnClick: false,
+                headerId: item.id,
+                menu: item.isGroupHeader ? this.getColumnMenu(item) : undefined,
+                checkHandler: this.onColumnCheckChange,
+                scope: this
+            });
+            if (itemsLn === 1) {
+                menuItem.disabled = true;
             }
+            menuItems.push(menuItem);
+
+            // If the header is ever destroyed - for instance by dragging out the last remaining sub header,
+            // then the associated menu item must also be destroyed.
+            item.on({
+                destroy: Ext.Function.bind(menuItem.destroy, menuItem)
+            });
+        }
+        return menuItems;
+    },
+
+    onColumnCheckChange: function(checkItem, checked) {
+        var header = Ext.getCmp(checkItem.headerId);
+        header[checked ? 'show' : 'hide']();
+    },
+
+    /**
+     * Get the columns used for generating a template via TableChunker.
+     * Returns an array of all columns and their
+     *  - dataIndex
+     *  - align
+     *  - width
+     *  - id
+     *  - columnId - used to create an identifying CSS class
+     *  - cls The tdCls configuration from the Column object
+     *  @private
+     */
+    getColumnsForTpl: function(flushCache) {
+        var cols    = [],
+            headers   = this.getGridColumns(flushCache),
+            headersLn = headers.length,
+            i = 0,
+            header,
+            width;
+
+        for (; i < headersLn; i++) {
+            header = headers[i];
+
+            if (header.hidden || header.up('headercontainer[hidden=true]')) {
+                width = 0;
+            } else {
+                width = header.getDesiredWidth();
+                // IE6 and IE7 bug.
+                // Setting the width of the first TD does not work - ends up with a 1 pixel discrepancy.
+                // We need to increment the passed with in this case.
+                if ((i === 0) && (Ext.isIE6 || Ext.isIE7)) {
+                    width += 1;
+                }
+            }
+            cols.push({
+                dataIndex: header.dataIndex,
+                align: header.align,
+                width: width,
+                id: header.id,
+                cls: header.tdCls,
+                columnId: header.getItemId()
+            });
+        }
+        return cols;
+    },
+
+    /**
+     * Returns the number of <b>grid columns</b> descended from this HeaderContainer.
+     * Group Columns are HeaderContainers. All grid columns are returned, including hidden ones.
+     */
+    getColumnCount: function() {
+        return this.getGridColumns().length;
+    },
+
+    /**
+     * Gets the full width of all columns that are visible.
+     */
+    getFullWidth: function(flushCache) {
+        var fullWidth = 0,
+            headers     = this.getVisibleGridColumns(flushCache),
+            headersLn   = headers.length,
+            i         = 0;
+
+        for (; i < headersLn; i++) {
+            if (!isNaN(headers[i].width)) {
+                // use headers getDesiredWidth if its there
+                if (headers[i].getDesiredWidth) {
+                    fullWidth += headers[i].getDesiredWidth();
+                // if injected a diff cmp use getWidth
+                } else {
+                    fullWidth += headers[i].getWidth();
+                }
+            }
+        }
+        return fullWidth;
+    },
+
+    // invoked internally by a header when not using triStateSorting
+    clearOtherSortStates: function(activeHeader) {
+        var headers   = this.getGridColumns(),
+            headersLn = headers.length,
+            i         = 0,
+            oldSortState;
+
+        for (; i < headersLn; i++) {
+            if (headers[i] !== activeHeader) {
+                oldSortState = headers[i].sortState;
+                // unset the sortstate and dont recurse
+                headers[i].setSortState(null, true);
+                //if (!silent && oldSortState !== null) {
+                //    this.fireEvent('sortchange', this, headers[i], null);
+                //}
+            }
+        }
+    },
+
+    /**
+     * Returns an array of the <b>visible</b> columns in the grid. This goes down to the lowest column header
+     * level, and does not return <i>grouped</i> headers which contain sub headers.
+     * @param {Boolean} refreshCache If omitted, the cached set of columns will be returned. Pass true to refresh the cache.
+     * @returns {Array}
+     */
+    getVisibleGridColumns: function(refreshCache) {
+        return Ext.ComponentQuery.query(':not([hidden])', this.getGridColumns(refreshCache));
+    },
+
+    /**
+     * Returns an array of all columns which map to Store fields. This goes down to the lowest column header
+     * level, and does not return <i>grouped</i> headers which contain sub headers.
+     * @param {Boolean} refreshCache If omitted, the cached set of columns will be returned. Pass true to refresh the cache.
+     * @returns {Array}
+     */
+    getGridColumns: function(refreshCache) {
+        var me = this,
+            result = refreshCache ? null : me.gridDataColumns;
+
+        // Not already got the column cache, so collect the base columns
+        if (!result) {
+            me.gridDataColumns = result = [];
+            me.cascade(function(c) {
+                if ((c !== me) && !c.isGroupHeader) {
+                    result.push(c);
+                }
+            });
+        }
+
+        return result;
+    },
+
+    /**
+     * @private
+     * For use by column headers in determining whether there are any hideable columns when deciding whether or not
+     * the header menu should be disabled.
+     */
+    getHideableColumns: function(refreshCache) {
+        var me = this,
+            result = refreshCache ? null : me.hideableColumns;
+
+        if (!result) {
+            result = me.hideableColumns = me.query('[hideable]');
+        }
+        return result;
+    },
+
+    /**
+     * Get the index of a leaf level header regardless of what the nesting
+     * structure is.
+     */
+    getHeaderIndex: function(header) {
+        var columns = this.getGridColumns();
+        return Ext.Array.indexOf(columns, header);
+    },
+
+    /**
+     * Get a leaf level header by index regardless of what the nesting
+     * structure is.
+     */
+    getHeaderAtIndex: function(index) {
+        var columns = this.getGridColumns();
+        return columns[index];
+    },
+
+    /**
+     * Maps the record data to base it on the header id's.
+     * This correlates to the markup/template generated by
+     * TableChunker.
+     */
+    prepareData: function(data, rowIdx, record, view, panel) {
+        var obj       = {},
+            headers   = this.gridDataColumns || this.getGridColumns(),
+            headersLn = headers.length,
+            colIdx    = 0,
+            header,
+            headerId,
+            renderer,
+            value,
+            metaData,
+            store = panel.store;
+
+        for (; colIdx < headersLn; colIdx++) {
+            metaData = {
+                tdCls: '',
+                style: ''
+            };
+            header = headers[colIdx];
+            headerId = header.id;
+            renderer = header.renderer;
+            value = data[header.dataIndex];
+
+            // When specifying a renderer as a string, it always resolves
+            // to Ext.util.Format
+            if (typeof renderer === "string") {
+                header.renderer = renderer = Ext.util.Format[renderer];
+            }
+
+            if (typeof renderer === "function") {
+                value = renderer.call(
+                    header.scope || this.ownerCt,
+                    value,
+                    // metadata per cell passing an obj by reference so that
+                    // it can be manipulated inside the renderer
+                    metaData,
+                    record,
+                    rowIdx,
+                    colIdx,
+                    store,
+                    view
+                );
+            }
+
+            // <debug>
+            if (metaData.css) {
+                // This warning attribute is used by the compat layer
+                obj.cssWarning = true;
+                metaData.tdCls = metaData.css;
+                delete metaData.css;
+            }
+            // </debug>
+
+            obj[headerId+'-modified'] = record.isModified(header.dataIndex) ? Ext.baseCSSPrefix + 'grid-dirty-cell' : '';
+            obj[headerId+'-tdCls'] = metaData.tdCls;
+            obj[headerId+'-tdAttr'] = metaData.tdAttr;
+            obj[headerId+'-style'] = metaData.style;
+            if (value === undefined || value === null || value === '') {
+                value = '&#160;';
+            }
+            obj[headerId] = value;
+        }
+        return obj;
+    },
+
+    expandToFit: function(header) {
+        if (this.view) {
+            this.view.expandToFit(header);
         }
-        return null;
     }
 });