rg6
rg6

Reputation: 329

Bootstrap Tooltips with AJAX (One More Time)

Mundane, Common Use Case

I would like a tool tip that shows a loading GIF immediately on mouseover that is replaced by HTML resulting from an AJAX success callback. There are almost as many broken answers for AJAX tool tips as people asking this exact question online. In particular, I have been modeling my efforts on this, this, and especially several answers here.

The problem that I consistently encounter for all these solutions, even those specifically claiming to address the issue, is that tool tips occasionally appear late and fail to disappear. It is not a library issue either, because I have had the same problem in the past with jQuery-UI tool tips.

Libraries

Previously tried with jQuery 1 and had the same problem:

My page also relies on the jQuery Datatables v1.10.11 library, but this is orthogonal to the tool tip issue other than the fact that my particular use case generates more and faster AJAX tool tip requests with variable latencies, which increases the probability of observing the "sticking" problem.

Current Attempt

My current solution is based roughly on the answer by 'towr' here that is very close to working. I have tried every solution above that on the page and they either are broken for one reason or another or have the "sticking" tool tip issue.

So after an AJAX request loads data to populate the table, I iterate over the cells by class $( ".tooltip-cell" ).on( "mouseover", set_tooltip ), where set_tooltip is a function that does the following

var $e = $(this);
if( $e.attr( "title" ) === undefined ) { // request data once per cell
    // show loading GIF
    $e.attr( "title", "<img src='images/wait.gif'/>" );
    $e.tooltip(
        {
            html: true,
            trigger: "hover"
        }
        ).tooltip( "show" );
    // perform AJAX GET
    $.ajax(
            {
                url: "ajax/tooltip_data.php",
                data:
                        {
                            lookup_id: $e.attr("lookup_id")
                        },
                success:
                        function(response) {
                            // prepare final version of tooltip
                            $e.attr( "title", response ).tooltip( "fixTitle" );
                            // if mouse still hovers, display final version
                            if( $e.is( ":hover" ) ) {
                                $e.tooltip( "show" );
                            }
                        },
                dataType: "html"
            }
            );
}

The problem here is the hover detection in the AJAX success callback. Now apparently the .is(":hover") has been deprecated for quite awhile, so I have tried various alternative methods, such as $( ":focus :active" ).filter( $e ).length > 0, but the result is always either that all tool tips are shown and stick or no tool tips are shown. It is easy to reproduce this by introducing a setTimeout( function() {}, 2000 ); to wrap the code inside the callback. Then the problem always occurs. In fact, I can create 10 adjacent tool tips, run the mouse across them, and after 2 seconds, they will all appear with the AJAX HTML and stick, despite the fact that the mouse is not hovering on any of them. With print statements and debugging, I can verify that the value inside if( <hover-check> ) is correctly, in turn, each of the 10 elements, so somehow the hover check is either itself incorrect or returning stale information.

The Crux

How can I accurately determine if a mouse is currently positioned on top of an element at the time that the callback corresponding to that element has prepared the tool tip content?

Other Thoughts

I have had two complicating issues while working on this, but I do not think they are related.

1) I get an error that .tooltip() is undefined unless my jQuery JS include is in <head> and my Bootstrap JS include is at the bottom of my HTML body. If I either move Bootstrap up or jQuery down (always keeping jQuery above Bootstrap), I get the error.

2) The solution here seems nice, but no matter how closely I try to duplicate the code, I get $el.data(...) undefined errors for the line $el.data('tooltip').tip().is(':visible') in my code. This is despite the fact that the linked demo page itself works fine for me.

General Hope

I hope that this question can become a canonical resource for people who want to do this, because I actually spent 16 (yes, really 16) hours yesterday working on this, implementing every variation I could imagine of every solution I could find online.

Upvotes: 6

Views: 3859

Answers (1)

rg6
rg6

Reputation: 329

Core Idea

For me, the key was in the solution provided here, which I had linked in the original question.

The issue with this solution as it appeared in the source code was the use of $el.data('tooltip').tip().is(':visible') which invariably caused the error $el.data(...) undefined, but the idea of checking the display status of the tooltip rather than the hover status of the mouse seemed promising.

A close examination of the Bootstrap v3.3.6 tooltip operation revealed that it sets 'aria-describedby' on the hover element to the element ID of the tooltip. With this information, we can do $( "#" + $el.attr( "aria-describedby" ) ).is( ":visible" )

I don't know how future-proof this will be, but it is, finally, "stick"-proof.

Code

$( ".ajax-tooltip-required" ).tooltip(
        {
            html: true,
            trigger: "manual"
        }
        ).on(
        {
            mouseenter:
                function() {
                    var $el = $(this);
                    if( $el.data( "fetched" ) === undefined ) {
                        $el.data( "fetched", true );
                        $el.attr( "data-original-title", "<img src='images/wait.gif'/>" ).tooltip( "show" );
                        $.ajax(
                            {
                                url: "ajax/tooltip_data.php",
                                data:                                   {
                                        lookup_id: $el.attr("lookup_id")
                                    },
                                success:
                                    function( response ) {
                                        $el.attr( "data-original-title", response );
                                        if( $( "#" + $el.attr( "aria-describedby" ) ).is( ":visible" ) ) {
                                            $el.tooltip( "show" );
                                        }
                                    },
                                dataType: "html"
                            }
                            );
                    } else {
                        $(this).tooltip( "show" );
                    }
                },
            mouseleave:
                function() {
                    $(this).tooltip( "hide" );
                }
        }
        );

Upvotes: 1

Related Questions