Search code examples
jquerylivecss-position

Mouseover triggered on absolute positioned div


Objective

Have a small magnifying glass icon that appears in the top right corner of a table cell when the table cell is hovered over. Mousing over the magnifying glass icon and clicking it will open a dialog window to show detailed information about the item in that particular table cell. I want to reuse the same icon for hundreds of table cells without recreating it each time.

Partial Solution

Have a single <span> that is absolutely positioned and hidden. When a _previewable table cell is hovered, the <span> is moved to the correct location and shown. This <span> is also moved in the DOM to be a child of the _previewable table cell. This enables a click handler attached to the <span> to find the _previewable parent, and get information from it's jquery data() object that is used to populate the contents of the dialog.

Here is a very simplified version of my HTML:

<body>
    <span id="options">
        <a class="ui-state-default ui-corner-all">
            <span class="ui-icon ui-icon-search"></span>
            Preview
        </a>
    </span>
    <table>
         <tr>
             <td class="_previewable">
                 <img scr="user_1.png"/>
                 <span>Bob Smith</span>
             </td>
        </tr>
   </table>
</body>

And this CSS:

#options {
    position: absolute;
    display: none;
}

With this jQuery code:

var $options = $('#options');
$options.click(function() {
    $item = $(this).parents("._previewable");
    // Show popup based on data in $item.data("id");
    Layout.renderPopup($item.data("id"),$item.data("popup"));               
});

$('._previewable').live('mouseover mouseout',function(event) {
    if (event.type == 'mouseover') {
        var $target = $(this);
        var $parent = $target.offsetParent()[0];

        var left = $parent.scrollLeft + $target.position().left 
            + $target.outerWidth() - $options.outerWidth() + 1;
        var top = $parent.scrollTop + $target.position().top + 2;

        $options.appendTo($target);
        $options.css({
            "left": left + "px",
            "top": top + "px"
        }).show();
    }
    else {
        // On mouseout, $options continues to be a child of $(this)
        $options.hide();
    }
});     

Problem

This solution works perfectly until the contents of my table are reloaded or changed via AJAX. Because the <span> was moved from the <body> to be a child of the cell, it gets thrown out and replaced during the AJAX call. So my first thought is to move the <span> back to the body on mouseout of the table cell, like this:

    else {
        // On mouseout, $options is moved back to be a child of body
        $options.appendTo("body");
        $options.hide();
    }

However, with this, the <span> disappears as soon as it is mouseover. The mouseout event seems to be called on _previewable when the mouse moves into the <span>, even though the <span> is a child of _previewable and fully displayed within the boundaries of the _previewable table cell. At this point, I've only tested this in Chrome.

Questions

  1. Why would mouseout be called on _previewable, when the mouse is still within the boundaries of _previewable? Is it because the <span> is absolutely positioned?

  2. How can I make this work, without recreating the <span> and it's click handler on each AJAX table referesh?


Solution

  • If you change your events then the rest of what you have (appending to <body>) will work, like this:

    $('._previewable').live('mouseenter mouseleave',function(event) {
    

    Unlike mouseover and mouseout, the mouseenter and mouseleave events won't fire when moving to or from a child, so they'll behave like you want in this case.

    For cleanliness though, I'd bind .live('mouseenter) and .live('mouseleave') separately and remove the if(), it's much easier to look and see what's going on, might be just me though :)