How can I keep Bootstrap popovers alive while being hovered?
Asked Answered
H

23

137

I am using a Bootstrap popover to create a hover card showing user info, and I am triggering it on mouseover of a button. I want to keep this popover alive while the popover itself is being hovered, but it disappears as soon as the user stops hovering over the button. How can I do this?

$('#example').popover({
    html : true,
    trigger : 'manual',
    content : function() {
        return '<div class="box">Popover</div>';
    }
});

$(document).on('mouseover', '#example', function(){
    $('#example').popover('show');
});

$(document).on('mouseleave', '#example', function(){
    $('#example').popover('hide');
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.4/jquery.js"></script>
<script src="https://unpkg.com/@popperjs/core@2"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"/>

<a href="#" id="example" class="btn btn-danger" rel="popover" >hover for popover</a>
Harrovian answered 13/4, 2013 at 15:42 Comment(2)
you want to keep what alive? i hover over the button and it stayed opened?Xylol
read last line of questionHarrovian
H
88

I have came after another solution to this...here is the code

    $('.selector').popover({
        html: true,
        trigger: 'manual',
        container: $(this).attr('id'),
        placement: 'top',
        content: function () {
            $return = '<div class="hover-hovercard"></div>';
        }
    }).on("mouseenter", function () {
        var _this = this;
        $(this).popover("show");
        $(this).siblings(".popover").on("mouseleave", function () {
            $(_this).popover('hide');
        });
    }).on("mouseleave", function () {
        var _this = this;
        setTimeout(function () {
            if (!$(".popover:hover").length) {
                $(_this).popover("hide")
            }
        }, 100);
    });
Harrovian answered 1/8, 2013 at 14:43 Comment(4)
It is important to add animation: false otherwise moving the mouse over the link repeatedly will cause it to not work correctlyTurnage
I have a small modification to your code @vikas (gist.github.com/Nitrodist/7913848). It rechecks the condition after 50ms so that it doesn't stay stuck open. That is, it continuously rechecks it every 50ms.Newcomb
How can this be adapted this so it works on live elements just added to the document?Transilluminate
Unfortunately, none of these solutions are working. I appreciate the effort and all the contributions. There are all steps in the right direction. However, the correct answer should not have been marked as correct answer and I have know idea how to change that especially for such an old topic. I have written a solution that seems like a robust solution. I am still testing it to smooth out some edges. I will continue it once I am 100% confident about it.Uncharitable
S
205

Test with code snippet below:

Small modification (From the solution provided by vikas) to suit my use case.

  1. Open popover on hover event for the popover button
  2. Keep popover open when hovering over the popover box
  3. Close popover on mouseleave for either the popover button, or the popover box.

$(".pop").popover({
    trigger: "manual",
    html: true,
    animation: false
  })
  .on("mouseenter", function() {
    var _this = this;
    $(this).popover("show");
    $(".popover").on("mouseleave", function() {
      $(_this).popover('hide');
    });
  }).on("mouseleave", function() {
    var _this = this;
    setTimeout(function() {
      if (!$(".popover:hover").length) {
        $(_this).popover("hide");
      }
    }, 300);
  });
<!DOCTYPE html>
<html>

<head>
  <link data-require="bootstrap-css@*" data-semver="3.2.0" rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css" />
  <script data-require="jquery@*" data-semver="2.1.1" src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
  <script data-require="bootstrap@*" data-semver="3.2.0" src="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.js"></script>

  <link rel="stylesheet" href="style.css" />

</head>

<body>
  <h2 class='text-primary'>Another Great "KISS" Bootstrap Popover example!</h2>
  <p class='text-muted'>KISS = Keep It Simple S....</p>

  <p class='text-primary'>Goal:</p>
  <ul>
    <li>Open popover on hover event for the popover button</li>
    <li>Keep popover open when hovering over the popover box</li>
    <li>Close popover on mouseleave for either the popover button, or the popover box.</li>
  </ul>

  <button type="button" class="btn btn-danger pop" data-container="body" data-toggle="popover" data-placement="right" data-content="Optional parameter: Skip if this was not requested<br>                                    A placement group is a logical grouping of instances within a single Availability                                     Zone. Using placement groups enables applications to get the full-bisection bandwidth                                     and low-latency network performance required for tightly coupled, node-to-node                                     communication typical of HPC applications.<br>                                    This only applies to cluster compute instances: cc2.8xlarge, cg1.4xlarge, cr1.8xlarge, hi1.4xlarge and hs1.8xlarge.<br>                                    More info: <a href=&quot;http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/placement-groups.html&quot; target=&quot;_blank&quot;>Click here...</a>"
    data-original-title="" title="">
    HOVER OVER ME
    </button>
  <br><br>
  <button type="button" class="btn btn-info pop" data-container="body" data-toggle="popover" data-placement="right" data-content="Optional parameter: Skip if this was not requested<br>                                    A placement group is a logical grouping of instances within a single Availability                                     Zone. Using placement groups enables applications to get the full-bisection bandwidth                                     and low-latency network performance required for tightly coupled, node-to-node                                     communication typical of HPC applications.<br>                                    This only applies to cluster compute instances: cc2.8xlarge, cg1.4xlarge, cr1.8xlarge, hi1.4xlarge and hs1.8xlarge.<br>                                    More info: <a href=&quot;http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/placement-groups.html&quot; target=&quot;_blank&quot;>Click here...</a>"
    data-original-title="" title="">
    HOVER OVER ME... Again!
    </button><br><br>
  <button type="button" class="btn btn-success pop" data-container="body" data-toggle="popover" data-placement="right" data-content="Optional parameter: Skip if this was not requested<br>                                    A placement group is a logical grouping of instances within a single Availability                                     Zone. Using placement groups enables applications to get the full-bisection bandwidth                                     and low-latency network performance required for tightly coupled, node-to-node                                     communication typical of HPC applications.<br>                                    This only applies to cluster compute instances: cc2.8xlarge, cg1.4xlarge, cr1.8xlarge, hi1.4xlarge and hs1.8xlarge.<br>                                    More info: <a href=&quot;http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/placement-groups.html&quot; target=&quot;_blank&quot;>Click here...</a>"
    data-original-title="" title="">
    Okay one more time... !
    </button>
  <br><br>
  <p class='text-info'>Hope that helps you... Drove me crazy for a while</p>
  <script src="script.js"></script>
</body>

</html>
Slaphappy answered 30/10, 2013 at 14:3 Comment(11)
This works perfectly, I did notice that there was a missing ; in you second $(_this).popover("hide"). But thank you, it was so simple and clean!Illinium
This answer is amazing. Works great on BS3 as of May 2015 ^^Alvinia
I used it in a table and I added container: 'body' to the options because it made the cells shift. Great answer!Lituus
The popover gets hidden if you enter it and then go back to the trigger element all before 300ms. To fix it, check if BOTH the popover and its trigger are :hover before hiding it in setTimeout. I'd also use setTimeout and the same approach on mouseleave the popover itself, to fix the flickering.Solan
Be sure to set animation:false to fix the flicker - check in the Plunker link I have above. It works perfectly for me.Slaphappy
this sadly does not work in the firefox. chrome is working fine. in FF the popover disappear when you hit the link element for the popover. It only stays open if you hover to the popover itself.Spheno
It works in firefox. Even just tested again on FF version 54.0, it still works well. Maybe you are looking for different functionality than what I was trying to solve.Slaphappy
It works for my first Popover but not for the second one, what can be done there?Endmost
Nice, I love this code! Works great! Thanks! :-) .pop is your popover button/link that triggers the .popover box.Budde
Thank you for the answer. I was using mouseout instead of mouseleave, which was causing troubles. Your answer pointed me to the solution. ... One thing though that I think I did better is to have the jQuery element in a variable and call .popover on it, which I think is better on performance side :)Butanol
Remove the ending semicolon and add .on('click', function (e) { e.preventDefault(); }) to the end of the code, so that anchor popover links with '#' target won't navigate one back to the top of the page.Achondroplasia
H
88

I have came after another solution to this...here is the code

    $('.selector').popover({
        html: true,
        trigger: 'manual',
        container: $(this).attr('id'),
        placement: 'top',
        content: function () {
            $return = '<div class="hover-hovercard"></div>';
        }
    }).on("mouseenter", function () {
        var _this = this;
        $(this).popover("show");
        $(this).siblings(".popover").on("mouseleave", function () {
            $(_this).popover('hide');
        });
    }).on("mouseleave", function () {
        var _this = this;
        setTimeout(function () {
            if (!$(".popover:hover").length) {
                $(_this).popover("hide")
            }
        }, 100);
    });
Harrovian answered 1/8, 2013 at 14:43 Comment(4)
It is important to add animation: false otherwise moving the mouse over the link repeatedly will cause it to not work correctlyTurnage
I have a small modification to your code @vikas (gist.github.com/Nitrodist/7913848). It rechecks the condition after 50ms so that it doesn't stay stuck open. That is, it continuously rechecks it every 50ms.Newcomb
How can this be adapted this so it works on live elements just added to the document?Transilluminate
Unfortunately, none of these solutions are working. I appreciate the effort and all the contributions. There are all steps in the right direction. However, the correct answer should not have been marked as correct answer and I have know idea how to change that especially for such an old topic. I have written a solution that seems like a robust solution. I am still testing it to smooth out some edges. I will continue it once I am 100% confident about it.Uncharitable
H
28

Here's my take: http://jsfiddle.net/WojtekKruszewski/Zf3m7/22/

Sometimes while moving mouse from popover trigger to actual popover content diagonally, you hover over elements below. I wanted to handle such situations – as long as you reach popover content before the timeout fires, you're safe (the popover won't disappear). It requires delay option.

This hack basically overrides Popover leave function, but calls the original (which starts timer to hide the popover). Then it attaches a one-off listener to mouseenter popover content element's.

If mouse enters the popover, the timer is cleared. Then it turns it listens to mouseleave on popover and if it's triggered, it calls the original leave function so that it could start hide timer.

var originalLeave = $.fn.popover.Constructor.prototype.leave;
$.fn.popover.Constructor.prototype.leave = function(obj){
  var self = obj instanceof this.constructor ?
    obj : $(obj.currentTarget)[this.type](this.getDelegateOptions()).data('bs.' + this.type)
  var container, timeout;

  originalLeave.call(this, obj);

  if(obj.currentTarget) {
    container = $(obj.currentTarget).siblings('.popover')
    timeout = self.timeout;
    container.one('mouseenter', function(){
      //We entered the actual popover – call off the dogs
      clearTimeout(timeout);
      //Let's monitor popover content instead
      container.one('mouseleave', function(){
        $.fn.popover.Constructor.prototype.leave.call(self, self);
      });
    })
  }
};
Hypophysis answered 2/9, 2013 at 11:54 Comment(6)
Finding the container could be improved by using container = self.$tip; This way, the popover can even be found when the container property is set. There's a fiddle here: jsfiddle.net/dennis_c/xJc65Volpe
These both work except when you go from hovering over the popover back to hovering over the button the popover flashes/gets redisplayed. Being a noob could someone suggest a fix for that? I'd guess the 'mouseleave' function should check to see if the mouse is over the triggering button before doing it's thing? But I'm a bit lost in the way it's wired together. +1 for the jsfiddleTriviality
@Triviality i have solved this issue in my fork of @Wojtek_Kruszewski 's fiddle: jsfiddle.net/HugeHugh/pN26d see the part that checks if (!thisTip.is(':visible')) before calling the originalShow()Zolnay
If the popover is initialized with the option container: 'body', this solution will not work as expected. The variable container needs to be replaced with self.$tip. Check my answer for more details: https://mcmap.net/q/166387/-how-can-i-keep-bootstrap-popovers-alive-while-being-hoveredNonaligned
Brilliant. This works for when using the 'selector' parameter, unlike the other answers.Hygrometry
Here is an improved version which fixes a bug when leaving and reentering the tip still hided it, and also fixes the scenario when the tip is attached to the body jsfiddle.net/Zf3m7/1499Fourflusher
P
23

I think an easy way would be this:

$('.popover').each(function () {
                    var $this = $(this);
                    $this.popover({
                        trigger: 'hover',
                        content: 'Content Here',
                        container: $this
                    })
                });

This way the popover is created inside the target element itself. so when you move your mouse over the popover, it's still over the element. Bootstrap 3.3.2 works well with this. Older version may have some problems with animation, so you may want to disable "animation:false"

Phonsa answered 27/1, 2015 at 7:44 Comment(2)
I know this thread is old, but this is the best, cleanest solution in my opinion and should be ranked higher. The only caveat is that this would break, if you position the popover (in a weird way) "away" from the trigger element. But as long as the distance between the two is zero (e.g. they overlap), this works beautifully and does not require any custom JS. Thank you!Giorgi
That is the best, clean, easiest solution so far. Should be ranked higher! I added delay: { "hide": 400 } to add some delay before hiding and it works great! 👍Borough
X
15

I used the trigger set to hover and gave the container set to the #element and finally adding a placement of the box to right.

This should be your setup:

$('#example').popover({
    html: true,
    trigger: 'hover',
    container: '#example',
    placement: 'right',
    content: function () {
        return '<div class="box"></div>';
    }
});

and #example css needs position:relative; check the jsfiddle below:

https://jsfiddle.net/9qn6pw4p/1/

Edited

This fiddle has both links that work with no problems http://jsfiddle.net/davidchase03/FQE57/4/

Xylol answered 13/4, 2013 at 16:40 Comment(13)
hmm that works, can I use jquery ajax in content option, to take the content from server side..will that work or i need to do some extra work for thatHarrovian
@vikasdevde yes you can use ajax in the content but you need to setup up for that to work... please mark the answer right if it solved your OP.. so others can benefitXylol
but if we use the link itself as a container then the whole popover becomes a link....try itHarrovian
if you put a link inside of the box it will still link out.. correct?Xylol
but if you click anywhere on popover, it will take you to the href of the button..Harrovian
you are right...the links inside popover will work, but if you click anywhere inside popover it will take you to the linkHarrovian
so why not change the a to button then it wont matter? and change .box cursor to default...Xylol
im not sure what the issue is right now? your question was to keep the popover box opened on hover?Xylol
I have the solution now I can surround link with a <span> tag, and I will call the popover on this span tag instead of <a> tag... here is the working http://jsfiddle.net/testtracker/FQE57/6/Harrovian
None of the jsfiddle's work for me. Chrome Mar 20 2014.Triviality
@DavidChase Can you show me how i start to do that?How to use ajax to bring data from a server.?I would like only how to start,because i'm not sure,please.Laynelayney
@user455318 thanks, that worked perfectly. Thanks DavidChase for the original solution, a way more elegant solution.Causeuse
Using the content parameter works even in Bootstrap 5 ! Thank you.Escadrille
L
13

Here is a solution I devised that seems to work well while also allowing you to use the normal Bootstrap implementation for turning on all popovers.

Original fiddle: https://jsfiddle.net/eXpressive/hfear592/

Ported to this question:

<a href="#" id="example" class="btn btn-danger" rel="popover" >hover for popover</a>

$('#example').popover({
    html : true,
    trigger : 'hover',
    content : function() {
        return '<div class="box"></div>';
    }
}).on('hide.bs.popover', function () {
    if ($(".popover:hover").length) {
      return false;
    }                
}); 

$('body').on('mouseleave', '.popover', function(){
    $('.popover').popover('hide');
});
Lawanda answered 24/8, 2017 at 15:29 Comment(1)
This is the best answer! It avoids having to set container which may not be possible/desirable in certain cases (such as when popover container has to be something different, e.g. to escape parent overflow:hidden).Palazzo
A
7

This is how I did with bootstrap popover with help of other bits around the net. Dynamically gets the title and content from die various products displayed on site. Each product or popover gets unique id. Popover will disappear when exiting the product( $this .pop) or the popover. Timeout is used where will display the popover until exit through product instead of popover.

$(".pop").each(function () {
        var $pElem = $(this);
        $pElem.popover(
            {
                html: true,
                trigger: "manual",
                title: getPopoverTitle($pElem.attr("id")),
                content: getPopoverContent($pElem.attr("id")),
                container: 'body',
                animation:false
            }
        );
    }).on("mouseenter", function () {
        var _this = this;
        $(this).popover("show");
        console.log("mouse entered");
        $(".popover").on("mouseleave", function () {
            $(_this).popover('hide');
        });
    }).on("mouseleave", function () {
        var _this = this;
        setTimeout(function () {
            if (!$(".popover:hover").length) {
                $(_this).popover("hide");
            }
        }, 100);
    });
    function getPopoverTitle(target) {
        return $("#" + target + "_content > h3.popover-title").html();
    };

    function getPopoverContent(target) {
        return $("#" + target + "_content > div.popover-content").html();
    };
Alkalify answered 11/10, 2013 at 7:37 Comment(1)
This will also work if the popover is not a child of the target element. +1Among
A
6

I agree that the best way is to use the one given by: David Chase, Cu Ly, and others that the simplest way to do this is to use the container: $(this) property as follows:

$(selectorString).each(function () {
  var $this = $(this);
  $this.popover({
    html: true,
    placement: "top",
    container: $this,
    trigger: "hover",
    title: "Popover",
    content: "Hey, you hovered on element"
  });
});

I want to point out here that the popover in this case will inherit all properties of the current element. So, for example, if you do this for a .btn element(bootstrap), you won't be able to select text inside the popover. Just wanted to record that since I spent quite some time banging my head on this.

Annular answered 30/6, 2017 at 5:52 Comment(1)
Too bad Bootstrap does not allow data-container="this" so this could be initialized without additional each()...Audette
S
3

This is a solution for Bootstrap 5

let el = document.getElementById('poptest');
let popover = new bootstrap.Popover(el, { delay: { show: 0, hide: 500 }});

el.addEventListener('shown.bs.popover', function(ev) {
  let oldHandler = popover.hide, pel = $(popover.tip);
  pel.on('mouseenter', () => popover.hide = () => 1);
  pel.on('mouseleave', () => {
    popover.hide = oldHandler;
    popover.hide();
  });
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@popperjs/[email protected]/dist/umd/popper.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.1.3/js/bootstrap.bundle.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.1.3/css/bootstrap.min.css" rel="stylesheet"/>

<button type="button" class="btn btn-secondary m-5" data-bs-trigger="hover" data-bs-container="body" data-bs-toggle="popover" data-bs-placement="bottom" data-bs-content="Hover here and it won't close!" id="poptest">
  Popover on top
</button>

The concept is we override the hide function on mouseenter so it doesn't automatically close and then restore it on mouseleave.

Salic answered 19/3, 2022 at 4:42 Comment(2)
Boostrap5 does not depend on jquery. Could you provide a pure JS solution?Skier
@Skier here it is, below.Scatterbrain
F
1

Vikas answer works perfectly for me, here I also add support for the delay (show / hide).

var popover = $('#example');
var options = {
    animation : true,
    html: true,
    trigger: 'manual',
    placement: 'right',
    delay: {show: 500, hide: 100}
};   
popover
    .popover(options)
    .on("mouseenter", function () {

        var t = this;
        var popover = $(this);    
        setTimeout(function () {

            if (popover.is(":hover")) {

                popover.popover("show");
                popover.siblings(".popover").on("mouseleave", function () {
                    $(t).popover('hide');
                });
            }
        }, options.delay.show);
    })
    .on("mouseleave", function () {
        var t = this;
        var popover = $(this);

        setTimeout(function () {
            if (popover.siblings(".popover").length && !popover.siblings(".popover").is(":hover")) {
                $(t).popover("hide")
            }
        }, options.delay.hide);
    });     

Also please pay attention I changed:

if (!$(".popover:hover").length) {

with:

if (popover.siblings(".popover").length && !popover.siblings(".popover").is(":hover")) {

so that it references exactly at that opened popover, and not any other (since now, through the delay, more than 1 could be open at the same time)

Footworn answered 8/1, 2014 at 4:18 Comment(1)
The comment I did at the end is actually not right when using container: body, if so still gotta use Vikas' solution for that one lineFootworn
N
1

The chosen answer works but will fail if the popover is initialized with the body as the container.

$('a').popover({ container: 'body' });

A solution based on the chosen answer is the following code that needs to be placed before using the popover.

var originalLeave = $.fn.popover.Constructor.prototype.leave;
$.fn.popover.Constructor.prototype.leave = function(obj) {
    var self = obj instanceof this.constructor ? obj : $(obj.currentTarget)[this.type](this.getDelegateOptions()).data('bs.' + this.type);
    originalLeave.call(this, obj);

    if (obj.currentTarget) {
        self.$tip.one('mouseenter', function() {
            clearTimeout(self.timeout);
            self.$tip.one('mouseleave', function() {
                $.fn.popover.Constructor.prototype.leave.call(self, self);
            });
        })
    }
};

The change is minimal using self.$tip instead of traversing the DOM expecting the popover to be always a siblings of the element.

Nonaligned answered 25/2, 2015 at 23:36 Comment(0)
M
1

I recently needed to get this working with KO and the above solutions didn't work well when having a delay on show and hide. The below should fix this. Based on how bootstrap tooltips work. Hope this helps someone.

var options = {
                delay: { show: 1000, hide: 50 },
                trigger: 'manual',                      
                html: true
            };
var $popover = $(element).popover(options);

$popover.on('mouseenter', function () { // This is entering the triggering element
    var self = this;

    clearTimeout(self.timeout);
    self.hoverState = 'in';

    self.timeout = setTimeout(function () {
        if (self.hoverState == 'in') {
            $(self).popover("show");

            $(".popover, .popover *").on('mouseover', function () { // This is moving over the popover
                clearTimeout(self.timeout);
            });                                                                 

            $(".popover").on('mouseleave', function () { // This is leaving the popover
                self.timeout = setTimeout(function () {
                    if (self.hoverState == 'out') {
                        $(self).popover('hide');
                    }
                }, options.delay.hide);
            });
        }
    }, options.delay.show);
}).on('mouseleave', function (event) { // This is leaving the triggering element
    var self = this;

    clearTimeout(self.timeout);
    self.hoverState = 'out';

    self.timeout = setTimeout(function () {                             
        if (self.hoverState == 'out') {
            $(self).popover('hide');
        }

    }, options.delay.hide);
});
Matelote answered 12/4, 2020 at 3:52 Comment(0)
E
1

I know I'm kinda late to the party but I was looking for a solution for this..and I bumped into this post. Here is my take on this, maybe it will help some of you.

The html part:

<button type="button" class="btn btn-lg btn-danger" data-content="test" data-placement="right" data-toggle="popover" title="Popover title" >Hover to toggle popover</button><br>
// with custom html stored in a separate element, using "data-target"
<button type="button" class="btn btn-lg btn-danger" data-target="#custom-html" data-placement="right" data-toggle="popover" >Hover to toggle popover</button>

<div id="custom-html" style="display: none;">
    <strong>Helloooo!!</strong>
</div>

The js part:

$(function () {
        let popover = '[data-toggle="popover"]';

        let popoverId = function(element) {
            return $(element).popover().data('bs.popover').tip.id;
        }

        $(popover).popover({
            trigger: 'manual',
            html: true,
            animation: false
        })
        .on('show.bs.popover', function() {
            // hide all other popovers  
            $(popover).popover("hide");
        })
        .on("mouseenter", function() {
            // add custom html from element
            let target = $(this).data('target');
            $(this).popover().data('bs.popover').config.content = $(target).html();

            // show the popover
            $(this).popover("show");
            
            $('#' + popoverId(this)).on("mouseleave", () => {
               $(this).popover("hide");
            });

        }).on("mouseleave", function() {
            setTimeout(() => {
                if (!$("#" + popoverId(this) + ":hover").length) {
                    $(this).popover("hide");
                }
            }, 100);
        });
    })
Elvaelvah answered 10/11, 2020 at 15:35 Comment(0)
M
0

Same thing for tooltips:

For me following solution works because it does not add event listeners on every 'mouseenter' and it is possible to hover back on the tooltip element which keeps the tooltip alive.

$ ->

  $('.element').tooltip({
    html: true,
    trigger: 'manual'
  }).
  on 'mouseenter', ->
    clearTimeout window.tooltipTimeout
    $(this).tooltip('show') unless $('.tooltip:visible').length > 0
  .
  on 'mouseleave', ->
    _this = this
    window.tooltipTimeout = setTimeout ->
      $(_this).tooltip('hide')
    , 100

$(document).on 'mouseenter', '.tooltip', ->
  clearTimeout window.tooltipTimeout

$(document).on 'mouseleave', '.tooltip', ->
  trigger = $($(this).siblings('.element')[0])
  window.tooltipTimeout = setTimeout ->
    trigger.tooltip('hide')
  , 100
Memorabilia answered 12/11, 2013 at 10:13 Comment(0)
C
0

This solution worked out fine for me: (now its bulletproof) ;-)

function enableThumbPopover() {
    var counter;

    $('.thumbcontainer').popover({
        trigger: 'manual',
        animation: false,
        html: true,
        title: function () {
            return $(this).parent().find('.thumbPopover > .title').html();
        },
        content: function () {
            return $(this).parent().find('.thumbPopover > .body').html();
        },
        container: 'body',
        placement: 'auto'
    }).on("mouseenter",function () {
        var _this = this; // thumbcontainer

        console.log('thumbcontainer mouseenter')
        // clear the counter
        clearTimeout(counter);
        // Close all other Popovers
        $('.thumbcontainer').not(_this).popover('hide');

        // start new timeout to show popover
        counter = setTimeout(function(){
            if($(_this).is(':hover'))
            {
                $(_this).popover("show");
            }
            $(".popover").on("mouseleave", function () {
                $('.thumbcontainer').popover('hide');
            });
        }, 400);

    }).on("mouseleave", function () {
        var _this = this;

        setTimeout(function () {
            if (!$(".popover:hover").length) {
                if(!$(this).is(':hover'))
                {
                    $(_this).popover('hide');
                }
            }
        }, 200);
    });
}
Coffin answered 9/4, 2014 at 7:43 Comment(0)
V
0
        $(function() {
            $("[data-toggle = 'popover']").popover({
                placement: 'left',
                html: true,
                trigger: "  focus",
            }).on("mouseenter", function() {
                var _this = this;
                $(this).popover("show");
                $(this).siblings(".popover").on("mouseleave", function() {
                    $(_this).popover('hide');
                });
            }).on("mouseleave", function() {
                var _this = this;
                setTimeout(function() {
                    if (!$(".popover:hover").length) {
                        $(_this).popover("hide")
                    }
                }, 100);
            });
        }); 
Vaish answered 9/9, 2016 at 21:9 Comment(0)
P
0

I found the mouseleave will not fire when weird things happen, like the window focus changes suddenly, then the user comes back to the browser. In cases like that, mouseleave will never fire until the cursor goes over and leaves the element again.

This solution I came up with relies on mouseenter on the window object, so it disappears when the mouse is moved anywhere else on the page.

This was designed to work with having multiple elements on the page that will trigger it (like in a table).

var allMenus = $(".menus");
allMenus.popover({
    html: true,
    trigger: "manual",
    placement: "bottom",
    content: $("#menuContent")[0].outerHTML
}).on("mouseenter", (e) => {
    allMenus.not(e.target).popover("hide");
    $(e.target).popover("show");
    e.stopPropagation();
}).on("shown.bs.popover", () => {
    $(window).on("mouseenter.hidepopover", (e) => {
        if ($(e.target).parents(".popover").length === 0) {
            allMenus.popover("hide");
            $(window).off("mouseenter.hidepopover");
        }
    });
});
Psychotic answered 2/3, 2018 at 1:58 Comment(0)
B
0

It will be more flexible with hover():

$(".my-popover").hover(
    function() {  // mouse in event
        $this = $(this);
        $this.popover({
            html: true,
            content: "Your content",
            trigger: "manual",
            animation: false
            });
        $this.popover("show");
        $(".popover").on("mouseleave", function() {
            $this.popover("hide");
        });
    },
    function() {  // mouse out event
        setTimeout(function() {
            if (!$(".popover:hover").length) {
                $this.popover("hide");
            }
        }, 100);
    } 
)
Beefcake answered 15/4, 2018 at 6:52 Comment(0)
M
0

Simple :)

$('[data-toggle="popover"]').popover( { "container":"body", "trigger":"focus", "html":true });
$('[data-toggle="popover"]').mouseenter(function(){
    $(this).trigger('focus');
});
Masera answered 23/8, 2018 at 14:32 Comment(0)
N
0

I found that the accepted answer and the similar ones to it had some flaws. Mainly that it's repeatedly adding that mouseleave listener to the element.

I've combined their solution with some custom code to achieve the functionality in question without memory leaks or listener bloat.

    var getPopoverTimeout = function ($el) {
        return $el.data('timeout');
    }
    $element.popover({
        trigger: "manual",
        html: true,
        content: ...,
        title: ...,
        container: $element
     }).on("mouseenter", function () {
        var $this = $(this);
        if (!$this.find('.popover').length) {
            $this.popover("show");
        } else if (getPopoverTimeout($element)) {
            clearTimeout(getPopoverTimeout($element));
        }
    }).on("mouseleave", function () {
        var $this = $(this);
        $element.data('timeout', setTimeout(function () {
            if (!$(".popover:hover").length) {
                $this.popover("hide")
            }
        }, 250));
    });

Which provides a nice 'hover-intent' like solution so that it doesn't flash in and out.

Neeley answered 29/10, 2020 at 14:37 Comment(0)
S
0

Here is my solution in pure JS for Bootstrap 5.

NOTE: while this post has jquery tag, neither the title nor the question itself mentions that solution has to be implemented using jquery explicitly, so I'm sure a lot of people who don't use jquery, like me, still gets directed here from search results. Additionally, neither bootstrap nor popper.js have jquery as dependency, so I am sure that solution in pure JS will be useful here.

'use strict';

function fn_noop() {};
function popover_elem_mouseenter_cb(evt) {
  // NOTE: 'this' -> 'elem_popover' caller
  const popover_instance = bootstrap.Popover.getInstance(this);
  const hide_func = popover_instance.hide;
  popover_instance.hide = fn_noop;
  this.addEventListener('mouseleave', (ev) => {
    popover_instance.hide = hide_func;
    popover_instance.hide();
  }, { once: true });
};

function main(evt) {
  const root_elem = document.getElementById('btn_container');
  const popover_handle = new bootstrap.Popover(root_elem, {
    animation: false,
    trigger: 'hover',
    html: true,
    selector: 'button',
    title: "Popover Example",
    delay: { show: 0, hide: 100 }, // NOTE: it's important have small delay here
    content: 'This popover will stay open as long as you hover the pointer over its content, so one can click on the <a href="https://stackoverflow.com" target="_blank" rel="noopener noreferrer">link</a>.'
  });
  root_elem.addEventListener('shown.bs.popover', function(evt) {
    const elem_evt_src = evt.target;
    const elem_popover = document.getElementById(elem_evt_src.getAttribute('aria-describedby')); // NOTE: 'aria-describedby' is a dynamic property added when popover gets shown
    elem_popover.addEventListener('mouseenter', popover_elem_mouseenter_cb, { once: true });
  });
  root_elem.addEventListener('hide.bs.popover', function(evt) {
    const elem_evt_src = evt.target;
    const elem_popover = document.getElementById(elem_evt_src.getAttribute('aria-describedby'));
    elem_popover.removeEventListener('mouseenter', popover_elem_mouseenter_cb);
  });
};

window.addEventListener('DOMContentLoaded', main, { once: true });
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>

<div id="btn_container">
  <button class="btn btn-sm btn-primary m-2">Display Popover on hover</button>
</div>
Scatterbrain answered 12/2 at 18:13 Comment(0)
A
0

To ensure that your popover remains open when hovering over its content, you need to manually trigger the popover event. This entails creating your own event listeners for mouse enter and mouse leave actions.

var counter;
$('[rel="popover"]').popover({
        container: 'body',
        html: true,
        trigger: 'manual',
        content: function () {
            var html = $($(this).data('popover-content')).html();
            return html;
        }
    }).on("mouseenter", function(e) {
        var _this = this;
        e.preventDefault();
        clearTimeout(counter);
        $('[rel="popover"]').not(_this).popover('hide');
        counter = setTimeout(function(){
            if($(_this).is(':hover'))
            {
                $(_this).popover("show");
            }
            $(".popover").on("mouseleave", function () {
                $(_this).popover('hide');
            });
        }, 400);
        
    }).on("mouseleave", function () {
        var _this = this;

        setTimeout(function () {
            if (!$(".popover:hover").length) {
                if(!$(_this).is(':hover')) // change $(this) to $(_this) 
                {
                   $(_this).popover('hide');
                }
            }
        }, 200);
    });
a[rel="popover"]{
    display: inline-block;
    margin: 20px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet"/>

<a href="#" rel="popover" data-trigger="focus" data-popover-content="#list-popover">Show Popover</a>
<a href="#" rel="popover" data-trigger="focus" data-popover-content="#list-popover">Show Popover</a>
<a href="#" rel="popover" data-trigger="focus" data-popover-content="#list-popover">Show Popover</a>

<div id="list-popover" class="hide">
  <ul class="nav nav-pills nav-stacked">
    <li><a href="#">Action</a></li>
    <li><a href="#">Another action</a></li>
    <li><a href="#">Something else here</a></li>
    <li><a href="#">Separated link</a></li>
  </ul>
</div>
<div id="output">
<div>
Adolphus answered 15/4 at 3:31 Comment(0)
V
-1

This is my code for show dynamics tooltips with delay and loaded by ajax.

$(window).on('load', function () {
    generatePopovers();
    
    $.fn.dataTable.tables({ visible: true, api: true }).on('draw.dt', function () {
        generatePopovers();
    });
});

$(document).ajaxStop(function () {
    generatePopovers();
});

function generatePopovers() {
var popover = $('a[href*="../Something.aspx"]'); //locate the elements to popover

popover.each(function (index) {
    var poplink = $(this);
    if (poplink.attr("data-toggle") == null) {
        console.log("RENDER POPOVER: " + poplink.attr('href'));
        poplink.attr("data-toggle", "popover");
        poplink.attr("data-html", "true");
        poplink.attr("data-placement", "top");
        poplink.attr("data-content", "Loading...");
        poplink.popover({
            animation: false,
            html: true,
            trigger: 'manual',
            container: 'body',
            placement: 'top'
        }).on("mouseenter", function () {
            var thispoplink = poplink;
            setTimeout(function () {
                if (thispoplink.is(":hover")) {
                    thispoplink.popover("show");
                    loadDynamicData(thispoplink); //load data by ajax if you want
                    $('body .popover').on("mouseleave", function () {
                        thispoplink.popover('hide');
                    });
                }
            }, 1000);
        }).on("mouseleave", function () {
            var thispoplink = poplink;
            setTimeout(function () {
                if (!$("body").find(".popover:hover").length) {
                    thispoplink.popover("hide");
                }
            }, 100);
        });
    }
});

function loadDynamicData(popover) {
    var params = new Object();
    params.somedata = popover.attr("href").split("somedata=")[1]; //obtain a parameter to send
    params = JSON.stringify(params);
    //check if the content is not seted
    if (popover.attr("data-content") == "Loading...") {
        $.ajax({
            type: "POST",
            url: "../Default.aspx/ObtainData",
            data: params,
            contentType: "application/json; charset=utf-8",
            dataType: 'json',
            success: function (data) {
                console.log(JSON.parse(data.d));
                var dato = JSON.parse(data.d);
                if (dato != null) {
                    popover.attr("data-content",dato.something); // here you can set the data returned
                    if (popover.is(":hover")) {
                        popover.popover("show"); //use this for reload the view
                    }
                }
            },

            failure: function (data) {
                itShowError("- Error AJAX.<br>");
            }
        });
    }
}
Verified answered 2/5, 2019 at 16:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.