]>
Commit | Line | Data |
---|---|---|
6527f429 DM |
1 | /**\r |
2 | * A mixin for groups of Focusable things (Components, Widgets, etc) that\r | |
3 | * should respond to arrow keys to navigate among the peers, but keep only\r | |
4 | * one of the peers tabbable by default (tabIndex=0)\r | |
5 | *\r | |
6 | * Some examples: Toolbars, Radio groups, Tab bars, Panel headers, Menus\r | |
7 | */\r | |
8 | \r | |
9 | Ext.define('Ext.util.FocusableContainer', {\r | |
10 | extend: 'Ext.Mixin',\r | |
11 | \r | |
12 | requires: [\r | |
13 | 'Ext.util.KeyNav'\r | |
14 | ],\r | |
15 | \r | |
16 | mixinConfig: {\r | |
17 | id: 'focusablecontainer',\r | |
18 | \r | |
19 | before: {\r | |
20 | onAdd: 'onFocusableChildAdd',\r | |
21 | onRemove: 'onFocusableChildRemove',\r | |
22 | destroy: 'destroyFocusableContainer',\r | |
23 | onFocusEnter: 'onFocusEnter'\r | |
24 | },\r | |
25 | \r | |
26 | after: {\r | |
27 | afterRender: 'initFocusableContainer',\r | |
28 | onFocusLeave: 'onFocusLeave',\r | |
29 | afterShow: 'activateFocusableContainerEl'\r | |
30 | }\r | |
31 | },\r | |
32 | \r | |
33 | isFocusableContainer: true,\r | |
34 | \r | |
35 | /**\r | |
36 | * @cfg {Boolean} [enableFocusableContainer=true] Enable or disable\r | |
37 | * navigation with arrow keys for this FocusableContainer. This option may\r | |
38 | * be useful with nested FocusableContainers such as Grid column headers,\r | |
39 | * when only the root container should handle keyboard events.\r | |
40 | */\r | |
41 | enableFocusableContainer: true,\r | |
42 | \r | |
43 | /**\r | |
44 | * @cfg {Number} [activeChildTabIndex=0] DOM tabIndex attribute to set on the\r | |
45 | * active Focusable child of this container when using the "Roaming tabindex"\r | |
46 | * technique. Set this value to > 0 to precisely control the tabbing order\r | |
47 | * of the components/containers on the page.\r | |
48 | */\r | |
49 | activeChildTabIndex: 0,\r | |
50 | \r | |
51 | /**\r | |
52 | * @cfg {Number} [inactiveChildTabIndex=-1] DOM tabIndex attribute to set on\r | |
53 | * inactive Focusable children of this container when using the "Roaming tabindex"\r | |
54 | * technique. This value rarely needs to be changed from its default.\r | |
55 | */\r | |
56 | inactiveChildTabIndex: -1,\r | |
57 | \r | |
58 | privates: {\r | |
59 | initFocusableContainer: function(clearChildren) {\r | |
60 | var items, i, len;\r | |
61 | \r | |
62 | // Allow nested containers to optionally disable\r | |
63 | // children containers' behavior\r | |
64 | if (this.enableFocusableContainer) {\r | |
65 | clearChildren = clearChildren != null ? clearChildren : true;\r | |
66 | this.doInitFocusableContainer(clearChildren);\r | |
67 | }\r | |
68 | \r | |
69 | // A FocusableContainer instance such as a toolbar could have decided\r | |
70 | // to opt out of FC behavior for some reason; it could have happened\r | |
71 | // after all or almost all child items have been initialized with\r | |
72 | // focusableContainer reference. We need to clean this up if we're not\r | |
73 | // going to behave like a FocusableContainer after all.\r | |
74 | else {\r | |
75 | items = this.getFocusables();\r | |
76 | \r | |
77 | for (i = 0, len = items.length; i < len; i++) {\r | |
78 | items[i].focusableContainer = null;\r | |
79 | }\r | |
80 | }\r | |
81 | },\r | |
82 | \r | |
83 | doInitFocusableContainer: function(clearChildren) {\r | |
84 | var me = this,\r | |
85 | el, child;\r | |
86 | \r | |
87 | el = me.getFocusableContainerEl();\r | |
88 | \r | |
89 | // This flag allows post factum initialization of the focusable container,\r | |
90 | // i.e. when container was empty initially and then some tabbable children\r | |
91 | // were added and we need to clear their tabIndices after priming our own\r | |
92 | // element's tabIndex.\r | |
93 | // This is useful for Panel and Window headers that might have tools\r | |
94 | // added dynamically.\r | |
95 | if (clearChildren) {\r | |
96 | me.clearFocusables();\r | |
97 | }\r | |
98 | \r | |
99 | // If we have no potentially focusable children, or all potentially focusable\r | |
100 | // children are presently disabled, don't init the container el tabIndex.\r | |
101 | // There is no point in tabbing into container when it can't shift focus\r | |
102 | // to a child.\r | |
103 | child = me.findNextFocusableChild({ step: 1, beforeRender: true });\r | |
104 | \r | |
105 | if (child) {\r | |
106 | // We set tabIndex on the focusable container el so that the user\r | |
107 | // could tab into it; we catch its focus event and focus a child instead\r | |
108 | me.activateFocusableContainerEl(el);\r | |
109 | }\r | |
110 | \r | |
111 | // Unsightly long names help to avoid possible clashing with class\r | |
112 | // or instance properties. We have to be extra careful in a mixin!\r | |
113 | me.focusableContainerMouseListener = me.mon(\r | |
114 | el, 'mousedown', me.onFocusableContainerMousedown, me\r | |
115 | );\r | |
116 | \r | |
117 | // Having keyNav doesn't hurt when container el is not focusable\r | |
118 | me.focusableKeyNav = me.createFocusableContainerKeyNav(el);\r | |
119 | },\r | |
120 | \r | |
121 | createFocusableContainerKeyNav: function(el) {\r | |
122 | var me = this;\r | |
123 | \r | |
124 | return new Ext.util.KeyNav(el, {\r | |
125 | eventName: 'keydown',\r | |
126 | ignoreInputFields: true,\r | |
127 | scope: me,\r | |
128 | \r | |
129 | tab: me.onFocusableContainerTabKey,\r | |
130 | enter: me.onFocusableContainerEnterKey,\r | |
131 | space: me.onFocusableContainerSpaceKey,\r | |
132 | up: me.onFocusableContainerUpKey,\r | |
133 | down: me.onFocusableContainerDownKey,\r | |
134 | left: me.onFocusableContainerLeftKey,\r | |
135 | right: me.onFocusableContainerRightKey\r | |
136 | });\r | |
137 | },\r | |
138 | \r | |
139 | destroyFocusableContainer: function() {\r | |
140 | if (this.enableFocusableContainer) {\r | |
141 | this.doDestroyFocusableContainer();\r | |
142 | }\r | |
143 | },\r | |
144 | \r | |
145 | doDestroyFocusableContainer: function() {\r | |
146 | var me = this;\r | |
147 | \r | |
148 | if (me.keyNav) {\r | |
149 | me.keyNav.destroy();\r | |
150 | }\r | |
151 | \r | |
152 | if (me.focusableContainerMouseListener) {\r | |
153 | me.focusableContainerMouseListener.destroy();\r | |
154 | }\r | |
155 | \r | |
156 | me.focusableKeyNav = me.focusableContainerMouseListener = null;\r | |
157 | },\r | |
158 | \r | |
159 | // Default FocusableContainer implies a flat list of focusable children\r | |
160 | getFocusables: function() {\r | |
161 | return this.items.items;\r | |
162 | },\r | |
163 | \r | |
164 | initDefaultFocusable: function(beforeRender) {\r | |
165 | var me = this,\r | |
166 | activeIndex = me.activeChildTabIndex,\r | |
167 | haveFocusable = false,\r | |
168 | items, item, i, len, tabIdx;\r | |
169 | \r | |
170 | items = me.getFocusables();\r | |
171 | len = items.length;\r | |
172 | \r | |
173 | if (!len) {\r | |
174 | return;\r | |
175 | }\r | |
176 | \r | |
177 | // Check if any child Focusable is already active.\r | |
178 | // Note that we're not determining *which* focusable child\r | |
179 | // to focus here, only that we have some focusables.\r | |
180 | for (i = 0; i < len; i++) {\r | |
181 | item = items[i];\r | |
182 | \r | |
183 | if (item.focusable && !item.disabled) {\r | |
184 | haveFocusable = true;\r | |
185 | tabIdx = item.getTabIndex();\r | |
186 | \r | |
187 | if (tabIdx != null && tabIdx >= activeIndex) {\r | |
188 | return item;\r | |
189 | }\r | |
190 | }\r | |
191 | }\r | |
192 | \r | |
193 | // No interactive children found, no point in going further\r | |
194 | if (!haveFocusable) {\r | |
195 | return;\r | |
196 | }\r | |
197 | \r | |
198 | // No child is focusable by default, so the first *interactive*\r | |
199 | // one gets initial childTabIndex. We are not looking for a focusable\r | |
200 | // child here because it may not be focusable yet if this happens\r | |
201 | // before rendering; we assume that an interactive child will become\r | |
202 | // focusable later and now activateFocusable() will just assign it\r | |
203 | // the respective tabIndex.\r | |
204 | item = me.findNextFocusableChild({\r | |
205 | beforeRender: beforeRender,\r | |
206 | items: items,\r | |
207 | step: true\r | |
208 | });\r | |
209 | \r | |
210 | if (item) {\r | |
211 | me.activateFocusable(item);\r | |
212 | }\r | |
213 | \r | |
214 | return item;\r | |
215 | },\r | |
216 | \r | |
217 | clearFocusables: function() {\r | |
218 | var me = this,\r | |
219 | items = me.getFocusables(),\r | |
220 | len = items.length,\r | |
221 | item, i;\r | |
222 | \r | |
223 | for (i = 0; i < len; i++) {\r | |
224 | item = items[i];\r | |
225 | \r | |
226 | if (item.focusable && !item.disabled) {\r | |
227 | me.deactivateFocusable(item);\r | |
228 | }\r | |
229 | }\r | |
230 | },\r | |
231 | \r | |
232 | activateFocusable: function(child, /* optional */ newTabIndex) {\r | |
233 | var activeIndex = newTabIndex != null ? newTabIndex : this.activeChildTabIndex;\r | |
234 | \r | |
235 | child.setTabIndex(activeIndex);\r | |
236 | },\r | |
237 | \r | |
238 | deactivateFocusable: function(child, /* optional */ newTabIndex) {\r | |
239 | var inactiveIndex = newTabIndex != null ? newTabIndex : this.inactiveChildTabIndex;\r | |
240 | \r | |
241 | child.setTabIndex(inactiveIndex);\r | |
242 | },\r | |
243 | \r | |
244 | onFocusableContainerTabKey: function() {\r | |
245 | return true;\r | |
246 | },\r | |
247 | \r | |
248 | onFocusableContainerEnterKey: function() {\r | |
249 | return true;\r | |
250 | },\r | |
251 | \r | |
252 | onFocusableContainerSpaceKey: function() {\r | |
253 | return true;\r | |
254 | },\r | |
255 | \r | |
256 | onFocusableContainerUpKey: function(e) {\r | |
257 | // Default action is to scroll the nearest vertically scrollable container\r | |
258 | e.preventDefault();\r | |
259 | \r | |
260 | return this.moveChildFocus(e, false);\r | |
261 | },\r | |
262 | \r | |
263 | onFocusableContainerDownKey: function(e) {\r | |
264 | // Ditto\r | |
265 | e.preventDefault();\r | |
266 | \r | |
267 | return this.moveChildFocus(e, true);\r | |
268 | },\r | |
269 | \r | |
270 | onFocusableContainerLeftKey: function(e) {\r | |
271 | // Default action is to scroll the nearest horizontally scrollable container\r | |
272 | e.preventDefault();\r | |
273 | \r | |
274 | return this.moveChildFocus(e, false);\r | |
275 | },\r | |
276 | \r | |
277 | onFocusableContainerRightKey: function(e) {\r | |
278 | // Ditto\r | |
279 | e.preventDefault();\r | |
280 | \r | |
281 | return this.moveChildFocus(e, true);\r | |
282 | },\r | |
283 | \r | |
284 | getFocusableFromEvent: function(e) {\r | |
285 | var child = Ext.Component.fromElement(e.getTarget());\r | |
286 | \r | |
287 | //<debug>\r | |
288 | if (!child) {\r | |
289 | Ext.raise("No focusable child found for keyboard event!");\r | |
290 | }\r | |
291 | //</debug>\r | |
292 | \r | |
293 | return child;\r | |
294 | },\r | |
295 | \r | |
296 | moveChildFocus: function(e, forward) {\r | |
297 | var child = this.getFocusableFromEvent(e);\r | |
298 | \r | |
299 | return this.focusChild(child, forward, e);\r | |
300 | },\r | |
301 | \r | |
302 | focusChild: function(child, forward) {\r | |
303 | var nextChild = this.findNextFocusableChild({\r | |
304 | child: child,\r | |
305 | step: forward\r | |
306 | });\r | |
307 | \r | |
308 | if (nextChild) {\r | |
309 | nextChild.focus();\r | |
310 | }\r | |
311 | \r | |
312 | return nextChild;\r | |
313 | },\r | |
314 | \r | |
315 | findNextFocusableChild: function(options) {\r | |
316 | // This method is private, so options should always be provided\r | |
317 | var beforeRender = options.beforeRender,\r | |
318 | items, item, child, step, idx, i, len;\r | |
319 | \r | |
320 | items = options.items || this.getFocusables();\r | |
321 | step = options.step != null ? options.step : 1;\r | |
322 | child = options.child;\r | |
323 | \r | |
324 | // If the child is null or undefined, idx will be -1.\r | |
325 | // The loop below will account for that, trying to find\r | |
326 | // the first focusable child from either end (depending on step)\r | |
327 | idx = Ext.Array.indexOf(items, child);\r | |
328 | \r | |
329 | // It's often easier to pass a boolean for 1/-1\r | |
330 | step = step === true ? 1 : step === false ? -1 : step;\r | |
331 | \r | |
332 | len = items.length;\r | |
333 | i = step > 0 ? (idx < len ? idx + step : 0) : (idx > 0 ? idx + step : len - 1);\r | |
334 | \r | |
335 | for (;; i += step) {\r | |
336 | // We're looking for the first or last focusable child\r | |
337 | // and we've reached the end of the items, so punt\r | |
338 | if (idx < 0 && (i >= len || i < 0)) {\r | |
339 | return null;\r | |
340 | }\r | |
341 | \r | |
342 | // Loop over forward\r | |
343 | else if (i >= len) {\r | |
344 | i = -1; // Iterator will increase it once more\r | |
345 | continue;\r | |
346 | }\r | |
347 | \r | |
348 | // Loop over backward\r | |
349 | else if (i < 0) {\r | |
350 | i = len;\r | |
351 | continue;\r | |
352 | }\r | |
353 | \r | |
354 | // Looped to the same item, give up\r | |
355 | else if (i === idx) {\r | |
356 | return null;\r | |
357 | }\r | |
358 | \r | |
359 | item = items[i];\r | |
360 | \r | |
361 | if (!item || !item.focusable || item.disabled) {\r | |
362 | continue;\r | |
363 | }\r | |
364 | \r | |
365 | // This loop can be run either at FocusableContainer init time,\r | |
366 | // or later when we need to navigate upon pressing an arrow key.\r | |
367 | // When we're navigating, we have to know exactly if the child is\r | |
368 | // focusable or not, hence only rendered children will make the cut.\r | |
369 | // At the init time item.isFocusable() may return false incorrectly\r | |
370 | // just because the item has not been rendered yet and its focusEl\r | |
371 | // is not defined, so we don't bother to call isFocusable and return\r | |
372 | // the first potentially focusable child.\r | |
373 | if (beforeRender || (item.isFocusable && item.isFocusable())) {\r | |
374 | return item;\r | |
375 | }\r | |
376 | }\r | |
377 | \r | |
378 | return null;\r | |
379 | },\r | |
380 | \r | |
381 | getFocusableContainerEl: function() {\r | |
382 | return this.el;\r | |
383 | },\r | |
384 | \r | |
385 | onFocusableChildAdd: function(child) {\r | |
386 | if (this.enableFocusableContainer) {\r | |
387 | return this.doFocusableChildAdd(child);\r | |
388 | }\r | |
389 | },\r | |
390 | \r | |
391 | activateFocusableContainerEl: function(el) {\r | |
392 | el = el || this.getFocusableContainerEl();\r | |
393 | \r | |
394 | // Might not yet be rendered\r | |
395 | if (el) {\r | |
396 | el.set({ tabIndex: this.activeChildTabIndex });\r | |
397 | }\r | |
398 | },\r | |
399 | \r | |
400 | deactivateFocusableContainerEl: function(el) {\r | |
401 | el = el || this.getFocusableContainerEl();\r | |
402 | \r | |
403 | if (el) {\r | |
404 | el.set({ tabIndex: undefined });\r | |
405 | }\r | |
406 | },\r | |
407 | \r | |
408 | isFocusableContainerActive: function() {\r | |
409 | var me = this,\r | |
410 | isActive = false,\r | |
411 | el, child, focusEl;\r | |
412 | \r | |
413 | el = me.getFocusableContainerEl();\r | |
414 | \r | |
415 | if (el && el.isTabbable && el.isTabbable()) {\r | |
416 | isActive = true;\r | |
417 | }\r | |
418 | else {\r | |
419 | child = me.lastFocusedChild;\r | |
420 | focusEl = child && child.getFocusEl && child.getFocusEl();\r | |
421 | \r | |
422 | if (focusEl && focusEl.isTabbable && focusEl.isTabbable()) {\r | |
423 | isActive = true;\r | |
424 | }\r | |
425 | }\r | |
426 | \r | |
427 | return isActive;\r | |
428 | },\r | |
429 | \r | |
430 | doFocusableChildAdd: function(child) {\r | |
431 | if (child.focusable) {\r | |
432 | child.focusableContainer = this;\r | |
433 | }\r | |
434 | },\r | |
435 | \r | |
436 | onFocusableChildRemove: function(child) {\r | |
437 | if (this.enableFocusableContainer) {\r | |
438 | return this.doFocusableChildRemove(child);\r | |
439 | }\r | |
440 | \r | |
441 | child.focusableContainer = null;\r | |
442 | },\r | |
443 | \r | |
444 | doFocusableChildRemove: function(child) {\r | |
445 | // If the focused child is being removed, we deactivate the FocusableContainer\r | |
446 | // So that it returns to the tabbing order.\r | |
447 | // For example, locking a grid column must return the owning HeaderContainer\r | |
448 | // to tabbability\r | |
449 | if (child === this.lastFocusedChild) {\r | |
450 | this.lastFocusedChild = null;\r | |
451 | this.activateFocusableContainerEl();\r | |
452 | }\r | |
453 | },\r | |
454 | \r | |
455 | onFocusableContainerMousedown: function(e, target) {\r | |
456 | var targetCmp = Ext.Component.fromElement(target);\r | |
457 | \r | |
458 | // Capture the timestamp for the mousedown. If we're navigating\r | |
459 | // into the container itself via the mouse we don't want to\r | |
460 | // default focus the first child like we would when using the keyboard.\r | |
461 | // By the time we get to the focusenter handling, we don't know what has caused\r | |
462 | // the focus to be triggered, so if the timestamp falls within some small epsilon,\r | |
463 | // the focus enter has been caused via the mouse and we can react accordingly.\r | |
464 | this.mousedownTimestamp = targetCmp === this ? Ext.Date.now() : 0;\r | |
465 | \r | |
466 | // Prevent focusing the container itself. DO NOT remove this clause, it is\r | |
467 | // untestable by our unit tests: injecting mousedown events will not cause\r | |
468 | // default action in the browser, the element never gets focus and tests\r | |
469 | // never fail. See http://www.w3.org/TR/DOM-Level-3-Events/#trusted-events\r | |
470 | if (targetCmp === this) {\r | |
471 | e.preventDefault();\r | |
472 | }\r | |
473 | },\r | |
474 | \r | |
475 | onFocusEnter: function(e) {\r | |
476 | var me = this,\r | |
477 | target = e.toComponent,\r | |
478 | mousedownTimestamp = me.mousedownTimestamp,\r | |
479 | epsilon = 50,\r | |
480 | child;\r | |
481 | \r | |
482 | if (!me.enableFocusableContainer) {\r | |
483 | return null;\r | |
484 | }\r | |
485 | \r | |
486 | me.mousedownTimestamp = 0;\r | |
487 | \r | |
488 | if (target === me) {\r | |
489 | if (!mousedownTimestamp || Ext.Date.now() - mousedownTimestamp > epsilon) {\r | |
490 | child = me.initDefaultFocusable();\r | |
491 | \r | |
492 | if (child) {\r | |
493 | me.deactivateFocusableContainerEl();\r | |
494 | child.focus();\r | |
495 | }\r | |
496 | }\r | |
497 | }\r | |
498 | else {\r | |
499 | me.deactivateFocusableContainerEl();\r | |
500 | }\r | |
501 | \r | |
502 | return target;\r | |
503 | },\r | |
504 | \r | |
505 | onFocusLeave: function(e) {\r | |
506 | var me = this,\r | |
507 | lastFocused = me.lastFocusedChild;\r | |
508 | \r | |
509 | if (!me.enableFocusableContainer) {\r | |
510 | return;\r | |
511 | }\r | |
512 | \r | |
513 | if (!me.destroyed && !me.destroying) {\r | |
514 | me.clearFocusables();\r | |
515 | \r | |
516 | if (lastFocused && !lastFocused.disabled) {\r | |
517 | me.activateFocusable(lastFocused);\r | |
518 | }\r | |
519 | else {\r | |
520 | me.activateFocusableContainerEl();\r | |
521 | }\r | |
522 | }\r | |
523 | },\r | |
524 | \r | |
525 | beforeFocusableChildBlur: Ext.privateFn,\r | |
526 | afterFocusableChildBlur: Ext.privateFn,\r | |
527 | \r | |
528 | beforeFocusableChildFocus: function(child) {\r | |
529 | var me = this;\r | |
530 | \r | |
531 | if (!me.enableFocusableContainer) {\r | |
532 | return;\r | |
533 | }\r | |
534 | \r | |
535 | me.clearFocusables();\r | |
536 | me.activateFocusable(child);\r | |
537 | \r | |
538 | if (child.needArrowKeys) {\r | |
539 | me.guardFocusableChild(child);\r | |
540 | }\r | |
541 | },\r | |
542 | \r | |
543 | guardFocusableChild: function(child) {\r | |
544 | var me = this,\r | |
545 | index = me.activeChildTabIndex,\r | |
546 | guard;\r | |
547 | \r | |
548 | guard = me.findNextFocusableChild({ child: child, step: -1 });\r | |
549 | \r | |
550 | if (guard) {\r | |
551 | guard.setTabIndex(index);\r | |
552 | }\r | |
553 | \r | |
554 | guard = me.findNextFocusableChild({ child: child, step: 1 });\r | |
555 | \r | |
556 | if (guard) {\r | |
557 | guard.setTabIndex(index);\r | |
558 | }\r | |
559 | },\r | |
560 | \r | |
561 | afterFocusableChildFocus: function(child) {\r | |
562 | if (!this.enableFocusableContainer) {\r | |
563 | return;\r | |
564 | }\r | |
565 | \r | |
566 | this.lastFocusedChild = child;\r | |
567 | },\r | |
568 | \r | |
569 | beforeFocusableChildEnable: Ext.privateFn,\r | |
570 | \r | |
571 | onFocusableChildEnable: function(child) {\r | |
572 | var me = this;\r | |
573 | \r | |
574 | if (!me.enableFocusableContainer) {\r | |
575 | return;\r | |
576 | }\r | |
577 | \r | |
578 | // Some Components like Buttons do not render tabIndex attribute\r | |
579 | // when they start their lifecycle disabled, or remove tabIndex\r | |
580 | // if they get disabled later. Subsequently, such Components will\r | |
581 | // reset their tabIndex to default configured value upon enabling.\r | |
582 | // We don't want these children to be tabbable so we reset their\r | |
583 | // tabIndex yet again, unless this child is the last focused one.\r | |
584 | if (child !== me.lastFocusedChild) {\r | |
585 | me.deactivateFocusable(child);\r | |
586 | \r | |
587 | if (!me.isFocusableContainerActive()) {\r | |
588 | me.activateFocusableContainerEl();\r | |
589 | }\r | |
590 | }\r | |
591 | },\r | |
592 | \r | |
593 | beforeFocusableChildDisable: function(child) {\r | |
594 | var me = this,\r | |
595 | nextTarget;\r | |
596 | \r | |
597 | if (!me.enableFocusableContainer || me.destroying || me.destroyed) {\r | |
598 | return;\r | |
599 | }\r | |
600 | \r | |
601 | // When currently focused child is about to be disabled,\r | |
602 | // it may lose the focus as well. For example, Buttons\r | |
603 | // will remove tabIndex attribute upon disabling, which\r | |
604 | // in turn will throw focus to the document body and cause\r | |
605 | // onFocusLeave to fire on the FocusableContainer.\r | |
606 | // We're focusing the next sibling to prevent that.\r | |
607 | if (child.hasFocus) {\r | |
608 | nextTarget = me.findNextFocusableChild({ child: child }) ||\r | |
609 | child.findFocusTarget();\r | |
610 | \r | |
611 | // Note that it is entirely possible not to find the nextTarget,\r | |
612 | // e.g. when we're disabling the last button in a toolbar rendered\r | |
613 | // directly into document body. We don't have a good way to handle\r | |
614 | // such cases at present.\r | |
615 | if (nextTarget) {\r | |
616 | nextTarget.focus();\r | |
617 | }\r | |
618 | }\r | |
619 | },\r | |
620 | \r | |
621 | onFocusableChildDisable: function(child) {\r | |
622 | var me = this,\r | |
623 | lastFocused = me.lastFocusedChild,\r | |
624 | firstFocusableChild;\r | |
625 | \r | |
626 | if (!me.enableFocusableContainer || me.destroying || me.destroyed) {\r | |
627 | return;\r | |
628 | }\r | |
629 | \r | |
630 | // If the disabled child was the last focused item of this\r | |
631 | // FocusableContainer, we have to reset the tabbability of\r | |
632 | // our container element.\r | |
633 | if (child === lastFocused) {\r | |
634 | me.activateFocusableContainerEl();\r | |
635 | }\r | |
636 | \r | |
637 | // It is also possible that the disabled child was the last\r | |
638 | // focusable child of this container, in which case we need\r | |
639 | // to make the container untabbable.\r | |
640 | firstFocusableChild = me.findNextFocusableChild({ step: 1 });\r | |
641 | \r | |
642 | if (!firstFocusableChild) {\r | |
643 | me.deactivateFocusableContainerEl();\r | |
644 | }\r | |
645 | },\r | |
646 | \r | |
647 | // TODO\r | |
648 | onFocusableChildShow: Ext.privateFn,\r | |
649 | onFocusableChildHide: Ext.privateFn,\r | |
650 | onFocusableChildMasked: Ext.privateFn,\r | |
651 | onFocusableChildDestroy: Ext.privateFn,\r | |
652 | onFocusableChildUpdate: Ext.privateFn\r | |
653 | }\r | |
654 | });\r |