+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',
+
+<span id='Ext-grid-header-Container-cfg-weight'> /**
+</span> * @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',
+<span id='Ext-grid-header-Container-cfg-defaultWidth'> /**
+</span> * @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,
+
+<span id='Ext-grid-header-Container-property-isGroupHeader'> /**
+</span> * <code>true</code> if this HeaderContainer is in fact a group header which contains sub headers.
+ * @type Boolean
+ * @property isGroupHeader
+ */
+
+<span id='Ext-grid-header-Container-cfg-sortable'> /**
+</span> * @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(
+<span id='Ext-grid-header-Container-event-columnresize'> /**
+</span> * @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',
+
+<span id='Ext-grid-header-Container-event-headerclick'> /**
+</span> * @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',
+
+<span id='Ext-grid-header-Container-event-headertriggerclick'> /**
+</span> * @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',
+
+<span id='Ext-grid-header-Container-event-columnmove'> /**
+</span> * @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',
+<span id='Ext-grid-header-Container-event-columnhide'> /**
+</span> * @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',
+<span id='Ext-grid-header-Container-event-columnshow'> /**
+</span> * @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',
+<span id='Ext-grid-header-Container-event-sortchange'> /**
+</span> * @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',
+<span id='Ext-grid-header-Container-event-menucreate'> /**
+</span> * @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);
+ }
+ }
+ }
+ },
+
+<span id='Ext-grid-header-Container-method-tempLock'> /**
+</span> * 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);
+ },
+
+<span id='Ext-grid-header-Container-method-getMenu'> /**
+</span> * 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;
+ },
+
+<span id='Ext-grid-header-Container-method-getMenuItems'> /**
+</span> * 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');
+ },
+
+<span id='Ext-grid-header-Container-method-getColumnMenu'> /**
+</span> * 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 = [],