How to dismiss a Twitter Bootstrap popover by clicking outside?

Viewed 262560

Can we get popovers to be dismissable in the same way as modals, ie. make them close when user clicks somewhere outside of them?

Unfortunately I can't just use real modal instead of popover, because modal means position:fixed and that would be no popover anymore. :(

41 Answers

This solution works fine :

$("body")   .on('click'     ,'[data-toggle="popover"]', function(e) { 
    e.stopPropagation();
});

$("body")   .on('click'     ,'.popover' , function(e) { 
     e.stopPropagation();
});

$("body")   .on('click'  , function(e) {
        $('[data-toggle="popover"]').popover('hide');
});

Bootstrap 5 UPDATE:

$(document).on('click', function (e) {
    var
        $popover,
        $target = $(e.target);

    //do nothing if there was a click on popover content
    if ($target.hasClass('popover') || $target.closest('.popover').length) {
        return;
    }

    $('[data-bs-toggle="popover"]').each(function () {
        $popover = $(this);

        if (!$popover.is(e.target) &&
            $popover.has(e.target).length === 0 &&
            $('.popover').has(e.target).length === 0)
        {
            $popover.popover('hide');
        } 
    });
})

For anyone looking for a solution that works with Bootstrap 5 and no jQuery, even when the popovers are dynamically generated (ie manually triggered):

document.querySelector('body').addEventListener('click', function(e) {
    var in_popover = e.target.closest(".popover");

    if (!in_popover) {
        var popovers = document.querySelectorAll('.popover.show');

        if (popovers[0]) {
            var triggler_selector = `[aria-describedby=${popovers[0].id}]`;

            if (!e.target.closest(triggler_selector)) {
                let the_trigger = document.querySelector(triggler_selector);
                if (the_trigger) {
                    bootstrap.Popover.getInstance(the_trigger).hide();
                }
            }
        }
    }
});

I found a good and simple solution to this problem. Doing it this way, we only have an event listener activated when it needs to be. Furthermore we don't have a problem with the popover not appearing because an event disarming the popup is firing at the same time.

Add the popover to the HTML

   <input id="popoverId" type="text" data-toggle="popover" data-trigger="manual" data-content="Popover content">

Show the popover when you want.

$('#popoverId').popover('show');

Add this listener to your Javascript. This listener will fire as soon as the popover is shown and will attach a listener to the body which will hide the popover when the user clicks anywhere on the body of the page and then remove the event listener until the popover will be shown again.

$('#popoverId').on('shown.bs.popover', function () {
    $('body').click(function () {
        $('#popoverId').popover('hide');
        $('body').off();
    });
});

According to highest two answers, I have a little fix:

<span class="btn btn-info btn-minier popover-info" data-rel="popover"
                                              data-placement="bottom" data-html="true" title=""
                                              data-content="popover-content"
                                              data-original-title="popover-title">
                                            <i class="ace-icon fa fa-info smaller-100"></i>
                                        </span>
            $('[data-rel=popover]').popover({html: true});
            $(document).on("shown.bs.popover", '[data-rel=popover]', function () {
                $('[data-rel="popover"][popover-show="1"]').popover('hide');
                $(this).attr('popover-show', '1');
            });
            $(document).on("hidden.bs.popover", '[data-rel=popover]', function () {
                if ($(this).attr('popover-show') === '0') {
                    // My important fix: using bootstrap 3.4.1, if hide popover by .popover('hide') and click to show, popover internal treat it is already shown and dispatch hidden event immediately without popover anything.
                    $(this).popover('toggle');
                } else {
                    $(this).attr('popover-show', '0');
                }
            });
            $('html').on('click', function (e) {
                if (typeof $(e.target).data('original-title') == 'undefined'
                    && typeof $(e.target).parent().data('original-title') == 'undefined'
                    && !$(e.target).parents().is('.popover.in')) {
                    $('[data-rel="popover"][popover-show="1"]').popover('hide');
                }
            });

This worked for me.

Breakdown:

First, add a click event listener to the body.

On Click, check whether or not there's an element with a popover class (Bootstrap removes the popover element from the DOM on dismissal).

If the .popover element exists, check the event.target which will tell you where the User clicked on the page. If it's part of the .popover element, do nothing. If not, hide the popover (docs here).

NOTE: The aria-describedby condition prevents the popover from being hidden when it is initially fired/shown.

document.body.addEventListener('click', event => {
  let isPopoverShown = Boolean(document.querySelector('.popover'));
  let isClickNotPopover = !event.target.closest('.popover');

  if (
        isPopoverShown 
     && isClickNotPopover 
     && !event.target?.getAttribute('aria-describedby')?.startsWith('popover')
  ) {
     $('.popover').popover('hide')
  }

}) 

Related