Upgrade to ExtJS 4.0.1 - Released 05/18/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             return;
441         }
442
443         parent.focus();
444     },
445
446     navigateSiblings: function(e, source, parent) {
447         var me = this,
448             src = source || me,
449             key = e.getKey(),
450             EO = Ext.EventObject,
451             goBack = e.shiftKey || key == EO.LEFT || key == EO.UP,
452             checkWhitelist = key == EO.LEFT || key == EO.RIGHT || key == EO.UP || key == EO.DOWN,
453             nextSelector = goBack ? 'prev' : 'next',
454             idx, next, focusedCmp;
455
456         focusedCmp = (src.focusedCmp &amp;&amp; src.focusedCmp.comp) || src.focusedCmp;
457         if (!focusedCmp &amp;&amp; !parent) {
458             return;
459         }
460
461         if (checkWhitelist &amp;&amp; me.isWhitelisted(focusedCmp)) {
462             return true;
463         }
464
465         parent = parent || focusedCmp.up();
466         if (parent) {
467             idx = focusedCmp ? Ext.Array.indexOf(parent.getRefItems(), focusedCmp) : -1;
468             next = Ext.ComponentQuery.query('&gt;:' + nextSelector + 'Focus(' + idx + ')', parent)[0];
469             if (next &amp;&amp; focusedCmp !== next) {
470                 next.focus();
471                 return next;
472             }
473         }
474     },
475
476     onComponentBlur: function(cmp, e) {
477         var me = this;
478
479         if (me.focusedCmp === cmp) {
480             me.previousFocusedCmp = cmp;
481             delete me.focusedCmp;
482         }
483
484         if (me.focusFrame) {
485             me.focusFrame.hide();
486         }
487     },
488
489     onComponentCreated: function(hash, id, cmp) {
490         this.setFocus(cmp, true, this.options);
491     },
492
493     onComponentDestroy: function(cmp) {
494         this.setFocus(cmp, false);
495     },
496
497     onComponentFocus: function(cmp, e) {
498         var me = this,
499             chain = me.focusChain;
500
501         if (!Ext.ComponentQuery.is(cmp, ':focusable')) {
502             me.clearComponent(cmp);
503
504             // Check our focus chain, so we don't run into a never ending recursion
505             // If we've attempted (unsuccessfully) to focus this component before,
506             // then we're caught in a loop of child-&gt;parent-&gt;...-&gt;child and we
507             // need to cut the loop off rather than feed into it.
508             if (chain[cmp.id]) {
509                 return;
510             }
511
512             // Try to focus the parent instead
513             var parent = cmp.up();
514             if (parent) {
515                 // Add component to our focus chain to detect infinite focus loop
516                 // before we fire off an attempt to focus our parent.
517                 // See the comments above.
518                 chain[cmp.id] = true;
519                 parent.focus();
520             }
521
522             return;
523         }
524
525         // Clear our focus chain when we have a focusable component
526         me.focusChain = {};
527
528         // Defer focusing for 90ms so components can do a layout/positioning
529         // and give us an ability to buffer focuses
530         clearTimeout(me.cmpFocusDelay);
531         if (arguments.length !== 2) {
532             me.cmpFocusDelay = Ext.defer(me.onComponentFocus, 90, me, [cmp, e]);
533             return;
534         }
535
536         if (me.fireEvent('beforecomponentfocus', me, cmp, me.previousFocusedCmp) === false) {
537             me.clearComponent(cmp);
538             return;
539         }
540
541         me.focusedCmp = cmp;
542
543         // If we have a focus frame, show it around the focused component
544         if (me.shouldShowFocusFrame(cmp)) {
545             var cls = '.' + me.focusFrameCls + '-',
546                 ff = me.focusFrame,
547                 fw = me.focusFrameWidth,
548                 box = cmp.el.getPageBox(),
549
550             // Size the focus frame's t/b/l/r according to the box
551             // This leaves a hole in the middle of the frame so user
552             // interaction w/ the mouse can continue
553                 bt = box.top,
554                 bl = box.left,
555                 bw = box.width,
556                 bh = box.height,
557                 ft = ff.child(cls + 'top'),
558                 fb = ff.child(cls + 'bottom'),
559                 fl = ff.child(cls + 'left'),
560                 fr = ff.child(cls + 'right');
561
562             ft.setWidth(bw - 2).setLeftTop(bl + 1, bt);
563             fb.setWidth(bw - 2).setLeftTop(bl + 1, bt + bh - fw);
564             fl.setHeight(bh - 2).setLeftTop(bl, bt + 1);
565             fr.setHeight(bh - 2).setLeftTop(bl + bw - fw, bt + 1);
566
567             ff.show();
568         }
569
570         me.fireEvent('componentfocus', me, cmp, me.previousFocusedCmp);
571     },
572
573     onComponentHide: function(cmp) {
574         var me = this,
575             CQ = Ext.ComponentQuery,
576             cmpHadFocus = false,
577             focusedCmp,
578             parent;
579
580         if (me.focusedCmp) {
581             focusedCmp = CQ.query('[id=' + me.focusedCmp.id + ']', cmp)[0];
582             cmpHadFocus = me.focusedCmp.id === cmp.id || focusedCmp;
583
584             if (focusedCmp) {
585                 me.clearComponent(focusedCmp);
586             }
587         }
588
589         me.clearComponent(cmp);
590
591         if (cmpHadFocus) {
592             parent = CQ.query('^:focusable', cmp)[0];
593             if (parent) {
594                 parent.focus();
595             }
596         }
597     },
598
599     removeDOM: function() {
600         var me = this;
601
602         // If we are still enabled globally, or there are still subscribers
603         // then we will halt here, since our DOM stuff is still being used
604         if (me.enabled || me.subscribers.length) {
605             return;
606         }
607
608         Ext.destroy(
609             me.focusEl,
610             me.focusFrame
611         );
612         delete me.focusEl;
613         delete me.focusFrame;
614         delete me.focusFrameWidth;
615     },
616
617 <span id='Ext-FocusManager-method-removeXTypeFromWhitelist'>    /**
618 </span>     * Removes the specified xtype from the {@link #whitelist}.
619      * @param {String/Array} xtype Removes the xtype(s) from the {@link #whitelist}.
620      */
621     removeXTypeFromWhitelist: function(xtype) {
622         var me = this;
623
624         if (Ext.isArray(xtype)) {
625             Ext.Array.forEach(xtype, me.removeXTypeFromWhitelist, me);
626             return;
627         }
628
629         Ext.Array.remove(me.whitelist, xtype);
630     },
631
632     setFocus: function(cmp, focusable, options) {
633         var me = this,
634             el, dom, data,
635
636             needsTabIndex = function(n) {
637                 return !Ext.Array.contains(me.tabIndexWhitelist, n.tagName.toLowerCase())
638                     &amp;&amp; n.tabIndex &lt;= 0;
639             };
640
641         options = options || {};
642
643         // Come back and do this after the component is rendered
644         if (!cmp.rendered) {
645             cmp.on('afterrender', Ext.pass(me.setFocus, arguments, me), me, { single: true });
646             return;
647         }
648
649         el = cmp.getFocusEl();
650         dom = el.dom;
651
652         // Decorate the component's focus el for focus-ability
653         if ((focusable &amp;&amp; !me.focusData[cmp.id]) || (!focusable &amp;&amp; me.focusData[cmp.id])) {
654             if (focusable) {
655                 data = {
656                     focusFrame: options.focusFrame
657                 };
658
659                 // Only set -1 tabIndex if we need it
660                 // inputs, buttons, and anchor tags do not need it,
661                 // and neither does any DOM that has it set already
662                 // programmatically or in markup.
663                 if (needsTabIndex(dom)) {
664                     data.tabIndex = dom.tabIndex;
665                     dom.tabIndex = -1;
666                 }
667
668                 el.on({
669                     focus: data.focusFn = Ext.bind(me.onComponentFocus, me, [cmp], 0),
670                     blur: data.blurFn = Ext.bind(me.onComponentBlur, me, [cmp], 0),
671                     scope: me
672                 });
673                 cmp.on({
674                     hide: me.onComponentHide,
675                     close: me.onComponentHide,
676                     beforedestroy: me.onComponentDestroy,
677                     scope: me
678                 });
679
680                 me.focusData[cmp.id] = data;
681             } else {
682                 data = me.focusData[cmp.id];
683                 if ('tabIndex' in data) {
684                     dom.tabIndex = data.tabIndex;
685                 }
686                 el.un('focus', data.focusFn, me);
687                 el.un('blur', data.blurFn, me);
688                 cmp.un('hide', me.onComponentHide, me);
689                 cmp.un('close', me.onComponentHide, me);
690                 cmp.un('beforedestroy', me.onComponentDestroy, me);
691
692                 delete me.focusData[cmp.id];
693             }
694         }
695     },
696
697     setFocusAll: function(focusable, options) {
698         var me = this,
699             cmps = Ext.ComponentManager.all.getArray(),
700             len = cmps.length,
701             cmp,
702             i = 0;
703
704         for (; i &lt; len; i++) {
705             me.setFocus(cmps[i], focusable, options);
706         }
707     },
708
709     setupSubscriberKeys: function(container, keys) {
710         var me = this,
711             el = container.getFocusEl(),
712             scope = keys.scope,
713             handlers = {
714                 backspace: me.focusLast,
715                 enter: me.navigateIn,
716                 esc: me.navigateOut,
717                 scope: me
718             },
719
720             navSiblings = function(e) {
721                 if (me.focusedCmp === container) {
722                     // Root the sibling navigation to this container, so that we
723                     // can automatically dive into the container, rather than forcing
724                     // the user to hit the enter key to dive in.
725                     return me.navigateSiblings(e, me, container);
726                 } else {
727                     return me.navigateSiblings(e);
728                 }
729             };
730
731         Ext.iterate(keys, function(key, cb) {
732             handlers[key] = function(e) {
733                 var ret = navSiblings(e);
734
735                 if (Ext.isFunction(cb) &amp;&amp; cb.call(scope || container, e, ret) === true) {
736                     return true;
737                 }
738
739                 return ret;
740             };
741         }, me);
742
743         return Ext.create('Ext.util.KeyNav', el, handlers);
744     },
745
746     shouldShowFocusFrame: function(cmp) {
747         var me = this,
748             opts = me.options || {};
749
750         if (!me.focusFrame || !cmp) {
751             return false;
752         }
753
754         // Global trumps
755         if (opts.focusFrame) {
756             return true;
757         }
758
759         if (me.focusData[cmp.id].focusFrame) {
760             return true;
761         }
762
763         return false;
764     },
765
766 <span id='Ext-FocusManager-method-subscribe'>    /**
767 </span>     * Subscribes an {@link Ext.container.Container} to provide basic keyboard focus navigation between its child {@link Ext.Component}'s.
768      * @param {Ext.container.Container} container A reference to the {@link Ext.container.Container} on which to enable keyboard functionality and focus management.
769      * @param {Boolean/Object} options An object of the following options:
770         - keys : Array/Object
771             An array containing the string names of navigation keys to be supported. The allowed values are:
772
773             - 'left'
774             - 'right'
775             - 'up'
776             - 'down'
777
778             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.:
779
780                 {
781                     left: this.onLeftKey,
782                     right: this.onRightKey,
783                     scope: this
784                 }
785
786         - focusFrame : Boolean (optional)
787             `true` to show the focus frame around a component when it is focused. Defaults to `false`.
788      * @markdown
789      */
790     subscribe: function(container, options) {
791         var me = this,
792             EA = Ext.Array,
793             data = {},
794             subs = me.subscribers,
795
796             // Recursively add focus ability as long as a descendent container isn't
797             // itself subscribed to the FocusManager, or else we'd have unwanted side
798             // effects for subscribing a descendent container twice.
799             safeSetFocus = function(cmp) {
800                 if (cmp.isContainer &amp;&amp; !subs.containsKey(cmp.id)) {
801                     EA.forEach(cmp.query('&gt;'), safeSetFocus);
802                     me.setFocus(cmp, true, options);
803                     cmp.on('add', data.onAdd, me);
804                 } else if (!cmp.isContainer) {
805                     me.setFocus(cmp, true, options);
806                 }
807             };
808
809         // We only accept containers
810         if (!container || !container.isContainer) {
811             return;
812         }
813
814         if (!container.rendered) {
815             container.on('afterrender', Ext.pass(me.subscribe, arguments, me), me, { single: true });
816             return;
817         }
818
819         // Init the DOM, incase this is the first time it will be used
820         me.initDOM(options);
821
822         // Create key navigation for subscriber based on keys option
823         data.keyNav = me.setupSubscriberKeys(container, options.keys);
824
825         // We need to keep track of components being added to our subscriber
826         // and any containers nested deeply within it (omg), so let's do that.
827         // Components that are removed are globally handled.
828         // Also keep track of destruction of our container for auto-unsubscribe.
829         data.onAdd = function(ct, cmp, idx) {
830             safeSetFocus(cmp);
831         };
832         container.on('beforedestroy', me.unsubscribe, me);
833
834         // Now we setup focusing abilities for the container and all its components
835         safeSetFocus(container);
836
837         // Add to our subscribers list
838         subs.add(container.id, data);
839     },
840
841 <span id='Ext-FocusManager-method-unsubscribe'>    /**
842 </span>     * Unsubscribes an {@link Ext.container.Container} from keyboard focus management.
843      * @param {Ext.container.Container} container A reference to the {@link Ext.container.Container} to unsubscribe from the FocusManager.
844      * @markdown
845      */
846     unsubscribe: function(container) {
847         var me = this,
848             EA = Ext.Array,
849             subs = me.subscribers,
850             data,
851
852             // Recursively remove focus ability as long as a descendent container isn't
853             // itself subscribed to the FocusManager, or else we'd have unwanted side
854             // effects for unsubscribing an ancestor container.
855             safeSetFocus = function(cmp) {
856                 if (cmp.isContainer &amp;&amp; !subs.containsKey(cmp.id)) {
857                     EA.forEach(cmp.query('&gt;'), safeSetFocus);
858                     me.setFocus(cmp, false);
859                     cmp.un('add', data.onAdd, me);
860                 } else if (!cmp.isContainer) {
861                     me.setFocus(cmp, false);
862                 }
863             };
864
865         if (!container || !subs.containsKey(container.id)) {
866             return;
867         }
868
869         data = subs.get(container.id);
870         data.keyNav.destroy();
871         container.un('beforedestroy', me.unsubscribe, me);
872         subs.removeAtKey(container.id);
873         safeSetFocus(container);
874         me.removeDOM();
875     }
876 });</pre>
877 </body>
878 </html>