| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731 | <!DOCTYPE html><html><head>  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />  <title>The source code</title>  <link href="../resources/prettify/prettify.css" type="text/css" rel="stylesheet" />  <script type="text/javascript" src="../resources/prettify/prettify.js"></script>  <style type="text/css">    .highlight { display: block; background-color: #ddd; }  </style>  <script type="text/javascript">    function highlight() {      document.getElementById(location.hash.replace(/#/, "")).className = "highlight";    }  </script></head><body onload="prettyPrint(); highlight();">  <pre class="prettyprint lang-js"><span id='Ext-FocusManager'>/**</span> * 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 `Ext.FocusManager.enable();`. In turn, you may * deactivate the FocusManager by subsequently calling `Ext.FocusManager.disable();`.  The * FocusManager is disabled by default. * * To enable the optional focus frame, pass `true` or `{focusFrame: true}` to {@link #method-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. * * @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.AbstractComponent',        'Ext.Component',        '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.     */    focusElementCls: Ext.baseCSSPrefix + 'focus-element',    focusFrameCls: Ext.baseCSSPrefix + 'focus-frame',<span id='Ext-FocusManager-property-whitelist'>    /**</span>     * @property {String[]} 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.     */    whitelist: [        'textfield'    ],    constructor: function(config) {        var me = this,            CQ = Ext.ComponentQuery;        me.mixins.observable.constructor.call(me, config);        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.             */            '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.             */            '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'        );        me.focusTask = new Ext.util.DelayedTask(me.handleComponentFocus, me);        // Gain control on Component focus, blur, hide and destroy        Ext.override(Ext.AbstractComponent, {            onFocus: function() {                this.callParent(arguments);                if (me.enabled && this.hasFocus) {                    Array.prototype.unshift.call(arguments, this);                    me.onComponentFocus.apply(me, arguments);                }            },            onBlur: function() {                this.callParent(arguments);                if (me.enabled && !this.hasFocus) {                    Array.prototype.unshift.call(arguments, this);                    me.onComponentBlur.apply(me, arguments);                }            },            onDestroy: function() {                this.callParent(arguments);                if (me.enabled) {                    Array.prototype.unshift.call(arguments, this);                    me.onComponentDestroy.apply(me, arguments);                }            }        });        Ext.override(Ext.Component, {            afterHide: function() {                this.callParent(arguments);                if (me.enabled) {                    Array.prototype.unshift.call(arguments, this);                    me.onComponentHide.apply(me, arguments);                }            }        });        // Setup KeyNav that's bound to document to catch all        // unhandled/bubbled key events for navigation        me.keyNav = new 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 = new Ext.util.HashMap();        me.focusChain = {};        // Setup some ComponentQuery pseudos        Ext.apply(CQ.pseudos, {            focusable: function(cmps) {                var len = cmps.length,                    results = [],                    i = 0,                    c;                for (; i < len; i++) {                    c = cmps[i];                    if (c.isFocusable()) {                        results.push(c);                    }                }                return results;            },            // Return the single next focusable sibling from the current idx in either direction (step -1 or 1)            nextFocus: function(cmps, idx, step) {                step = step || 1;                idx = parseInt(idx, 10);                var len = cmps.length,                    i = idx, c;                for (;;) {                    // Increment index, and loop round if off either end                    if ((i += step) >= len) {                        i = 0;                    } else if (i < 0) {                        i = len - 1;                    }                    // As soon as we loop back to the starting index, give up, there are no focusable siblings.                    if (i === idx) {                        return [];                    }                    // If we have found a focusable sibling, return it                    if ((c = cmps[i]).isFocusable()) {                        return [c];                    }                }                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/String[]} 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;        me.removeDOM();        // Stop handling key navigation        me.keyNav.disable();        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     * with the following options:     * @param {Boolean} [focusFrame=false] `true` to show the focus frame around a component when it is focused.     */    enable: function(options) {        var me = this;        if (options === true) {            options = { focusFrame: true };        }        me.options = options = options || {};        if (me.enabled) {            return;        }        // When calling addFocusListener on Containers, the FocusManager must be enabled, otherwise it won't do it.        me.enabled = true;        me.initDOM(options);        // Start handling key navigation        me.keyNav.enable();        // Finally, let's focus our global focus el so we start fresh        me.focusEl.focus();        delete me.focusedCmp;        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,            cls = me.focusFrameCls,            needListeners = Ext.ComponentQuery.query('{getFocusEl()}:not([focusListenerAdded])'),            i = 0, len = needListeners.length;        if (!Ext.isReady) {            return Ext.onReady(me.initDOM, me);        }        // When we are enabled, we must ensure that all Components which return a focusEl that is *not naturally focusable*        // have focus/blur listeners enabled to then trigger onFocus/onBlur handling so that we get to know about their focus action.        // These listeners are not added at initialization unless the FocusManager is enabled at that time.        for (; i < len; i++) {            needListeners[i].addFocusListener();        }        // Make the document body the global focus element        if (!me.focusEl) {            me.focusEl = Ext.getBody();            me.focusEl.dom.tabIndex = -1;        }        // 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.Element.DISPLAY);            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,            defaultRoot,            firstChild;        if (me.isWhitelisted(focusedCmp)) {            return true;        }        if (!focusedCmp) {            // No focus yet, so focus the first root cmp on the page            defaultRoot = me.getRootComponents()[0];            if (defaultRoot) {                // If the default root is based upon the body, then it will already be focused, and will not fire a focus event to                // trigger its own onFocus processing, so we have to programatically blur it first.                if (defaultRoot.getFocusEl() === me.focusEl) {                    me.focusEl.blur();                }                defaultRoot.focus();            }        } else {            // Drill into child ref items of the focused cmp, if applicable.            // This works for any Component with a getRefItems implementation.            firstChild = focusedCmp.hasFocus ? Ext.ComponentQuery.query('>:focusable', focusedCmp)[0] : focusedCmp;            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);                    if (focusedCmp.isVisible(true)) {                        focusedCmp.focus();                    } else {                        me.navigateOut();                    }                }            }        }    },    navigateOut: function(e) {        var me = this,            parent;        if (!me.focusedCmp || !(parent = me.focusedCmp.up(':focusable'))) {            me.focusEl.focus();        } else {            parent.focus();        }        // In some browsers (Chrome) FocusManager can handle this before other        // handlers. Ext Windows have their own Esc key handling, so we need to        // return true here to allow the event to bubble.        return true;    },    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, siblings;        focusedCmp = (src.focusedCmp && src.focusedCmp.comp) || src.focusedCmp;        if (!focusedCmp && !parent) {            return true;        }        if (checkWhitelist && me.isWhitelisted(focusedCmp)) {            return true;        }        // If no focused Component, or a root level one was focused, then siblings are root components.        if (!focusedCmp || focusedCmp.is(':root')) {            siblings = me.getRootComponents();        } else {            // Else if the focused component has a parent, get siblings from there            parent = parent || focusedCmp.up();            if (parent) {                siblings = parent.getRefItems();            }        }        // Navigate if we have found siblings.        if (siblings) {            idx = focusedCmp ? Ext.Array.indexOf(siblings, focusedCmp) : -1;            next = Ext.ComponentQuery.query(':' + nextSelector + 'Focus(' + idx + ')', siblings)[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();        }    },    onComponentFocus: function(cmp, e) {        var me = this,            chain = me.focusChain,            parent;        if (!cmp.isFocusable()) {            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            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 = {};        // Capture the focusEl to frame now.        // Button returns its encapsulating element during the focus phase        // So that element gets styled and framed.        me.focusTask.delay(10, null, null, [cmp, cmp.getFocusEl()]);    },    handleComponentFocus: function(cmp, focusEl) {        var me = this,            cls,            ff,            fw,            box,            bt,            bl,            bw,            bh,            ft,            fb,            fl,            fr;        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)) {            cls = '.' + me.focusFrameCls + '-';            ff = me.focusFrame;            box = focusEl.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).setLeftTop(bl, bt);            fb.setWidth(bw).setLeftTop(bl, bt + bh - 2);            fl.setHeight(bh - 2).setLeftTop(bl, bt + 2);            fr.setHeight(bh - 2).setLeftTop(bl + bw - 2, bt + 2);            ff.show();        }        me.fireEvent('componentfocus', me, cmp, me.previousFocusedCmp);    },    onComponentHide: function(cmp) {        var me = this,            cmpHadFocus = false,            focusedCmp = me.focusedCmp,            parent;        if (focusedCmp) {            // See if the Component being hidden was the focused Component, or owns the focused Component            // In these cases, focus needs to be removed from the focused Component to the nearest focusable ancestor            cmpHadFocus = cmp.hasFocus || (cmp.isContainer && cmp.isAncestor(me.focusedCmp));        }        me.clearComponent(cmp);        // Move focus onto the nearest focusable ancestor, or this is there is none        if (cmpHadFocus && (parent = cmp.up(':focusable'))) {            parent.focus();        } else {            me.focusEl.focus();        }    },    onComponentDestroy: function() {    },    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.focusFrame        );        delete me.focusEl;        delete me.focusFrame;    },<span id='Ext-FocusManager-method-removeXTypeFromWhitelist'>    /**</span>     * Removes the specified xtype from the {@link #whitelist}.     * @param {String/String[]} 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);    },    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 new Ext.util.KeyNav(el, handlers);    },    shouldShowFocusFrame: function(cmp) {        var me = this,            opts = me.options || {},            cmpFocusEl = cmp.getFocusEl(),            cmpFocusElTag = Ext.getDom(cmpFocusEl).tagName;        // Do not show a focus frame if        // 1. We are configured not to.        // 2. No Component was passed        if (!me.focusFrame || !cmp) {            return false;        }        // Global trumps        if (opts.focusFrame) {            return true;        }        if (me.focusData[cmp.id].focusFrame) {            return true;        }        return false;    }});</pre></body></html>
 |