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