]> git.proxmox.com Git - extjs.git/blame - extjs/classic/classic/src/util/Focusable.js
add extjs 6.0.1 sources
[extjs.git] / extjs / classic / classic / src / util / Focusable.js
CommitLineData
6527f429
DM
1/**\r
2 * A mixin for individually focusable things (Components, Widgets, etc)\r
3 */\r
4Ext.define('Ext.util.Focusable', {\r
5 mixinId: 'focusable',\r
6\r
7 /**\r
8 * @property {Boolean} hasFocus\r
9 * `true` if this component has focus.\r
10 * @private\r
11 */\r
12 hasFocus: false,\r
13 \r
14 /**\r
15 * @property {Boolean} focusable\r
16 * @readonly\r
17 *\r
18 * `true` for interactive Components, `false` for static Components.\r
19 * For Containers, this property reflects interactiveness of the\r
20 * Container itself, not its children. See {@link #isFocusable}.\r
21 *\r
22 * **Note:** Plain components are static, so not focusable.\r
23 */\r
24 focusable: false,\r
25 \r
26 /**\r
27 * @property {Ext.dom.Element} focusEl The component's focusEl.\r
28 * Available after rendering.\r
29 * @private\r
30 */\r
31 \r
32 /**\r
33 * @cfg {Number} [tabIndex] DOM tabIndex attribute for this Focusable's\r
34 * focusEl.\r
35 */\r
36 \r
37 /**\r
38 * @cfg {String} [focusCls='x-focus'] CSS class that will be added to focused\r
39 * Component, and removed when Component blurs.\r
40 */\r
41 focusCls: 'focus',\r
42 \r
43 /**\r
44 * @event focus\r
45 * Fires when this Component receives focus.\r
46 * @param {Ext.Component} this\r
47 * @param {Ext.event.Event} event The focus event.\r
48 */\r
49\r
50 /**\r
51 * @event blur\r
52 * Fires when this Component loses focus.\r
53 * @param {Ext.Component} this\r
54 * @param {Ext.event.Event} event The blur event.\r
55 */\r
56 \r
57 /**\r
58 * @event focusenter\r
59 * Fires when focus enters this Component's hierarchy.\r
60 * @param {Ext.Component} this\r
61 * @param {Ext.event.Event} event The focusenter event.\r
62 */\r
63 \r
64 /**\r
65 * @event focusleave\r
66 * Fires when focus leaves this Component's hierarchy.\r
67 * @param {Ext.Component} this\r
68 * @param {Ext.event.Event} event The focusleave event.\r
69 */\r
70 \r
71 /**\r
72 * Template method to do any Focusable related initialization that\r
73 * does not involve event listeners creation.\r
74 * @protected\r
75 */\r
76 initFocusable: Ext.emptyFn,\r
77 \r
78 /**\r
79 * Template method to do any event listener initialization for a Focusable.\r
80 * This generally happens after the focusEl is available.\r
81 * @protected\r
82 */\r
83 initFocusableEvents: function() {\r
84 // If *not* naturally focusable, then we look for the tabIndex property\r
85 // to be defined which indicates that the element should be made focusable.\r
86 this.initFocusableElement();\r
87 },\r
88 \r
89 /**\r
90 * Returns the focus styling holder element associated with this Focusable.\r
91 * By default it is the same element as {@link #getFocusEl getFocusEl}.\r
92 *\r
93 * @return {Ext.Element} The focus styling element.\r
94 * @protected\r
95 */\r
96 getFocusClsEl: function() {\r
97 return this.getFocusEl();\r
98 },\r
99\r
100 /**\r
101 * Returns the focus holder element associated with this Focusable. At the\r
102 * level of the Focusable base, this function returns `this.el` (or for Widgets,\r
103 * `this.element`).\r
104 *\r
105 * Subclasses with embedded focusable elements (such as Window, Field and Button)\r
106 * should override this for use by {@link Ext.util.Focusable#method-focus focus}\r
107 * method.\r
108 *\r
109 * @return {Ext.Element}\r
110 * @protected\r
111 */\r
112 getFocusEl: function() {\r
113 return this.element || this.el;\r
114 },\r
115\r
116 destroyFocusable: function() {\r
117 var me = this;\r
118 \r
119 Ext.destroy(me.focusListeners);\r
120 \r
121 me.focusListeners = me.focusEnterEvent = me.focusTask = null;\r
122 me.focusEl = me.ariaEl = null;\r
123 },\r
124\r
125 enableFocusable: Ext.emptyFn,\r
126\r
127 disableFocusable: function() {\r
128 var me = this,\r
129 focusTarget,\r
130 focusCls = me.focusCls,\r
131 focusClsEl;\r
132\r
133 // If this is disabled while focused, by default, focus would return to document.body.\r
134 // This must be avoided, both for the convenience of keyboard users, and also\r
135 // for when focus is tracked within a tree, such as below an expanded ComboBox.\r
136 if (me.hasFocus) {\r
137 focusTarget = me.findFocusTarget();\r
138 if (focusTarget) {\r
139 focusTarget.focus();\r
140 }\r
141 }\r
142 focusClsEl = me.getFocusClsEl();\r
143 \r
144 if (focusCls && focusClsEl) {\r
145 focusClsEl.removeCls(me.removeClsWithUI(focusCls, true));\r
146 }\r
147 },\r
148 \r
149 /**\r
150 * Determine if this Focusable can receive focus at this time.\r
151 *\r
152 * Note that Containers can be non-focusable themselves while delegating\r
153 * focus treatment to a child Component; see\r
154 * {@link Ext.container.Container #defaultFocus} for more information.\r
155 *\r
156 * @param {Boolean} [deep=false] Optionally determine if the container itself\r
157 * is focusable, or if container's focus is delegated to a child component\r
158 * and that child is focusable.\r
159 *\r
160 * @return {Boolean} True if component is focusable, false if not.\r
161 */\r
162 isFocusable: function(deep) {\r
163 var me = this,\r
164 focusEl;\r
165 \r
166 if (!me.focusable && (!me.isContainer || !deep)) {\r
167 return false;\r
168 }\r
169 \r
170 focusEl = me.getFocusEl();\r
171 \r
172 if (focusEl && me.canFocus()) {\r
173\r
174 // getFocusEl might return a Component if a Container wishes to\r
175 // delegate focus to a descendant. Both Component and Element\r
176 // implement isFocusable, so always ask that.\r
177 return focusEl.isFocusable(deep);\r
178 }\r
179 \r
180 return false;\r
181 },\r
182 \r
183 canFocus: function(/* private */ skipVisibility, includeFocusTarget) {\r
184 var me = this,\r
185 canFocus;\r
186 \r
187 // Containers may have focusable children while being non-focusable\r
188 // themselves; this is why we only account for me.focusable for\r
189 // ordinary Components here and below.\r
190 canFocus = (me.isContainer || me.focusable) && me.rendered &&\r
191 !me.destroying && !me.destroyed && !me.disabled &&\r
192 (skipVisibility || me.isVisible(true));\r
193 \r
194 return canFocus || (includeFocusTarget && !!me.findFocusTarget());\r
195 },\r
196 \r
197 /**\r
198 * Try to focus this component.\r
199 *\r
200 * If this component is disabled, a close relation will be targeted for focus instead\r
201 * to keep focus localized for keyboard users.\r
202 * @param {Mixed} [selectText] If applicable, `true` to also select all the text in this component, or an array consisting of start and end (defaults to start) position of selection.\r
203 * @param {Boolean/Number} [delay] Delay the focus this number of milliseconds (true for 10 milliseconds).\r
204 * @param {Function} [callback] Only needed if the `delay` parameter is used. A function to call upon focus.\r
205 * @param {Function} [scope] Only needed if the `delay` parameter is used. The scope (`this` reference) in which to execute the callback.\r
206 * @return {Ext.Component} The focused Component. Usually `this` Component. Some Containers may\r
207 * delegate focus to a descendant Component ({@link Ext.window.Window Window}s can do this through their\r
208 * {@link Ext.window.Window#defaultFocus defaultFocus} config option. If this component is disabled, a closely\r
209 * related component will be focused and that will be returned.\r
210 */\r
211 focus: function(selectText, delay, callback, scope) {\r
212 var me = this,\r
213 focusTarget, focusElDom, containerScrollTop;\r
214\r
215 if ((!me.focusable && !me.isContainer) || me.destroyed || me.destroying) {\r
216 return;\r
217 }\r
218\r
219 // If delay is wanted, queue a call to this function.\r
220 if (delay) {\r
221 me.getFocusTask().delay(Ext.isNumber(delay) ? delay : 10, me.focus, me, [selectText, false, callback, scope]);\r
222 return me;\r
223 }\r
224\r
225 // An immediate focus call must cancel any outstanding delayed focus calls.\r
226 me.cancelFocus();\r
227 \r
228 // Assignment in conditional here to avoid calling getFocusEl()\r
229 // if me.canFocus() returns false\r
230 if (me.canFocus()) {\r
231 if (focusTarget = me.getFocusEl()) {\r
232\r
233 // getFocusEl might return a Component if a Container wishes to delegate focus to a\r
234 // descendant via its defaultFocus configuration.\r
235 if (focusTarget.isComponent) {\r
236 return focusTarget.focus(selectText, delay, callback, scope);\r
237 }\r
238\r
239 focusElDom = focusTarget.dom;\r
240\r
241 // If it was an Element with a dom property\r
242 if (focusElDom) {\r
243 if (me.floating) {\r
244 containerScrollTop = me.container.dom.scrollTop;\r
245 }\r
246\r
247 // Focus the element.\r
248 // The Ext.event.publisher.Focus publisher listens for global focus changes and\r
249 // The ComponentManager responds by invoking the onFocusEnter and onFocusLeave methods\r
250 // of the components involved.\r
251 focusTarget.focus();\r
252\r
253 if (selectText) {\r
254 if (Ext.isArray(selectText)) {\r
255 if (me.selectText) {\r
256 me.selectText.apply(me, selectText);\r
257 }\r
258 } else if (focusElDom.select) {\r
259 // This method both focuses and selects the element.\r
260 focusElDom.select();\r
261 } else if (me.selectText) {\r
262 me.selectText();\r
263 }\r
264 }\r
265\r
266 // Call the callback when focus is done\r
267 Ext.callback(callback, scope);\r
268 }\r
269\r
270 // Focusing a floating Component brings it to the front of its stack.\r
271 // this is performed by its zIndexManager. Pass preventFocus true to avoid recursion.\r
272 if (me.floating) {\r
273 if (containerScrollTop !== undefined) {\r
274 me.container.dom.scrollTop = containerScrollTop;\r
275 }\r
276 }\r
277 }\r
278 }\r
279 else {\r
280 // If we are asked to focus while not able to focus though disablement/invisibility etc,\r
281 // focus may revert to document.body if the current focus is being hidden or destroyed.\r
282 // This must be avoided, both for the convenience of keyboard users, and also\r
283 // for when focus is tracked within a tree, such as below an expanded ComboBox.\r
284 focusTarget = me.findFocusTarget();\r
285 \r
286 if (focusTarget) {\r
287 return focusTarget.focus(selectText, delay, callback, scope);\r
288 }\r
289 }\r
290\r
291 return me;\r
292 },\r
293\r
294 /**\r
295 * Cancel any deferred focus on this component\r
296 * @protected\r
297 */\r
298 cancelFocus: function() {\r
299 var task = this.getFocusTask();\r
300\r
301 if (task) {\r
302 task.cancel();\r
303 }\r
304 },\r
305 \r
306 /**\r
307 * @method\r
308 * Template method to do any pre-blur processing.\r
309 * @protected\r
310 * @param {Ext.event.Event} e The event object\r
311 */\r
312 beforeBlur: Ext.emptyFn,\r
313\r
314 /**\r
315 * @private\r
316 */\r
317 onBlur: function(e) {\r
318 var me = this,\r
319 container = me.focusableContainer,\r
320 focusCls = me.focusCls,\r
321 focusClsEl;\r
322 \r
323 if (!me.focusable || me.destroying) {\r
324 return;\r
325 }\r
326\r
327 me.beforeBlur(e);\r
328 \r
329 if (container) {\r
330 container.beforeFocusableChildBlur(me, e);\r
331 }\r
332 \r
333 focusClsEl = me.getFocusClsEl();\r
334 \r
335 if (focusCls && focusClsEl) {\r
336 focusClsEl.removeCls(me.removeClsWithUI(focusCls, true));\r
337 }\r
338 \r
339 if (me.validateOnBlur) {\r
340 me.validate();\r
341 }\r
342 \r
343 me.hasFocus = false;\r
344 me.fireEvent('blur', me, e);\r
345 me.postBlur(e);\r
346 \r
347 if (container) {\r
348 container.afterFocusableChildBlur(me, e);\r
349 }\r
350 },\r
351\r
352 /**\r
353 * @method\r
354 * Template method to do any post-blur processing.\r
355 * @protected\r
356 * @param {Ext.event.Event} e The event object\r
357 */\r
358 postBlur: Ext.emptyFn,\r
359\r
360 /**\r
361 * @method\r
362 * Template method to do any pre-focus processing.\r
363 * @protected\r
364 * @param {Ext.event.Event} e The event object\r
365 */\r
366 beforeFocus: Ext.emptyFn,\r
367\r
368 /**\r
369 * @private\r
370 */\r
371 onFocus: function(e) {\r
372 var me = this,\r
373 container = me.focusableContainer,\r
374 focusCls = me.focusCls,\r
375 focusClsEl;\r
376 \r
377 if (!me.focusable) {\r
378 return;\r
379 }\r
380\r
381 if (me.canFocus()) {\r
382 me.beforeFocus(e);\r
383 \r
384 if (container) {\r
385 container.beforeFocusableChildFocus(me, e);\r
386 }\r
387 \r
388 focusClsEl = me.getFocusClsEl();\r
389 \r
390 if (focusCls && focusClsEl) {\r
391 focusClsEl.addCls(me.addClsWithUI(focusCls, true));\r
392 }\r
393 \r
394 if (!me.hasFocus) {\r
395 me.hasFocus = true;\r
396 me.fireEvent('focus', me, e);\r
397 }\r
398 \r
399 me.postFocus(e);\r
400 \r
401 if (container) {\r
402 container.afterFocusableChildFocus(me, e);\r
403 }\r
404 }\r
405 },\r
406 \r
407 /**\r
408 * @method\r
409 * Template method to do any post-focus processing.\r
410 * @protected\r
411 * @param {Ext.event.Event} e The event object\r
412 */\r
413 postFocus: Ext.emptyFn,\r
414 \r
415 /**\r
416 * Return the actual tabIndex for this Focusable.\r
417 *\r
418 * @return {Number} tabIndex attribute value\r
419 */\r
420 getTabIndex: function() {\r
421 var me = this,\r
422 el, index;\r
423 \r
424 if (!me.focusable) {\r
425 return;\r
426 }\r
427 \r
428 el = me.rendered && me.getFocusEl();\r
429 \r
430 if (el) {\r
431 // getFocusEl may return a child Component\r
432 if (el.isComponent) {\r
433 index = el.getTabIndex();\r
434 }\r
435 \r
436 // We can't query el.dom.tabIndex because IE8 will return 0\r
437 // when tabIndex attribute is not present.\r
438 else if (el.isElement) {\r
439 index = el.getAttribute('tabIndex');\r
440 }\r
441 \r
442 // A component can be configured with el: '#id' to look up\r
443 // its main element from the DOM rather than render it; in\r
444 // such case getTabIndex() may happen to be called before\r
445 // said lookup has happened; indeterminate result follows.\r
446 else {\r
447 return;\r
448 }\r
449 \r
450 me.tabIndex = index;\r
451 }\r
452 else {\r
453 index = me.tabIndex;\r
454 }\r
455 \r
456 return index - 0; // getAttribute returns a string\r
457 },\r
458 \r
459 /**\r
460 * Set the tabIndex property for this Focusable. If the focusEl\r
461 * is avalable, set tabIndex attribute on it, too.\r
462 *\r
463 * @param {Number} newTabIndex new tabIndex to set\r
464 */\r
465 setTabIndex: function(newTabIndex, /* private */ focusEl) {\r
466 var me = this,\r
467 el;\r
468 \r
469 if (!me.focusable) {\r
470 return;\r
471 }\r
472 \r
473 me.tabIndex = newTabIndex;\r
474 \r
475 if (!me.rendered) {\r
476 return;\r
477 }\r
478 \r
479 el = focusEl || me.getFocusEl();\r
480 \r
481 if (el) {\r
482 // getFocusEl may return a child Component\r
483 if (el.isComponent) {\r
484 el.setTabIndex(newTabIndex);\r
485 }\r
486 \r
487 // Or if a component is configured with el: '#id', it may\r
488 // still be a string by the time setTabIndex is called from\r
489 // FocusableContainer.\r
490 else if (el.isElement) {\r
491 el.set({ tabIndex: newTabIndex });\r
492 }\r
493 }\r
494 },\r
495 \r
496 /**\r
497 * @template\r
498 * @protected\r
499 * Called when focus enters this Component's hierarchy\r
500 * @param {Object} e\r
501 * @param {Ext.event.Event} e.event The underlying DOM event.\r
502 * @param {HTMLElement} e.target The element gaining focus.\r
503 * @param {HTMLElement} e.relatedTarget The element losing focus.\r
504 * @param {Ext.Component} e.toComponent The Component gaining focus.\r
505 * @param {Ext.Component} e.fromComponent The Component losing focus.\r
506 */\r
507 onFocusEnter: function(e) {\r
508 var me = this;\r
509\r
510 // Focusing must being a floating component to the front.\r
511 // Only bring to front if this component is not the manager's\r
512 // topmost component (may be a result of focusOnToFront).\r
513 if (me.floating && me !== me.zIndexManager.getActive()) {\r
514 me.toFront(true);\r
515 }\r
516\r
517 // Save all information about how we received focus so that\r
518 // we can do appropriate things when asked to revertFocus\r
519 me.focusEnterEvent = e;\r
520 me.containsFocus = true;\r
521 me.fireEvent('focusenter', me, e);\r
522 },\r
523\r
524 /**\r
525 * @template\r
526 * @protected\r
527 * Called when focus exits from this Component's hierarchy\r
528 * @param {Ext.event.Event} e\r
529 * @param {Ext.event.Event} e.event The underlying DOM event.\r
530 * @param {HTMLElement} e.target The element gaining focus.\r
531 * @param {HTMLElement} e.relatedTarget The element losing focus.\r
532 * @param {Ext.Component} e.toComponent The Component gaining focus.\r
533 * @param {Ext.Component} e.fromComponent The Component losing focus.\r
534 */\r
535 onFocusLeave: function(e) {\r
536 var me = this;\r
537\r
538 me.focusEnterEvent = null;\r
539 me.containsFocus = false;\r
540 me.fireEvent('focusleave', me, e);\r
541 },\r
542\r
543 privates: {\r
544 \r
545 /**\r
546 * Returns focus to the cached previously focused Component or element.\r
547 *\r
548 * Usually called by onHide.\r
549 *\r
550 * @private\r
551 */\r
552 revertFocus: function() {\r
553 var me = this,\r
554 previousFocus = me.previousFocus,\r
555 focusEvent = me.focusEnterEvent,\r
556 focusTarget;\r
557 \r
558 me.previousFocus = null;\r
559 \r
560 // Before hiding, restore focus to what was focused when we were shown\r
561 // unless we're explicitly told not to (think Panel collapse/expand).\r
562 if (me.preventRefocus || !me.el.contains(Ext.Element.getActiveElement())) {\r
563 return;\r
564 }\r
565 \r
566 // Floating menus need an ability to specify previous focus explicitly,\r
567 // they take it from their parent upon show. Otherwise they would try to\r
568 // refocus the menu item that opened the submenu, which is pointless.\r
569 focusTarget = previousFocus || (focusEvent && focusEvent.fromComponent);\r
570 \r
571 // If reverting back to a Component, it will re-route to a close focusable relation\r
572 // if it is not now focusable. But check that it's a Component because it can be\r
573 // a Widget instead!\r
574 if (focusTarget) {\r
575 // Components have very useful canFocus method that we can use\r
576 // to determine if the component can either focus itself, or find\r
577 // another related target to focus. It is important that we don't\r
578 // just blindly try to focus something for the sake of it!\r
579 if (focusTarget.canFocus && focusTarget.canFocus(false, true)) {\r
580 focusTarget.focus();\r
581 \r
582 return;\r
583 }\r
584 else {\r
585 // focusTarget can be a plain DOM element, too\r
586 if (focusTarget.nodeType) {\r
587 focusTarget = Ext.fly(focusTarget);\r
588 }\r
589 \r
590 // Element can do a simple check to see if it's focusable.\r
591 if (focusTarget.isFocusable && focusTarget.isFocusable()) {\r
592 focusTarget.focus();\r
593 \r
594 return;\r
595 }\r
596 }\r
597 }\r
598 \r
599 // Try falling back to the relatedTarget of the focus event. If that\r
600 // doesn't work, there's nothing else we can do. :(\r
601 if (focusEvent) {\r
602 focusTarget = Ext.fly(focusEvent.relatedTarget);\r
603 \r
604 // TODO: Remove extra check when IE8 retires.\r
605 if (Ext.isIE8 || (focusTarget.isFocusable && focusTarget.isFocusable())) {\r
606 focusTarget.focus();\r
607 }\r
608 }\r
609 },\r
610\r
611 /**\r
612 * Finds an alternate Component to focus if this Component is disabled while focused, or\r
613 * focused while disabled, or otherwise unable to focus.\r
614 * \r
615 * In both cases, focus must not be lost to document.body, but must move to an intuitively\r
616 * connectible Component, either a sibling, or uncle or nephew.\r
617 *\r
618 * This is both for the convenience of keyboard users, and also for when focus is tracked\r
619 * within a Component tree such as for ComboBoxes and their dropdowns.\r
620 *\r
621 * For example, a ComboBox with a PagingToolbar in is BoundList. If the "Next Page"\r
622 * button is hit, the LoadMask shows and focuses, the next page is the last page, so\r
623 * the "Next Page" button is disabled. When the LoadMask hides, it attempt to focus the\r
624 * last focused Component which is the disabled "Next Page" button. In this situation,\r
625 * focus should move to a sibling within the PagingToolbar.\r
626 * \r
627 * @return {Ext.Component} A closely related focusable Component to which focus can move.\r
628 * @private\r
629 */\r
630 findFocusTarget: function() {\r
631 var me = this,\r
632 owner,\r
633 focusTargets;\r
634\r
635 for (owner = me.up(':not([disabled])'); owner; owner = owner.up(':not([disabled])')) {\r
636 // Use CQ to find a target that is focusable, and not this Component.\r
637 // Cannot use owner.child() because the parent might not be a Container.\r
638 // Non-Container Components may still have ownership relationships with\r
639 // other Components. eg: BoundList with PagingToolbar\r
640 focusTargets = Ext.ComponentQuery.query(':focusable:not([hasFocus])', owner);\r
641 if (focusTargets.length) {\r
642 return focusTargets[0];\r
643 }\r
644\r
645 // We found no focusable siblings in our owner, but the owner may itself be focusable,\r
646 // it is not always a Container - could be the owning Field of a BoundList.\r
647 if (owner.isFocusable && owner.isFocusable()) {\r
648 return owner;\r
649 }\r
650 }\r
651 },\r
652 \r
653 /**\r
654 * Sets up the focus listener on this Component's {@link #getFocusEl focusEl} if it has one.\r
655 *\r
656 * Form Components which must implicitly participate in tabbing order usually have a naturally\r
657 * focusable element as their {@link #getFocusEl focusEl}, and it is the DOM event of that\r
658 * receiving focus which drives the Component's `onFocus` handling, and the DOM event of it\r
659 * being blurred which drives the `onBlur` handling.\r
660 * @private\r
661 */\r
662 initFocusableElement: function() {\r
663 var me = this,\r
664 tabIndex = me.tabIndex,\r
665 focusEl = me.getFocusEl();\r
666\r
667 if (focusEl && !focusEl.isComponent) {\r
668 // Cache focusEl as a property for speedier lookups\r
669 me.focusEl = focusEl;\r
670 \r
671 // focusEl is not available until after rendering, and rendering tabIndex\r
672 // into focusEl is not always convenient. So we apply it here if Component's\r
673 // tabIndex property is set and Component is otherwise focusable.\r
674 if (tabIndex != null && me.canFocus(true)) {\r
675 me.setTabIndex(tabIndex, focusEl);\r
676 }\r
677 \r
678 // This attribute is a shortcut to look up a Component by its Elements\r
679 // It only makes sense on focusable elements, so we set it here\r
680 focusEl.dom.setAttribute('data-componentid', me.id);\r
681 \r
682 // Only focusable components can be keyboard-interactive\r
683 if (me.config.keyHandlers) {\r
684 me.initKeyHandlers(focusEl);\r
685 }\r
686 }\r
687 },\r
688\r
689 /**\r
690 * @private\r
691 */\r
692 getFocusTask: function() {\r
693 if (!this.focusTask) {\r
694 this.focusTask = Ext.focusTask;\r
695 }\r
696 \r
697 return this.focusTask;\r
698 },\r
699 \r
700 /**\r
701 * @private\r
702 */\r
703 handleFocusEvent: function(e) {\r
704 var event;\r
705 \r
706 // handleFocusEvent and handleBlurEvent are called by ComponentManager\r
707 // passing the normalized element event that might or might not cause\r
708 // component focus or blur. The component itself makes the decision\r
709 // whether focus/blur happens or not. This is necessary for components\r
710 // that might have more than one focusable element within the component's\r
711 // DOM structure, like Ext.button.Split.\r
712 if (this.isFocusing(e)) {\r
713 event = new Ext.event.Event(e.event);\r
714 event.type = 'focus';\r
715 event.relatedTarget = e.fromElement;\r
716 event.target = e.toElement;\r
717 \r
718 this.onFocus(event);\r
719 }\r
720 },\r
721 \r
722 /**\r
723 * @private\r
724 */\r
725 handleBlurEvent: function(e) {\r
726 var event;\r
727 \r
728 if (this.isBlurring(e)) {\r
729 event = new Ext.event.Event(e.event);\r
730 event.type = 'blur';\r
731 event.target = e.fromElement;\r
732 event.relatedTarget = e.toElement;\r
733 \r
734 this.onBlur(event);\r
735 }\r
736 },\r
737 \r
738 /**\r
739 * @private\r
740 */\r
741 isFocusing: function(e) {\r
742 var from = e.fromElement,\r
743 to = e.toElement,\r
744 focusEl;\r
745 \r
746 if (this.focusable) {\r
747 focusEl = this.getFocusEl();\r
748 \r
749 if (focusEl) {\r
750 if (focusEl.isComponent) {\r
751 return focusEl.isFocusing(from, to);\r
752 }\r
753 else {\r
754 return to === focusEl.dom && from !== to;\r
755 }\r
756 }\r
757 }\r
758 \r
759 return false;\r
760 },\r
761 \r
762 /**\r
763 * @private\r
764 */\r
765 isBlurring: function(e) {\r
766 var from = e.fromElement,\r
767 to = e.toElement,\r
768 focusEl;\r
769 \r
770 if (this.focusable) {\r
771 focusEl = this.getFocusEl();\r
772 \r
773 if (focusEl) {\r
774 if (focusEl.isComponent) {\r
775 return focusEl.isBlurring(from, to);\r
776 }\r
777 else {\r
778 return from === focusEl.dom && from !== to;\r
779 }\r
780 }\r
781 }\r
782 \r
783 return false;\r
784 },\r
785 \r
786 /**\r
787 * @private\r
788 */\r
789 blur: function() {\r
790 var me = this,\r
791 focusEl;\r
792 \r
793 if (!me.focusable || !me.canFocus()) {\r
794 return;\r
795 }\r
796 \r
797 focusEl = me.getFocusEl();\r
798\r
799 if (focusEl) {\r
800 me.blurring = true;\r
801 focusEl.blur();\r
802 delete me.blurring;\r
803 }\r
804 \r
805 return me;\r
806 },\r
807 \r
808 disableTabbing: function() {\r
809 var me = this,\r
810 el = me.el,\r
811 focusEl;\r
812 \r
813 if (el) {\r
814 el.saveTabbableState();\r
815 }\r
816 \r
817 focusEl = me.getFocusEl();\r
818 \r
819 if (focusEl) {\r
820 // focusEl may happen to be a focus delegate for a container\r
821 if (focusEl.isComponent) {\r
822 focusEl.disableTabbing();\r
823 }\r
824 \r
825 // Alternatively focusEl may happen to be outside of the main el,\r
826 // or else it can be a string reference to an element that\r
827 // has not been resolved yet\r
828 else if (focusEl.isElement && el && !el.contains(focusEl)) {\r
829 focusEl.saveTabbableState();\r
830 }\r
831 }\r
832 },\r
833 \r
834 enableTabbing: function() {\r
835 var me = this,\r
836 el = me.el,\r
837 focusEl;\r
838 \r
839 focusEl = me.getFocusEl();\r
840 \r
841 if (focusEl) {\r
842 if (focusEl.isComponent) {\r
843 focusEl.enableTabbing();\r
844 }\r
845 else if (focusEl.isElement && el && !el.contains(focusEl)) {\r
846 focusEl.restoreTabbableState();\r
847 }\r
848 }\r
849 \r
850 if (el) {\r
851 el.restoreTabbableState();\r
852 }\r
853 }\r
854 }\r
855},\r
856\r
857function() {\r
858 // One global DelayedTask to assign focus\r
859 // So that the last focus call wins.\r
860 if (!Ext.focusTask) {\r
861 Ext.focusTask = new Ext.util.DelayedTask();\r
862 }\r
863});\r