views:

221

answers:

2

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?

+1  A: 

That looks like a whole lot of work just to avoid having the little tiny image in each cell. You're not really gaining very much by "reusing" just one instance of the element. Browsers will only have to download the image once. And from a performance point of view, fiddling with the DOM tree takes more resources than just switching the display property of a highlighted element.

Simplest approach may just be to put the <span> in every cell you ever print out, then use CSS to hide/display.

dmb
I agree, but in my actual application, there are quite a few elements, not just a single icon.
Tauren
+1  A: 

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

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

Unlike mouseover and mouseout, the mousenter 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 :)

Nick Craver
@Nick: Perfect! Thanks so much. Strange thing was it didn't work with live('mouseenter mouseleave',...), but when I split it into two separate live bindings (as you suggested), it worked.
Tauren