Upgrade to ExtJS 4.0.2 - Released 06/09/2011
[extjs.git] / docs / source / FocusManager.html
1 <!DOCTYPE html>
2 <html>
3 <head>
4   <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
5   <title>The source code</title>
6   <link href="../prettify/prettify.css" type="text/css" rel="stylesheet" />
7   <script type="text/javascript" src="../prettify/prettify.js"></script>
8   <style type="text/css">
9     .highlight { display: block; background-color: #ddd; }
10   </style>
11   <script type="text/javascript">
12     function highlight() {
13       document.getElementById(location.hash.replace(/#/, "")).className = "highlight";
14     }
15   </script>
16 </head>
17 <body onload="prettyPrint(); highlight();">
18   <pre class="prettyprint lang-js"><span id='Ext-FocusManager'>/**
19 </span> * @class Ext.FocusManager
20
21 The FocusManager is responsible for globally:
22
23 1. Managing component focus
24 2. Providing basic keyboard navigation
25 3. (optional) Provide a visual cue for focused components, in the form of a focus ring/frame.
26
27 To activate the FocusManager, simply call {@link #enable `Ext.FocusManager.enable();`}. In turn, you may
28 deactivate the FocusManager by subsequently calling {@link #disable `Ext.FocusManager.disable();`}.  The
29 FocusManager is disabled by default.
30
31 To enable the optional focus frame, pass `true` or `{focusFrame: true}` to {@link #enable}.
32
33 Another feature of the FocusManager is to provide basic keyboard focus navigation scoped to any {@link Ext.container.Container}
34 that would like to have navigation between its child {@link Ext.Component}'s. The {@link Ext.container.Container} can simply
35 call {@link #subscribe Ext.FocusManager.subscribe} to take advantage of this feature, and can at any time call
36 {@link #unsubscribe Ext.FocusManager.unsubscribe} to turn the navigation off.
37
38  * @singleton
39  * @markdown
40  * @author Jarred Nicholls &lt;jarred@sencha.com&gt;
41  * @docauthor Jarred Nicholls &lt;jarred@sencha.com&gt;
42  */
43 Ext.define('Ext.FocusManager', {
44     singleton: true,
45     alternateClassName: 'Ext.FocusMgr',
46
47     mixins: {
48         observable: 'Ext.util.Observable'
49     },
50
51     requires: [
52         'Ext.ComponentManager',
53         'Ext.ComponentQuery',
54         'Ext.util.HashMap',
55         'Ext.util.KeyNav'
56     ],
57
58 <span id='Ext-FocusManager-property-enabled'>    /**
59 </span>     * @property {Boolean} enabled
60      * Whether or not the FocusManager is currently enabled
61      */
62     enabled: false,
63
64 <span id='Ext-FocusManager-property-focusedCmp'>    /**
65 </span>     * @property {Ext.Component} focusedCmp
66      * The currently focused component. Defaults to `undefined`.
67      * @markdown
68      */
69
70     focusElementCls: Ext.baseCSSPrefix + 'focus-element',
71
72     focusFrameCls: Ext.baseCSSPrefix + 'focus-frame',
73
74 <span id='Ext-FocusManager-property-whitelist'>    /**
75 </span>     * @property {Array} whitelist
76      * A list of xtypes that should ignore certain navigation input keys and
77      * allow for the default browser event/behavior. These input keys include:
78      *
79      * 1. Backspace
80      * 2. Delete
81      * 3. Left
82      * 4. Right
83      * 5. Up
84      * 6. Down
85      *
86      * The FocusManager will not attempt to navigate when a component is an xtype (or descendents thereof)
87      * that belongs to this whitelist. E.g., an {@link Ext.form.field.Text} should allow
88      * the user to move the input cursor left and right, and to delete characters, etc.
89      *
90      * This whitelist currently defaults to `['textfield']`.
91      * @markdown
92      */
93     whitelist: [
94         'textfield'
95     ],
96
97     tabIndexWhitelist: [
98         'a',
99         'button',
100         'embed',
101         'frame',
102         'iframe',
103         'img',
104         'input',
105         'object',
106         'select',
107         'textarea'
108     ],
109
110     constructor: function() {
111         var me = this,
112             CQ = Ext.ComponentQuery;
113
114         me.addEvents(
115 <span id='Ext-FocusManager-event-beforecomponentfocus'>            /**
116 </span>             * @event beforecomponentfocus
117              * Fires before a component becomes focused. Return `false` to prevent
118              * the component from gaining focus.
119              * @param {Ext.FocusManager} fm A reference to the FocusManager singleton
120              * @param {Ext.Component} cmp The component that is being focused
121              * @param {Ext.Component} previousCmp The component that was previously focused,
122              * or `undefined` if there was no previously focused component.
123              * @markdown
124              */
125             'beforecomponentfocus',
126
127 <span id='Ext-FocusManager-event-componentfocus'>            /**
128 </span>             * @event componentfocus
129              * Fires after a component becomes focused.
130              * @param {Ext.FocusManager} fm A reference to the FocusManager singleton
131              * @param {Ext.Component} cmp The component that has been focused
132              * @param {Ext.Component} previousCmp The component that was previously focused,
133              * or `undefined` if there was no previously focused component.
134              * @markdown
135              */
136             'componentfocus',
137
138 <span id='Ext-FocusManager-event-disable'>            /**
139 </span>             * @event disable
140              * Fires when the FocusManager is disabled
141              * @param {Ext.FocusManager} fm A reference to the FocusManager singleton
142              */
143             'disable',
144
145 <span id='Ext-FocusManager-event-enable'>            /**
146 </span>             * @event enable
147              * Fires when the FocusManager is enabled
148              * @param {Ext.FocusManager} fm A reference to the FocusManager singleton
149              */
150             'enable'
151         );
152
153         // Setup KeyNav that's bound to document to catch all
154         // unhandled/bubbled key events for navigation
155         me.keyNav = Ext.create('Ext.util.KeyNav', Ext.getDoc(), {
156             disabled: true,
157             scope: me,
158
159             backspace: me.focusLast,
160             enter: me.navigateIn,
161             esc: me.navigateOut,
162             tab: me.navigateSiblings
163
164             //space: me.navigateIn,
165             //del: me.focusLast,
166             //left: me.navigateSiblings,
167             //right: me.navigateSiblings,
168             //down: me.navigateSiblings,
169             //up: me.navigateSiblings
170         });
171
172         me.focusData = {};
173         me.subscribers = Ext.create('Ext.util.HashMap');
174         me.focusChain = {};
175
176         // Setup some ComponentQuery pseudos
177         Ext.apply(CQ.pseudos, {
178             focusable: function(cmps) {
179                 var len = cmps.length,
180                     results = [],
181                     i = 0,
182                     c,
183
184                     isFocusable = function(x) {
185                         return x &amp;&amp; x.focusable !== false &amp;&amp; CQ.is(x, '[rendered]:not([destroying]):not([isDestroyed]):not([disabled]){isVisible(true)}{el &amp;&amp; c.el.dom &amp;&amp; c.el.isVisible()}');
186                     };
187
188                 for (; i &lt; len; i++) {
189                     c = cmps[i];
190                     if (isFocusable(c)) {
191                         results.push(c);
192                     }
193                 }
194
195                 return results;
196             },
197
198             nextFocus: function(cmps, idx, step) {
199                 step = step || 1;
200                 idx = parseInt(idx, 10);
201
202                 var len = cmps.length,
203                     i = idx + step,
204                     c;
205
206                 for (; i != idx; i += step) {
207                     if (i &gt;= len) {
208                         i = 0;
209                     } else if (i &lt; 0) {
210                         i = len - 1;
211                     }
212
213                     c = cmps[i];
214                     if (CQ.is(c, ':focusable')) {
215                         return [c];
216                     } else if (c.placeholder &amp;&amp; CQ.is(c.placeholder, ':focusable')) {
217                         return [c.placeholder];
218                     }
219                 }
220
221                 return [];
222             },
223
224             prevFocus: function(cmps, idx) {
225                 return this.nextFocus(cmps, idx, -1);
226             },
227
228             root: function(cmps) {
229                 var len = cmps.length,
230                     results = [],
231                     i = 0,
232                     c;
233
234                 for (; i &lt; len; i++) {
235                     c = cmps[i];
236                     if (!c.ownerCt) {
237                         results.push(c);
238                     }
239                 }
240
241                 return results;
242             }
243         });
244     },
245
246 <span id='Ext-FocusManager-method-addXTypeToWhitelist'>    /**
247 </span>     * Adds the specified xtype to the {@link #whitelist}.
248      * @param {String/Array} xtype Adds the xtype(s) to the {@link #whitelist}.
249      */
250     addXTypeToWhitelist: function(xtype) {
251         var me = this;
252
253         if (Ext.isArray(xtype)) {
254             Ext.Array.forEach(xtype, me.addXTypeToWhitelist, me);
255             return;
256         }
257
258         if (!Ext.Array.contains(me.whitelist, xtype)) {
259             me.whitelist.push(xtype);
260         }
261     },
262
263     clearComponent: function(cmp) {
264         clearTimeout(this.cmpFocusDelay);
265         if (!cmp.isDestroyed) {
266             cmp.blur();
267         }
268     },
269
270 <span id='Ext-FocusManager-method-disable'>    /**
271 </span>     * Disables the FocusManager by turning of all automatic focus management and keyboard navigation
272      */
273     disable: function() {
274         var me = this;
275
276         if (!me.enabled) {
277             return;
278         }
279
280         delete me.options;
281         me.enabled = false;
282
283         Ext.ComponentManager.all.un('add', me.onComponentCreated, me);
284
285         me.removeDOM();
286
287         // Stop handling key navigation
288         me.keyNav.disable();
289
290         // disable focus for all components
291         me.setFocusAll(false);
292
293         me.fireEvent('disable', me);
294     },
295
296 <span id='Ext-FocusManager-method-enable'>    /**
297 </span>     * Enables the FocusManager by turning on all automatic focus management and keyboard navigation
298      * @param {Boolean/Object} options Either `true`/`false` to turn on the focus frame, or an object of the following options:
299         - focusFrame : Boolean
300             `true` to show the focus frame around a component when it is focused. Defaults to `false`.
301      * @markdown
302      */
303     enable: function(options) {
304         var me = this;
305
306         if (options === true) {
307             options = { focusFrame: true };
308         }
309         me.options = options = options || {};
310
311         if (me.enabled) {
312             return;
313         }
314
315         // Handle components that are newly added after we are enabled
316         Ext.ComponentManager.all.on('add', me.onComponentCreated, me);
317
318         me.initDOM(options);
319
320         // Start handling key navigation
321         me.keyNav.enable();
322
323         // enable focus for all components
324         me.setFocusAll(true, options);
325
326         // Finally, let's focus our global focus el so we start fresh
327         me.focusEl.focus();
328         delete me.focusedCmp;
329
330         me.enabled = true;
331         me.fireEvent('enable', me);
332     },
333
334     focusLast: function(e) {
335         var me = this;
336
337         if (me.isWhitelisted(me.focusedCmp)) {
338             return true;
339         }
340
341         // Go back to last focused item
342         if (me.previousFocusedCmp) {
343             me.previousFocusedCmp.focus();
344         }
345     },
346
347     getRootComponents: function() {
348         var me = this,
349             CQ = Ext.ComponentQuery,
350             inline = CQ.query(':focusable:root:not([floating])'),
351             floating = CQ.query(':focusable:root[floating]');
352
353         // Floating items should go to the top of our root stack, and be ordered
354         // by their z-index (highest first)
355         floating.sort(function(a, b) {
356             return a.el.getZIndex() &gt; b.el.getZIndex();
357         });
358
359         return floating.concat(inline);
360     },
361
362     initDOM: function(options) {
363         var me = this,
364             sp = '&amp;#160',
365             cls = me.focusFrameCls;
366
367         if (!Ext.isReady) {
368             Ext.onReady(me.initDOM, me);
369             return;
370         }
371
372         // Create global focus element
373         if (!me.focusEl) {
374             me.focusEl = Ext.getBody().createChild({
375                 tabIndex: '-1',
376                 cls: me.focusElementCls,
377                 html: sp
378             });
379         }
380
381         // Create global focus frame
382         if (!me.focusFrame &amp;&amp; options.focusFrame) {
383             me.focusFrame = Ext.getBody().createChild({
384                 cls: cls,
385                 children: [
386                     { cls: cls + '-top' },
387                     { cls: cls + '-bottom' },
388                     { cls: cls + '-left' },
389                     { cls: cls + '-right' }
390                 ],
391                 style: 'top: -100px; left: -100px;'
392             });
393             me.focusFrame.setVisibilityMode(Ext.core.Element.DISPLAY);
394             me.focusFrameWidth = me.focusFrame.child('.' + cls + '-top').getHeight();
395             me.focusFrame.hide().setLeftTop(0, 0);
396         }
397     },
398
399     isWhitelisted: function(cmp) {
400         return cmp &amp;&amp; Ext.Array.some(this.whitelist, function(x) {
401             return cmp.isXType(x);
402         });
403     },
404
405     navigateIn: function(e) {
406         var me = this,
407             focusedCmp = me.focusedCmp,
408             rootCmps,
409             firstChild;
410
411         if (!focusedCmp) {
412             // No focus yet, so focus the first root cmp on the page
413             rootCmps = me.getRootComponents();
414             if (rootCmps.length) {
415                 rootCmps[0].focus();
416             }
417         } else {
418             // Drill into child ref items of the focused cmp, if applicable.
419             // This works for any Component with a getRefItems implementation.
420             firstChild = Ext.ComponentQuery.query('&gt;:focusable', focusedCmp)[0];
421             if (firstChild) {
422                 firstChild.focus();
423             } else {
424                 // Let's try to fire a click event, as if it came from the mouse
425                 if (Ext.isFunction(focusedCmp.onClick)) {
426                     e.button = 0;
427                     focusedCmp.onClick(e);
428                     focusedCmp.focus();
429                 }
430             }
431         }
432     },
433
434     navigateOut: function(e) {
435         var me = this,
436             parent;
437
438         if (!me.focusedCmp || !(parent = me.focusedCmp.up(':focusable'))) {
439             me.focusEl.focus();
440         } else {
441             parent.focus();
442         }
443
444         // In some browsers (Chrome) FocusManager can handle this before other
445         // handlers. Ext Windows have their own Esc key handling, so we need to
446         // return true here to allow the event to bubble.
447         return true;
448     },
449
450     navigateSiblings: function(e, source, parent) {
451         var me = this,
452             src = source || me,
453             key = e.getKey(),
454             EO = Ext.EventObject,
455             goBack = e.shiftKey || key == EO.LEFT || key == EO.UP,
456             checkWhitelist = key == EO.LEFT || key == EO.RIGHT || key == EO.UP || key == EO.DOWN,
457             nextSelector = goBack ? 'prev' : 'next',
458             idx, next, focusedCmp;
459
460         focusedCmp = (src.focusedCmp &amp;&amp; src.focusedCmp.comp) || src.focusedCmp;
461         if (!focusedCmp &amp;&amp; !parent) {
462             return;
463         }
464
465         if (checkWhitelist &amp;&amp; me.isWhitelisted(focusedCmp)) {
466             return true;
467         }
468
469         parent = parent || focusedCmp.up();
470         if (parent) {
471             idx = focusedCmp ? Ext.Array.indexOf(parent.getRefItems(), focusedCmp) : -1;
472             next = Ext.ComponentQuery.query('&gt;:' + nextSelector + 'Focus(' + idx + ')', parent)[0];
473             if (next &amp;&amp; focusedCmp !== next) {
474                 next.focus();
475                 return next;
476             }
477         }
478     },
479
480     onComponentBlur: function(cmp, e) {
481         var me = this;
482
483         if (me.focusedCmp === cmp) {
484             me.previousFocusedCmp = cmp;
485             delete me.focusedCmp;
486         }
487
488         if (me.focusFrame) {
489             me.focusFrame.hide();
490         }
491     },
492
493     onComponentCreated: function(hash, id, cmp) {
494         this.setFocus(cmp, true, this.options);
495     },
496
497     onComponentDestroy: function(cmp) {
498         this.setFocus(cmp, false);
499     },
500
501     onComponentFocus: function(cmp, e) {
502         var me = this,
503             chain = me.focusChain;
504
505         if (!Ext.ComponentQuery.is(cmp, ':focusable')) {
506             me.clearComponent(cmp);
507
508             // Check our focus chain, so we don't run into a never ending recursion
509             // If we've attempted (unsuccessfully) to focus this component before,
510             // then we're caught in a loop of child-&gt;parent-&gt;...-&gt;child and we
511             // need to cut the loop off rather than feed into it.
512             if (chain[cmp.id]) {
513                 return;
514             }
515
516             // Try to focus the parent instead
517             var parent = cmp.up();
518             if (parent) {
519                 // Add component to our focus chain to detect infinite focus loop
520                 // before we fire off an attempt to focus our parent.
521                 // See the comments above.
522                 chain[cmp.id] = true;
523                 parent.focus();
524             }
525
526             return;
527         }
528
529         // Clear our focus chain when we have a focusable component
530         me.focusChain = {};
531
532         // Defer focusing for 90ms so components can do a layout/positioning
533         // and give us an ability to buffer focuses
534         clearTimeout(me.cmpFocusDelay);
535         if (arguments.length !== 2) {
536             me.cmpFocusDelay = Ext.defer(me.onComponentFocus, 90, me, [cmp, e]);
537             return;
538         }
539
540         if (me.fireEvent('beforecomponentfocus', me, cmp, me.previousFocusedCmp) === false) {
541             me.clearComponent(cmp);
542             return;
543         }
544
545         me.focusedCmp = cmp;
546
547         // If we have a focus frame, show it around the focused component
548         if (me.shouldShowFocusFrame(cmp)) {
549             var cls = '.' + me.focusFrameCls + '-',
550                 ff = me.focusFrame,
551                 fw = me.focusFrameWidth,
552                 box = cmp.el.getPageBox(),
553
554             // Size the focus frame's t/b/l/r according to the box
555             // This leaves a hole in the middle of the frame so user
556             // interaction w/ the mouse can continue
557                 bt = box.top,
558                 bl = box.left,
559                 bw = box.width,
560                 bh = box.height,
561                 ft = ff.child(cls + 'top'),
562                 fb = ff.child(cls + 'bottom'),
563                 fl = ff.child(cls + 'left'),
564                 fr = ff.child(cls + 'right');
565
566             ft.setWidth(bw - 2).setLeftTop(bl + 1, bt);
567             fb.setWidth(bw - 2).setLeftTop(bl + 1, bt + bh - fw);
568             fl.setHeight(bh - 2).setLeftTop(bl, bt + 1);
569             fr.setHeight(bh - 2).setLeftTop(bl + bw - fw, bt + 1);
570
571             ff.show();
572         }
573
574         me.fireEvent('componentfocus', me, cmp, me.previousFocusedCmp);
575     },
576
577     onComponentHide: function(cmp) {
578         var me = this,
579             CQ = Ext.ComponentQuery,
580             cmpHadFocus = false,
581             focusedCmp,
582             parent;
583
584         if (me.focusedCmp) {
585             focusedCmp = CQ.query('[id=' + me.focusedCmp.id + ']', cmp)[0];
586             cmpHadFocus = me.focusedCmp.id === cmp.id || focusedCmp;
587
588             if (focusedCmp) {
589                 me.clearComponent(focusedCmp);
590             }
591         }
592
593         me.clearComponent(cmp);
594
595         if (cmpHadFocus) {
596             parent = CQ.query('^:focusable', cmp)[0];
597             if (parent) {
598                 parent.focus();
599             }
600         }
601     },
602
603     removeDOM: function() {
604         var me = this;
605
606         // If we are still enabled globally, or there are still subscribers
607         // then we will halt here, since our DOM stuff is still being used
608         if (me.enabled || me.subscribers.length) {
609             return;
610         }
611
612         Ext.destroy(
613             me.focusEl,
614             me.focusFrame
615         );
616         delete me.focusEl;
617         delete me.focusFrame;
618         delete me.focusFrameWidth;
619     },
620
621 <span id='Ext-FocusManager-method-removeXTypeFromWhitelist'>    /**
622 </span>     * Removes the specified xtype from the {@link #whitelist}.
623      * @param {String/Array} xtype Removes the xtype(s) from the {@link #whitelist}.
624      */
625     removeXTypeFromWhitelist: function(xtype) {
626         var me = this;
627
628         if (Ext.isArray(xtype)) {
629             Ext.Array.forEach(xtype, me.removeXTypeFromWhitelist, me);
630             return;
631         }
632
633         Ext.Array.remove(me.whitelist, xtype);
634     },
635
636     setFocus: function(cmp, focusable, options) {
637         var me = this,
638             el, dom, data,
639
640             needsTabIndex = function(n) {
641                 return !Ext.Array.contains(me.tabIndexWhitelist, n.tagName.toLowerCase())
642                     &amp;&amp; n.tabIndex &lt;= 0;
643             };
644
645         options = options || {};
646
647         // Come back and do this after the component is rendered
648         if (!cmp.rendered) {
649             cmp.on('afterrender', Ext.pass(me.setFocus, arguments, me), me, { single: true });
650             return;
651         }
652
653         el = cmp.getFocusEl();
654         dom = el.dom;
655
656         // Decorate the component's focus el for focus-ability
657         if ((focusable &amp;&amp; !me.focusData[cmp.id]) || (!focusable &amp;&amp; me.focusData[cmp.id])) {
658             if (focusable) {
659                 data = {
660                     focusFrame: options.focusFrame
661                 };
662
663                 // Only set -1 tabIndex if we need it
664                 // inputs, buttons, and anchor tags do not need it,
665                 // and neither does any DOM that has it set already
666                 // programmatically or in markup.
667                 if (needsTabIndex(dom)) {
668                     data.tabIndex = dom.tabIndex;
669                     dom.tabIndex = -1;
670                 }
671
672                 el.on({
673                     focus: data.focusFn = Ext.bind(me.onComponentFocus, me, [cmp], 0),
674                     blur: data.blurFn = Ext.bind(me.onComponentBlur, me, [cmp], 0),
675                     scope: me
676                 });
677                 cmp.on({
678                     hide: me.onComponentHide,
679                     close: me.onComponentHide,
680                     beforedestroy: me.onComponentDestroy,
681                     scope: me
682                 });
683
684                 me.focusData[cmp.id] = data;
685             } else {
686                 data = me.focusData[cmp.id];
687                 if ('tabIndex' in data) {
688                     dom.tabIndex = data.tabIndex;
689                 }
690                 el.un('focus', data.focusFn, me);
691                 el.un('blur', data.blurFn, me);
692                 cmp.un('hide', me.onComponentHide, me);
693                 cmp.un('close', me.onComponentHide, me);
694                 cmp.un('beforedestroy', me.onComponentDestroy, me);
695
696                 delete me.focusData[cmp.id];
697             }
698         }
699     },
700
701     setFocusAll: function(focusable, options) {
702         var me = this,
703             cmps = Ext.ComponentManager.all.getArray(),
704             len = cmps.length,
705             cmp,
706             i = 0;
707
708         for (; i &lt; len; i++) {
709             me.setFocus(cmps[i], focusable, options);
710         }
711     },
712
713     setupSubscriberKeys: function(container, keys) {
714         var me = this,
715             el = container.getFocusEl(),
716             scope = keys.scope,
717             handlers = {
718                 backspace: me.focusLast,
719                 enter: me.navigateIn,
720                 esc: me.navigateOut,
721                 scope: me
722             },
723
724             navSiblings = function(e) {
725                 if (me.focusedCmp === container) {
726                     // Root the sibling navigation to this container, so that we
727                     // can automatically dive into the container, rather than forcing
728                     // the user to hit the enter key to dive in.
729                     return me.navigateSiblings(e, me, container);
730                 } else {
731                     return me.navigateSiblings(e);
732                 }
733             };
734
735         Ext.iterate(keys, function(key, cb) {
736             handlers[key] = function(e) {
737                 var ret = navSiblings(e);
738
739                 if (Ext.isFunction(cb) &amp;&amp; cb.call(scope || container, e, ret) === true) {
740                     return true;
741                 }
742
743                 return ret;
744             };
745         }, me);
746
747         return Ext.create('Ext.util.KeyNav', el, handlers);
748     },
749
750     shouldShowFocusFrame: function(cmp) {
751         var me = this,
752             opts = me.options || {};
753
754         if (!me.focusFrame || !cmp) {
755             return false;
756         }
757
758         // Global trumps
759         if (opts.focusFrame) {
760             return true;
761         }
762
763         if (me.focusData[cmp.id].focusFrame) {
764             return true;
765         }
766
767         return false;
768     },
769
770 <span id='Ext-FocusManager-method-subscribe'>    /**
771 </span>     * Subscribes an {@link Ext.container.Container} to provide basic keyboard focus navigation between its child {@link Ext.Component}'s.
772      * @param {Ext.container.Container} container A reference to the {@link Ext.container.Container} on which to enable keyboard functionality and focus management.
773      * @param {Boolean/Object} options An object of the following options:
774         - keys : Array/Object
775             An array containing the string names of navigation keys to be supported. The allowed values are:
776
777             - 'left'
778             - 'right'
779             - 'up'
780             - 'down'
781
782             Or, an object containing those key names as keys with `true` or a callback function as their value. A scope may also be passed. E.g.:
783
784                 {
785                     left: this.onLeftKey,
786                     right: this.onRightKey,
787                     scope: this
788                 }
789
790         - focusFrame : Boolean (optional)
791             `true` to show the focus frame around a component when it is focused. Defaults to `false`.
792      * @markdown
793      */
794     subscribe: function(container, options) {
795         var me = this,
796             EA = Ext.Array,
797             data = {},
798             subs = me.subscribers,
799
800             // Recursively add focus ability as long as a descendent container isn't
801             // itself subscribed to the FocusManager, or else we'd have unwanted side
802             // effects for subscribing a descendent container twice.
803             safeSetFocus = function(cmp) {
804                 if (cmp.isContainer &amp;&amp; !subs.containsKey(cmp.id)) {
805                     EA.forEach(cmp.query('&gt;'), safeSetFocus);
806                     me.setFocus(cmp, true, options);
807                     cmp.on('add', data.onAdd, me);
808                 } else if (!cmp.isContainer) {
809                     me.setFocus(cmp, true, options);
810                 }
811             };
812
813         // We only accept containers
814         if (!container || !container.isContainer) {
815             return;
816         }
817
818         if (!container.rendered) {
819             container.on('afterrender', Ext.pass(me.subscribe, arguments, me), me, { single: true });
820             return;
821         }
822
823         // Init the DOM, incase this is the first time it will be used
824         me.initDOM(options);
825
826         // Create key navigation for subscriber based on keys option
827         data.keyNav = me.setupSubscriberKeys(container, options.keys);
828
829         // We need to keep track of components being added to our subscriber
830         // and any containers nested deeply within it (omg), so let's do that.
831         // Components that are removed are globally handled.
832         // Also keep track of destruction of our container for auto-unsubscribe.
833         data.onAdd = function(ct, cmp, idx) {
834             safeSetFocus(cmp);
835         };
836         container.on('beforedestroy', me.unsubscribe, me);
837
838         // Now we setup focusing abilities for the container and all its components
839         safeSetFocus(container);
840
841         // Add to our subscribers list
842         subs.add(container.id, data);
843     },
844
845 <span id='Ext-FocusManager-method-unsubscribe'>    /**
846 </span>     * Unsubscribes an {@link Ext.container.Container} from keyboard focus management.
847      * @param {Ext.container.Container} container A reference to the {@link Ext.container.Container} to unsubscribe from the FocusManager.
848      * @markdown
849      */
850     unsubscribe: function(container) {
851         var me = this,
852             EA = Ext.Array,
853             subs = me.subscribers,
854             data,
855
856             // Recursively remove focus ability as long as a descendent container isn't
857             // itself subscribed to the FocusManager, or else we'd have unwanted side
858             // effects for unsubscribing an ancestor container.
859             safeSetFocus = function(cmp) {
860                 if (cmp.isContainer &amp;&amp; !subs.containsKey(cmp.id)) {
861                     EA.forEach(cmp.query('&gt;'), safeSetFocus);
862                     me.setFocus(cmp, false);
863                     cmp.un('add', data.onAdd, me);
864                 } else if (!cmp.isContainer) {
865                     me.setFocus(cmp, false);
866                 }
867             };
868
869         if (!container || !subs.containsKey(container.id)) {
870             return;
871         }
872
873         data = subs.get(container.id);
874         data.keyNav.destroy();
875         container.un('beforedestroy', me.unsubscribe, me);
876         subs.removeAtKey(container.id);
877         safeSetFocus(container);
878         me.removeDOM();
879     }
880 });</pre>
881 </body>
882 </html>