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