]> git.proxmox.com Git - extjs.git/blob - extjs/classic/classic/src/grid/header/Container.js
add extjs 6.0.1 sources
[extjs.git] / extjs / classic / classic / src / grid / header / Container.js
1 /**
2 * Headercontainer is a docked container (_`top` or `bottom` only_) that holds the
3 * headers ({@link Ext.grid.column.Column grid columns}) of a
4 * {@link Ext.grid.Panel grid} or {@link Ext.tree.Panel tree}. The headercontainer
5 * handles resizing, moving, and hiding columns. As columns are hidden, moved or
6 * resized, the headercontainer triggers changes within the grid or tree's
7 * {@link Ext.view.Table view}. You will not generally need to instantiate this class
8 * directly.
9 *
10 * You may use the
11 * {@link Ext.panel.Table#method-getHeaderContainer getHeaderContainer()}
12 * accessor method to access the tree or grid's headercontainer.
13 *
14 * Grids and trees also have an alias to the two more useful headercontainer methods:
15 *
16 * - **{@link Ext.panel.Table#method-getColumns getColumns}** - aliases
17 * {@link Ext.grid.header.Container#getGridColumns}
18 * - **{@link Ext.panel.Table#method-getVisibleColumns getVisibleColumns}** - aliases
19 * {@link Ext.grid.header.Container#getVisibleGridColumns}
20 */
21 Ext.define('Ext.grid.header.Container', {
22 extend: 'Ext.container.Container',
23 requires: [
24 'Ext.grid.ColumnLayout',
25 'Ext.grid.plugin.HeaderResizer',
26 'Ext.grid.plugin.HeaderReorderer',
27 'Ext.util.KeyNav'
28 ],
29 uses: [
30 'Ext.grid.column.Column',
31 'Ext.grid.ColumnManager',
32 'Ext.menu.Menu',
33 'Ext.menu.CheckItem',
34 'Ext.menu.Separator'
35 ],
36
37 mixins: [
38 'Ext.util.FocusableContainer'
39 ],
40
41 border: true,
42
43 alias: 'widget.headercontainer',
44
45 baseCls: Ext.baseCSSPrefix + 'grid-header-ct',
46
47 dock: 'top',
48
49 /**
50 * @cfg {Number} weight
51 * HeaderContainer overrides the default weight of 0 for all docked items to 100.
52 * This is so that it has more priority over things like toolbars.
53 */
54 weight: 100,
55
56 defaultType: 'gridcolumn',
57
58 detachOnRemove: false,
59
60 /**
61 * @cfg {Number} defaultWidth
62 * Width of the header if no width or flex is specified.
63 */
64 defaultWidth: 100,
65
66 /**
67 * @cfg {Boolean} [sealed=false]
68 * Specify as `true` to constrain column dragging so that a column cannot be dragged into or out of this column.
69 *
70 * **Note that this config is only valid for column headers which contain child column headers, eg:**
71 * {
72 * sealed: true
73 * text: 'ExtJS',
74 * columns: [{
75 * text: '3.0.4',
76 * dataIndex: 'ext304'
77 * }, {
78 * text: '4.1.0',
79 * dataIndex: 'ext410'
80 * }
81 * }
82 *
83 */
84
85 //<locale>
86 sortAscText: 'Sort Ascending',
87 //</locale>
88 //<locale>
89 sortDescText: 'Sort Descending',
90 //</locale>
91 //<locale>
92 sortClearText: 'Clear Sort',
93 //</locale>
94 //<locale>
95 columnsText: 'Columns',
96 //</locale>
97
98 headerOpenCls: Ext.baseCSSPrefix + 'column-header-open',
99
100 menuSortAscCls: Ext.baseCSSPrefix + 'hmenu-sort-asc',
101
102 menuSortDescCls: Ext.baseCSSPrefix + 'hmenu-sort-desc',
103
104 menuColsIcon: Ext.baseCSSPrefix + 'cols-icon',
105
106 blockEvents: false,
107
108 dragging: false,
109
110 // May be set to false by a SptreadSheetSelectionModel
111 sortOnClick: true,
112
113 // Disable FocusableContainer behavior by default, since we only want it
114 // to be enabled for the root header container (we'll set the flag in initComponent)
115 enableFocusableContainer: false,
116
117 childHideCount: 0,
118
119 /**
120 * @property {Boolean} isGroupHeader
121 * True if this HeaderContainer is in fact a group header which contains sub headers.
122 */
123
124 /**
125 * @cfg {Boolean} sortable
126 * Provides the default sortable state for all Headers within this HeaderContainer.
127 * Also turns on or off the menus in the HeaderContainer. Note that the menu is
128 * shared across every header and therefore turning it off will remove the menu
129 * items for every header.
130 */
131 sortable: true,
132
133 /**
134 * @cfg {Boolean} [enableColumnHide=true]
135 * False to disable column hiding within this grid.
136 */
137 enableColumnHide: true,
138
139 /**
140 * @event columnresize
141 * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
142 * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
143 * @param {Number} width
144 */
145
146 /**
147 * @event headerclick
148 * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
149 * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
150 * @param {Ext.event.Event} e
151 * @param {HTMLElement} t
152 */
153
154 /**
155 * @event headercontextmenu
156 * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
157 * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
158 * @param {Ext.event.Event} e
159 * @param {HTMLElement} t
160 */
161
162 /**
163 * @event headertriggerclick
164 * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
165 * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
166 * @param {Ext.event.Event} e
167 * @param {HTMLElement} t
168 */
169
170 /**
171 * @event columnmove
172 * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
173 * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
174 * @param {Number} fromIdx
175 * @param {Number} toIdx
176 */
177
178 /**
179 * @event columnhide
180 * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
181 * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
182 */
183
184 /**
185 * @event columnshow
186 * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
187 * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
188 */
189
190 /**
191 * @event columnschanged
192 * Fired after the columns change in any way, when a column has been hidden or shown, or when a column
193 * is added to or removed from this header container.
194 * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
195 */
196
197 /**
198 * @event sortchange
199 * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
200 * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
201 * @param {String} direction
202 */
203
204 /**
205 * @event menucreate
206 * Fired immediately after the column header menu is created.
207 * @param {Ext.grid.header.Container} ct This instance
208 * @param {Ext.menu.Menu} menu The Menu that was created
209 */
210
211 /**
212 * @event headermenucreate
213 * Fired immediately after the column header menu is created.
214 * @param {Ext.panel.Table} grid This grid instance
215 * @param {Ext.menu.Menu} menu The Menu that was created
216 * @param {Ext.grid.header.Container} headerCt This header container
217 * @member Ext.panel.Table
218 */
219
220 initComponent: function() {
221 var me = this;
222
223 me.plugins = me.plugins || [];
224 me.defaults = me.defaults || {};
225
226 // TODO: Pass in configurations to turn on/off dynamic
227 // resizing and disable resizing all together
228
229 // Only set up a Resizer and Reorderer for the topmost HeaderContainer.
230 // Nested Group Headers are themselves HeaderContainers
231 if (!me.isColumn) {
232 if (me.enableColumnResize) {
233 me.resizer = new Ext.grid.plugin.HeaderResizer();
234 me.plugins.push(me.resizer);
235 }
236 if (me.enableColumnMove) {
237 me.reorderer = new Ext.grid.plugin.HeaderReorderer();
238 me.plugins.push(me.reorderer);
239 }
240 }
241
242 // If this is a leaf column header, and is NOT functioning as a container,
243 // use Container layout with a no-op calculate method.
244 if (me.isColumn && !me.isGroupHeader) {
245 if (!me.items || me.items.length === 0) {
246 me.isContainer = me.isFocusableContainer = false;
247 me.focusable = true;
248 me.layout = {
249 type: 'container',
250 calculate: Ext.emptyFn
251 };
252 }
253 }
254 // HeaderContainer and Group header needs a gridcolumn layout.
255 else {
256 me.layout = Ext.apply({
257 type: 'gridcolumn',
258 align: 'stretch'
259 }, me.initialConfig.layout);
260
261 // All HeaderContainers need to know this so that leaf Columns can adjust for cell border width if using content box model
262 me.defaults.columnLines = me.columnLines;
263
264 // If the header isn't a column ([isColumn] or [isGroupHeader]), then it's the root header.
265 if (!me.isGroupHeader) {
266 me.isRootHeader = true;
267
268 // The root header is a focusableContainer if it's not carrying hidden headers.
269 if (!me.hiddenHeaders) {
270 me.enableFocusableContainer = true;
271 me.ariaRole = 'rowgroup';
272 }
273
274 // Create column managers for the root header.
275 me.columnManager = new Ext.grid.ColumnManager(false, me);
276 me.visibleColumnManager = new Ext.grid.ColumnManager(true, me);
277
278 // In the grid config, if grid.columns is a header container instance and not a columns
279 // config, then it currently has no knowledge of a containing grid. Create the column
280 // manager now and bind it to the grid later in Ext.panel.Table:initComponent().
281 //
282 // In most cases, though, grid.columns will be a config, so the grid is already known
283 // and the column manager can be bound to it.
284 if (me.grid) {
285 me.grid.columnManager = me.columnManager;
286 me.grid.visibleColumnManager = me.visibleColumnManager;
287 }
288 } else {
289 // Is a group header, also create column managers.
290 me.visibleColumnManager = new Ext.grid.ColumnManager(true, me);
291 me.columnManager = new Ext.grid.ColumnManager(false, me);
292 }
293 }
294
295 me.menuTask = new Ext.util.DelayedTask(me.updateMenuDisabledState, me);
296 me.callParent();
297 },
298
299 insertNestedHeader: function (moveHeader) {
300 var me = this,
301 fromCt = moveHeader.ownerCt,
302 toCt = me.ownerCt,
303 layoutOwner = toCt.layout.owner,
304 toIndex;
305
306 if (fromCt) {
307 if (me.isGroupHeader && !toCt.isNestedParent) {
308 toIndex = layoutOwner.items.indexOf(me);
309 }
310
311 fromCt.remove(moveHeader, false);
312 }
313
314 if (toIndex === undefined) {
315 toIndex = layoutOwner.items.indexOf(me);
316 }
317
318 layoutOwner.insert(toIndex, moveHeader);
319 },
320
321 isNested: function () {
322 return !!this.getRootHeaderCt().down('[isNestedParent]');
323 },
324
325 isNestedGroupHeader: function () {
326 // The owner only has one item that isn't hidden and it's me; hide the owner.
327 var header = this,
328 items = header.getRefOwner().query('>:not([hidden])');
329
330 return (items.length === 1 && items[0] === header);
331 },
332
333 maybeShowNestedGroupHeader: function () {
334 // Group headers are special in that they are auto-hidden when their subheaders are all
335 // hidden and auto-shown when the first subheader is reshown. They are the only headers
336 // that should now be auto-shown or -hidden.
337 //
338 // It follows that since group headers are dictated by some automation depending upon the
339 // state of their child items that all group headers should be shown if anyone in the
340 // hierarchy is shown since these special group headers only contain one child, which is
341 // the next group header in the stack.
342 // This only should apply to the following grouped header scenario:
343 //
344 // +-----------------------------------+
345 // | Group 1 |
346 // |-----------------------------------|
347 // | Group 2 |
348 // other |-----------------------------------| other
349 // headers | Group 3 | headers
350 // |-----------------------------------|
351 // | Field3 | Field4 | Field5 | Field6 |
352 // |===================================|
353 // | view |
354 // +-----------------------------------+
355 //
356 var items = this.items,
357 item;
358
359 if (items && items.length === 1 && (item = items.getAt(0)) && item.hidden) {
360 item.show();
361 }
362 },
363
364 setNestedParent: function (target) {
365 // Here we need to prevent the removal of ancestor group headers from occuring if a flag is set. This
366 // is needed when there are stacked group headers and only the deepest nested group header has leaf items
367 // in its collection. In this specific scenario, the group headers above it only have 1 item, which is its
368 // child nested group header.
369 //
370 // If we don't set this flag, then all of the grouped headers will be recursively removed all the way up to
371 // the root container b/c Ext.grid.header.Container#onRemove will remove all containers that don't contain
372 // any items.
373 //
374 // Note that if an ownerCt only has one item, then we know that this item is the group header that we're
375 // currently dragging.
376 //
377 // Also, note that we mark the owner as the target header because everything up to that should be removed.
378 //
379 // We have to reset any previous headers that may have been target.ownerCts!
380 target.isNestedParent = false;
381 target.ownerCt.isNestedParent = !!(this.ownerCt.items.length === 1 && target.ownerCt.items.length === 1);
382 },
383
384 initEvents: function() {
385 var me = this,
386 onHeaderCtEvent,
387 listeners;
388
389 me.callParent();
390
391 // If this is top level, listen for events to delegate to descendant headers.
392 if (!me.isColumn && !me.isGroupHeader) {
393 onHeaderCtEvent = me.onHeaderCtEvent;
394 listeners = {
395 click: onHeaderCtEvent,
396 dblclick: onHeaderCtEvent,
397 contextmenu: onHeaderCtEvent,
398 mouseover: me.onHeaderCtMouseOver,
399 mouseout: me.onHeaderCtMouseOut,
400 scope: me
401 };
402
403 if (Ext.supports.Touch) {
404 listeners.longpress = me.onHeaderCtLongPress;
405 }
406 me.mon(me.el, listeners);
407 }
408 },
409
410 onHeaderCtEvent: function(e, t) {
411 var me = this,
412 headerEl = me.getHeaderElByEvent(e),
413 header,
414 targetEl,
415 activeHeader;
416
417 if (me.longPressFired) {
418 // if we just showed the menu as a result of a longpress, do not process
419 // the click event and sort the column.
420 me.longPressFired = false;
421 return;
422 }
423
424 if (headerEl && !me.blockEvents) {
425 header = Ext.getCmp(headerEl.id);
426 if (header) {
427 targetEl = header[header.clickTargetName];
428 // If there's no possibility that the mouseEvent was on child header items,
429 // or it was definitely in our titleEl, then process it
430 if ((!header.isGroupHeader && !header.isContainer) || e.within(targetEl)) {
431 if (e.type === 'click' || e.type === 'tap') {
432 // The header decides which header to activate on click
433 // on Touch, anywhere in the splitter zone activates
434 // the left header.
435 activeHeader = header.onTitleElClick(e, targetEl, me.sortOnClick);
436 if (activeHeader) {
437 // If activated by touch, there is no trigger el to align with, so align to the header element.
438 me.onHeaderTriggerClick(activeHeader, e, e.pointerType === 'touch' ? activeHeader.el : activeHeader.triggerEl);
439 } else {
440 me.onHeaderClick(header, e, t);
441 }
442 }
443 else if (e.type === 'contextmenu') {
444 me.onHeaderContextMenu(header, e, t);
445 } else if (e.type === 'dblclick' && header.resizable) {
446 header.onTitleElDblClick(e, targetEl.dom);
447 }
448 }
449 }
450 }
451 },
452
453 blockNextEvent: function() {
454 this.blockEvents = true;
455 Ext.asap(this.unblockEvents, this);
456 },
457
458 unblockEvents: function() {
459 this.blockEvents = false;
460 },
461
462 onHeaderCtMouseOver: function(e, t) {
463 var headerEl,
464 header,
465 targetEl;
466
467 // Only proces the mouse entering this HeaderContainer.
468 // From header to header, and exiting this HeaderContainer we track using mouseout events.
469 if (!e.within(this.el, true)) {
470 headerEl = e.getTarget('.' + Ext.grid.column.Column.prototype.baseCls);
471 header = headerEl && Ext.getCmp(headerEl.id);
472 if (header) {
473 targetEl = header[header.clickTargetName];
474 if (e.within(targetEl)) {
475 header.onTitleMouseOver(e, targetEl.dom);
476 }
477 }
478 }
479 },
480
481 onHeaderCtMouseOut: function(e, t) {
482 var headerSelector = '.' + Ext.grid.column.Column.prototype.baseCls,
483 outHeaderEl = e.getTarget(headerSelector),
484 inHeaderEl = e.getRelatedTarget(headerSelector),
485 header,
486 targetEl;
487
488 // It's a mouseenter/leave, not an internal element change within a Header
489 if (outHeaderEl !== inHeaderEl) {
490 if (outHeaderEl) {
491 header = Ext.getCmp(outHeaderEl.id);
492 if (header) {
493 targetEl = header[header.clickTargetName];
494 header.onTitleMouseOut(e, targetEl.dom);
495 }
496 }
497 if (inHeaderEl) {
498 header = Ext.getCmp(inHeaderEl.id);
499 if (header) {
500 targetEl = header[header.clickTargetName];
501 header.onTitleMouseOver(e, targetEl.dom);
502 }
503 }
504 }
505 },
506
507 onHeaderCtLongPress: function(e) {
508 var me = this,
509 headerEl = me.getHeaderElByEvent(e),
510 header = Ext.getCmp(headerEl.id);
511
512 if (!header.menuDisabled) {
513 me.longPressFired = true;
514 me.showMenuBy(e, headerEl, header);
515 }
516 },
517
518 getHeaderElByEvent: function(e) {
519 return e.getTarget('.' + Ext.grid.column.Column.prototype.baseCls);
520 },
521
522 isLayoutRoot: function(){
523 // Since we're docked, the width is always calculated
524 // If we're hidden, the height is explicitly 0, which
525 // means we'll be considered a layout root. However, we
526 // still need the view to layout to update the underlying
527 // table to match the size.
528 if (this.hiddenHeaders) {
529 return false;
530 }
531 return this.callParent();
532 },
533
534 // Find the topmost HeaderContainer
535 getRootHeaderCt: function() {
536 var me = this;
537 return me.isRootHeader ? me : me.up('[isRootHeader]');
538 },
539
540 onDestroy: function() {
541 var me = this;
542
543 if (me.menu) {
544 me.menu.un('hide', me.onMenuHide, me);
545 }
546 me.menuTask.cancel();
547 me.callParent();
548 Ext.destroy(me.visibleColumnManager, me.columnManager, me.menu);
549 me.columnManager = me.visibleColumnManager = null;
550 },
551
552 applyColumnsState: function(columns, storeState) {
553 if (!columns || !columns.length) {
554 return;
555 }
556
557 var me = this,
558 items = me.items.items,
559 count = items.length,
560 i = 0,
561 length = columns.length,
562 c, col, columnState, index,
563 moved = false,
564 newOrder = [],
565 stateHash = {},
566 newCols = [];
567
568 // Create state lookup hash
569 // {
570 // col_name: {
571 // index: 0,
572 // width: 100
573 // },
574 // col_email: {
575 // index: 1,
576 // width: 100
577 // }
578 // }
579 for (c = 0; c < length; c++) {
580 columnState = columns[c];
581 columnState.index = c;
582 stateHash[columnState.id] = columnState;
583 }
584
585 for (i = 0; i < count; i++) {
586 col = items[i];
587 columnState = stateHash[col.getStateId()];
588
589 // There's a column state for this column.
590 // Add it to the newOrder array at the specified index
591 if (columnState) {
592 index = columnState.index;
593 newOrder[index] = col;
594 if (i !== index) {
595 moved = true;
596 }
597
598 if (col.applyColumnState) {
599 col.applyColumnState(columnState, storeState);
600 }
601 }
602 // A new column.
603 // It must be inserted at this index after state restoration,
604 else {
605 newCols.push({
606 index: i,
607 column: col
608 });
609 }
610 }
611
612 // If any saved columns were missing, close the gaps where they were
613 newOrder = Ext.Array.clean(newOrder);
614
615 // New column encountered.
616 // Insert them into the newOrder at their configured position
617 length = newCols.length;
618 if (length) {
619 for (i = 0; i < length; i++) {
620 columnState = newCols[i];
621 index = columnState.index;
622 if (index < newOrder.length) {
623 moved = true;
624 Ext.Array.splice(newOrder, index, 0, columnState.column);
625 } else {
626 newOrder.push(columnState.column);
627 }
628 }
629 }
630
631 if (moved) {
632 // This flag will prevent the groupheader from being removed by its owner when it (temporarily) has no child items.
633 me.applyingState = true;
634 me.removeAll(false);
635 delete me.applyingState;
636
637 me.add(newOrder);
638 me.purgeCache();
639 }
640 },
641
642 getColumnsState: function () {
643 var me = this,
644 columns = [],
645 state;
646
647 me.items.each(function (col) {
648 state = col.getColumnState && col.getColumnState();
649 if (state) {
650 columns.push(state);
651 }
652 });
653
654 return columns;
655 },
656
657 // Invalidate column cache on add
658 // We cannot refresh the View on every add because this method is called
659 // when the HeaderDropZone moves Headers around, that will also refresh the view
660 onAdd: function(c) {
661 var me = this;
662
663 //<debug>
664 if (!me._usedIDs) {
665 me._usedIDs = {};
666 }
667 if (me._usedIDs[c.headerId]) {
668 Ext.log.warn(this.$className + ' attempted to reuse an existing id: ' + c.headerId);
669 }
670 me._usedIDs[c.headerId] = true;
671 //</debug>
672
673 me.callParent(arguments);
674
675 me.onHeadersChanged(c, me.isDDMoveInGrid);
676 },
677
678 move: function(fromIdx, toIdx) {
679 var me = this,
680 items = me.items,
681 headerToMove;
682
683 if (fromIdx.isComponent) {
684 headerToMove = fromIdx;
685 fromIdx = items.indexOf(headerToMove);
686 } else {
687 headerToMove = items.getAt(fromIdx);
688 }
689
690 // Take real grid column index of column being moved
691 headerToMove.visibleFromIdx = me.getRootHeaderCt().visibleColumnManager.indexOf(headerToMove);
692
693 me.callParent(arguments);
694 },
695
696 onMove: function(headerToMove, fromIdx, toIdx) {
697 var me = this,
698 gridHeaderCt = me.getRootHeaderCt(),
699 gridVisibleColumnManager = gridHeaderCt.visibleColumnManager,
700 numColsToMove = 1,
701 visibleToIdx;
702
703 // Purges cache so that indexOf returns new position of header
704 me.onHeadersChanged(headerToMove, true);
705
706 visibleToIdx = gridVisibleColumnManager.indexOf(headerToMove);
707 if (visibleToIdx >= headerToMove.visibleFromIdx) {
708 visibleToIdx++;
709 }
710
711 me.callParent(arguments);
712
713 // If what is being moved is a group header, then pass the correct column count
714 if (headerToMove.isGroupHeader) {
715 numColsToMove = headerToMove.visibleColumnManager.getColumns().length;
716 }
717
718 gridHeaderCt.onHeaderMoved(headerToMove, numColsToMove, headerToMove.visibleFromIdx, visibleToIdx);
719 },
720
721 // @private
722 maybeContinueRemove: function () {
723 var me = this;
724
725 // Note that if the column is a group header and is the current target of a drag, we don't want to remove it
726 // if it since it could be one of any number of (empty) nested group headers.
727 // See #isNested.
728 //
729 // There are also other scenarios in which the remove should not occur. For instance, when applying column
730 // state to a groupheader, the subheaders are all removed before being re-added in their stateful order,
731 // and the groupheader should not be removed in the meantime.
732 // See EXTJS-17577.
733 return (me.isGroupHeader && !me.applyingState) && !me.isNestedParent && me.ownerCt && !me.items.getCount();
734 },
735
736 // Invalidate column cache on remove
737 // We cannot refresh the View on every remove because this method is called
738 // when the HeaderDropZone moves Headers around, that will also refresh the view
739 onRemove: function(c, isDestroying) {
740 var me = this,
741 ownerCt = me.ownerCt,
742 lastHiddenHeader = c.lastHiddenHeader;
743
744 me.callParent([c, isDestroying]);
745
746 //<debug>
747 if (!me._usedIDs) {
748 me._usedIDs = {};
749 }
750 delete me._usedIDs[c.headerId];
751 //</debug>
752
753 if (!me.destroying) {
754 // isDDMoveInGrid flag set by Ext.grid.header.DropZone when moving into another container *within the same grid*.
755 // This stops header change processing from being executed twice, once on remove and then on the subsequent add.
756 if (!me.isDDMoveInGrid) {
757 me.onHeadersChanged(c, false);
758 }
759
760 if (me.maybeContinueRemove()) {
761 // Detach the header from the DOM here. Since we're removing and destroying the container,
762 // the inner DOM may get overwritten, since Container::deatchOnRemove gets processed after
763 // onRemove.
764 if (c.rendered) {
765 me.detachComponent(c);
766 }
767 // If we don't have any items left and we're a group, remove ourselves.
768 // This will cascade up if necessary
769 Ext.suspendLayouts();
770 ownerCt.remove(me);
771 Ext.resumeLayouts(true);
772 }
773 }
774 },
775
776 // Private
777 // Called to clear all caches of columns whenever columns are added, removed to just moved.
778 // We need to be informed if it's just a move operation so that we don't call the heavier
779 // grid.onHeadersChanged which refreshes the view.
780 // The onMove handler ensures that grid.inHeaderMove is called which just swaps cells.
781 onHeadersChanged: function(c, isMove) {
782 var gridPanel,
783 gridHeaderCt = this.getRootHeaderCt();
784
785 // Each HeaderContainer up the chain must have its cache purged so that its getGridColumns method will return correct results.
786 this.purgeHeaderCtCache(this);
787
788 if (gridHeaderCt) {
789 gridHeaderCt.onColumnsChanged();
790 if (!c.isGroupHeader) {
791 gridPanel = gridHeaderCt.ownerCt;
792
793 // If it an add or remove operation causing this header change call, then inform the grid which refreshes.
794 // Moving calls the onHeaderMoved method of the grid which just swaps cells.
795 if (gridPanel && !isMove) {
796 gridPanel.onHeadersChanged(gridHeaderCt, c);
797 }
798 }
799 }
800 },
801
802 // Private
803 onHeaderMoved: function(header, colsToMove, fromIdx, toIdx) {
804 var me = this,
805 gridSection = me.ownerCt;
806
807 if (me.rendered) {
808 if (gridSection && gridSection.onHeaderMove) {
809 gridSection.onHeaderMove(me, header, colsToMove, fromIdx, toIdx);
810 }
811 me.fireEvent('columnmove', me, header, fromIdx, toIdx);
812 }
813 },
814
815 // Private
816 // Only called on the grid's headerCt.
817 // Called whenever a column is added or removed or moved at any level below.
818 // Ensures that the gridColumns caches are cleared.
819 onColumnsChanged: function() {
820 var me = this,
821 menu = me.menu,
822 columnItemSeparator,
823 columnItem;
824
825 if (me.rendered) {
826 me.fireEvent('columnschanged', me);
827
828 // Column item (and its associated menu) menu has to be destroyed (if it exits) when columns are changed.
829 // It will be recreated just before the main container menu is next shown.
830 if (menu && (columnItemSeparator = menu.child('#columnItemSeparator'))) {
831 columnItem = menu.child('#columnItem');
832
833 // Destroy the column visibility items
834 // They will be recreated before the next show
835 columnItemSeparator.destroy();
836 columnItem.destroy();
837 }
838 }
839 },
840
841 /**
842 * @private
843 */
844 lookupComponent: function(comp) {
845 var result = this.callParent(arguments);
846
847 // Apply default width unless it's a group header (in which case it must be left to shrinkwrap), or it's flexed.
848 // Test whether width is undefined so that width: null can be used to have the header shrinkwrap its text.
849 if (!result.isGroupHeader && result.width === undefined && !result.flex) {
850 result.width = this.defaultWidth;
851 }
852 return result;
853 },
854
855 /**
856 * @private
857 * Synchronize column UI visible sort state with Store's sorters.
858 */
859 setSortState: function() {
860 var store = this.up('[store]').store,
861 columns = this.visibleColumnManager.getColumns(),
862 len = columns.length, i,
863 header, sorter;
864
865 for (i = 0; i < len; i++) {
866 header = columns[i];
867
868 // Access the column's custom sorter in preference to one keyed on the data index.
869 sorter = header.getSorter();
870 if (sorter) {
871 // If the column was configured with a sorter, we must check that the sorter
872 // is part of the store's sorter collection to update the UI to the correct state.
873 // The store may not actually BE sorted by that sorter.
874 if (!store.getSorters().contains(sorter)) {
875 sorter = null;
876 }
877 } else {
878 sorter = store.getSorters().get(header.getSortParam());
879 }
880
881 // Important: A null sorter for this column will *clear* the UI sort indicator.
882 header.setSortState(sorter);
883 }
884 },
885
886 getHeaderMenu: function(){
887 var menu = this.getMenu(),
888 item;
889
890 if (menu) {
891 item = menu.child('#columnItem');
892 if (item) {
893 return item.menu;
894 }
895 }
896 return null;
897 },
898
899 onHeaderVisibilityChange: function(header, visible){
900 var me = this,
901 menu = me.getHeaderMenu(),
902 item;
903
904 // Invalidate column collections upon column hide/show
905 me.purgeHeaderCtCache(header.ownerCt);
906
907 if (menu) {
908 // If the header was hidden programmatically, sync the Menu state
909 item = me.getMenuItemForHeader(menu, header);
910 if (item) {
911 item.setChecked(visible, true);
912 }
913 // delay this since the headers may fire a number of times if we're hiding/showing groups
914 if (menu.isVisible()) {
915 me.menuTask.delay(50);
916 }
917 }
918 },
919
920 updateMenuDisabledState: function(menu) {
921 var me = this,
922 columns = me.query('gridcolumn:not([hidden])'),
923 i,
924 len = columns.length,
925 item,
926 checkItem,
927 method;
928
929 // If called from menu creation, it will be passed to avoid infinite recursion
930 if (!menu) {
931 menu = me.getMenu();
932 }
933
934 for (i = 0; i < len; ++i) {
935 item = columns[i];
936 checkItem = me.getMenuItemForHeader(menu, item);
937 if (checkItem) {
938 method = item.isHideable() ? 'enable' : 'disable';
939 if (checkItem.menu) {
940 method += 'CheckChange';
941 }
942 checkItem[method]();
943 }
944 }
945 },
946
947 getMenuItemForHeader: function(menu, header) {
948 return header ? menu.down('menucheckitem[headerId=' + header.id + ']') : null;
949 },
950
951 onHeaderShow: function (header) {
952 var me = this,
953 ownerCt = me.ownerCt,
954 lastHiddenHeader = header.lastHiddenHeader;
955
956 if (!ownerCt) {
957 return;
958 }
959
960 if (me.forceFit) {
961 delete me.flex;
962 }
963
964 // If lastHiddenHeader exists we know that header is a groupHeader and if all its subheaders
965 // are hidden then we need to show the last one that was hidden.
966 if (lastHiddenHeader && !header.query('[hidden=false]').length) {
967 lastHiddenHeader.show();
968 header.lastHiddenHeader = null;
969 }
970
971 me.onHeaderVisibilityChange(header, true);
972 ownerCt.onHeaderShow(me, header);
973
974 me.fireEvent('columnshow', me, header);
975 me.fireEvent('columnschanged', this);
976 },
977
978 onHeaderHide: function (header) {
979 var me = this,
980 ownerCt = me.ownerCt;
981
982 if (!ownerCt) {
983 return;
984 }
985
986 me.onHeaderVisibilityChange(header, false);
987 ownerCt.onHeaderHide(me, header);
988
989 me.fireEvent('columnhide', me, header);
990 me.fireEvent('columnschanged', this);
991 },
992
993 onHeaderResize: function(header, w) {
994 var me = this,
995 gridSection = me.ownerCt;
996
997 if (gridSection) {
998 gridSection.onHeaderResize(me, header, w);
999 }
1000 me.fireEvent('columnresize', me, header, w);
1001 },
1002
1003 onHeaderClick: function(header, e, t) {
1004 var me = this,
1005 selModel = header.getView().getSelectionModel();
1006
1007 header.fireEvent('headerclick', me, header, e, t);
1008 if (me.fireEvent('headerclick', me, header, e, t) !== false) {
1009 if (selModel.onHeaderClick) {
1010 selModel.onHeaderClick(me, header, e);
1011 }
1012 }
1013 },
1014
1015 onHeaderContextMenu: function(header, e, t) {
1016 header.fireEvent('headercontextmenu', this, header, e, t);
1017 this.fireEvent('headercontextmenu', this, header, e, t);
1018 },
1019
1020 onHeaderTriggerClick: function(header, e, t) {
1021 var me = this;
1022 if (header.fireEvent('headertriggerclick', me, header, e, t) !== false && me.fireEvent('headertriggerclick', me, header, e, t) !== false) {
1023
1024 // If menu is already active...
1025 if (header.activeMenu) {
1026 // Click/tap toggles the menu visibility.
1027 if (e.pointerType) {
1028 header.activeMenu.hide();
1029 } else {
1030 header.activeMenu.focus();
1031 }
1032 }
1033 else {
1034 me.showMenuBy(e, t, header);
1035 }
1036 }
1037 },
1038
1039 /**
1040 * @private
1041 *
1042 * Shows the column menu under the target element passed. This method is used when the trigger element on the column
1043 * header is clicked on and rarely should be used otherwise.
1044 *
1045 * @param {Ext.event.Event} [event] The event which triggered the current handler. If omitted
1046 * or a key event, the menu autofocuses its first item.
1047 * @param {HTMLElement/Ext.dom.Element} t The target to show the menu by
1048 * @param {Ext.grid.header.Container} header The header container that the trigger was clicked on.
1049 */
1050 showMenuBy: function(clickEvent, t, header) {
1051 var menu = this.getMenu(),
1052 ascItem = menu.down('#ascItem'),
1053 descItem = menu.down('#descItem'),
1054 sortableMth,
1055 isTouch = clickEvent && clickEvent.pointerType === 'touch';
1056
1057 // Use ownerCmp as the upward link. Menus *must have no ownerCt* - they are global floaters.
1058 // Upward navigation is done using the up() method.
1059 menu.activeHeader = menu.ownerCmp = header;
1060 header.setMenuActive(menu);
1061
1062 // enable or disable asc & desc menu items based on header being sortable
1063 sortableMth = header.sortable ? 'enable' : 'disable';
1064 if (ascItem) {
1065 ascItem[sortableMth]();
1066 }
1067 if (descItem) {
1068 descItem[sortableMth]();
1069 }
1070
1071 // Pointer-invoked menus do not auto focus, key invoked ones do.
1072 menu.autoFocus = !clickEvent || clickEvent.keyCode;
1073
1074 // For longpress t is the header, for click/hover t is the trigger
1075 menu.showBy(t, 'tl-bl?');
1076
1077 // Menu show was vetoed by event handler - clear context
1078 if (!menu.isVisible()) {
1079 this.onMenuHide(menu);
1080 }
1081 },
1082
1083 hideMenu: function() {
1084 if (this.menu) {
1085 this.menu.hide();
1086 }
1087 },
1088
1089 // remove the trigger open class when the menu is hidden
1090 onMenuHide: function(menu) {
1091 menu.activeHeader.setMenuActive(false);
1092 },
1093
1094 purgeHeaderCtCache: function (headerCt) {
1095 while (headerCt) {
1096 headerCt.purgeCache();
1097 if (headerCt.isRootHeader) {
1098 return;
1099 }
1100 headerCt = headerCt.ownerCt;
1101 }
1102 },
1103
1104 purgeCache: function() {
1105 var me = this,
1106 visibleColumnManager = me.visibleColumnManager,
1107 columnManager = me.columnManager;
1108
1109 // Delete column cache - column order has changed.
1110 me.gridVisibleColumns = me.gridDataColumns = me.hideableColumns = null;
1111
1112 // ColumnManager. Only the top
1113 if (visibleColumnManager) {
1114 visibleColumnManager.invalidate();
1115 columnManager.invalidate();
1116 }
1117 },
1118
1119 /**
1120 * Gets the menu (and will create it if it doesn't already exist)
1121 * @private
1122 */
1123 getMenu: function() {
1124 var me = this,
1125 grid = me.view && me.view.ownerGrid;
1126
1127 if (!me.menu) {
1128 me.menu = new Ext.menu.Menu({
1129 hideOnParentHide: false, // Persists when owning ColumnHeader is hidden
1130 items: me.getMenuItems(),
1131 listeners: {
1132 beforeshow: me.beforeMenuShow,
1133 hide: me.onMenuHide,
1134 scope: me
1135 }
1136 });
1137 me.fireEvent('menucreate', me, me.menu);
1138 if (grid) {
1139 grid.fireEvent('headermenucreate', grid, me.menu, me);
1140 }
1141 }
1142 return me.menu;
1143 },
1144
1145 // Render our menus to the first enclosing scrolling element so that they scroll with the grid
1146 beforeMenuShow: function(menu) {
1147 var me = this,
1148 columnItem = menu.child('#columnItem'),
1149 hideableColumns,
1150 insertPoint;
1151
1152 // If a change of column structure caused destruction of the column menu item
1153 // or the main menu was created without the column menu item because it began with no hideable headers
1154 // Then create it and its menu now.
1155 if (!columnItem) {
1156 hideableColumns = me.enableColumnHide ? me.getColumnMenu(me) : null;
1157
1158 // Insert after the "Sort Ascending", "Sort Descending" menu items if they are present.
1159 insertPoint = me.sortable ? 2 : 0;
1160
1161 if (hideableColumns && hideableColumns.length) {
1162 menu.insert(insertPoint, [{
1163 itemId: 'columnItemSeparator',
1164 xtype: 'menuseparator'
1165 }, {
1166 itemId: 'columnItem',
1167 text: me.columnsText,
1168 iconCls: me.menuColsIcon,
1169 menu: {
1170 items: hideableColumns
1171 },
1172 hideOnClick: false
1173 }]);
1174 }
1175 }
1176
1177 me.updateMenuDisabledState(me.menu);
1178 // TODO: rendering the menu to the nearest overlfowing ancestor has been disabled
1179 // since DomQuery is no longer available by default in 5.0
1180 // if (!menu.rendered) {
1181 // menu.render(this.el.up('{overflow=auto}') || document.body);
1182 // }
1183 },
1184
1185 /**
1186 * Returns an array of menu items to be placed into the shared menu
1187 * across all headers in this header container.
1188 * @return {Array} menuItems
1189 */
1190 getMenuItems: function() {
1191 var me = this,
1192 menuItems = [],
1193 hideableColumns = me.enableColumnHide ? me.getColumnMenu(me) : null;
1194
1195 if (me.sortable) {
1196 menuItems = [{
1197 itemId: 'ascItem',
1198 text: me.sortAscText,
1199 iconCls: me.menuSortAscCls,
1200 handler: me.onSortAscClick,
1201 scope: me
1202 },{
1203 itemId: 'descItem',
1204 text: me.sortDescText,
1205 iconCls: me.menuSortDescCls,
1206 handler: me.onSortDescClick,
1207 scope: me
1208 }];
1209 }
1210 if (hideableColumns && hideableColumns.length) {
1211 if (me.sortable) {
1212 menuItems.push({
1213 itemId: 'columnItemSeparator',
1214 xtype: 'menuseparator'
1215 });
1216 }
1217 menuItems.push({
1218 itemId: 'columnItem',
1219 text: me.columnsText,
1220 iconCls: me.menuColsIcon,
1221 menu: hideableColumns,
1222 hideOnClick: false
1223 });
1224 }
1225 return menuItems;
1226 },
1227
1228 // sort asc when clicking on item in menu
1229 onSortAscClick: function() {
1230 var menu = this.getMenu(),
1231 activeHeader = menu.activeHeader;
1232
1233 activeHeader.sort('ASC');
1234 },
1235
1236 // sort desc when clicking on item in menu
1237 onSortDescClick: function() {
1238 var menu = this.getMenu(),
1239 activeHeader = menu.activeHeader;
1240
1241 activeHeader.sort('DESC');
1242 },
1243
1244 /**
1245 * Returns an array of menu CheckItems corresponding to all immediate children
1246 * of the passed Container which have been configured as hideable.
1247 */
1248 getColumnMenu: function(headerContainer) {
1249 var menuItems = [],
1250 i = 0,
1251 item,
1252 items = headerContainer.query('>gridcolumn[hideable]'),
1253 itemsLn = items.length,
1254 menuItem;
1255
1256 for (; i < itemsLn; i++) {
1257 item = items[i];
1258 menuItem = new Ext.menu.CheckItem({
1259 text: item.menuText || item.text,
1260 checked: !item.hidden,
1261 hideOnClick: false,
1262 headerId: item.id,
1263 menu: item.isGroupHeader ? this.getColumnMenu(item) : undefined,
1264 checkHandler: this.onColumnCheckChange,
1265 scope: this
1266 });
1267 menuItems.push(menuItem);
1268 }
1269 // Prevent creating a submenu if we have no items
1270 return menuItems.length ? menuItems : null;
1271 },
1272
1273 onColumnCheckChange: function(checkItem, checked) {
1274 var header = Ext.getCmp(checkItem.headerId);
1275
1276 if (header.rendered) {
1277 header[checked ? 'show' : 'hide']();
1278 } else {
1279 header.hidden = !checked;
1280 }
1281 },
1282
1283 /**
1284 * Returns the number of <b>grid columns</b> descended from this HeaderContainer.
1285 * Group Columns are HeaderContainers. All grid columns are returned, including hidden ones.
1286 */
1287 getColumnCount: function() {
1288 return this.getGridColumns().length;
1289 },
1290
1291 /**
1292 * Gets the full width of all columns that are visible for setting width of tables.
1293 */
1294 getTableWidth: function() {
1295 var fullWidth = 0,
1296 headers = this.getVisibleGridColumns(),
1297 headersLn = headers.length,
1298 i;
1299
1300 for (i = 0; i < headersLn; i++) {
1301 fullWidth += headers[i].getCellWidth() || 0;
1302 }
1303 return fullWidth;
1304 },
1305
1306 /**
1307 * Returns an array of the **visible** columns in the grid. This goes down to the
1308 * lowest column header level, and does not return **grouped** headers which contain
1309 * sub headers.
1310 *
1311 * See also {@link Ext.grid.header.Container#getGridColumns}
1312 * @return {Ext.grid.column.Column[]} columns An array of visible columns. Returns
1313 * an empty array if no visible columns are found.
1314 */
1315 getVisibleGridColumns: function() {
1316 var me = this,
1317 allColumns, rootHeader,
1318 result, len, i, column;
1319
1320 if (me.gridVisibleColumns) {
1321 return me.gridVisibleColumns;
1322 }
1323
1324 allColumns = me.getGridColumns();
1325 rootHeader = me.getRootHeaderCt();
1326 result = [];
1327 len = allColumns.length;
1328
1329 // Use an inline check instead of ComponentQuery filtering for better performance for
1330 // repeated grid row rendering - as in buffered rendering.
1331 for (i = 0; i < len; i++) {
1332 column = allColumns[i];
1333
1334 if (!column.hidden && !column.isColumnHidden(rootHeader)) {
1335 result[result.length] = column;
1336 }
1337 }
1338
1339 me.gridVisibleColumns = result;
1340
1341 return result;
1342 },
1343
1344 isColumnHidden: function(rootHeader) {
1345 var owner = this.getRefOwner();
1346 while (owner && owner !== rootHeader) {
1347 if (owner.hidden) {
1348 return true;
1349 }
1350 owner = owner.getRefOwner();
1351 }
1352 return false;
1353 },
1354
1355 /**
1356 * @method getGridColumns
1357 * Returns an array of all columns which exist in the grid's View, visible or not.
1358 * This goes down to the leaf column header level, and does not return **grouped**
1359 * headers which contain sub headers.
1360 *
1361 * It includes hidden headers even though they are not rendered. This is for
1362 * collection of menu items for the column hide/show menu.
1363 *
1364 * Headers which have a hidden ancestor have a `hiddenAncestor: true` property
1365 * injected so that descendants are known to be hidden without interrogating that
1366 * header's ownerCt axis for a hidden ancestor.
1367 *
1368 * See also {@link Ext.grid.header.Container#getVisibleGridColumns}
1369 * @return {Ext.grid.column.Column[]} columns An array of columns. Returns an
1370 * empty array if no columns are found.
1371 */
1372 /** @ignore */
1373 getGridColumns: function(/* private - used in recursion*/inResult, hiddenAncestor) {
1374 if (!inResult && this.gridDataColumns) {
1375 return this.gridDataColumns;
1376 }
1377
1378 var me = this,
1379 result = inResult || [],
1380 items, i, len, item,
1381 lastVisibleColumn;
1382
1383 hiddenAncestor = hiddenAncestor || me.hidden;
1384 if (me.items) {
1385 items = me.items.items;
1386
1387 // An ActionColumn (Columns extend HeaderContainer) may have an items *array* being the action items that it renders.
1388 if (items) {
1389 for (i = 0, len = items.length; i < len; i++) {
1390 item = items[i];
1391 if (item.isGroupHeader) {
1392 // Group headers will need a visibleIndex for if/when they're removed from their owner.
1393 // See Ext.layout.container.Container#moveItemBefore.
1394 item.visibleIndex = result.length;
1395 item.getGridColumns(result, hiddenAncestor);
1396 } else {
1397 item.hiddenAncestor = hiddenAncestor;
1398 result.push(item);
1399 }
1400 }
1401 }
1402 }
1403 if (!inResult) {
1404 me.gridDataColumns = result;
1405 }
1406
1407 // If top level, correct first and last visible column flags
1408 if (!inResult && len) {
1409 // Set firstVisible and lastVisible flags
1410 for (i = 0, len = result.length; i < len; i++) {
1411 item = result[i];
1412
1413 // The column index within all (visible AND hidden) leaf level columns.
1414 // Used as the cellIndex in TableView's cell renderer call
1415 item.fullColumnIndex = i;
1416 item.isFirstVisible = item.isLastVisible = false;
1417 if (!(item.hidden || item.hiddenAncestor)) {
1418 if (!lastVisibleColumn) {
1419 item.isFirstVisible = true;
1420 }
1421 lastVisibleColumn = item;
1422 }
1423 }
1424 // If we haven't hidden all columns, tag the last visible one encountered
1425 if (lastVisibleColumn) {
1426 lastVisibleColumn.isLastVisible = true;
1427 }
1428 }
1429
1430 return result;
1431 },
1432
1433 /**
1434 * @private
1435 * For use by column headers in determining whether there are any hideable columns when deciding whether or not
1436 * the header menu should be disabled.
1437 */
1438 getHideableColumns: function() {
1439 var me = this,
1440 result = me.hideableColumns;
1441
1442 if (!result) {
1443 result = me.hideableColumns = me.query('[hideable]');
1444 }
1445 return result;
1446 },
1447
1448 /**
1449 * Returns the index of a leaf level header regardless of what the nesting
1450 * structure is.
1451 *
1452 * If a group header is passed, the index of the first leaf level header within it is returned.
1453 *
1454 * @param {Ext.grid.column.Column} header The header to find the index of
1455 * @return {Number} The index of the specified column header
1456 */
1457 getHeaderIndex: function (header) {
1458 // Binding the columnManager to a column makes it backwards-compatible with versions
1459 // that only bind the columnManager to a root header.
1460 if (!this.columnManager) {
1461 this.columnManager = this.getRootHeaderCt().columnManager;
1462 }
1463
1464 return this.columnManager.getHeaderIndex(header);
1465 },
1466
1467 /**
1468 * Get a leaf level header by index regardless of what the nesting
1469 * structure is.
1470 *
1471 * @param {Number} index The column index for which to retrieve the column.
1472 */
1473 getHeaderAtIndex: function (index) {
1474 // Binding the columnManager to a column makes it backwards-compatible with versions
1475 // that only bind the columnManager to a root header.
1476 if (!this.columnManager) {
1477 this.columnManager = this.getRootHeaderCt().columnManager;
1478 }
1479
1480 return this.columnManager.getHeaderAtIndex(index);
1481 },
1482
1483 /**
1484 * When passed a column index, returns the closet *visible* column to that. If the column at the passed index is visible,
1485 * that is returned. If it is hidden, either the next visible, or the previous visible column is returned.
1486 *
1487 * @param {Number} index Position at which to find the closest visible column.
1488 */
1489 getVisibleHeaderClosestToIndex: function (index) {
1490 // Binding the columnManager to a column makes it backwards-compatible with versions
1491 // that only bind the columnManager to a root header.
1492 if (!this.visibleColumnManager) {
1493 this.visibleColumnManager = this.getRootHeaderCt().visibleColumnManager;
1494 }
1495
1496 return this.visibleColumnManager.getVisibleHeaderClosestToIndex(index);
1497 },
1498
1499 applyForceFit: function (header) {
1500 var me = this,
1501 view = me.view,
1502 minWidth = Ext.grid.plugin.HeaderResizer.prototype.minColWidth,
1503 // Used when a column's max contents are larger than the available view width.
1504 useMinWidthForFlex = false,
1505 defaultWidth = Ext.grid.header.Container.prototype.defaultWidth,
1506 availFlex = me.el.dom.clientWidth - (view.el.dom.scrollHeight > view.el.dom.clientHeight ? Ext.getScrollbarSize().width : 0),
1507 totalFlex = 0,
1508 items = me.getVisibleGridColumns(),
1509 hidden = header.hidden,
1510 len, i,
1511 item,
1512 maxAvailFlexOneColumn,
1513 myWidth;
1514
1515 function getTotalFlex() {
1516 for (i = 0, len = items.length; i < len; i++) {
1517 item = items[i];
1518
1519 // Skip the current header.
1520 if (item === header) {
1521 continue;
1522 }
1523
1524 item.flex = item.flex || item.width || item.getWidth();
1525 totalFlex += item.flex;
1526 item.width = null;
1527 }
1528 }
1529
1530 function applyWidth() {
1531 // The currently-sized column (whether resized or reshown) will already
1532 // have a width, so all other columns will need to be flexed.
1533 var isCurrentHeader;
1534
1535 for (i = 0, len = items.length; i < len; i++) {
1536 item = items[i];
1537 isCurrentHeader = (item === header);
1538
1539 if (useMinWidthForFlex && !isCurrentHeader) {
1540 // The selected column is extremely large so set all the others as flex minWidth.
1541 item.flex = minWidth;
1542 item.width = null;
1543 } else if (!isCurrentHeader) {
1544 // Note that any widths MUST be converted to flex. Imagine that all but one columns
1545 // are hidden. The widths of each column very easily could be greater than the total
1546 // available width (think about the how visible header widths increase as sibling
1547 // columns are hidden), so they cannot be reliably used to size the header, and the only
1548 // safe approach is to convert any all widths to flex (except for the current header).
1549 myWidth = item.flex || defaultWidth;
1550 item.flex = Math.max(Math.ceil((myWidth / totalFlex) * availFlex), minWidth);
1551 item.width = null;
1552 }
1553
1554 item.setWidth(item.width || item.flex);
1555 }
1556 }
1557
1558 Ext.suspendLayouts();
1559
1560 // Determine the max amount of flex that a single column can have.
1561 maxAvailFlexOneColumn = (availFlex - ((items.length + 1) * minWidth));
1562
1563 // First, remove the header's flex as it should always receive a set width
1564 // since it is the header being operated on.
1565 header.flex = null;
1566
1567 if (hidden) {
1568 myWidth = header.width || header.savedWidth;
1569 header.savedWidth = null;
1570 } else {
1571 myWidth = view.getMaxContentWidth(header);
1572 }
1573
1574 // We need to know if the max content width of the selected column would blow out the
1575 // grid. If so, all the other visible columns will be flexed to minWidth.
1576 if (myWidth > maxAvailFlexOneColumn) {
1577 header.width = maxAvailFlexOneColumn;
1578 useMinWidthForFlex = true;
1579 } else {
1580 header.width = myWidth;
1581
1582 // Substract the current header's width from the available flex + some padding
1583 // to ensure that the last column doesn't get nudged out of the view.
1584 availFlex -= myWidth + defaultWidth;
1585 getTotalFlex();
1586 }
1587
1588 applyWidth();
1589
1590 Ext.resumeLayouts(true);
1591 },
1592
1593 autoSizeColumn: function (header) {
1594 var view = this.view;
1595
1596 if (view) {
1597 view.autoSizeColumn(header);
1598 if (this.forceFit) {
1599 this.applyForceFit(header);
1600 }
1601 }
1602 },
1603
1604 getRefItems: function(deep) {
1605 // Override to include the header menu in the component tree
1606 var result = this.callParent([deep]);
1607
1608 if (this.menu) {
1609 result.push(this.menu);
1610 }
1611 return result;
1612 },
1613
1614 privates: {
1615 beginChildHide: function() {
1616 ++this.childHideCount;
1617 },
1618
1619 endChildHide: function() {
1620 --this.childHideCount;
1621 },
1622
1623 getFocusables: function() {
1624 return this.isRootHeader ?
1625 this.getVisibleGridColumns() :
1626 this.items.items;
1627 },
1628
1629 createFocusableContainerKeyNav: function(el) {
1630 var me = this;
1631
1632 return new Ext.util.KeyNav(el, {
1633 scope: me,
1634
1635 down: me.showHeaderMenu,
1636 left: me.onFocusableContainerLeftKey,
1637 right: me.onFocusableContainerRightKey,
1638 home: me.onHomeKey,
1639 end: me.onEndKey,
1640
1641 space: me.onHeaderActivate,
1642 enter: me.onHeaderActivate
1643 });
1644 },
1645
1646 onHomeKey: function(e) {
1647 return this.focusChild(null, true, e);
1648 },
1649
1650 onEndKey: function(e) {
1651 return this.focusChild(null, false, e);
1652 },
1653
1654 showHeaderMenu: function(e) {
1655 var column = this.getFocusableFromEvent(e);
1656
1657 // DownArrow event must be from a column, not a Component within the column (eg filter fields)
1658 if (column && column.isColumn && column.triggerEl) {
1659 this.onHeaderTriggerClick(column, e, column.triggerEl);
1660 }
1661 },
1662
1663 onHeaderActivate: function(e) {
1664 var column = this.getFocusableFromEvent(e),
1665 view,
1666 lastFocused;
1667
1668 // Remember that not every descendant of a headerCt is a column! It could be a child component of a column.
1669 if (column && column.isColumn) {
1670 view = column.getView();
1671
1672 // Sort the column is configured that way.
1673 // sortOnClick may be set to false by SpreadsheelSelectionModel to allow click to select a column.
1674 if (column.sortable && this.sortOnClick) {
1675 lastFocused = view.getNavigationModel().getLastFocused();
1676 column.toggleSortState();
1677
1678 // After keyboard sort, bring last focused record into view
1679 if (lastFocused) {
1680 view.ownerCt.ensureVisible(lastFocused.record);
1681 }
1682 }
1683 // onHeaderClick is a necessary part of accessibility processing, sortable or not.
1684 this.onHeaderClick(column, e, column.el);
1685 }
1686 },
1687
1688 onFocusableContainerMousedown: function(e, target) {
1689 var targetCmp = Ext.Component.fromElement(target);
1690
1691 if (targetCmp === this) {
1692 e.preventDefault();
1693 } else {
1694 // The DDManager (Header Containers are draggable) prevents mousedown default
1695 // So we must explicitly focus the header
1696 targetCmp.focus();
1697 }
1698 }
1699 }
1700 });
1701