]>
Commit | Line | Data |
---|---|---|
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 | |
21 | Ext.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 |