# Yahoo! UI Library

### Container 2.6.0

Yahoo! UI Library > container > OverlayManager.js (source view)

Show Private Show Protected Show Deprecated

    (function () {

        /**
        * OverlayManager is used for maintaining the focus status of
        * multiple Overlays.
        * @namespace YAHOO.widget
        * @namespace YAHOO.widget
        * @class OverlayManager
        * @constructor
        * @param {Array} overlays Optional. A collection of Overlays to register
        * with the manager.
        * @param {Object} userConfig  The object literal representing the user
        * configuration of the OverlayManager
        */
        YAHOO.widget.OverlayManager = function (userConfig) {
            this.init(userConfig);
        };

        var Overlay = YAHOO.widget.Overlay,
            Event = YAHOO.util.Event,
            Dom = YAHOO.util.Dom,
            Config = YAHOO.util.Config,
            CustomEvent = YAHOO.util.CustomEvent,
            OverlayManager = YAHOO.widget.OverlayManager;

        /**
        * The CSS class representing a focused Overlay
        * @property OverlayManager.CSS_FOCUSED
        * @static
        * @final
        * @type String
        */
        OverlayManager.CSS_FOCUSED = "focused";

        OverlayManager.prototype = {

            /**
            * The class's constructor function
            * @property contructor
            * @type Function
            */
            constructor: OverlayManager,

            /**
            * The array of Overlays that are currently registered
            * @property overlays
            * @type YAHOO.widget.Overlay[]
            */
            overlays: null,

            /**
            * Initializes the default configuration of the OverlayManager
            * @method initDefaultConfig
            */
            initDefaultConfig: function () {
                /**
                * The collection of registered Overlays in use by
                * the OverlayManager
                * @config overlays
                * @type YAHOO.widget.Overlay[]
                * @default null
                */
                this.cfg.addProperty("overlays", { suppressEvent: true } );

                /**
                * The default DOM event that should be used to focus an Overlay
                * @config focusevent
                * @type String
                * @default "mousedown"
                */
                this.cfg.addProperty("focusevent", { value: "mousedown" } );
            },

            /**
            * Initializes the OverlayManager
            * @method init
            * @param {Overlay[]} overlays Optional. A collection of Overlays to
            * register with the manager.
            * @param {Object} userConfig  The object literal representing the user
            * configuration of the OverlayManager
            */
            init: function (userConfig) {

                /**
                * The OverlayManager's Config object used for monitoring
                * configuration properties.
                * @property cfg
                * @type Config
                */
                this.cfg = new Config(this);

                this.initDefaultConfig();

                if (userConfig) {
                    this.cfg.applyConfig(userConfig, true);
                }
                this.cfg.fireQueue();

                /**
                * The currently activated Overlay
                * @property activeOverlay
                * @private
                * @type YAHOO.widget.Overlay
                */
                var activeOverlay = null;

                /**
                * Returns the currently focused Overlay
                * @method getActive
                * @return {Overlay} The currently focused Overlay
                */
                this.getActive = function () {
                    return activeOverlay;
                };

                /**
                * Focuses the specified Overlay
                * @method focus
                * @param {Overlay} overlay The Overlay to focus
                * @param {String} overlay The id of the Overlay to focus
                */
                this.focus = function (overlay) {
                    var o = this.find(overlay);
                    if (o) {
                        o.focus();
                    }
                };

                /**
                * Removes the specified Overlay from the manager
                * @method remove
                * @param {Overlay} overlay The Overlay to remove
                * @param {String} overlay The id of the Overlay to remove
                */
                this.remove = function (overlay) {

                    var o = this.find(overlay),
                            originalZ;

                    if (o) {
                        if (activeOverlay == o) {
                            activeOverlay = null;
                        }

                        var bDestroyed = (o.element === null && o.cfg === null) ? true : false;

                        if (!bDestroyed) {
                            // Set it's zindex so that it's sorted to the end.
                            originalZ = Dom.getStyle(o.element, "zIndex");
                            o.cfg.setProperty("zIndex", -1000, true);
                        }

                        this.overlays.sort(this.compareZIndexDesc);
                        this.overlays = this.overlays.slice(0, (this.overlays.length - 1));

                        o.hideEvent.unsubscribe(o.blur);
                        o.destroyEvent.unsubscribe(this._onOverlayDestroy, o);
                        o.focusEvent.unsubscribe(this._onOverlayFocusHandler, o);
                        o.blurEvent.unsubscribe(this._onOverlayBlurHandler, o);

                        if (!bDestroyed) {
                            Event.removeListener(o.element, this.cfg.getProperty("focusevent"), this._onOverlayElementFocus);
                            o.cfg.setProperty("zIndex", originalZ, true);
                            o.cfg.setProperty("manager", null);
                        }

                        /* _managed Flag for custom or existing. Don't want to remove existing */
                        if (o.focusEvent._managed) { o.focusEvent = null; }
                        if (o.blurEvent._managed) { o.blurEvent = null; }

                        if (o.focus._managed) { o.focus = null; }
                        if (o.blur._managed) { o.blur = null; }
                    }
                };

                /**
                * Removes focus from all registered Overlays in the manager
                * @method blurAll
                */
                this.blurAll = function () {

                    var nOverlays = this.overlays.length,
                        i;

                    if (nOverlays > 0) {
                        i = nOverlays - 1;
                        do {
                            this.overlays[i].blur();
                        }
                        while(i--);
                    }
                };

                /**
                 * Updates the state of the OverlayManager and overlay, as a result of the overlay
                 * being blurred.
                 *
                 * @method _manageBlur
                 * @param {Overlay} overlay The overlay instance which got blurred.
                 * @protected
                 */
                this._manageBlur = function (overlay) {
                    var changed = false;
                    if (activeOverlay == overlay) {
                        Dom.removeClass(activeOverlay.element, OverlayManager.CSS_FOCUSED);
                        activeOverlay = null;
                        changed = true;
                    }
                    return changed;
                };

                /**
                 * Updates the state of the OverlayManager and overlay, as a result of the overlay
                 * receiving focus.
                 *
                 * @method _manageFocus
                 * @param {Overlay} overlay The overlay instance which got focus.
                 * @protected
                 */
                this._manageFocus = function(overlay) {
                    var changed = false;
                    if (activeOverlay != overlay) {
                        if (activeOverlay) {
                            activeOverlay.blur();
                        }
                        activeOverlay = overlay;
                        this.bringToTop(activeOverlay);
                        Dom.addClass(activeOverlay.element, OverlayManager.CSS_FOCUSED);
                        changed = true;
                    }
                    return changed;
                };

                var overlays = this.cfg.getProperty("overlays");

                if (! this.overlays) {
                    this.overlays = [];
                }

                if (overlays) {
                    this.register(overlays);
                    this.overlays.sort(this.compareZIndexDesc);
                }
            },

            /**
            * @method _onOverlayElementFocus
            * @description Event handler for the DOM event that is used to focus
            * the Overlay instance as specified by the "focusevent"
            * configuration property.
            * @private
            * @param {Event} p_oEvent Object representing the DOM event
            * object passed back by the event utility (Event).
            */
            _onOverlayElementFocus: function (p_oEvent) {

                var oTarget = Event.getTarget(p_oEvent),
                    oClose = this.close;

                if (oClose && (oTarget == oClose || Dom.isAncestor(oClose, oTarget))) {
                    this.blur();
                } else {
                    this.focus();
                }
            },

            /**
            * @method _onOverlayDestroy
            * @description "destroy" event handler for the Overlay.
            * @private
            * @param {String} p_sType String representing the name of the event
            * that was fired.
            * @param {Array} p_aArgs Array of arguments sent when the event
            * was fired.
            * @param {Overlay} p_oOverlay Object representing the overlay that
            * fired the event.
            */
            _onOverlayDestroy: function (p_sType, p_aArgs, p_oOverlay) {
                this.remove(p_oOverlay);
            },

            /**
            * @method _onOverlayFocusHandler
            *
            * focusEvent Handler, used to delegate to _manageFocus with the
            * correct arguments.
            *
            * @private
            * @param {String} p_sType String representing the name of the event
            * that was fired.
            * @param {Array} p_aArgs Array of arguments sent when the event
            * was fired.
            * @param {Overlay} p_oOverlay Object representing the overlay that
            * fired the event.
            */
            _onOverlayFocusHandler: function(p_sType, p_aArgs, p_oOverlay) {
                this._manageFocus(p_oOverlay);
            },

            /**
            * @method _onOverlayBlurHandler
            *
            * blurEvent Handler, used to delegate to _manageBlur with the
            * correct arguments.
            *
            * @private
            * @param {String} p_sType String representing the name of the event
            * that was fired.
            * @param {Array} p_aArgs Array of arguments sent when the event
            * was fired.
            * @param {Overlay} p_oOverlay Object representing the overlay that
            * fired the event.
            */
            _onOverlayBlurHandler: function(p_sType, p_aArgs, p_oOverlay) {
                this._manageBlur(p_oOverlay);
            },

            /**
             * Subscribes to the Overlay based instance focusEvent, to allow the OverlayManager to
             * monitor focus state.
             *
             * If the instance already has a focusEvent (e.g. Menu), OverlayManager will subscribe
             * to the existing focusEvent, however if a focusEvent or focus method does not exist
             * on the instance, the _bindFocus method will add them, and the focus method will
             * update the OverlayManager's state directly.
             *
             * @method _bindFocus
             * @param {Overlay} overlay The overlay for which focus needs to be managed
             * @protected
             */
            _bindFocus : function(overlay) {
                var mgr = this;

                if (!overlay.focusEvent) {
                    overlay.focusEvent = overlay.createEvent("focus");
                    overlay.focusEvent.signature = CustomEvent.LIST;
                    overlay.focusEvent._managed = true;
                } else {
                    overlay.focusEvent.subscribe(mgr._onOverlayFocusHandler, overlay, mgr);
                }

                if (!overlay.focus) {
                    Event.on(overlay.element, mgr.cfg.getProperty("focusevent"), mgr._onOverlayElementFocus, null, overlay);
                    overlay.focus = function () {
                        if (mgr._manageFocus(this)) {
                            // For Panel/Dialog
                            if (this.cfg.getProperty("visible") && this.focusFirst) {
                                this.focusFirst();
                            }
                            this.focusEvent.fire();
                        }
                    };
                    overlay.focus._managed = true;
                }
            },

            /**
             * Subscribes to the Overlay based instance's blurEvent to allow the OverlayManager to
             * monitor blur state.
             *
             * If the instance already has a blurEvent (e.g. Menu), OverlayManager will subscribe
             * to the existing blurEvent, however if a blurEvent or blur method does not exist
             * on the instance, the _bindBlur method will add them, and the blur method
             * update the OverlayManager's state directly.
             *
             * @method _bindBlur
             * @param {Overlay} overlay The overlay for which blur needs to be managed
             * @protected
             */
            _bindBlur : function(overlay) {
                var mgr = this;

                if (!overlay.blurEvent) {
                    overlay.blurEvent = overlay.createEvent("blur");
                    overlay.blurEvent.signature = CustomEvent.LIST;
                    overlay.focusEvent._managed = true;
                } else {
                    overlay.blurEvent.subscribe(mgr._onOverlayBlurHandler, overlay, mgr);
                }

                if (!overlay.blur) {
                    overlay.blur = function () {
                        if (mgr._manageBlur(this)) {
                            this.blurEvent.fire();
                        }
                    };
                    overlay.blur._managed = true;
                }

                overlay.hideEvent.subscribe(overlay.blur);
            },

            /**
             * Subscribes to the Overlay based instance's destroyEvent, to allow the Overlay
             * to be removed for the OverlayManager when destroyed.
             *
             * @method _bindDestroy
             * @param {Overlay} overlay The overlay instance being managed
             * @protected
             */
            _bindDestroy : function(overlay) {
                var mgr = this;
                overlay.destroyEvent.subscribe(mgr._onOverlayDestroy, overlay, mgr);
            },

            /**
             * Ensures the zIndex configuration property on the managed overlay based instance
             * is set to the computed zIndex value from the DOM (with "auto" translating to 0).
             *
             * @method _syncZIndex
             * @param {Overlay} overlay The overlay instance being managed
             * @protected
             */
            _syncZIndex : function(overlay) {
                var zIndex = Dom.getStyle(overlay.element, "zIndex");
                if (!isNaN(zIndex)) {
                    overlay.cfg.setProperty("zIndex", parseInt(zIndex, 10));
                } else {
                    overlay.cfg.setProperty("zIndex", 0);
                }
            },

            /**
            * Registers an Overlay or an array of Overlays with the manager. Upon
            * registration, the Overlay receives functions for focus and blur,
            * along with CustomEvents for each.
            *
            * @method register
            * @param {Overlay} overlay  An Overlay to register with the manager.
            * @param {Overlay[]} overlay  An array of Overlays to register with
            * the manager.
            * @return {boolean} true if any Overlays are registered.
            */
            register: function (overlay) {

                var zIndex,
                    registered = false,
                    i,
                    n;

                if (overlay instanceof Overlay) {

                    overlay.cfg.addProperty("manager", { value: this } );

                    this._bindFocus(overlay);
                    this._bindBlur(overlay);
                    this._bindDestroy(overlay);
                    this._syncZIndex(overlay);

                    this.overlays.push(overlay);
                    this.bringToTop(overlay);

                    registered = true;

                } else if (overlay instanceof Array) {

                    for (i = 0, n = overlay.length; i < n; i++) {
                        registered = this.register(overlay[i]) || registered;
                    }

                }

                return registered;
            },

            /**
            * Places the specified Overlay instance on top of all other
            * Overlay instances.
            * @method bringToTop
            * @param {YAHOO.widget.Overlay} p_oOverlay Object representing an
            * Overlay instance.
            * @param {String} p_oOverlay String representing the id of an
            * Overlay instance.
            */
            bringToTop: function (p_oOverlay) {

                var oOverlay = this.find(p_oOverlay),
                    nTopZIndex,
                    oTopOverlay,
                    aOverlays;

                if (oOverlay) {

                    aOverlays = this.overlays;
                    aOverlays.sort(this.compareZIndexDesc);

                    oTopOverlay = aOverlays[0];

                    if (oTopOverlay) {
                        nTopZIndex = Dom.getStyle(oTopOverlay.element, "zIndex");

                        if (!isNaN(nTopZIndex)) {

                            var bRequiresBump = false;

                            if (oTopOverlay !== oOverlay) {
                                bRequiresBump = true;
                            } else if (aOverlays.length > 1) {
                                var nNextZIndex = Dom.getStyle(aOverlays[1].element, "zIndex");
                                // Don't rely on DOM order to stack if 2 overlays are at the same zindex.
                                if (!isNaN(nNextZIndex) && (nTopZIndex == nNextZIndex)) {
                                    bRequiresBump = true;
                                }
                            }

                            if (bRequiresBump) {
                                oOverlay.cfg.setProperty("zindex", (parseInt(nTopZIndex, 10) + 2));
                            }
                        }
                        aOverlays.sort(this.compareZIndexDesc);
                    }
                }
            },

            /**
            * Attempts to locate an Overlay by instance or ID.
            * @method find
            * @param {Overlay} overlay  An Overlay to locate within the manager
            * @param {String} overlay  An Overlay id to locate within the manager
            * @return {Overlay} The requested Overlay, if found, or null if it
            * cannot be located.
            */
            find: function (overlay) {

                var isInstance = overlay instanceof Overlay,
                    overlays = this.overlays,
                    n = overlays.length,
                    found = null,
                    o,
                    i;

                if (isInstance || typeof overlay == "string") {
                    for (i = n-1; i >= 0; i--) {
                        o = overlays[i];
                        if ((isInstance && (o === overlay)) || (o.id == overlay)) {
                            found = o;
                            break;
                        }
                    }
                }

                return found;
            },

            /**
            * Used for sorting the manager's Overlays by z-index.
            * @method compareZIndexDesc
            * @private
            * @return {Number} 0, 1, or -1, depending on where the Overlay should
            * fall in the stacking order.
            */
            compareZIndexDesc: function (o1, o2) {

                var zIndex1 = (o1.cfg) ? o1.cfg.getProperty("zIndex") : null, // Sort invalid (destroyed)
                    zIndex2 = (o2.cfg) ? o2.cfg.getProperty("zIndex") : null; // objects at bottom.

                if (zIndex1 === null && zIndex2 === null) {
                    return 0;
                } else if (zIndex1 === null){
                    return 1;
                } else if (zIndex2 === null) {
                    return -1;
                } else if (zIndex1 > zIndex2) {
                    return -1;
                } else if (zIndex1 < zIndex2) {
                    return 1;
                } else {
                    return 0;
                }
            },

            /**
            * Shows all Overlays in the manager.
            * @method showAll
            */
            showAll: function () {
                var overlays = this.overlays,
                    n = overlays.length,
                    i;

                for (i = n - 1; i >= 0; i--) {
                    overlays[i].show();
                }
            },

            /**
            * Hides all Overlays in the manager.
            * @method hideAll
            */
            hideAll: function () {
                var overlays = this.overlays,
                    n = overlays.length,
                    i;

                for (i = n - 1; i >= 0; i--) {
                    overlays[i].hide();
                }
            },

            /**
            * Returns a string representation of the object.
            * @method toString
            * @return {String} The string representation of the OverlayManager
            */
            toString: function () {
                return "OverlayManager";
            }
        };
    }());

Copyright © 2008 Yahoo! Inc. All rights reserved.