4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
5 <title>The source code</title>
6 <link href="../resources/prettify/prettify.css" type="text/css" rel="stylesheet" />
7 <script type="text/javascript" src="../resources/prettify/prettify.js"></script>
8 <style type="text/css">
9 .highlight { display: block; background-color: #ddd; }
11 <script type="text/javascript">
12 function highlight() {
13 document.getElementById(location.hash.replace(/#/, "")).className = "highlight";
17 <body onload="prettyPrint(); highlight();">
18 <pre class="prettyprint lang-js"><span id='Ext-grid-header-Container'>/**
19 </span> * @class Ext.grid.header.Container
20 * @extends Ext.container.Container
22 * Container which holds headers and is docked at the top or bottom of a TablePanel.
23 * The HeaderContainer drives resizing/moving/hiding of columns within the TableView.
24 * As headers are hidden, moved or resized the headercontainer is responsible for
25 * triggering changes within the view.
27 Ext.define('Ext.grid.header.Container', {
28 extend: 'Ext.container.Container',
30 'Ext.grid.ColumnLayout',
31 'Ext.grid.column.Column',
35 'Ext.grid.plugin.HeaderResizer',
36 'Ext.grid.plugin.HeaderReorderer'
40 alias: 'widget.headercontainer',
42 baseCls: Ext.baseCSSPrefix + 'grid-header-ct',
45 <span id='Ext-grid-header-Container-cfg-weight'> /**
46 </span> * @cfg {Number} weight
47 * HeaderContainer overrides the default weight of 0 for all docked items to 100.
48 * This is so that it has more priority over things like toolbars.
51 defaultType: 'gridcolumn',
52 <span id='Ext-grid-header-Container-cfg-defaultWidth'> /**
53 </span> * @cfg {Number} defaultWidth
54 * Width of the header if no width or flex is specified. Defaults to 100.
59 sortAscText: 'Sort Ascending',
60 sortDescText: 'Sort Descending',
61 sortClearText: 'Clear Sort',
62 columnsText: 'Columns',
64 lastHeaderCls: Ext.baseCSSPrefix + 'column-header-last',
65 firstHeaderCls: Ext.baseCSSPrefix + 'column-header-first',
66 headerOpenCls: Ext.baseCSSPrefix + 'column-header-open',
68 // private; will probably be removed by 4.0
75 <span id='Ext-grid-header-Container-property-isGroupHeader'> /**
76 </span> * <code>true</code> if this HeaderContainer is in fact a group header which contains sub headers.
78 * @property isGroupHeader
81 <span id='Ext-grid-header-Container-cfg-sortable'> /**
82 </span> * @cfg {Boolean} sortable
83 * Provides the default sortable state for all Headers within this HeaderContainer.
84 * Also turns on or off the menus in the HeaderContainer. Note that the menu is
85 * shared across every header and therefore turning it off will remove the menu
86 * items for every header.
90 initComponent: function() {
94 me.plugins = me.plugins || [];
96 // TODO: Pass in configurations to turn on/off dynamic
97 // resizing and disable resizing all together
99 // Only set up a Resizer and Reorderer for the topmost HeaderContainer.
100 // Nested Group Headers are themselves HeaderContainers
102 me.resizer = Ext.create('Ext.grid.plugin.HeaderResizer');
103 me.reorderer = Ext.create('Ext.grid.plugin.HeaderReorderer');
104 if (!me.enableColumnResize) {
105 me.resizer.disable();
107 if (!me.enableColumnMove) {
108 me.reorderer.disable();
110 me.plugins.push(me.reorderer, me.resizer);
113 // Base headers do not need a box layout
114 if (me.isHeader && !me.items) {
117 // HeaderContainer and Group header needs a gridcolumn layout.
121 availableSpaceOffset: me.availableSpaceOffset,
126 me.defaults = me.defaults || {};
127 Ext.applyIf(me.defaults, {
128 width: me.defaultWidth,
129 triStateSort: me.triStateSort,
130 sortable: me.sortable
134 <span id='Ext-grid-header-Container-event-columnresize'> /**
135 </span> * @event columnresize
136 * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
137 * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
138 * @param {Number} width
142 <span id='Ext-grid-header-Container-event-headerclick'> /**
143 </span> * @event headerclick
144 * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
145 * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
146 * @param {Ext.EventObject} e
147 * @param {HTMLElement} t
151 <span id='Ext-grid-header-Container-event-headertriggerclick'> /**
152 </span> * @event headertriggerclick
153 * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
154 * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
155 * @param {Ext.EventObject} e
156 * @param {HTMLElement} t
158 'headertriggerclick',
160 <span id='Ext-grid-header-Container-event-columnmove'> /**
161 </span> * @event columnmove
162 * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
163 * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
164 * @param {Number} fromIdx
165 * @param {Number} toIdx
168 <span id='Ext-grid-header-Container-event-columnhide'> /**
169 </span> * @event columnhide
170 * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
171 * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
174 <span id='Ext-grid-header-Container-event-columnshow'> /**
175 </span> * @event columnshow
176 * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
177 * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
180 <span id='Ext-grid-header-Container-event-sortchange'> /**
181 </span> * @event sortchange
182 * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
183 * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
184 * @param {String} direction
187 <span id='Ext-grid-header-Container-event-menucreate'> /**
188 </span> * @event menucreate
189 * Fired immediately after the column header menu is created.
190 * @param {Ext.grid.header.Container} ct This instance
191 * @param {Ext.menu.Menu} menu The Menu that was created
197 onDestroy: function() {
198 Ext.destroy(this.resizer, this.reorderer);
202 applyDefaults: function(config){
204 * Ensure header.Container defaults don't get applied to a RowNumberer
205 * if an xtype is supplied. This isn't an ideal solution however it's
206 * much more likely that a RowNumberer with no options will be created,
207 * wanting to use the defaults specified on the class as opposed to
208 * those setup on the Container.
210 if (config && !config.isComponent && config.xtype == 'rownumberer') {
213 return this.callParent([config]);
216 applyColumnsState: function(columns) {
217 if (!columns || !columns.length) {
226 Ext.each(columns, function (columnState) {
227 col = me.down('gridcolumn[headerId=' + columnState.id + ']');
229 index = me.items.indexOf(col);
231 me.moveHeader(index, i);
234 if (col.applyColumnState) {
235 col.applyColumnState(columnState);
242 getColumnsState: function () {
247 me.items.each(function (col) {
248 state = col.getColumnState && col.getColumnState();
257 // Invalidate column cache on add
258 // We cannot refresh the View on every add because this method is called
259 // when the HeaderDropZone moves Headers around, that will also refresh the view
263 c.headerId = c.initialConfig.id || ('h' + (++me.headerCounter));
266 if (Ext.global.console && Ext.global.console.warn) {
267 if (!me._usedIDs) me._usedIDs = {};
268 if (me._usedIDs[c.headerId]) {
269 Ext.global.console.warn(this.$className, 'attempted to reuse an existing id', c.headerId);
271 me._usedIDs[c.headerId] = true;
274 me.callParent(arguments);
278 // Invalidate column cache on remove
279 // We cannot refresh the View on every remove because this method is called
280 // when the HeaderDropZone moves Headers around, that will also refresh the view
281 onRemove: function(c) {
283 me.callParent(arguments);
287 afterRender: function() {
289 var store = this.up('[store]').store,
290 sorters = store.sorters,
291 first = sorters.first(),
295 hd = this.down('gridcolumn[dataIndex=' + first.property +']');
297 hd.setSortState(first.direction, false, true);
302 afterLayout: function() {
303 if (!this.isHeader) {
305 topHeaders = me.query('>gridcolumn:not([hidden])'),
310 me.callParent(arguments);
312 if (topHeaders.length) {
313 firstHeaderEl = topHeaders[0].el;
314 if (firstHeaderEl !== me.pastFirstHeaderEl) {
315 if (me.pastFirstHeaderEl) {
316 me.pastFirstHeaderEl.removeCls(me.firstHeaderCls);
318 firstHeaderEl.addCls(me.firstHeaderCls);
319 me.pastFirstHeaderEl = firstHeaderEl;
322 lastHeaderEl = topHeaders[topHeaders.length - 1].el;
323 if (lastHeaderEl !== me.pastLastHeaderEl) {
324 if (me.pastLastHeaderEl) {
325 me.pastLastHeaderEl.removeCls(me.lastHeaderCls);
327 lastHeaderEl.addCls(me.lastHeaderCls);
328 me.pastLastHeaderEl = lastHeaderEl;
335 onHeaderShow: function(header, preventLayout) {
336 // Pass up to the GridSection
338 gridSection = me.ownerCt,
340 topItems, topItemsVisible,
347 colCheckItem = menu.down('menucheckitem[headerId=' + header.id + ']');
349 colCheckItem.setChecked(true, true);
352 // There's more than one header visible, and we've disabled some checked items... re-enable them
353 topItems = menu.query('#columnItem>menucheckitem[checked]');
354 topItemsVisible = topItems.length;
355 if ((me.getVisibleGridColumns().length > 1) && me.disabledMenuItems && me.disabledMenuItems.length) {
356 if (topItemsVisible == 1) {
357 Ext.Array.remove(me.disabledMenuItems, topItems[0]);
359 for (i = 0, len = me.disabledMenuItems.length; i < len; i++) {
360 itemToEnable = me.disabledMenuItems[i];
361 if (!itemToEnable.isDestroyed) {
362 itemToEnable[itemToEnable.menu ? 'enableCheckChange' : 'enable']();
365 if (topItemsVisible == 1) {
366 me.disabledMenuItems = topItems;
368 me.disabledMenuItems = [];
373 // Only update the grid UI when we are notified about base level Header shows;
374 // Group header shows just cause a layout of the HeaderContainer
375 if (!header.isGroupHeader) {
377 me.view.onHeaderShow(me, header, true);
380 gridSection.onHeaderShow(me, header);
383 me.fireEvent('columnshow', me, header);
385 // The header's own hide suppresses cascading layouts, so lay the headers out now
386 if (preventLayout !== true) {
391 doComponentLayout: function(){
393 if (me.view && me.view.saveScrollState) {
394 me.view.saveScrollState();
396 me.callParent(arguments);
397 if (me.view && me.view.restoreScrollState) {
398 me.view.restoreScrollState();
402 onHeaderHide: function(header, suppressLayout) {
403 // Pass up to the GridSection
405 gridSection = me.ownerCt,
411 // If the header was hidden programmatically, sync the Menu state
412 colCheckItem = menu.down('menucheckitem[headerId=' + header.id + ']');
414 colCheckItem.setChecked(false, true);
416 me.setDisabledItems();
419 // Only update the UI when we are notified about base level Header hides;
420 if (!header.isGroupHeader) {
422 me.view.onHeaderHide(me, header, true);
425 gridSection.onHeaderHide(me, header);
428 // The header's own hide suppresses cascading layouts, so lay the headers out now
429 if (!suppressLayout) {
433 me.fireEvent('columnhide', me, header);
436 setDisabledItems: function(){
444 // Find what to disable. If only one top level item remaining checked, we have to disable stuff.
445 itemsToDisable = menu.query('#columnItem>menucheckitem[checked]');
446 if ((itemsToDisable.length === 1)) {
447 if (!me.disabledMenuItems) {
448 me.disabledMenuItems = [];
451 // If down to only one column visible, also disable any descendant checkitems
452 if ((me.getVisibleGridColumns().length === 1) && itemsToDisable[0].menu) {
453 itemsToDisable = itemsToDisable.concat(itemsToDisable[0].menu.query('menucheckitem[checked]'));
456 len = itemsToDisable.length;
457 // Disable any further unchecking at any level.
458 for (i = 0; i < len; i++) {
459 itemToDisable = itemsToDisable[i];
460 if (!Ext.Array.contains(me.disabledMenuItems, itemToDisable)) {
462 // If we only want to disable check change: it might be a disabled item, so enable it prior to
463 // setting its correct disablement level.
464 itemToDisable.disabled = false;
465 itemToDisable[itemToDisable.menu ? 'disableCheckChange' : 'disable']();
466 me.disabledMenuItems.push(itemToDisable);
472 <span id='Ext-grid-header-Container-method-tempLock'> /**
473 </span> * Temporarily lock the headerCt. This makes it so that clicking on headers
474 * don't trigger actions like sorting or opening of the header menu. This is
475 * done because extraneous events may be fired on the headers after interacting
476 * with a drag drop operation.
479 tempLock: function() {
481 Ext.Function.defer(function() {
486 onHeaderResize: function(header, w, suppressFocus) {
488 if (this.view && this.view.rendered) {
489 this.view.onHeaderResize(header, w, suppressFocus);
493 onHeaderClick: function(header, e, t) {
494 this.fireEvent("headerclick", this, header, e, t);
497 onHeaderTriggerClick: function(header, e, t) {
498 // generate and cache menu, provide ability to cancel/etc
499 if (this.fireEvent("headertriggerclick", this, header, e, t) !== false) {
500 this.showMenuBy(t, header);
504 showMenuBy: function(t, header) {
505 var menu = this.getMenu(),
506 ascItem = menu.down('#ascItem'),
507 descItem = menu.down('#descItem'),
510 menu.activeHeader = menu.ownerCt = header;
511 menu.setFloatParent(header);
512 // TODO: remove coupling to Header's titleContainer el
513 header.titleContainer.addCls(this.headerOpenCls);
515 // enable or disable asc & desc menu items based on header being sortable
516 sortableMth = header.sortable ? 'enable' : 'disable';
518 ascItem[sortableMth]();
521 descItem[sortableMth]();
526 // remove the trigger open class when the menu is hidden
527 onMenuDeactivate: function() {
528 var menu = this.getMenu();
529 // TODO: remove coupling to Header's titleContainer el
530 menu.activeHeader.titleContainer.removeCls(this.headerOpenCls);
533 moveHeader: function(fromIdx, toIdx) {
535 // An automatically expiring lock
537 this.onHeaderMoved(this.move(fromIdx, toIdx), fromIdx, toIdx);
540 purgeCache: function() {
542 // Delete column cache - column order has changed.
543 delete me.gridDataColumns;
544 delete me.hideableColumns;
546 // Menu changes when columns are moved. It will be recreated.
553 onHeaderMoved: function(header, fromIdx, toIdx) {
555 gridSection = me.ownerCt;
557 if (gridSection && gridSection.onHeaderMove) {
558 gridSection.onHeaderMove(me, header, fromIdx, toIdx);
560 me.fireEvent("columnmove", me, header, fromIdx, toIdx);
563 <span id='Ext-grid-header-Container-method-getMenu'> /**
564 </span> * Gets the menu (and will create it if it doesn't already exist)
567 getMenu: function() {
571 me.menu = Ext.create('Ext.menu.Menu', {
572 hideOnParentHide: false, // Persists when owning ColumnHeader is hidden
573 items: me.getMenuItems(),
575 deactivate: me.onMenuDeactivate,
579 me.setDisabledItems();
580 me.fireEvent('menucreate', me, me.menu);
585 <span id='Ext-grid-header-Container-method-getMenuItems'> /**
586 </span> * Returns an array of menu items to be placed into the shared menu
587 * across all headers in this header container.
588 * @returns {Array} menuItems
590 getMenuItems: function() {
593 hideableColumns = me.enableColumnHide ? me.getColumnMenu(me) : null;
598 text: me.sortAscText,
599 cls: Ext.baseCSSPrefix + 'hmenu-sort-asc',
600 handler: me.onSortAscClick,
604 text: me.sortDescText,
605 cls: Ext.baseCSSPrefix + 'hmenu-sort-desc',
606 handler: me.onSortDescClick,
610 if (hideableColumns && hideableColumns.length) {
611 menuItems.push('-', {
612 itemId: 'columnItem',
613 text: me.columnsText,
614 cls: Ext.baseCSSPrefix + 'cols-icon',
615 menu: hideableColumns
621 // sort asc when clicking on item in menu
622 onSortAscClick: function() {
623 var menu = this.getMenu(),
624 activeHeader = menu.activeHeader;
626 activeHeader.setSortState('ASC');
629 // sort desc when clicking on item in menu
630 onSortDescClick: function() {
631 var menu = this.getMenu(),
632 activeHeader = menu.activeHeader;
634 activeHeader.setSortState('DESC');
637 <span id='Ext-grid-header-Container-method-getColumnMenu'> /**
638 </span> * Returns an array of menu CheckItems corresponding to all immediate children of the passed Container which have been configured as hideable.
640 getColumnMenu: function(headerContainer) {
644 items = headerContainer.query('>gridcolumn[hideable]'),
645 itemsLn = items.length,
648 for (; i < itemsLn; i++) {
650 menuItem = Ext.create('Ext.menu.CheckItem', {
652 checked: !item.hidden,
655 menu: item.isGroupHeader ? this.getColumnMenu(item) : undefined,
656 checkHandler: this.onColumnCheckChange,
660 menuItem.disabled = true;
662 menuItems.push(menuItem);
664 // If the header is ever destroyed - for instance by dragging out the last remaining sub header,
665 // then the associated menu item must also be destroyed.
667 destroy: Ext.Function.bind(menuItem.destroy, menuItem)
673 onColumnCheckChange: function(checkItem, checked) {
674 var header = Ext.getCmp(checkItem.headerId);
675 header[checked ? 'show' : 'hide']();
678 <span id='Ext-grid-header-Container-method-getColumnsForTpl'> /**
679 </span> * Get the columns used for generating a template via TableChunker.
680 * Returns an array of all columns and their
685 * - columnId - used to create an identifying CSS class
686 * - cls The tdCls configuration from the Column object
689 getColumnsForTpl: function(flushCache) {
691 headers = this.getGridColumns(flushCache),
692 headersLn = headers.length,
697 for (; i < headersLn; i++) {
700 if (header.hidden || header.up('headercontainer[hidden=true]')) {
703 width = header.getDesiredWidth();
705 // Setting the width of the first TD does not work - ends up with a 1 pixel discrepancy.
706 // We need to increment the passed with in this case.
707 if ((i === 0) && (Ext.isIE6 || Ext.isIE7)) {
712 dataIndex: header.dataIndex,
717 columnId: header.getItemId()
723 <span id='Ext-grid-header-Container-method-getColumnCount'> /**
724 </span> * Returns the number of <b>grid columns</b> descended from this HeaderContainer.
725 * Group Columns are HeaderContainers. All grid columns are returned, including hidden ones.
727 getColumnCount: function() {
728 return this.getGridColumns().length;
731 <span id='Ext-grid-header-Container-method-getFullWidth'> /**
732 </span> * Gets the full width of all columns that are visible.
734 getFullWidth: function(flushCache) {
736 headers = this.getVisibleGridColumns(flushCache),
737 headersLn = headers.length,
740 for (; i < headersLn; i++) {
741 if (!isNaN(headers[i].width)) {
742 // use headers getDesiredWidth if its there
743 if (headers[i].getDesiredWidth) {
744 fullWidth += headers[i].getDesiredWidth();
745 // if injected a diff cmp use getWidth
747 fullWidth += headers[i].getWidth();
754 // invoked internally by a header when not using triStateSorting
755 clearOtherSortStates: function(activeHeader) {
756 var headers = this.getGridColumns(),
757 headersLn = headers.length,
761 for (; i < headersLn; i++) {
762 if (headers[i] !== activeHeader) {
763 oldSortState = headers[i].sortState;
764 // unset the sortstate and dont recurse
765 headers[i].setSortState(null, true);
766 //if (!silent && oldSortState !== null) {
767 // this.fireEvent('sortchange', this, headers[i], null);
773 <span id='Ext-grid-header-Container-method-getVisibleGridColumns'> /**
774 </span> * Returns an array of the <b>visible</b> columns in the grid. This goes down to the lowest column header
775 * level, and does not return <i>grouped</i> headers which contain sub headers.
776 * @param {Boolean} refreshCache If omitted, the cached set of columns will be returned. Pass true to refresh the cache.
779 getVisibleGridColumns: function(refreshCache) {
780 return Ext.ComponentQuery.query(':not([hidden])', this.getGridColumns(refreshCache));
783 <span id='Ext-grid-header-Container-method-getGridColumns'> /**
784 </span> * Returns an array of all columns which map to Store fields. This goes down to the lowest column header
785 * level, and does not return <i>grouped</i> headers which contain sub headers.
786 * @param {Boolean} refreshCache If omitted, the cached set of columns will be returned. Pass true to refresh the cache.
789 getGridColumns: function(refreshCache) {
791 result = refreshCache ? null : me.gridDataColumns;
793 // Not already got the column cache, so collect the base columns
795 me.gridDataColumns = result = [];
796 me.cascade(function(c) {
797 if ((c !== me) && !c.isGroupHeader) {
806 <span id='Ext-grid-header-Container-method-getHideableColumns'> /**
808 * For use by column headers in determining whether there are any hideable columns when deciding whether or not
809 * the header menu should be disabled.
811 getHideableColumns: function(refreshCache) {
813 result = refreshCache ? null : me.hideableColumns;
816 result = me.hideableColumns = me.query('[hideable]');
821 <span id='Ext-grid-header-Container-method-getHeaderIndex'> /**
822 </span> * Get the index of a leaf level header regardless of what the nesting
825 getHeaderIndex: function(header) {
826 var columns = this.getGridColumns();
827 return Ext.Array.indexOf(columns, header);
830 <span id='Ext-grid-header-Container-method-getHeaderAtIndex'> /**
831 </span> * Get a leaf level header by index regardless of what the nesting
834 getHeaderAtIndex: function(index) {
835 var columns = this.getGridColumns();
836 return columns[index];
839 <span id='Ext-grid-header-Container-method-prepareData'> /**
840 </span> * Maps the record data to base it on the header id's.
841 * This correlates to the markup/template generated by
844 prepareData: function(data, rowIdx, record, view, panel) {
846 headers = this.gridDataColumns || this.getGridColumns(),
847 headersLn = headers.length,
856 for (; colIdx < headersLn; colIdx++) {
861 header = headers[colIdx];
862 headerId = header.id;
863 renderer = header.renderer;
864 value = data[header.dataIndex];
866 // When specifying a renderer as a string, it always resolves
867 // to Ext.util.Format
868 if (typeof renderer === "string") {
869 header.renderer = renderer = Ext.util.Format[renderer];
872 if (typeof renderer === "function") {
873 value = renderer.call(
874 header.scope || this.ownerCt,
876 // metadata per cell passing an obj by reference so that
877 // it can be manipulated inside the renderer
889 // This warning attribute is used by the compat layer
890 obj.cssWarning = true;
891 metaData.tdCls = metaData.css;
896 obj[headerId+'-modified'] = record.isModified(header.dataIndex) ? Ext.baseCSSPrefix + 'grid-dirty-cell' : '';
897 obj[headerId+'-tdCls'] = metaData.tdCls;
898 obj[headerId+'-tdAttr'] = metaData.tdAttr;
899 obj[headerId+'-style'] = metaData.style;
900 if (value === undefined || value === null || value === '') {
901 value = '&#160;';
903 obj[headerId] = value;
908 expandToFit: function(header) {
910 this.view.expandToFit(header);