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