Upgrade to ExtJS 4.0.7 - Released 10/19/2011
[extjs.git] / src / grid / column / Column.js
1 /*
2
3 This file is part of Ext JS 4
4
5 Copyright (c) 2011 Sencha Inc
6
7 Contact:  http://www.sencha.com/contact
8
9 GNU General Public License Usage
10 This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file.  Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
11
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
13
14 */
15 /**
16  * This class specifies the definition for a column inside a {@link Ext.grid.Panel}. It encompasses
17  * both the grid header configuration as well as displaying data within the grid itself. If the
18  * {@link #columns} configuration is specified, this column will become a column group and can
19  * contain other columns inside. In general, this class will not be created directly, rather
20  * an array of column configurations will be passed to the grid:
21  *
22  *     @example
23  *     Ext.create('Ext.data.Store', {
24  *         storeId:'employeeStore',
25  *         fields:['firstname', 'lastname', 'senority', 'dep', 'hired'],
26  *         data:[
27  *             {firstname:"Michael", lastname:"Scott", senority:7, dep:"Manangement", hired:"01/10/2004"},
28  *             {firstname:"Dwight", lastname:"Schrute", senority:2, dep:"Sales", hired:"04/01/2004"},
29  *             {firstname:"Jim", lastname:"Halpert", senority:3, dep:"Sales", hired:"02/22/2006"},
30  *             {firstname:"Kevin", lastname:"Malone", senority:4, dep:"Accounting", hired:"06/10/2007"},
31  *             {firstname:"Angela", lastname:"Martin", senority:5, dep:"Accounting", hired:"10/21/2008"}
32  *         ]
33  *     });
34  *
35  *     Ext.create('Ext.grid.Panel', {
36  *         title: 'Column Demo',
37  *         store: Ext.data.StoreManager.lookup('employeeStore'),
38  *         columns: [
39  *             {text: 'First Name',  dataIndex:'firstname'},
40  *             {text: 'Last Name',  dataIndex:'lastname'},
41  *             {text: 'Hired Month',  dataIndex:'hired', xtype:'datecolumn', format:'M'},
42  *             {text: 'Department (Yrs)', xtype:'templatecolumn', tpl:'{dep} ({senority})'}
43  *         ],
44  *         width: 400,
45  *         renderTo: Ext.getBody()
46  *     });
47  *
48  * # Convenience Subclasses
49  *
50  * There are several column subclasses that provide default rendering for various data types
51  *
52  *  - {@link Ext.grid.column.Action}: Renders icons that can respond to click events inline
53  *  - {@link Ext.grid.column.Boolean}: Renders for boolean values
54  *  - {@link Ext.grid.column.Date}: Renders for date values
55  *  - {@link Ext.grid.column.Number}: Renders for numeric values
56  *  - {@link Ext.grid.column.Template}: Renders a value using an {@link Ext.XTemplate} using the record data
57  *
58  * # Setting Sizes
59  *
60  * The columns are laid out by a {@link Ext.layout.container.HBox} layout, so a column can either
61  * be given an explicit width value or a flex configuration. If no width is specified the grid will
62  * automatically the size the column to 100px. For column groups, the size is calculated by measuring
63  * the width of the child columns, so a width option should not be specified in that case.
64  *
65  * # Header Options
66  *
67  *  - {@link #text}: Sets the header text for the column
68  *  - {@link #sortable}: Specifies whether the column can be sorted by clicking the header or using the column menu
69  *  - {@link #hideable}: Specifies whether the column can be hidden using the column menu
70  *  - {@link #menuDisabled}: Disables the column header menu
71  *  - {@link #draggable}: Specifies whether the column header can be reordered by dragging
72  *  - {@link #groupable}: Specifies whether the grid can be grouped by the column dataIndex. See also {@link Ext.grid.feature.Grouping}
73  *
74  * # Data Options
75  *
76  *  - {@link #dataIndex}: The dataIndex is the field in the underlying {@link Ext.data.Store} to use as the value for the column.
77  *  - {@link #renderer}: Allows the underlying store value to be transformed before being displayed in the grid
78  */
79 Ext.define('Ext.grid.column.Column', {
80     extend: 'Ext.grid.header.Container',
81     alias: 'widget.gridcolumn',
82     requires: ['Ext.util.KeyNav'],
83     alternateClassName: 'Ext.grid.Column',
84
85     baseCls: Ext.baseCSSPrefix + 'column-header ' + Ext.baseCSSPrefix + 'unselectable',
86
87     // Not the standard, automatically applied overCls because we must filter out overs of child headers.
88     hoverCls: Ext.baseCSSPrefix + 'column-header-over',
89
90     handleWidth: 5,
91
92     sortState: null,
93
94     possibleSortStates: ['ASC', 'DESC'],
95
96     renderTpl:
97         '<div id="{id}-titleContainer" class="' + Ext.baseCSSPrefix + 'column-header-inner">' +
98             '<span id="{id}-textEl" class="' + Ext.baseCSSPrefix + 'column-header-text">' +
99                 '{text}' +
100             '</span>' +
101             '<tpl if="!values.menuDisabled">'+
102                 '<div id="{id}-triggerEl" class="' + Ext.baseCSSPrefix + 'column-header-trigger"></div>'+
103             '</tpl>' +
104         '</div>',
105
106     /**
107      * @cfg {Object[]} columns
108      * An optional array of sub-column definitions. This column becomes a group, and houses the columns defined in the
109      * `columns` config.
110      *
111      * Group columns may not be sortable. But they may be hideable and moveable. And you may move headers into and out
112      * of a group. Note that if all sub columns are dragged out of a group, the group is destroyed.
113      */
114
115     /**
116      * @cfg {String} dataIndex
117      * The name of the field in the grid's {@link Ext.data.Store}'s {@link Ext.data.Model} definition from
118      * which to draw the column's value. **Required.**
119      */
120     dataIndex: null,
121
122     /**
123      * @cfg {String} text
124      * The header text to be used as innerHTML (html tags are accepted) to display in the Grid.
125      * **Note**: to have a clickable header with no text displayed you can use the default of `&#160;` aka `&nbsp;`.
126      */
127     text: '&#160;',
128
129     /**
130      * @cfg {Boolean} sortable
131      * False to disable sorting of this column. Whether local/remote sorting is used is specified in
132      * `{@link Ext.data.Store#remoteSort}`. Defaults to true.
133      */
134     sortable: true,
135
136     /**
137      * @cfg {Boolean} groupable
138      * If the grid uses a {@link Ext.grid.feature.Grouping}, this option may be used to disable the header menu
139      * item to group by the column selected. By default, the header menu group option is enabled. Set to false to
140      * disable (but still show) the group option in the header menu for the column.
141      */
142
143     /**
144      * @cfg {Boolean} fixed
145      * @deprecated.
146      * True to prevent the column from being resizable.
147      */
148
149     /**
150      * @cfg {Boolean} resizable
151      * Set to <code>false</code> to prevent the column from being resizable. Defaults to <code>true</code>
152      */
153
154     /**
155      * @cfg {Boolean} hideable
156      * False to prevent the user from hiding this column. Defaults to true.
157      */
158     hideable: true,
159
160     /**
161      * @cfg {Boolean} menuDisabled
162      * True to disable the column header menu containing sort/hide options. Defaults to false.
163      */
164     menuDisabled: false,
165
166     /**
167      * @cfg {Function} renderer
168      * A renderer is an 'interceptor' method which can be used transform data (value, appearance, etc.)
169      * before it is rendered. Example:
170      *
171      *     {
172      *         renderer: function(value){
173      *             if (value === 1) {
174      *                 return '1 person';
175      *             }
176      *             return value + ' people';
177      *         }
178      *     }
179      *
180      * @cfg {Object} renderer.value The data value for the current cell
181      * @cfg {Object} renderer.metaData A collection of metadata about the current cell; can be used or modified
182      * by the renderer. Recognized properties are: tdCls, tdAttr, and style.
183      * @cfg {Ext.data.Model} renderer.record The record for the current row
184      * @cfg {Number} renderer.rowIndex The index of the current row
185      * @cfg {Number} renderer.colIndex The index of the current column
186      * @cfg {Ext.data.Store} renderer.store The data store
187      * @cfg {Ext.view.View} renderer.view The current view
188      * @cfg {String} renderer.return The HTML string to be rendered.
189      */
190     renderer: false,
191
192     /**
193      * @cfg {String} align
194      * Sets the alignment of the header and rendered columns. Defaults to 'left'.
195      */
196     align: 'left',
197
198     /**
199      * @cfg {Boolean} draggable
200      * False to disable drag-drop reordering of this column. Defaults to true.
201      */
202     draggable: true,
203
204     // Header does not use the typical ComponentDraggable class and therefore we
205     // override this with an emptyFn. It is controlled at the HeaderDragZone.
206     initDraggable: Ext.emptyFn,
207
208     /**
209      * @cfg {String} tdCls
210      * A CSS class names to apply to the table cells for this column.
211      */
212
213     /**
214      * @cfg {Object/String} editor
215      * An optional xtype or config object for a {@link Ext.form.field.Field Field} to use for editing.
216      * Only applicable if the grid is using an {@link Ext.grid.plugin.Editing Editing} plugin.
217      */
218
219     /**
220      * @cfg {Object/String} field
221      * Alias for {@link #editor}.
222      * @deprecated 4.0.5 Use {@link #editor} instead.
223      */
224
225     /**
226      * @property {Ext.Element} triggerEl
227      * Element that acts as button for column header dropdown menu.
228      */
229
230     /**
231      * @property {Ext.Element} textEl
232      * Element that contains the text in column header.
233      */
234
235     /**
236      * @private
237      * Set in this class to identify, at runtime, instances which are not instances of the
238      * HeaderContainer base class, but are in fact, the subclass: Header.
239      */
240     isHeader: true,
241
242     initComponent: function() {
243         var me = this,
244             i,
245             len,
246             item;
247
248         if (Ext.isDefined(me.header)) {
249             me.text = me.header;
250             delete me.header;
251         }
252
253         // Flexed Headers need to have a minWidth defined so that they can never be squeezed out of existence by the
254         // HeaderContainer's specialized Box layout, the ColumnLayout. The ColumnLayout's overridden calculateChildboxes
255         // method extends the available layout space to accommodate the "desiredWidth" of all the columns.
256         if (me.flex) {
257             me.minWidth = me.minWidth || Ext.grid.plugin.HeaderResizer.prototype.minColWidth;
258         }
259         // Non-flexed Headers may never be squeezed in the event of a shortfall so
260         // always set their minWidth to their current width.
261         else {
262             me.minWidth = me.width;
263         }
264
265         if (!me.triStateSort) {
266             me.possibleSortStates.length = 2;
267         }
268
269         // A group header; It contains items which are themselves Headers
270         if (Ext.isDefined(me.columns)) {
271             me.isGroupHeader = true;
272
273             //<debug>
274             if (me.dataIndex) {
275                 Ext.Error.raise('Ext.grid.column.Column: Group header may not accept a dataIndex');
276             }
277             if ((me.width && me.width !== Ext.grid.header.Container.prototype.defaultWidth) || me.flex) {
278                 Ext.Error.raise('Ext.grid.column.Column: Group header does not support setting explicit widths or flexs. The group header width is calculated by the sum of its children.');
279             }
280             //</debug>
281
282             // The headers become child items
283             me.items = me.columns;
284             delete me.columns;
285             delete me.flex;
286             me.width = 0;
287
288             // Acquire initial width from sub headers
289             for (i = 0, len = me.items.length; i < len; i++) {
290                 item = me.items[i];
291                 if (!item.hidden) {
292                     me.width += item.width || Ext.grid.header.Container.prototype.defaultWidth;
293                 }
294                 //<debug>
295                 if (item.flex) {
296                     Ext.Error.raise('Ext.grid.column.Column: items of a grouped header do not support flexed values. Each item must explicitly define its width.');
297                 }
298                 //</debug>
299             }
300             me.minWidth = me.width;
301
302             me.cls = (me.cls||'') + ' ' + Ext.baseCSSPrefix + 'group-header';
303             me.sortable = false;
304             me.resizable = false;
305             me.align = 'center';
306         }
307
308         me.addChildEls('titleContainer', 'triggerEl', 'textEl');
309
310         // Initialize as a HeaderContainer
311         me.callParent(arguments);
312     },
313
314     onAdd: function(childHeader) {
315         childHeader.isSubHeader = true;
316         childHeader.addCls(Ext.baseCSSPrefix + 'group-sub-header');
317         this.callParent(arguments);
318     },
319
320     onRemove: function(childHeader) {
321         childHeader.isSubHeader = false;
322         childHeader.removeCls(Ext.baseCSSPrefix + 'group-sub-header');
323         this.callParent(arguments);
324     },
325
326     initRenderData: function() {
327         var me = this;
328
329         Ext.applyIf(me.renderData, {
330             text: me.text,
331             menuDisabled: me.menuDisabled
332         });
333         return me.callParent(arguments);
334     },
335
336     applyColumnState: function (state) {
337         var me = this,
338             defined = Ext.isDefined;
339             
340         // apply any columns
341         me.applyColumnsState(state.columns);
342
343         // Only state properties which were saved should be restored.
344         // (Only user-changed properties were saved by getState)
345         if (defined(state.hidden)) {
346             me.hidden = state.hidden;
347         }
348         if (defined(state.locked)) {
349             me.locked = state.locked;
350         }
351         if (defined(state.sortable)) {
352             me.sortable = state.sortable;
353         }
354         if (defined(state.width)) {
355             delete me.flex;
356             me.width = state.width;
357         } else if (defined(state.flex)) {
358             delete me.width;
359             me.flex = state.flex;
360         }
361     },
362
363     getColumnState: function () {
364         var me = this,
365             columns = [],
366             state = {
367                 id: me.headerId
368             };
369
370         me.savePropsToState(['hidden', 'sortable', 'locked', 'flex', 'width'], state);
371         
372         if (me.isGroupHeader) {
373             me.items.each(function(column){
374                 columns.push(column.getColumnState());
375             });
376             if (columns.length) {
377                 state.columns = columns;
378             }
379         } else if (me.isSubHeader && me.ownerCt.hidden) {
380             // don't set hidden on the children so they can auto height
381             delete me.hidden;
382         }
383
384         if ('width' in state) {
385             delete state.flex; // width wins
386         }
387         return state;
388     },
389
390     /**
391      * Sets the header text for this Column.
392      * @param {String} text The header to display on this Column.
393      */
394     setText: function(text) {
395         this.text = text;
396         if (this.rendered) {
397             this.textEl.update(text);
398         }
399     },
400
401     // Find the topmost HeaderContainer: An ancestor which is NOT a Header.
402     // Group Headers are themselves HeaderContainers
403     getOwnerHeaderCt: function() {
404         return this.up(':not([isHeader])');
405     },
406
407     /**
408      * Returns the true grid column index associated with this column only if this column is a base level Column. If it
409      * is a group column, it returns `false`.
410      * @return {Number}
411      */
412     getIndex: function() {
413         return this.isGroupColumn ? false : this.getOwnerHeaderCt().getHeaderIndex(this);
414     },
415
416     onRender: function() {
417         var me = this,
418             grid = me.up('tablepanel');
419
420         // Disable the menu if there's nothing to show in the menu, ie:
421         // Column cannot be sorted, grouped or locked, and there are no grid columns which may be hidden
422         if (grid && (!me.sortable || grid.sortableColumns === false) && !me.groupable && !me.lockable && (grid.enableColumnHide === false || !me.getOwnerHeaderCt().getHideableColumns().length)) {
423             me.menuDisabled = true;
424         }
425         me.callParent(arguments);
426     },
427
428     afterRender: function() {
429         var me = this,
430             el = me.el;
431
432         me.callParent(arguments);
433
434         el.addCls(Ext.baseCSSPrefix + 'column-header-align-' + me.align).addClsOnOver(me.overCls);
435
436         me.mon(el, {
437             click:     me.onElClick,
438             dblclick:  me.onElDblClick,
439             scope:     me
440         });
441
442         // BrowserBug: Ie8 Strict Mode, this will break the focus for this browser,
443         // must be fixed when focus management will be implemented.
444         if (!Ext.isIE8 || !Ext.isStrict) {
445             me.mon(me.getFocusEl(), {
446                 focus: me.onTitleMouseOver,
447                 blur: me.onTitleMouseOut,
448                 scope: me
449             });
450         }
451
452         me.mon(me.titleContainer, {
453             mouseenter:  me.onTitleMouseOver,
454             mouseleave:  me.onTitleMouseOut,
455             scope:      me
456         });
457
458         me.keyNav = Ext.create('Ext.util.KeyNav', el, {
459             enter: me.onEnterKey,
460             down: me.onDownKey,
461             scope: me
462         });
463     },
464
465     /**
466      * Sets the width of this Column.
467      * @param {Number} width New width.
468      */
469     setWidth: function(width, /* private - used internally */ doLayout) {
470         var me = this,
471             headerCt = me.ownerCt,
472             siblings,
473             len, i,
474             oldWidth = me.getWidth(),
475             groupWidth = 0,
476             sibling;
477
478         if (width !== oldWidth) {
479             me.oldWidth = oldWidth;
480
481             // Non-flexed Headers may never be squeezed in the event of a shortfall so
482             // always set the minWidth to their current width.
483             me.minWidth = me.width = width;
484
485             // Bubble size changes upwards to group headers
486             if (headerCt.isGroupHeader) {
487                 siblings = headerCt.items.items;
488                 len = siblings.length;
489
490                 for (i = 0; i < len; i++) {
491                     sibling = siblings[i];
492                     if (!sibling.hidden) {
493                         groupWidth += (sibling === me) ? width : sibling.getWidth();
494                     }
495                 }
496                 headerCt.setWidth(groupWidth, doLayout);
497             } else if (doLayout !== false) {
498                 // Allow the owning Container to perform the sizing
499                 headerCt.doLayout();
500             }
501         }
502     },
503
504     afterComponentLayout: function(width, height) {
505         var me = this,
506             ownerHeaderCt = this.getOwnerHeaderCt();
507
508         me.callParent(arguments);
509
510         // Only changes at the base level inform the grid's HeaderContainer which will update the View
511         // Skip this if the width is null or undefined which will be the Box layout's initial pass  through the child Components
512         // Skip this if it's the initial size setting in which case there is no ownerheaderCt yet - that is set afterRender
513         if (width && !me.isGroupHeader && ownerHeaderCt) {
514             ownerHeaderCt.onHeaderResize(me, width, true);
515         }
516         if (me.oldWidth && (width !== me.oldWidth)) {
517             ownerHeaderCt.fireEvent('columnresize', ownerHeaderCt, this, width);
518         }
519         delete me.oldWidth;
520     },
521
522     // private
523     // After the container has laid out and stretched, it calls this to correctly pad the inner to center the text vertically
524     // Total available header height must be passed to enable padding for inner elements to be calculated.
525     setPadding: function(headerHeight) {
526         var me = this,
527             lineHeight = Ext.util.TextMetrics.measure(me.textEl.dom, me.text).height;
528
529         // Top title containing element must stretch to match height of sibling group headers
530         if (!me.isGroupHeader) {
531             if (me.titleContainer.getHeight() < headerHeight) {
532                 me.titleContainer.dom.style.height = headerHeight + 'px';
533             }
534         }
535         headerHeight = me.titleContainer.getViewSize().height;
536
537         // Vertically center the header text in potentially vertically stretched header
538         if (lineHeight) {
539             me.titleContainer.setStyle({
540                 paddingTop: Math.max(((headerHeight - lineHeight) / 2), 0) + 'px'
541             });
542         }
543
544         // Only IE needs this
545         if (Ext.isIE && me.triggerEl) {
546             me.triggerEl.setHeight(headerHeight);
547         }
548     },
549
550     onDestroy: function() {
551         var me = this;
552         // force destroy on the textEl, IE reports a leak
553         Ext.destroy(me.textEl, me.keyNav);
554         delete me.keyNav;
555         me.callParent(arguments);
556     },
557
558     onTitleMouseOver: function() {
559         this.titleContainer.addCls(this.hoverCls);
560     },
561
562     onTitleMouseOut: function() {
563         this.titleContainer.removeCls(this.hoverCls);
564     },
565
566     onDownKey: function(e) {
567         if (this.triggerEl) {
568             this.onElClick(e, this.triggerEl.dom || this.el.dom);
569         }
570     },
571
572     onEnterKey: function(e) {
573         this.onElClick(e, this.el.dom);
574     },
575
576     /**
577      * @private
578      * Double click
579      * @param e
580      * @param t
581      */
582     onElDblClick: function(e, t) {
583         var me = this,
584             ownerCt = me.ownerCt;
585         if (ownerCt && Ext.Array.indexOf(ownerCt.items, me) !== 0 && me.isOnLeftEdge(e) ) {
586             ownerCt.expandToFit(me.previousSibling('gridcolumn'));
587         }
588     },
589
590     onElClick: function(e, t) {
591
592         // The grid's docked HeaderContainer.
593         var me = this,
594             ownerHeaderCt = me.getOwnerHeaderCt();
595
596         if (ownerHeaderCt && !ownerHeaderCt.ddLock) {
597             // Firefox doesn't check the current target in a within check.
598             // Therefore we check the target directly and then within (ancestors)
599             if (me.triggerEl && (e.target === me.triggerEl.dom || t === me.triggerEl.dom || e.within(me.triggerEl))) {
600                 ownerHeaderCt.onHeaderTriggerClick(me, e, t);
601             // if its not on the left hand edge, sort
602             } else if (e.getKey() || (!me.isOnLeftEdge(e) && !me.isOnRightEdge(e))) {
603                 me.toggleSortState();
604                 ownerHeaderCt.onHeaderClick(me, e, t);
605             }
606         }
607     },
608
609     /**
610      * @private
611      * Process UI events from the view. The owning TablePanel calls this method, relaying events from the TableView
612      * @param {String} type Event type, eg 'click'
613      * @param {Ext.view.Table} view TableView Component
614      * @param {HTMLElement} cell Cell HtmlElement the event took place within
615      * @param {Number} recordIndex Index of the associated Store Model (-1 if none)
616      * @param {Number} cellIndex Cell index within the row
617      * @param {Ext.EventObject} e Original event
618      */
619     processEvent: function(type, view, cell, recordIndex, cellIndex, e) {
620         return this.fireEvent.apply(this, arguments);
621     },
622
623     toggleSortState: function() {
624         var me = this,
625             idx,
626             nextIdx;
627
628         if (me.sortable) {
629             idx = Ext.Array.indexOf(me.possibleSortStates, me.sortState);
630
631             nextIdx = (idx + 1) % me.possibleSortStates.length;
632             me.setSortState(me.possibleSortStates[nextIdx]);
633         }
634     },
635
636     doSort: function(state) {
637         var ds = this.up('tablepanel').store;
638         ds.sort({
639             property: this.getSortParam(),
640             direction: state
641         });
642     },
643
644     /**
645      * Returns the parameter to sort upon when sorting this header. By default this returns the dataIndex and will not
646      * need to be overriden in most cases.
647      * @return {String}
648      */
649     getSortParam: function() {
650         return this.dataIndex;
651     },
652
653     //setSortState: function(state, updateUI) {
654     //setSortState: function(state, doSort) {
655     setSortState: function(state, skipClear, initial) {
656         var me = this,
657             colSortClsPrefix = Ext.baseCSSPrefix + 'column-header-sort-',
658             ascCls = colSortClsPrefix + 'ASC',
659             descCls = colSortClsPrefix + 'DESC',
660             nullCls = colSortClsPrefix + 'null',
661             ownerHeaderCt = me.getOwnerHeaderCt(),
662             oldSortState = me.sortState;
663
664         if (oldSortState !== state && me.getSortParam()) {
665             me.addCls(colSortClsPrefix + state);
666             // don't trigger a sort on the first time, we just want to update the UI
667             if (state && !initial) {
668                 me.doSort(state);
669             }
670             switch (state) {
671                 case 'DESC':
672                     me.removeCls([ascCls, nullCls]);
673                     break;
674                 case 'ASC':
675                     me.removeCls([descCls, nullCls]);
676                     break;
677                 case null:
678                     me.removeCls([ascCls, descCls]);
679                     break;
680             }
681             if (ownerHeaderCt && !me.triStateSort && !skipClear) {
682                 ownerHeaderCt.clearOtherSortStates(me);
683             }
684             me.sortState = state;
685             ownerHeaderCt.fireEvent('sortchange', ownerHeaderCt, me, state);
686         }
687     },
688
689     hide: function() {
690         var me = this,
691             items,
692             len, i,
693             lb,
694             newWidth = 0,
695             ownerHeaderCt = me.getOwnerHeaderCt();
696
697         // Hiding means setting to zero width, so cache the width
698         me.oldWidth = me.getWidth();
699
700         // Hiding a group header hides itself, and then informs the HeaderContainer about its sub headers (Suppressing header layout)
701         if (me.isGroupHeader) {
702             items = me.items.items;
703             me.callParent(arguments);
704             ownerHeaderCt.onHeaderHide(me);
705             for (i = 0, len = items.length; i < len; i++) {
706                 items[i].hidden = true;
707                 ownerHeaderCt.onHeaderHide(items[i], true);
708             }
709             return;
710         }
711
712         // TODO: Work with Jamie to produce a scheme where we can show/hide/resize without triggering a layout cascade
713         lb = me.ownerCt.componentLayout.layoutBusy;
714         me.ownerCt.componentLayout.layoutBusy = true;
715         me.callParent(arguments);
716         me.ownerCt.componentLayout.layoutBusy = lb;
717
718         // Notify owning HeaderContainer
719         ownerHeaderCt.onHeaderHide(me);
720
721         if (me.ownerCt.isGroupHeader) {
722             // If we've just hidden the last header in a group, then hide the group
723             items = me.ownerCt.query('>:not([hidden])');
724             if (!items.length) {
725                 me.ownerCt.hide();
726             }
727             // Size the group down to accommodate fewer sub headers
728             else {
729                 for (i = 0, len = items.length; i < len; i++) {
730                     newWidth += items[i].getWidth();
731                 }
732                 me.ownerCt.minWidth = newWidth;
733                 me.ownerCt.setWidth(newWidth);
734             }
735         }
736     },
737
738     show: function() {
739         var me = this,
740             ownerCt = me.ownerCt,
741             ownerCtCompLayout = ownerCt.componentLayout,
742             ownerCtCompLayoutBusy = ownerCtCompLayout.layoutBusy,
743             ownerCtLayout = ownerCt.layout,
744             ownerCtLayoutBusy = ownerCtLayout.layoutBusy,
745             items,
746             len, i,
747             item,
748             newWidth = 0;
749
750         // TODO: Work with Jamie to produce a scheme where we can show/hide/resize without triggering a layout cascade
751
752         // Suspend our owner's layouts (both component and container):
753         ownerCtCompLayout.layoutBusy = ownerCtLayout.layoutBusy = true;
754
755         me.callParent(arguments);
756
757         ownerCtCompLayout.layoutBusy = ownerCtCompLayoutBusy;
758         ownerCtLayout.layoutBusy = ownerCtLayoutBusy;
759
760         // If a sub header, ensure that the group header is visible
761         if (me.isSubHeader) {
762             if (!ownerCt.isVisible()) {
763                 ownerCt.show();
764             }
765         }
766
767         // If we've just shown a group with all its sub headers hidden, then show all its sub headers
768         if (me.isGroupHeader && !me.query(':not([hidden])').length) {
769             items = me.query('>*');
770             for (i = 0, len = items.length; i < len; i++) {
771                 item = items[i];
772                 item.preventLayout = true;
773                 item.show();
774                 newWidth += item.getWidth();
775                 delete item.preventLayout;
776             }
777             me.setWidth(newWidth);
778         }
779
780         // Resize the owning group to accommodate
781         if (ownerCt.isGroupHeader && me.preventLayout !== true) {
782             items = ownerCt.query('>:not([hidden])');
783             for (i = 0, len = items.length; i < len; i++) {
784                 newWidth += items[i].getWidth();
785             }
786             ownerCt.minWidth = newWidth;
787             ownerCt.setWidth(newWidth);
788         }
789
790         // Notify owning HeaderContainer
791         ownerCt = me.getOwnerHeaderCt();
792         if (ownerCt) {
793             ownerCt.onHeaderShow(me, me.preventLayout);
794         }
795     },
796
797     getDesiredWidth: function() {
798         var me = this;
799         if (me.rendered && me.componentLayout && me.componentLayout.lastComponentSize) {
800             // headers always have either a width or a flex
801             // because HeaderContainer sets a defaults width
802             // therefore we can ignore the natural width
803             // we use the componentLayout's tracked width so that
804             // we can calculate the desired width when rendered
805             // but not visible because its being obscured by a layout
806             return me.componentLayout.lastComponentSize.width;
807         // Flexed but yet to be rendered this could be the case
808         // where a HeaderContainer and Headers are simply used as data
809         // structures and not rendered.
810         }
811         else if (me.flex) {
812             // this is going to be wrong, the defaultWidth
813             return me.width;
814         }
815         else {
816             return me.width;
817         }
818     },
819
820     getCellSelector: function() {
821         return '.' + Ext.baseCSSPrefix + 'grid-cell-' + this.getItemId();
822     },
823
824     getCellInnerSelector: function() {
825         return this.getCellSelector() + ' .' + Ext.baseCSSPrefix + 'grid-cell-inner';
826     },
827
828     isOnLeftEdge: function(e) {
829         return (e.getXY()[0] - this.el.getLeft() <= this.handleWidth);
830     },
831
832     isOnRightEdge: function(e) {
833         return (this.el.getRight() - e.getXY()[0] <= this.handleWidth);
834     }
835
836     // intentionally omit getEditor and setEditor definitions bc we applyIf into columns
837     // when the editing plugin is injected
838
839     /**
840      * @method getEditor
841      * Retrieves the editing field for editing associated with this header. Returns false if there is no field
842      * associated with the Header the method will return false. If the field has not been instantiated it will be
843      * created. Note: These methods only has an implementation if a Editing plugin has been enabled on the grid.
844      * @param {Object} record The {@link Ext.data.Model Model} instance being edited.
845      * @param {Object} defaultField An object representing a default field to be created
846      * @return {Ext.form.field.Field} field
847      */
848     /**
849      * @method setEditor
850      * Sets the form field to be used for editing. Note: This method only has an implementation if an Editing plugin has
851      * been enabled on the grid.
852      * @param {Object} field An object representing a field to be created. If no xtype is specified a 'textfield' is
853      * assumed.
854      */
855 });
856