--- /dev/null
+<!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'>/**
+</span> * @class Ext.FocusManager
+
+The FocusManager is responsible for globally:
+
+1. Managing component focus
+2. Providing basic keyboard navigation
+3. (optional) Provide a visual cue for focused components, in the form of a focus ring/frame.
+
+To activate the FocusManager, simply call {@link #enable `Ext.FocusManager.enable();`}. In turn, you may
+deactivate the FocusManager by subsequently calling {@link #disable `Ext.FocusManager.disable();`}. The
+FocusManager is disabled by default.
+
+To enable the optional focus frame, pass `true` or `{focusFrame: true}` to {@link #enable}.
+
+Another feature of the FocusManager is to provide basic keyboard focus navigation scoped to any {@link Ext.container.Container}
+that would like to have navigation between its child {@link Ext.Component}'s. The {@link Ext.container.Container} can simply
+call {@link #subscribe Ext.FocusManager.subscribe} to take advantage of this feature, and can at any time call
+{@link #unsubscribe Ext.FocusManager.unsubscribe} to turn the navigation off.
+
+ * @singleton
+ * @markdown
+ * @author Jarred Nicholls <jarred@sencha.com>
+ * @docauthor Jarred Nicholls <jarred@sencha.com>
+ */
+Ext.define('Ext.FocusManager', {
+ singleton: true,
+ alternateClassName: 'Ext.FocusMgr',
+
+ mixins: {
+ observable: 'Ext.util.Observable'
+ },
+
+ requires: [
+ 'Ext.ComponentManager',
+ 'Ext.ComponentQuery',
+ 'Ext.util.HashMap',
+ 'Ext.util.KeyNav'
+ ],
+
+<span id='Ext-FocusManager-property-enabled'> /**
+</span> * @property {Boolean} enabled
+ * Whether or not the FocusManager is currently enabled
+ */
+ enabled: false,
+
+<span id='Ext-FocusManager-property-focusedCmp'> /**
+</span> * @property {Ext.Component} focusedCmp
+ * The currently focused component. Defaults to `undefined`.
+ * @markdown
+ */
+
+ focusElementCls: Ext.baseCSSPrefix + 'focus-element',
+
+ focusFrameCls: Ext.baseCSSPrefix + 'focus-frame',
+
+<span id='Ext-FocusManager-property-whitelist'> /**
+</span> * @property {Array} whitelist
+ * A list of xtypes that should ignore certain navigation input keys and
+ * allow for the default browser event/behavior. These input keys include:
+ *
+ * 1. Backspace
+ * 2. Delete
+ * 3. Left
+ * 4. Right
+ * 5. Up
+ * 6. Down
+ *
+ * The FocusManager will not attempt to navigate when a component is an xtype (or descendents thereof)
+ * that belongs to this whitelist. E.g., an {@link Ext.form.field.Text} should allow
+ * the user to move the input cursor left and right, and to delete characters, etc.
+ *
+ * This whitelist currently defaults to `['textfield']`.
+ * @markdown
+ */
+ whitelist: [
+ 'textfield'
+ ],
+
+ tabIndexWhitelist: [
+ 'a',
+ 'button',
+ 'embed',
+ 'frame',
+ 'iframe',
+ 'img',
+ 'input',
+ 'object',
+ 'select',
+ 'textarea'
+ ],
+
+ constructor: function() {
+ var me = this,
+ CQ = Ext.ComponentQuery;
+
+ me.addEvents(
+<span id='Ext-FocusManager-event-beforecomponentfocus'> /**
+</span> * @event beforecomponentfocus
+ * Fires before a component becomes focused. Return `false` to prevent
+ * the component from gaining focus.
+ * @param {Ext.FocusManager} fm A reference to the FocusManager singleton
+ * @param {Ext.Component} cmp The component that is being focused
+ * @param {Ext.Component} previousCmp The component that was previously focused,
+ * or `undefined` if there was no previously focused component.
+ * @markdown
+ */
+ 'beforecomponentfocus',
+
+<span id='Ext-FocusManager-event-componentfocus'> /**
+</span> * @event componentfocus
+ * Fires after a component becomes focused.
+ * @param {Ext.FocusManager} fm A reference to the FocusManager singleton
+ * @param {Ext.Component} cmp The component that has been focused
+ * @param {Ext.Component} previousCmp The component that was previously focused,
+ * or `undefined` if there was no previously focused component.
+ * @markdown
+ */
+ 'componentfocus',
+
+<span id='Ext-FocusManager-event-disable'> /**
+</span> * @event disable
+ * Fires when the FocusManager is disabled
+ * @param {Ext.FocusManager} fm A reference to the FocusManager singleton
+ */
+ 'disable',
+
+<span id='Ext-FocusManager-event-enable'> /**
+</span> * @event enable
+ * Fires when the FocusManager is enabled
+ * @param {Ext.FocusManager} fm A reference to the FocusManager singleton
+ */
+ 'enable'
+ );
+
+ // Setup KeyNav that's bound to document to catch all
+ // unhandled/bubbled key events for navigation
+ me.keyNav = Ext.create('Ext.util.KeyNav', Ext.getDoc(), {
+ disabled: true,
+ scope: me,
+
+ backspace: me.focusLast,
+ enter: me.navigateIn,
+ esc: me.navigateOut,
+ tab: me.navigateSiblings
+
+ //space: me.navigateIn,
+ //del: me.focusLast,
+ //left: me.navigateSiblings,
+ //right: me.navigateSiblings,
+ //down: me.navigateSiblings,
+ //up: me.navigateSiblings
+ });
+
+ me.focusData = {};
+ me.subscribers = Ext.create('Ext.util.HashMap');
+ me.focusChain = {};
+
+ // Setup some ComponentQuery pseudos
+ Ext.apply(CQ.pseudos, {
+ focusable: function(cmps) {
+ var len = cmps.length,
+ results = [],
+ i = 0,
+ c,
+
+ isFocusable = function(x) {
+ return x && x.focusable !== false && CQ.is(x, '[rendered]:not([destroying]):not([isDestroyed]):not([disabled]){isVisible(true)}{el && c.el.dom && c.el.isVisible()}');
+ };
+
+ for (; i < len; i++) {
+ c = cmps[i];
+ if (isFocusable(c)) {
+ results.push(c);
+ }
+ }
+
+ return results;
+ },
+
+ nextFocus: function(cmps, idx, step) {
+ step = step || 1;
+ idx = parseInt(idx, 10);
+
+ var len = cmps.length,
+ i = idx + step,
+ c;
+
+ for (; i != idx; i += step) {
+ if (i >= len) {
+ i = 0;
+ } else if (i < 0) {
+ i = len - 1;
+ }
+
+ c = cmps[i];
+ if (CQ.is(c, ':focusable')) {
+ return [c];
+ } else if (c.placeholder && CQ.is(c.placeholder, ':focusable')) {
+ return [c.placeholder];
+ }
+ }
+
+ return [];
+ },
+
+ prevFocus: function(cmps, idx) {
+ return this.nextFocus(cmps, idx, -1);
+ },
+
+ root: function(cmps) {
+ var len = cmps.length,
+ results = [],
+ i = 0,
+ c;
+
+ for (; i < len; i++) {
+ c = cmps[i];
+ if (!c.ownerCt) {
+ results.push(c);
+ }
+ }
+
+ return results;
+ }
+ });
+ },
+
+<span id='Ext-FocusManager-method-addXTypeToWhitelist'> /**
+</span> * Adds the specified xtype to the {@link #whitelist}.
+ * @param {String/Array} xtype Adds the xtype(s) to the {@link #whitelist}.
+ */
+ addXTypeToWhitelist: function(xtype) {
+ var me = this;
+
+ if (Ext.isArray(xtype)) {
+ Ext.Array.forEach(xtype, me.addXTypeToWhitelist, me);
+ return;
+ }
+
+ if (!Ext.Array.contains(me.whitelist, xtype)) {
+ me.whitelist.push(xtype);
+ }
+ },
+
+ clearComponent: function(cmp) {
+ clearTimeout(this.cmpFocusDelay);
+ if (!cmp.isDestroyed) {
+ cmp.blur();
+ }
+ },
+
+<span id='Ext-FocusManager-method-disable'> /**
+</span> * Disables the FocusManager by turning of all automatic focus management and keyboard navigation
+ */
+ disable: function() {
+ var me = this;
+
+ if (!me.enabled) {
+ return;
+ }
+
+ delete me.options;
+ me.enabled = false;
+
+ Ext.ComponentManager.all.un('add', me.onComponentCreated, me);
+
+ me.removeDOM();
+
+ // Stop handling key navigation
+ me.keyNav.disable();
+
+ // disable focus for all components
+ me.setFocusAll(false);
+
+ me.fireEvent('disable', me);
+ },
+
+<span id='Ext-FocusManager-method-enable'> /**
+</span> * Enables the FocusManager by turning on all automatic focus management and keyboard navigation
+ * @param {Boolean/Object} options Either `true`/`false` to turn on the focus frame, or an object of the following options:
+ - focusFrame : Boolean
+ `true` to show the focus frame around a component when it is focused. Defaults to `false`.
+ * @markdown
+ */
+ enable: function(options) {
+ var me = this;
+
+ if (options === true) {
+ options = { focusFrame: true };
+ }
+ me.options = options = options || {};
+
+ if (me.enabled) {
+ return;
+ }
+
+ // Handle components that are newly added after we are enabled
+ Ext.ComponentManager.all.on('add', me.onComponentCreated, me);
+
+ me.initDOM(options);
+
+ // Start handling key navigation
+ me.keyNav.enable();
+
+ // enable focus for all components
+ me.setFocusAll(true, options);
+
+ // Finally, let's focus our global focus el so we start fresh
+ me.focusEl.focus();
+ delete me.focusedCmp;
+
+ me.enabled = true;
+ me.fireEvent('enable', me);
+ },
+
+ focusLast: function(e) {
+ var me = this;
+
+ if (me.isWhitelisted(me.focusedCmp)) {
+ return true;
+ }
+
+ // Go back to last focused item
+ if (me.previousFocusedCmp) {
+ me.previousFocusedCmp.focus();
+ }
+ },
+
+ getRootComponents: function() {
+ var me = this,
+ CQ = Ext.ComponentQuery,
+ inline = CQ.query(':focusable:root:not([floating])'),
+ floating = CQ.query(':focusable:root[floating]');
+
+ // Floating items should go to the top of our root stack, and be ordered
+ // by their z-index (highest first)
+ floating.sort(function(a, b) {
+ return a.el.getZIndex() > b.el.getZIndex();
+ });
+
+ return floating.concat(inline);
+ },
+
+ initDOM: function(options) {
+ var me = this,
+ sp = '&#160',
+ cls = me.focusFrameCls;
+
+ if (!Ext.isReady) {
+ Ext.onReady(me.initDOM, me);
+ return;
+ }
+
+ // Create global focus element
+ if (!me.focusEl) {
+ me.focusEl = Ext.getBody().createChild({
+ tabIndex: '-1',
+ cls: me.focusElementCls,
+ html: sp
+ });
+ }
+
+ // Create global focus frame
+ if (!me.focusFrame && options.focusFrame) {
+ me.focusFrame = Ext.getBody().createChild({
+ cls: cls,
+ children: [
+ { cls: cls + '-top' },
+ { cls: cls + '-bottom' },
+ { cls: cls + '-left' },
+ { cls: cls + '-right' }
+ ],
+ style: 'top: -100px; left: -100px;'
+ });
+ me.focusFrame.setVisibilityMode(Ext.core.Element.DISPLAY);
+ me.focusFrameWidth = me.focusFrame.child('.' + cls + '-top').getHeight();
+ me.focusFrame.hide().setLeftTop(0, 0);
+ }
+ },
+
+ isWhitelisted: function(cmp) {
+ return cmp && Ext.Array.some(this.whitelist, function(x) {
+ return cmp.isXType(x);
+ });
+ },
+
+ navigateIn: function(e) {
+ var me = this,
+ focusedCmp = me.focusedCmp,
+ rootCmps,
+ firstChild;
+
+ if (!focusedCmp) {
+ // No focus yet, so focus the first root cmp on the page
+ rootCmps = me.getRootComponents();
+ if (rootCmps.length) {
+ rootCmps[0].focus();
+ }
+ } else {
+ // Drill into child ref items of the focused cmp, if applicable.
+ // This works for any Component with a getRefItems implementation.
+ firstChild = Ext.ComponentQuery.query('>:focusable', focusedCmp)[0];
+ if (firstChild) {
+ firstChild.focus();
+ } else {
+ // Let's try to fire a click event, as if it came from the mouse
+ if (Ext.isFunction(focusedCmp.onClick)) {
+ e.button = 0;
+ focusedCmp.onClick(e);
+ focusedCmp.focus();
+ }
+ }
+ }
+ },
+
+ navigateOut: function(e) {
+ var me = this,
+ parent;
+
+ if (!me.focusedCmp || !(parent = me.focusedCmp.up(':focusable'))) {
+ me.focusEl.focus();
+ return;
+ }
+
+ parent.focus();
+ },
+
+ navigateSiblings: function(e, source, parent) {
+ var me = this,
+ src = source || me,
+ key = e.getKey(),
+ EO = Ext.EventObject,
+ goBack = e.shiftKey || key == EO.LEFT || key == EO.UP,
+ checkWhitelist = key == EO.LEFT || key == EO.RIGHT || key == EO.UP || key == EO.DOWN,
+ nextSelector = goBack ? 'prev' : 'next',
+ idx, next, focusedCmp;
+
+ focusedCmp = (src.focusedCmp && src.focusedCmp.comp) || src.focusedCmp;
+ if (!focusedCmp && !parent) {
+ return;
+ }
+
+ if (checkWhitelist && me.isWhitelisted(focusedCmp)) {
+ return true;
+ }
+
+ parent = parent || focusedCmp.up();
+ if (parent) {
+ idx = focusedCmp ? Ext.Array.indexOf(parent.getRefItems(), focusedCmp) : -1;
+ next = Ext.ComponentQuery.query('>:' + nextSelector + 'Focus(' + idx + ')', parent)[0];
+ if (next && focusedCmp !== next) {
+ next.focus();
+ return next;
+ }
+ }
+ },
+
+ onComponentBlur: function(cmp, e) {
+ var me = this;
+
+ if (me.focusedCmp === cmp) {
+ me.previousFocusedCmp = cmp;
+ delete me.focusedCmp;
+ }
+
+ if (me.focusFrame) {
+ me.focusFrame.hide();
+ }
+ },
+
+ onComponentCreated: function(hash, id, cmp) {
+ this.setFocus(cmp, true, this.options);
+ },
+
+ onComponentDestroy: function(cmp) {
+ this.setFocus(cmp, false);
+ },
+
+ onComponentFocus: function(cmp, e) {
+ var me = this,
+ chain = me.focusChain;
+
+ if (!Ext.ComponentQuery.is(cmp, ':focusable')) {
+ me.clearComponent(cmp);
+
+ // Check our focus chain, so we don't run into a never ending recursion
+ // If we've attempted (unsuccessfully) to focus this component before,
+ // then we're caught in a loop of child->parent->...->child and we
+ // need to cut the loop off rather than feed into it.
+ if (chain[cmp.id]) {
+ return;
+ }
+
+ // Try to focus the parent instead
+ var parent = cmp.up();
+ if (parent) {
+ // Add component to our focus chain to detect infinite focus loop
+ // before we fire off an attempt to focus our parent.
+ // See the comments above.
+ chain[cmp.id] = true;
+ parent.focus();
+ }
+
+ return;
+ }
+
+ // Clear our focus chain when we have a focusable component
+ me.focusChain = {};
+
+ // Defer focusing for 90ms so components can do a layout/positioning
+ // and give us an ability to buffer focuses
+ clearTimeout(me.cmpFocusDelay);
+ if (arguments.length !== 2) {
+ me.cmpFocusDelay = Ext.defer(me.onComponentFocus, 90, me, [cmp, e]);
+ return;
+ }
+
+ if (me.fireEvent('beforecomponentfocus', me, cmp, me.previousFocusedCmp) === false) {
+ me.clearComponent(cmp);
+ return;
+ }
+
+ me.focusedCmp = cmp;
+
+ // If we have a focus frame, show it around the focused component
+ if (me.shouldShowFocusFrame(cmp)) {
+ var cls = '.' + me.focusFrameCls + '-',
+ ff = me.focusFrame,
+ fw = me.focusFrameWidth,
+ box = cmp.el.getPageBox(),
+
+ // Size the focus frame's t/b/l/r according to the box
+ // This leaves a hole in the middle of the frame so user
+ // interaction w/ the mouse can continue
+ bt = box.top,
+ bl = box.left,
+ bw = box.width,
+ bh = box.height,
+ ft = ff.child(cls + 'top'),
+ fb = ff.child(cls + 'bottom'),
+ fl = ff.child(cls + 'left'),
+ fr = ff.child(cls + 'right');
+
+ ft.setWidth(bw - 2).setLeftTop(bl + 1, bt);
+ fb.setWidth(bw - 2).setLeftTop(bl + 1, bt + bh - fw);
+ fl.setHeight(bh - 2).setLeftTop(bl, bt + 1);
+ fr.setHeight(bh - 2).setLeftTop(bl + bw - fw, bt + 1);
+
+ ff.show();
+ }
+
+ me.fireEvent('componentfocus', me, cmp, me.previousFocusedCmp);
+ },
+
+ onComponentHide: function(cmp) {
+ var me = this,
+ CQ = Ext.ComponentQuery,
+ cmpHadFocus = false,
+ focusedCmp,
+ parent;
+
+ if (me.focusedCmp) {
+ focusedCmp = CQ.query('[id=' + me.focusedCmp.id + ']', cmp)[0];
+ cmpHadFocus = me.focusedCmp.id === cmp.id || focusedCmp;
+
+ if (focusedCmp) {
+ me.clearComponent(focusedCmp);
+ }
+ }
+
+ me.clearComponent(cmp);
+
+ if (cmpHadFocus) {
+ parent = CQ.query('^:focusable', cmp)[0];
+ if (parent) {
+ parent.focus();
+ }
+ }
+ },
+
+ removeDOM: function() {
+ var me = this;
+
+ // If we are still enabled globally, or there are still subscribers
+ // then we will halt here, since our DOM stuff is still being used
+ if (me.enabled || me.subscribers.length) {
+ return;
+ }
+
+ Ext.destroy(
+ me.focusEl,
+ me.focusFrame
+ );
+ delete me.focusEl;
+ delete me.focusFrame;
+ delete me.focusFrameWidth;
+ },
+
+<span id='Ext-FocusManager-method-removeXTypeFromWhitelist'> /**
+</span> * Removes the specified xtype from the {@link #whitelist}.
+ * @param {String/Array} xtype Removes the xtype(s) from the {@link #whitelist}.
+ */
+ removeXTypeFromWhitelist: function(xtype) {
+ var me = this;
+
+ if (Ext.isArray(xtype)) {
+ Ext.Array.forEach(xtype, me.removeXTypeFromWhitelist, me);
+ return;
+ }
+
+ Ext.Array.remove(me.whitelist, xtype);
+ },
+
+ setFocus: function(cmp, focusable, options) {
+ var me = this,
+ el, dom, data,
+
+ needsTabIndex = function(n) {
+ return !Ext.Array.contains(me.tabIndexWhitelist, n.tagName.toLowerCase())
+ && n.tabIndex <= 0;
+ };
+
+ options = options || {};
+
+ // Come back and do this after the component is rendered
+ if (!cmp.rendered) {
+ cmp.on('afterrender', Ext.pass(me.setFocus, arguments, me), me, { single: true });
+ return;
+ }
+
+ el = cmp.getFocusEl();
+ dom = el.dom;
+
+ // Decorate the component's focus el for focus-ability
+ if ((focusable && !me.focusData[cmp.id]) || (!focusable && me.focusData[cmp.id])) {
+ if (focusable) {
+ data = {
+ focusFrame: options.focusFrame
+ };
+
+ // Only set -1 tabIndex if we need it
+ // inputs, buttons, and anchor tags do not need it,
+ // and neither does any DOM that has it set already
+ // programmatically or in markup.
+ if (needsTabIndex(dom)) {
+ data.tabIndex = dom.tabIndex;
+ dom.tabIndex = -1;
+ }
+
+ el.on({
+ focus: data.focusFn = Ext.bind(me.onComponentFocus, me, [cmp], 0),
+ blur: data.blurFn = Ext.bind(me.onComponentBlur, me, [cmp], 0),
+ scope: me
+ });
+ cmp.on({
+ hide: me.onComponentHide,
+ close: me.onComponentHide,
+ beforedestroy: me.onComponentDestroy,
+ scope: me
+ });
+
+ me.focusData[cmp.id] = data;
+ } else {
+ data = me.focusData[cmp.id];
+ if ('tabIndex' in data) {
+ dom.tabIndex = data.tabIndex;
+ }
+ el.un('focus', data.focusFn, me);
+ el.un('blur', data.blurFn, me);
+ cmp.un('hide', me.onComponentHide, me);
+ cmp.un('close', me.onComponentHide, me);
+ cmp.un('beforedestroy', me.onComponentDestroy, me);
+
+ delete me.focusData[cmp.id];
+ }
+ }
+ },
+
+ setFocusAll: function(focusable, options) {
+ var me = this,
+ cmps = Ext.ComponentManager.all.getArray(),
+ len = cmps.length,
+ cmp,
+ i = 0;
+
+ for (; i < len; i++) {
+ me.setFocus(cmps[i], focusable, options);
+ }
+ },
+
+ setupSubscriberKeys: function(container, keys) {
+ var me = this,
+ el = container.getFocusEl(),
+ scope = keys.scope,
+ handlers = {
+ backspace: me.focusLast,
+ enter: me.navigateIn,
+ esc: me.navigateOut,
+ scope: me
+ },
+
+ navSiblings = function(e) {
+ if (me.focusedCmp === container) {
+ // Root the sibling navigation to this container, so that we
+ // can automatically dive into the container, rather than forcing
+ // the user to hit the enter key to dive in.
+ return me.navigateSiblings(e, me, container);
+ } else {
+ return me.navigateSiblings(e);
+ }
+ };
+
+ Ext.iterate(keys, function(key, cb) {
+ handlers[key] = function(e) {
+ var ret = navSiblings(e);
+
+ if (Ext.isFunction(cb) && cb.call(scope || container, e, ret) === true) {
+ return true;
+ }
+
+ return ret;
+ };
+ }, me);
+
+ return Ext.create('Ext.util.KeyNav', el, handlers);
+ },
+
+ shouldShowFocusFrame: function(cmp) {
+ var me = this,
+ opts = me.options || {};
+
+ if (!me.focusFrame || !cmp) {
+ return false;
+ }
+
+ // Global trumps
+ if (opts.focusFrame) {
+ return true;
+ }
+
+ if (me.focusData[cmp.id].focusFrame) {
+ return true;
+ }
+
+ return false;
+ },
+
+<span id='Ext-FocusManager-method-subscribe'> /**
+</span> * Subscribes an {@link Ext.container.Container} to provide basic keyboard focus navigation between its child {@link Ext.Component}'s.
+ * @param {Ext.container.Container} container A reference to the {@link Ext.container.Container} on which to enable keyboard functionality and focus management.
+ * @param {Boolean/Object} options An object of the following options:
+ - keys : Array/Object
+ An array containing the string names of navigation keys to be supported. The allowed values are:
+
+ - 'left'
+ - 'right'
+ - 'up'
+ - 'down'
+
+ 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.:
+
+ {
+ left: this.onLeftKey,
+ right: this.onRightKey,
+ scope: this
+ }
+
+ - focusFrame : Boolean (optional)
+ `true` to show the focus frame around a component when it is focused. Defaults to `false`.
+ * @markdown
+ */
+ subscribe: function(container, options) {
+ var me = this,
+ EA = Ext.Array,
+ data = {},
+ subs = me.subscribers,
+
+ // Recursively add focus ability as long as a descendent container isn't
+ // itself subscribed to the FocusManager, or else we'd have unwanted side
+ // effects for subscribing a descendent container twice.
+ safeSetFocus = function(cmp) {
+ if (cmp.isContainer && !subs.containsKey(cmp.id)) {
+ EA.forEach(cmp.query('>'), safeSetFocus);
+ me.setFocus(cmp, true, options);
+ cmp.on('add', data.onAdd, me);
+ } else if (!cmp.isContainer) {
+ me.setFocus(cmp, true, options);
+ }
+ };
+
+ // We only accept containers
+ if (!container || !container.isContainer) {
+ return;
+ }
+
+ if (!container.rendered) {
+ container.on('afterrender', Ext.pass(me.subscribe, arguments, me), me, { single: true });
+ return;
+ }
+
+ // Init the DOM, incase this is the first time it will be used
+ me.initDOM(options);
+
+ // Create key navigation for subscriber based on keys option
+ data.keyNav = me.setupSubscriberKeys(container, options.keys);
+
+ // We need to keep track of components being added to our subscriber
+ // and any containers nested deeply within it (omg), so let's do that.
+ // Components that are removed are globally handled.
+ // Also keep track of destruction of our container for auto-unsubscribe.
+ data.onAdd = function(ct, cmp, idx) {
+ safeSetFocus(cmp);
+ };
+ container.on('beforedestroy', me.unsubscribe, me);
+
+ // Now we setup focusing abilities for the container and all its components
+ safeSetFocus(container);
+
+ // Add to our subscribers list
+ subs.add(container.id, data);
+ },
+
+<span id='Ext-FocusManager-method-unsubscribe'> /**
+</span> * Unsubscribes an {@link Ext.container.Container} from keyboard focus management.
+ * @param {Ext.container.Container} container A reference to the {@link Ext.container.Container} to unsubscribe from the FocusManager.
+ * @markdown
+ */
+ unsubscribe: function(container) {
+ var me = this,
+ EA = Ext.Array,
+ subs = me.subscribers,
+ data,
+
+ // Recursively remove focus ability as long as a descendent container isn't
+ // itself subscribed to the FocusManager, or else we'd have unwanted side
+ // effects for unsubscribing an ancestor container.
+ safeSetFocus = function(cmp) {
+ if (cmp.isContainer && !subs.containsKey(cmp.id)) {
+ EA.forEach(cmp.query('>'), safeSetFocus);
+ me.setFocus(cmp, false);
+ cmp.un('add', data.onAdd, me);
+ } else if (!cmp.isContainer) {
+ me.setFocus(cmp, false);
+ }
+ };
+
+ if (!container || !subs.containsKey(container.id)) {
+ return;
+ }
+
+ data = subs.get(container.id);
+ data.keyNav.destroy();
+ container.un('beforedestroy', me.unsubscribe, me);
+ subs.removeAtKey(container.id);
+ safeSetFocus(container);
+ me.removeDOM();
+ }
+});</pre></pre></body></html>
\ No newline at end of file