Search code examples
javascripttooltip

tooltip in pure JS


I am trying this code but i get: document.getElementsByName(...).style is undefined

I have also a problem with the delegation, i think. Any help?

<html>
    <head>
    <style type="text/css">
    #toolTip {
        position:relative;
        width:200px;
        margin-top:-90px;
    }

    #toolTip p {

        padding:10px;
        background-color:#f9f9f9;
        border:solid 1px #a0c7ff;
        -moz-border-radius:5px;-ie-border-radius:5px;-webkit-border-radius:5px;-o-border-radius:5px;border-radius:5px;
    }

    #tailShadow {
        position:absolute;
        bottom:-8px;
        left:28px;
        width:0;height:0;
        border:solid 2px #fff;
        box-shadow:0 0 10px 1px #555;
    }

    #tail1 {
        position:absolute;
        bottom:-20px;
        left:20px;
        width:0;height:0;
        border-color:#a0c7ff transparent transparent transparent;
        border-width:10px;
        border-style:solid;
    }

    #tail2 {
        position:absolute;
        bottom:-18px;
        left:20px;
        width:0;height:0;
        border-color:#f9f9f9 transparent transparent transparent;
        border-width:10px;
        border-style:solid;
    }
    </style>
    <script type='text/javascript'>
    function load () {
            var elements = document.getElementsByName('toolTip');
            for(var i=0; i<elements.length; i++) {
                document.getElementsByName(elements[i]).style.visibility = 'hidden';
            }
        }
    </script>

    </head>



    <body onload="load()">
    <br><br><br><br><br><br><br><br><br><br><br><br>
    <a class="hd" 
    onMouseOver="document.getElementsByName('toolTip')[0].style.visibility = 'visible'"
    onmouseout ="document.getElementsByName('toolTip')[0].style.visibility = 'hidden'">aqui</a>
    <div id="toolTip" name="toolTip">
        <p>i can haz css tooltip</p>
        <div id="tailShadow"></div>
        <div id="tail1"></div>
        <div id="tail2"></div>
    </div>

    <br><br><br>
    <a class="hd" 
    onMouseOver="document.getElementsByName('toolTip')[0].style.visibility = 'visible'"
    onmouseout ="document.getElementsByName('toolTip')[0].style.visibility = 'hidden'">aqui</a>
    <div id="toolTip" name="toolTip">
        <p>i can haz css tooltip</p>
        <div id="tailShadow"></div>
        <div id="tail1"></div>
        <div id="tail2"></div>
    </div>

    </body>
    </html>

demo


Solution

  • Try changing the id toolTip to a class:

    <div class="toolTip">...</div>
    

    And change your JS to use the display style-thing, rather than visibility, nd the onmouseover's are best dealt with using JS event delegation:

    function load()
    {
        var i, tooltips = document.getElementsByClassName('toolTip'),
        mouseOver = function(e)
        {//handler for mouseover
            e = e || window.event;
            var i, target = e.target || e.srcElement,
            targetToolTip = target.nextElementSibling || nextSibling;//gets the next element in DOM (ie the tooltip)
            //check if mouse is over a relevant element:
            if (target.tagName.toLowerCase() !== 'a' || !target.className.match(/\bhd\b/))
            {//nope? stop here, then
                return e;
            }
            targetToolTip.style.display = 'block';//make visible
            for (i=0;i<tooltips.length;i++)
            {//closures are neat --> you have a reference to all tooltip elements from load scope
                if (tooltips[i] !== targetToolTip)
                {//not the one you need to see
                    tooltips[i].style.display = 'none';
                }
            }
        };
        for (i=0;i<tooltips.length;i++)
        {
            tooltips[i].style.display = 'none';
        }
        //add listener:
        if (document.body.addEventListener)
        {//IE > 9, chrome, safari, FF...
            document.body.addEventListener('mouseover',mouseOver,false);
        }
        else
        {//IE8
            document.body.attachEvent('onmouseover',mouseOver);
        }
    }
    

    Google JavaScript event delegation and closures if this code isn't clear, but that's just how I would tackle this kind of thing. IMO, it's fairly efficient (you could use the closure scope to keep track of the tooltip that's currently visible and not loop through all of them, too, that would be even better:

    function load()
    {
        var i, tooltips = document.getElementsByClassName('toolTip'),
        currentToolTip,//<-- reference currently visible
        mouseOver = function(e)
        {
            e = e || window.event;
            var i, target = e.target || e.srcElement,
            targetToolTip = target.nextElementSibling || nextSibling;
    
            if (target.tagName.toLowerCase() !== 'a' || !target.className.match(/\bhd\b/) || targetToolTip === currentToolTip)
            {//add check for currently visible TT, if so, no further action required
                return e;
            }
            if (currentToolTip !== undefined)
            {
                currentToolTip.style.display = 'none';//hide currently visible
            }
            targetToolTip.style.display = 'block';//make new visible
            currentToolTip = targetToolTip;//keep reference for next event
        };
        for (i=0;i<tooltips.length;i++)
        {
            tooltips[i].style.display = 'none';
        }
        if (document.body.addEventListener)
        {
            document.body.addEventListener('mouseover',mouseOver,false);
        }
        else
        {
            document.body.attachEvent('onmouseover',mouseOver);
        }
    }
    

    And you're there.

    Edit:
    To hide the tooltip on mouseout, you can either add a second listener directly:

    function load()
    {
        var i, tooltips = document.getElementsByClassName('toolTip'),
        currentToolTip,//<-- reference currently visible
        mouseOver = function(e)
        {
            e = e || window.event;
            var i, target = e.target || e.srcElement,
            targetToolTip = target.nextElementSibling || nextSibling;
    
            if (target.tagName.toLowerCase() !== 'a' || !target.className.match(/\bhd\b/) || targetToolTip === currentToolTip)
            {//add check for currently visible TT, if so, no further action required
                return e;
            }
            if (currentToolTip !== undefined)
            {
                currentToolTip.style.display = 'none';//hide currently visible
            }
            targetToolTip.style.display = 'block';//make new visible
            currentToolTip = targetToolTip;//keep reference for next event
        },
        mouseOut = function(e)
        {
            e = e || window.event;
            var movedTo = document.elementFromPoint(e.clientX,e.clientY);//check where the cursor is NOW
            if (movedTo === curentToolTip || currentToolTip === undefined)
            {//if cursor moved to tooltip, don't hide it, if nothing is visible, stop
                return e;
            }
            currentTooltip.style.display = 'none';
            currentTooltip = undefined;//no currentToolTip anymore
        };
        for (i=0;i<tooltips.length;i++)
        {
            tooltips[i].style.display = 'none';
        }
        if (document.body.addEventListener)
        {
            document.body.addEventListener('mouseover',mouseOver,false);
            document.body.addEventListener('mouseout',mouseOut,false);
        }
        else
        {
            document.body.attachEvent('onmouseover',mouseOver);
            document.body.attachEvent('onmouseout',mouseOut);
        }
    }
    

    Note, this is completely untested. I'm not entirely sure if IE < 9 supports elementFromPoint (gets the DOM element that is rendered at certain coordinates), or even if the IE event object has the clientX and clientY properties, but I figure a quick google will tell you more, including how to get the coordinates and the element that is to be found under the cursor in old, crummy, ghastly IE8, but this should help you on your way. Of course, if you don't want the contents of the tooltip to be selectable, just change the mouseOut function to:

    mouseOut = function(e)
    {
        e = e || window.event;
        var target = e.target || e.srcElement;
        if (currentToolTip)
        {
            currentToolTip.style.diplay = 'none';
            currentToolTip = undefined;
        }
    };
    

    No need to check if the mouseout was on the correct element, just check if there is a current tooltip, and hide it.