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