Yahoo! UI Library

Container  2.4.1

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

Show Private Show Protected
(function () {

    /**
    * Tooltip is an implementation of Overlay that behaves like an OS tooltip, 
    * displaying when the user mouses over a particular element, and 
    * disappearing on mouse out.
    * @namespace YAHOO.widget
    * @class Tooltip
    * @extends YAHOO.widget.Overlay
    * @constructor
    * @param {String} el The element ID representing the Tooltip <em>OR</em>
    * @param {HTMLElement} el The element representing the Tooltip
    * @param {Object} userConfig The configuration object literal containing 
    * the configuration that should be set for this Overlay. See configuration 
    * documentation for more details.
    */
    YAHOO.widget.Tooltip = function (el, userConfig) {
    
        YAHOO.widget.Tooltip.superclass.constructor.call(this, el, userConfig);
    
    };


    var Lang = YAHOO.lang,
        Event = YAHOO.util.Event,
        Dom = YAHOO.util.Dom,
        Tooltip = YAHOO.widget.Tooltip,
    
        m_oShadowTemplate,
        
        /**
        * Constant representing the Tooltip's configuration properties
        * @property DEFAULT_CONFIG
        * @private
        * @final
        * @type Object
        */
        DEFAULT_CONFIG = {
        
            "PREVENT_OVERLAP": { 
                key: "preventoverlap", 
                value: true, 
                validator: Lang.isBoolean, 
                supercedes: ["x", "y", "xy"] 
            },
        
            "SHOW_DELAY": { 
                key: "showdelay", 
                value: 200, 
                validator: Lang.isNumber 
            }, 
        
            "AUTO_DISMISS_DELAY": { 
                key: "autodismissdelay", 
                value: 5000, 
                validator: Lang.isNumber 
            }, 
        
            "HIDE_DELAY": { 
                key: "hidedelay", 
                value: 250, 
                validator: Lang.isNumber 
            }, 
        
            "TEXT": { 
                key: "text", 
                suppressEvent: true 
            }, 
        
            "CONTAINER": { 
                key: "container"
            }
        
        };

    
    /**
    * Constant representing the Tooltip CSS class
    * @property YAHOO.widget.Tooltip.CSS_TOOLTIP
    * @static
    * @final
    * @type String
    */
    Tooltip.CSS_TOOLTIP = "yui-tt";


    /* 
        "hide" event handler that sets a Tooltip instance's "width"
        configuration property back to its original value before 
        "setWidthToOffsetWidth" was called.
    */
    
    function restoreOriginalWidth(p_sType, p_aArgs, p_oObject) {

        var sOriginalWidth = p_oObject[0],
            sNewWidth = p_oObject[1],
            oConfig = this.cfg,
            sCurrentWidth = oConfig.getProperty("width");

        if (sCurrentWidth == sNewWidth) {
            
            oConfig.setProperty("width", sOriginalWidth);
        
        }

        this.unsubscribe("hide", this._onHide, p_oObject);
    
    }

    /* 
        "beforeShow" event handler that sets a Tooltip instance's "width"
        configuration property to the value of its root HTML 
        elements's offsetWidth
    */

    function setWidthToOffsetWidth(p_sType, p_aArgs) {

        var oBody = document.body,
            oConfig = this.cfg,
            sOriginalWidth = oConfig.getProperty("width"),
            sNewWidth,
            oClone;

        
        if ((!sOriginalWidth || sOriginalWidth == "auto") && 
            (oConfig.getProperty("container") != oBody || 
            oConfig.getProperty("x") >= Dom.getViewportWidth() || 
            oConfig.getProperty("y") >= Dom.getViewportHeight())) {

            oClone = this.element.cloneNode(true);
            oClone.style.visibility = "hidden";
            oClone.style.top = "0px";
            oClone.style.left = "0px";
            
            oBody.appendChild(oClone);

            sNewWidth = (oClone.offsetWidth + "px");

            oBody.removeChild(oClone);

            oClone = null;

            oConfig.setProperty("width", sNewWidth);

            oConfig.refireEvent("xy");

            this.subscribe("hide", restoreOriginalWidth, 
                [(sOriginalWidth || ""), sNewWidth]);
        
        }

    }

    // "onDOMReady" that renders the ToolTip

    function onDOMReady(p_sType, p_aArgs, p_oObject) {
    
        this.render(p_oObject);
    
    }


    //  "init" event handler that automatically renders the Tooltip

    function onInit() {

        Event.onDOMReady(onDOMReady, this.cfg.getProperty("container"), this);

    }

    
    YAHOO.extend(Tooltip, YAHOO.widget.Overlay, { 
    
        /**
        * The Tooltip initialization method. This method is automatically 
        * called by the constructor. A Tooltip is automatically rendered by 
        * the init method, and it also is set to be invisible by default, 
        * and constrained to viewport by default as well.
        * @method init
        * @param {String} el The element ID representing the Tooltip <em>OR</em>
        * @param {HTMLElement} el The element representing the Tooltip
        * @param {Object} userConfig The configuration object literal 
        * containing the configuration that should be set for this Tooltip. 
        * See configuration documentation for more details.
        */
        init: function (el, userConfig) {

            this.logger = new YAHOO.widget.LogWriter(this.toString());

            Tooltip.superclass.init.call(this, el);

            this.beforeInitEvent.fire(Tooltip);

            Dom.addClass(this.element, Tooltip.CSS_TOOLTIP);

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

            this.cfg.queueProperty("visible", false);
            this.cfg.queueProperty("constraintoviewport", true);
    
            this.setBody("");

            this.subscribe("beforeShow", setWidthToOffsetWidth);
            this.subscribe("init", onInit);
            this.subscribe("render", this.onRender);
    
            this.initEvent.fire(Tooltip);

        },
        
        /**
        * Initializes the class's configurable properties which can be 
        * changed using the Overlay's Config object (cfg).
        * @method initDefaultConfig
        */
        initDefaultConfig: function () {

            Tooltip.superclass.initDefaultConfig.call(this);
        
            /**
            * Specifies whether the Tooltip should be kept from overlapping 
            * its context element.
            * @config preventoverlap
            * @type Boolean
            * @default true
            */
            this.cfg.addProperty(DEFAULT_CONFIG.PREVENT_OVERLAP.key, {
                value: DEFAULT_CONFIG.PREVENT_OVERLAP.value, 
                validator: DEFAULT_CONFIG.PREVENT_OVERLAP.validator, 
                supercedes: DEFAULT_CONFIG.PREVENT_OVERLAP.supercedes
            });
        
            /**
            * The number of milliseconds to wait before showing a Tooltip 
            * on mouseover.
            * @config showdelay
            * @type Number
            * @default 200
            */
            this.cfg.addProperty(DEFAULT_CONFIG.SHOW_DELAY.key, {
                handler: this.configShowDelay,
                value: 200, 
                validator: DEFAULT_CONFIG.SHOW_DELAY.validator
            });
        
            /**
            * The number of milliseconds to wait before automatically 
            * dismissing a Tooltip after the mouse has been resting on the 
            * context element.
            * @config autodismissdelay
            * @type Number
            * @default 5000
            */
            this.cfg.addProperty(DEFAULT_CONFIG.AUTO_DISMISS_DELAY.key, {
                handler: this.configAutoDismissDelay,
                value: DEFAULT_CONFIG.AUTO_DISMISS_DELAY.value,
                validator: DEFAULT_CONFIG.AUTO_DISMISS_DELAY.validator
            });
        
            /**
            * The number of milliseconds to wait before hiding a Tooltip 
            * on mouseover.
            * @config hidedelay
            * @type Number
            * @default 250
            */
            this.cfg.addProperty(DEFAULT_CONFIG.HIDE_DELAY.key, {
                handler: this.configHideDelay,
                value: DEFAULT_CONFIG.HIDE_DELAY.value, 
                validator: DEFAULT_CONFIG.HIDE_DELAY.validator
            });
        
            /**
            * Specifies the Tooltip's text. 
            * @config text
            * @type String
            * @default null
            */
            this.cfg.addProperty(DEFAULT_CONFIG.TEXT.key, {
                handler: this.configText,
                suppressEvent: DEFAULT_CONFIG.TEXT.suppressEvent
            });

            /**
            * Specifies the container element that the Tooltip's markup 
            * should be rendered into.
            * @config container
            * @type HTMLElement/String
            * @default document.body
            */
            this.cfg.addProperty(DEFAULT_CONFIG.CONTAINER.key, {
                handler: this.configContainer,
                value: document.body
            });
        
            /**
            * Specifies the element or elements that the Tooltip should be 
            * anchored to on mouseover.
            * @config context
            * @type HTMLElement[]/String[]
            * @default null
            */ 

            /**
            * String representing the width of the Tooltip.  <em>Please note:
            * </em> As of version 2.3 if either no value or a value of "auto" 
            * is specified, and the Toolip's "container" configuration property
            * is set to something other than <code>document.body</code> or 
            * its "context" element resides outside the immediately visible 
            * portion of the document, the width of the Tooltip will be 
            * calculated based on the offsetWidth of its root HTML and set just 
            * before it is made visible.  The original value will be 
            * restored when the Tooltip is hidden. This ensures the Tooltip is 
            * rendered at a usable width.  For more information see 
            * SourceForge bug #1685496 and SourceForge 
            * bug #1735423.
            * @config width
            * @type String
            * @default null
            */
        
        },
        
        // BEGIN BUILT-IN PROPERTY EVENT HANDLERS //
        
        /**
        * The default event handler fired when the "text" property is changed.
        * @method configText
        * @param {String} type The CustomEvent type (usually the property name)
        * @param {Object[]} args The CustomEvent arguments. For configuration 
        * handlers, args[0] will equal the newly applied value for the property.
        * @param {Object} obj The scope object. For configuration handlers, 
        * this will usually equal the owner.
        */
        configText: function (type, args, obj) {
            var text = args[0];
            if (text) {
                this.setBody(text);
            }
        },
        
        /**
        * The default event handler fired when the "container" property 
        * is changed.
        * @method configContainer
        * @param {String} type The CustomEvent type (usually the property name)
        * @param {Object[]} args The CustomEvent arguments. For 
        * configuration handlers, args[0] will equal the newly applied value 
        * for the property.
        * @param {Object} obj The scope object. For configuration handlers,
        * this will usually equal the owner.
        */
        configContainer: function (type, args, obj) {

            var container = args[0];

            if (typeof container == 'string') {

                this.cfg.setProperty("container", 
                    document.getElementById(container), true);

            }

        },
        
        /**
        * @method _removeEventListeners
        * @description Removes all of the DOM event handlers from the HTML
        *  element(s) that trigger the display of the tooltip.
        * @protected
        */
        _removeEventListeners: function () {
        
            var aElements = this._context,
                nElements,
                oElement,
                i;
        
            
            if (aElements) {
                nElements = aElements.length;
                if (nElements > 0) {
                    i = nElements - 1;
                    do {
                        oElement = aElements[i];
        
                        Event.removeListener(oElement, "mouseover", 
                            this.onContextMouseOver);

                        Event.removeListener(oElement, "mousemove", 
                            this.onContextMouseMove);

                        Event.removeListener(oElement, "mouseout", 
                            this.onContextMouseOut);
                    }
                    while (i--);
                }
            }
        },
        
        /**
        * The default event handler fired when the "context" property 
        * is changed.
        * @method configContext
        * @param {String} type The CustomEvent type (usually the property name)
        * @param {Object[]} args The CustomEvent arguments. For configuration 
        * handlers, args[0] will equal the newly applied value for the property.
        * @param {Object} obj The scope object. For configuration handlers,
        * this will usually equal the owner.
        */
        configContext: function (type, args, obj) {
        
            var context = args[0],
                aElements,
                nElements,
                oElement,
                i;
            
        
            if (context) {
        
                // Normalize parameter into an array
                if (! (context instanceof Array)) {

                    if (typeof context == "string") {

                        this.cfg.setProperty("context", 
                            [document.getElementById(context)], true);

                    } else { // Assuming this is an element

                        this.cfg.setProperty("context", [context], true);

                    }

                    context = this.cfg.getProperty("context");

                }
        
        
                // Remove any existing mouseover/mouseout listeners
                this._removeEventListeners();
        
                // Add mouseover/mouseout listeners to context elements
                this._context = context;
        
                aElements = this._context;
                
                if (aElements) {
            
                    nElements = aElements.length;
                    
                    if (nElements > 0) {
                    
                        i = nElements - 1;
                        
                        do {
            
                            oElement = aElements[i];
            
                            Event.on(oElement, "mouseover", 
                                this.onContextMouseOver, this);

                            Event.on(oElement, "mousemove", 
                                this.onContextMouseMove, this);

                            Event.on(oElement, "mouseout", 
                                this.onContextMouseOut, this);
                        
                        }
                        while (i--);
                    
                    }
            
                }
        
            }
        },
        
        // END BUILT-IN PROPERTY EVENT HANDLERS //
        
        // BEGIN BUILT-IN DOM EVENT HANDLERS //
        
        /**
        * The default event handler fired when the user moves the mouse while 
        * over the context element.
        * @method onContextMouseMove
        * @param {DOMEvent} e The current DOM event
        * @param {Object} obj The object argument
        */
        onContextMouseMove: function (e, obj) {
            obj.pageX = Event.getPageX(e);
            obj.pageY = Event.getPageY(e);
        
        },
        
        /**
        * The default event handler fired when the user mouses over the 
        * context element.
        * @method onContextMouseOver
        * @param {DOMEvent} e The current DOM event
        * @param {Object} obj The object argument
        */
        onContextMouseOver: function (e, obj) {
        
            var context = this;
        
            if (obj.hideProcId) {

                clearTimeout(obj.hideProcId);

                obj.logger.log("Clearing hide timer: " + 
                    obj.hideProcId, "time");

                obj.hideProcId = null;

            }

            Event.on(context, "mousemove", obj.onContextMouseMove, obj);

            if (context.title) {
                obj._tempTitle = context.title;
                context.title = "";
            }

            /**
            * The unique process ID associated with the thread responsible 
            * for showing the Tooltip.
            * @type int
            */
            obj.showProcId = obj.doShow(e, context);
            obj.logger.log("Setting show tooltip timeout: " + 
                obj.showProcId, "time");

        },
        
        /**
        * The default event handler fired when the user mouses out of 
        * the context element.
        * @method onContextMouseOut
        * @param {DOMEvent} e The current DOM event
        * @param {Object} obj The object argument
        */
        onContextMouseOut: function (e, obj) {
            var el = this;
        
            if (obj._tempTitle) {
                el.title = obj._tempTitle;
                obj._tempTitle = null;
            }
        
            if (obj.showProcId) {
                clearTimeout(obj.showProcId);
                obj.logger.log("Clearing show timer: " + 
                    obj.showProcId, "time");
                obj.showProcId = null;
            }
        
            if (obj.hideProcId) {
                clearTimeout(obj.hideProcId);
                obj.logger.log("Clearing hide timer: " + 
                    obj.hideProcId, "time");
                obj.hideProcId = null;
            }
        
        
            obj.hideProcId = setTimeout(function () {
                obj.hide();
    
            }, obj.cfg.getProperty("hidedelay"));
    
        },
        
        // END BUILT-IN DOM EVENT HANDLERS //
        
        /**
        * Processes the showing of the Tooltip by setting the timeout delay 
        * and offset of the Tooltip.
        * @method doShow
        * @param {DOMEvent} e The current DOM event
        * @return {Number} The process ID of the timeout function associated 
        * with doShow
        */
        doShow: function (e, context) {

            var yOffset = 25,
                me = this;

            if (YAHOO.env.ua.opera && context.tagName && 
                context.tagName.toUpperCase() == "A") {

                yOffset += 12;

            }

            return setTimeout(function () {

                var txt = me.cfg.getProperty("text");
                // title does not over-ride text
                if (me._tempTitle && (txt === "" || YAHOO.lang.isUndefined(txt) || YAHOO.lang.isNull(txt))) {
                    me.setBody(me._tempTitle);
                } else {
                    me.cfg.refireEvent("text");
                }

                me.logger.log("Show tooltip", "time");
                me.moveTo(me.pageX, me.pageY + yOffset);

                if (me.cfg.getProperty("preventoverlap")) {
                    me.preventOverlap(me.pageX, me.pageY);
                }

                Event.removeListener(context, "mousemove", 
                    me.onContextMouseMove);

                me.show();
                me.hideProcId = me.doHide();

                me.logger.log("Hide tooltip time active: " +
                    me.hideProcId, "time");

            }, this.cfg.getProperty("showdelay"));
        
        },
        
        /**
        * Sets the timeout for the auto-dismiss delay, which by default is 5 
        * seconds, meaning that a tooltip will automatically dismiss itself 
        * after 5 seconds of being displayed.
        * @method doHide
        */
        doHide: function () {
        
            var me = this;
        
            me.logger.log("Setting hide tooltip timeout", "time");
        
            return setTimeout(function () {
        
                me.logger.log("Hide tooltip", "time");
                me.hide();
        
            }, this.cfg.getProperty("autodismissdelay"));
        
        },
        
        /**
        * Fired when the Tooltip is moved, this event handler is used to 
        * prevent the Tooltip from overlapping with its context element.
        * @method preventOverlay
        * @param {Number} pageX The x coordinate position of the mouse pointer
        * @param {Number} pageY The y coordinate position of the mouse pointer
        */
        preventOverlap: function (pageX, pageY) {
        
            var height = this.element.offsetHeight,
                mousePoint = new YAHOO.util.Point(pageX, pageY),
                elementRegion = Dom.getRegion(this.element);
        
            elementRegion.top -= 5;
            elementRegion.left -= 5;
            elementRegion.right += 5;
            elementRegion.bottom += 5;
        
            this.logger.log("context " + elementRegion, "ttip");
            this.logger.log("mouse " + mousePoint, "ttip");
        
            if (elementRegion.contains(mousePoint)) {
                this.logger.log("OVERLAP", "warn");
                this.cfg.setProperty("y", (pageY - height - 5));
            }
        },


        /**
        * @method onRender
        * @description "render" event handler for the Tooltip.
        * @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.
        */
        onRender: function (p_sType, p_aArgs) {
    
            function sizeShadow() {
    
                var oElement = this.element,
                    oShadow = this._shadow;
            
                if (oShadow) {
            
                    oShadow.style.width = (oElement.offsetWidth + 6) + "px";
                    oShadow.style.height = (oElement.offsetHeight + 1) + "px"; 
            
                }
            
            }


            function addShadowVisibleClass() {
            
                Dom.addClass(this._shadow, "yui-tt-shadow-visible");
            
            }
            

            function removeShadowVisibleClass() {
        
                Dom.removeClass(this._shadow, "yui-tt-shadow-visible");
            
            }
    
    
            function createShadow() {
    
                var oShadow = this._shadow,
                    oElement,
                    Module,
                    nIE,
                    me;
    
                if (!oShadow) {
    
                    oElement = this.element;
                    Module = YAHOO.widget.Module;
                    nIE = YAHOO.env.ua.ie;
                    me = this;
    
                    if (!m_oShadowTemplate) {
        
                        m_oShadowTemplate = document.createElement("div");
                        m_oShadowTemplate.className = "yui-tt-shadow";
                    
                    }
        
                    oShadow = m_oShadowTemplate.cloneNode(false);
        
                    oElement.appendChild(oShadow);
                    
                    this._shadow = oShadow;
    
                    addShadowVisibleClass.call(this);
        
                    this.subscribe("beforeShow", addShadowVisibleClass);
                    this.subscribe("beforeHide", removeShadowVisibleClass);

                    if (nIE == 6 || 
                        (nIE == 7 && document.compatMode == "BackCompat")) {
                
                        window.setTimeout(function () { 
        
                            sizeShadow.call(me); 
        
                        }, 0);
    
                        this.cfg.subscribeToConfigEvent("width", sizeShadow);
                        this.cfg.subscribeToConfigEvent("height", sizeShadow);
                        this.subscribe("changeContent", sizeShadow);
    
                        Module.textResizeEvent.subscribe(sizeShadow, 
                                                            this, true);
                        
                        this.subscribe("destroy", function () {
                        
                            Module.textResizeEvent.unsubscribe(sizeShadow, 
                                                                    this);
                        
                        });
                
                    }
                
                }
    
            }
    
    
            function onBeforeShow() {
            
                createShadow.call(this);
    
                this.unsubscribe("beforeShow", onBeforeShow);
            
            }
    
    
            if (this.cfg.getProperty("visible")) {
    
                createShadow.call(this);
            
            }
            else {
    
                this.subscribe("beforeShow", onBeforeShow);
            
            }
        
        },
        
        /**
        * Removes the Tooltip element from the DOM and sets all child 
        * elements to null.
        * @method destroy
        */
        destroy: function () {
        
            // Remove any existing mouseover/mouseout listeners
            this._removeEventListeners();
        
            Tooltip.superclass.destroy.call(this);  
        
        },
        
        /**
        * Returns a string representation of the object.
        * @method toString
        * @return {String} The string representation of the Tooltip
        */
        toString: function () {
            return "Tooltip " + this.id;
        }
    
    });

}());

Copyright © 2007 Yahoo! Inc. All rights reserved.