Sourav
Sourav

Reputation: 17530

jQuery tooltip not working on dynamically loaded HTML content

Site consists of 3 pages, index.html, 1.html, 2.html.
The goal is to load html from 1.html which contains tooltip and some other htmls and display it in a div, after mouseover on the loaded tooltips I want tooltip texts to be fetched from 2.html.
However this code does not works, no error is shown and AJAX is never executed.

$(document).ready(function(){
    $( ".content span" ).tooltip({
        track:true,
        open: function( event, ui ) {
              var id = this.id; 
              var userid = $(this).attr('data-id');
              
              $.ajax({
                  url:'2.html',
                  type:'post',
                  data:{userid:userid},
                  success: function(response){
                      $("#"+id).tooltip('option','content',response);
                  }
              });
        }
    }); 
});
.container{
    margin: 0 auto;
    width: 30%;
}

.content{
    border: 1px solid black;
    padding: 5px;
    margin-bottom: 5px;
}
.content span{
    width: 250px;
}

.content span:hover{
    cursor: pointer;
}
<!doctype html>
<html>
    <head>
        <title>Dynamically show data in the Tooltip using AJAX</title>      
        <link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/themes/smoothness/jquery-ui.css">
        <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> 
        <script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>      
    </head>
    <body>
    <input type="button" id="btnLoad" value="Load"/>
        <div class='container' id="container"></div>
    </body>
<script>
$( "#btnLoad" ).click(function() {
$.ajax({
  url: "1.html",
  context: document.body
}).done(function( msg ) {
    $('#container').html(msg);
    $(this).tooltip();
  });
});
</script>
</html>

Sample HTML of 1.html file

            <div class='content'>
            <span title='Please wait..' data-id='1' id='user_1'>Pepsi</span>
        </div>

        <div class='content'>
            <span title='Please wait..' data-id='2' id='user_2'>7 Up</span>
        </div>

Sample HTML of 2.html file

This Tooltip text will be replaced with texts from Database

Upvotes: 0

Views: 2528

Answers (2)

Don&#39;t Panic
Don&#39;t Panic

Reputation: 14520

UPDATED

Your updated question is considerably clearer, and helped me track down what is happening. My original answer solved one of the issues, but not all. There are actually a series of problems.

Problem 1

The first problem is the one I previously described: jQuery selectors only match page elements that exist at page load. $(".content span").tooltip() will only initialise tooltips for spans that exist inside .content at page load. If you inject new spans later (or new .contents with spans inside them), they are not included, and they won't have tooltips.

The simplest fix is to use the (apparently undocumented?) selector tooltip option. Initialise the tooltips on an HTML element which exists at page load, and then use the selector option to filter to only match the elements you really want to have tooltips on. The key is the filtering part happens live, and so will match newly added elements.

In your case, we can't use .content for the tooltip, since that does not exist at page load either. We need some parent element which exists on the page before any of your AJAX stuff happens. Let's assume you have a parent like <div class="parent">, and your HTML will be injected into that (I guess you could also use body or document). So we initialise the tooltips on that parent div:

$(".parent").tooltip({
    // the selector option filters the elements that will actually have tooltips
    selector: '.content span'
});

Problem 2

Now we have basic tooltip functionality working, even for new content injected in to the page. The next step is to dynamically load content over AJAX, and update the tooltip's title with that content. Normally you would do that like this:

$(selector).tooltip('option', 'content', 'New tooltip text!');

where selector is something matching the individual span we're currently viewing the tooltip for. But in our case, that won't work - that span has no tooltip attached! Because we're initialising tooltips on .parent, the individual spans where the tooltips show up do not have a tooltip attached, and calling .tooltip() on them will fail with an error like:

cannot call methods on tooltip prior to initialization; attempted to call method 'option'

To reference the existing tooltip, we need to do $('.parent').tooltip(), but if we do:

// Don't do this
$('.parent').tooltip('option', 'content', 'New tooltip text!');

It would change every tooltip on the page to the same 'New tooltip text!', and we don't want that.

The .open event handler you are using to fire your AJAX receives the jQuery event as a parameter. And that event includes info about the currently targeted element. Using that, we can find which span on the page we are really currently looking at:

open: function(event, ui) {
  // Find real targeted tooltip
  let target = $(event.originalEvent.target);

So now we can manipulate that element's title:

$target.attr('title', 'New tooltip!');

We can also make it a tooltip!

$target.tooltip();

In fact this is not enough, because the .parent tooltip is already open and showing "Please wait ...". The above steps do update the title, but you have to move your mouse away and then back to see it. We want the content to refresh immediately, live. I tried some experimentation and found this works well:

// Temporarily disabling the parent tooltip hides the one that was already open
$('.parent').tooltip('disable');

// Update our target's title
$target.attr('title', 'New tooltip!');

// Initialise a tooltip on our target, and immediately open it
$target.tooltip().tooltip('open');

// Re-enable the parent tooltips, so this process will work again for other spans
$('.parent').tooltip('enable');

So we are actually adding new tooltip instances to the page, each time we mouse-over a matching span. This approach has the added advantage that the new tooltip replaces the old, parent one for the specific span. That means the parent tooltip options no longer apply to spans that have already had their tooltip content loaded, which means if you mouse-over it a 2nd time, it does not fire the AJAX request a 2nd time for that span. The content we got from the first AJAX call is already there and simply re-used.

Here's a working snippet demonstrating all of this.

I've used https://www.npoint.io/ which is a JSON storage bin, to simulate your AJAX calls. I have no affiliation with it, it just seems like a handy tool. Unfortunately it does not seem very reliable, and requests to it failed a few times while I was working. If that happens, pls retry.

I added a few bins with data matching your 1.html and 2.html files, the code below loads them and uses their content:

  • This bin includes your HTML content;
  • This bin includes dummy data for user ID 0;
  • This bin includes dummy data for user ID 1;
  • This bin includes dummy data for user ID 2;

$(document).ready(function () {

    // This URL returns a JSON response including HTML like your 1.html,
    // this way we can realistically simulate your 1.html AJAX call.
    let htmlContentUrl = 'https://api.npoint.io/6893d2fd7632835742cf';

    // These URLs return JSON responses including plain text like your
    // 2.html file, this way we can realistically simulate AJAX calls 
    // loading and using data from your DB
    let userContentUrls = [
        'https://api.npoint.io/06cf752b395286e41cb4',
        'https://api.npoint.io/2a3e0a338f92a803b3fc',
        'https://api.npoint.io/a9a4296244c36e8d5bfc'
    ];

    // We use this selector a few times, it is good practice to cache it
    var $parent = $('.parent');

    $("#btnLoad").click(function () {
        $.ajax({
            url: htmlContentUrl,
        }).done(function (msg) {
            $('#container').html(msg.content);
        });
    });

    $parent.tooltip({
        selector: '.conten span',
        track: true,
        open: function (event, ui) {
            // Find real targetted tooltip
            var $target = $(event.originalEvent.target);
            var userid = $target.attr('data-id');
            url = userContentUrls[userid];
            console.log('For userid', userid, 'url is', url);

            $.ajax({
                url: url,
                type: 'get',
                data: {
                    userid: userid
                },
                success: function (response) {
                    // We can't simply update the tooltip for $target, because
                    // it *has* no tooltip!  The tooltip is actually attached
                    // to .parent.  To refresh the already visible tooltip, we
                    // need to take a few steps.
                    $parent.tooltip('disable');
                    $target.attr('title', response.content);
                    $target.tooltip().tooltip('open');
                    $parent.tooltip('enable');
                }
            });
        }
    });
});
.container {
  margin: 0 auto;
  width: 30%;
}

.content {
  border: 1px solid black;
  padding: 5px;
  margin-bottom: 5px;
}

.content span {
  width: 250px;
}

.content span:hover {
  cursor: pointer;
}
<link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/themes/smoothness/jquery-ui.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>

<div class="parent">

  <div class="container">
    <div class="content">
      <span id="user_0" title="Please wait ..." data-id="0">Water</span>
    </div>
  </div>

  <input type="button" id="btnLoad" value="Load" />

  <div class='container' id="container"></div>

</div>

Side notes:

  • I added a tooltip (user ID 0) which is visible on page load, just to demonstrate everything is working both on page load and after new content is injected;

  • Convention is to use GET when retrieving data, and POST for changing data. Your AJAX calls are just retrieving data to display, so really should be GET;

  • I wasn't sure why you had context: document.body, but I don't think it is necessary, and to keep things simple I removed it;

  • If you are going to use a jQuery selector more than once, it makes sense to cache them. I did that for $parent, and $target.

Upvotes: 4

Qu&#226;n Ho&#224;ng
Qu&#226;n Ho&#224;ng

Reputation: 411

Can you try to push this line to your code.

$('body').tooltip({selector: '.content span'});

Right after

$(document).ready(function() {
    // push the above line here
    ... your current code

}

Upvotes: 0

Related Questions