X-Git-Url: http://git.ithinksw.org/extjs.git/blobdiff_plain/3789b528d8dd8aad4558e38e22d775bcab1cbd36..6746dc89c47ed01b165cc1152533605f97eb8e8d:/docs/source/Container3.html diff --git a/docs/source/Container3.html b/docs/source/Container3.html index 2e696230..5fddb911 100644 --- a/docs/source/Container3.html +++ b/docs/source/Container3.html @@ -15,108 +15,810 @@
-/** -* @class Ext.layout.container.Container -* @extends Ext.layout.container.AbstractContainer -* @private -* <p>This class is intended to be extended or created via the <tt><b>{@link Ext.container.Container#layout layout}</b></tt> -* configuration property. See <tt><b>{@link Ext.container.Container#layout}</b></tt> for additional details.</p> -*/ -Ext.define('Ext.layout.container.Container', { +/** + * @class Ext.grid.header.Container + * @extends Ext.container.Container + * + * 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.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, - /* Begin Definitions */ + alias: 'widget.headercontainer', - extend: 'Ext.layout.container.AbstractContainer', - alternateClassName: 'Ext.layout.ContainerLayout', - - /* End Definitions */ + baseCls: Ext.baseCSSPrefix + 'grid-header-ct', + dock: 'top', - layoutItem: function(item, box) { - box = box || {}; - if (item.componentLayout.initialized !== true) { - this.setItemSize(item, box.width || item.width || undefined, box.height || item.height || undefined); - // item.doComponentLayout(box.width || item.width || undefined, box.height || item.height || undefined); + /** + * @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' + ); }, - getLayoutTargetSize : function() { - var target = this.getTarget(), - ret; + onDestroy: function() { + Ext.destroy(this.resizer, this.reorderer); + this.callParent(); + }, - if (target) { - ret = target.getViewSize(); + // 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 = 'h' + (++me.headerCounter); + } + me.callParent(arguments); + me.purgeCache(); + }, - // IE in will sometimes return a width of 0 on the 1st pass of getViewSize. - // Use getStyleSize to verify the 0 width, the adjustment pass will then work properly - // with getViewSize - if (Ext.isIE && ret.width == 0){ - ret = target.getStyleSize(); + // 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); } + } + }, - ret.width -= target.getPadding('lr'); - ret.height -= target.getPadding('tb'); + 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 + } + } } - return ret; + }, - beforeLayout: function() { - if (this.owner.beforeLayout(arguments) !== false) { - return this.callParent(arguments); + onHeaderShow: function(header) { + // 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 = []; + } + } } - else { - return false; + + // 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 + me.doLayout(); }, - afterLayout: function() { - this.owner.afterLayout(arguments); - this.callParent(arguments); + 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); + } + } + } }, - /** - * @protected - * Returns all items that are rendered - * @return {Array} All matching items + /** + * 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 */ - getRenderedItems: function() { + 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); + } + this.fireEvent('columnresize', this, header, w); + }, + + 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; + + // 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(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, - target = me.getTarget(), - items = me.getLayoutItems(), - ln = items.length, - renderedItems = [], - i, item; + menuItems = [], + hideableColumns = me.enableColumnHide ? me.getColumnMenu(me) : null; + + if (me.sortable) { + menuItems = [{ + itemId: 'ascItem', + text: me.sortAscText, + cls: 'xg-hmenu-sort-asc', + handler: me.onSortAscClick, + scope: me + },{ + itemId: 'descItem', + text: me.sortDescText, + cls: 'xg-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; + }, - for (i = 0; i < ln; i++) { + // 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, + item, + items = headerContainer.query('>gridcolumn[hideable]'), + itemsLn = items.length, + menuItem; + + for (; i < itemsLn; i++) { item = items[i]; - if (item.rendered && me.isValidParent(item, target, i)) { - renderedItems.push(item); + 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; + }, - return renderedItems; + onColumnCheckChange: function(checkItem, checked) { + var header = Ext.getCmp(checkItem.headerId); + header[checked ? 'show' : 'hide'](); }, - /** - * @protected - * Returns all items that are both rendered and visible - * @return {Array} All matching items + /** + * 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 */ - getVisibleItems: function() { - var target = this.getTarget(), - items = this.getLayoutItems(), - ln = items.length, - visibleItems = [], - i, item; + getColumnsForTpl: function(flushCache) { + var cols = [], + headers = this.getGridColumns(flushCache), + headersLn = headers.length, + i = 0, + header, + width; - for (i = 0; i < ln; i++) { - item = items[i]; - if (item.rendered && this.isValidParent(item, target, i) && item.hidden !== true) { - visibleItems.push(item); + for (; i < headersLn; i++) { + header = headers[i]; + + if (header.hidden) { + 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; + }, + + /** + * 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 = ' '; } + obj[headerId] = value; } + return obj; + }, - return visibleItems; + expandToFit: function(header) { + if (this.view) { + this.view.expandToFit(header); + } } -});+}); +