[jquery] How can I keep Bootstrap popovers alive while being hovered?

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?

_x000D_
_x000D_
$('#example').popover({_x000D_
    html : true,_x000D_
    trigger : 'manual',_x000D_
    content : function() {_x000D_
        return '<div class="box">Popover</div>';_x000D_
    }_x000D_
});_x000D_
_x000D_
$(document).on('mouseover', '#example', function(){_x000D_
    $('#example').popover('show');_x000D_
});_x000D_
_x000D_
$(document).on('mouseleave', '#example', function(){_x000D_
    $('#example').popover('hide');_x000D_
});
_x000D_
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.4/jquery.js"></script>_x000D_
<script src="https://unpkg.com/@popperjs/core@2"></script>_x000D_
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>_x000D_
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"/>_x000D_
_x000D_
<a href="#" id="example" class="btn btn-danger" rel="popover" >hover for popover</a>
_x000D_
_x000D_
_x000D_

This question is related to jquery twitter-bootstrap popper.js

The answer is


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"


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);
});

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');
});

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/


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();
    };

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)


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");
        }
    });
});

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

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

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

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


Simple :)

$('[data-toggle="popover"]').popover( { "container":"body", "trigger":"focus", "html":true });
$('[data-toggle="popover"]').mouseenter(function(){
    $(this).trigger('focus');
});

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.


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);
    } 
)

        $(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);
            });
        }); 

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

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);
      });
    })
  }
};

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);
    });
}

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);
        });
    })

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.


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.

_x000D_
_x000D_
$(".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);
  });
_x000D_
<!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>
_x000D_
_x000D_
_x000D_


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.