9

I'm trying to hide the Bootstrap Popover when the user clicks anywhere outside the popover. (I'm really not sure why the creators of Bootstrap decided not to provide this functionality.)

I found the following code on the web but I really don't understand it.

// Hide popover on click anywhere on the document except itself
$(document).click(function(e) {
    // Check for click on the popup itself
    $('.popover').click(function() {
        return false; // Do nothing
    });  
    // Clicking on document other than popup then hide the popup
    $('.pop').popover('hide');  
});

The main thing I find confusing is the line $('.popover').click(function() { return false; });. Doesn't this line add an event handler for the click event? How does that prevent the call to popover('hide') that follows from hiding the popover?

And has anyone seen a better technique?

Note: I know variations of this question has been asked here before, but the answers to those questions involve code more complex than the code above. So my question is really about the code above

Jonathan Wood
  • 65,341
  • 71
  • 269
  • 466
  • Read this: http://stackoverflow.com/questions/1357118/event-preventdefault-vs-return-false – Icarus Jul 24 '13 at 18:44
  • @Icarus: Thanks but that doesn't really answer my question. I know that returning false stops further processing, but how does adding a click handler alter the behavior of the line that follows? And wouldn't this stop the click from working after the popover has been closed? – Jonathan Wood Jul 24 '13 at 18:49
  • That code does exactly what you described. Everytime someone clicks anywhere on the document (including `.popover`) it will add an event listener that does absolutely nothing. it's just a waste of memory, since there can be many event listeners on the same element, for the same event and they are just gonna end up stacking on each other – Dogoku Jul 24 '13 at 18:55
  • @Dogoku: Thanks, but why would the click handler even come into play when calling `popover('hide')`? Also, does the new handler get removed when the method returns? – Jonathan Wood Jul 24 '13 at 18:58
  • As far as I know, the handlers will just stack on each other. nothing is removed, just a waste of memory, since the handlers do nothing anyway – Dogoku Jul 24 '13 at 19:04
  • @Dogoku: In that case, each time the code is run another handler would be added endlessly. And the first time it ran, regular clicks would no longer work because the new handler(s) would block them. – Jonathan Wood Jul 24 '13 at 19:06
  • 1
    It should answer your question, at least this line should: "return false from **within a jQuery event handler is effectively the same as calling both e.preventDefault and e.stopPropagation**". In your code example, the `return false` inside the popover click handler will stop the propagation to the click event handler for the document, hence, not hiding it. – Icarus Jul 24 '13 at 19:14
  • @JonathanWood, that is true, if there is only **1** event handler registered. If there is more than one, they will all get fired – Dogoku Jul 24 '13 at 19:16
  • This is a possible duplicate question and has been answered thoroughly. Please see my solution here: http://stackoverflow.com/a/14857326/1060487 – mattdlockyer Mar 20 '14 at 19:23

5 Answers5

8

I made http://jsfiddle.net/BcczZ/2/, which hopefully answers your question

Example HTML

<div class="well>
    <a class="btn" data-toggle="popover" data-content="content.">Popover</a>
    <a class="btn btn-danger bad">Bad button</a>
</div>

JS

var $popover = $('[data-toggle=popover]').popover();

//first event handler for bad button
$('.bad').click(function () {
    alert("clicked");
});


$(document).on("click", function (e) {
    var $target = $(e.target),
    var isPopover = $target.is('[data-toggle=popover]'),
        inPopover = $target.closest('.popover').length > 0

    //Does nothing, only prints on console and wastes memory. BAD CODE, REMOVE IT
    $('.bad').click(function () { 
        console.log('clicked');
        return false;
    });

    //hide only if clicked on button or inside popover
    if (!isPopover && !inPopover) $popover.popover('hide');
});

As I mentioned in my comment, event handlers don't get overwritten, they just stack. Since there is already an event handler on the .bad button, it will be fired, along with any other event handler

Open your console in the jsfiddle, press 5 times somewhere on the page (not the popover button) and then click bad button you should see clicked printed the same amount of times you pressed

Hope it helps


P.S: If you think about it, you already saw this happening, especially in jQuery. Think of all the $(document).ready(...) that exist in a page using multiple jquery plugins. That line just registers an event handler on the document's ready event

omerfarukdogan
  • 839
  • 9
  • 26
Dogoku
  • 4,585
  • 3
  • 24
  • 34
  • Ok, thanks for the demonstration. Looking at the code I originally posted then, it hardly seems ideal to me as I could be adding a new handler that just returns false all day long. There could grow to be any number of them if the page isn't refreshed. Doesn't seem ideal at all. – Jonathan Wood Jul 24 '13 at 21:49
  • just remove the code that deals with the `bad button` and use the rest of my code. should work like you want it – Dogoku Jul 24 '13 at 22:30
1

I just did a more event based solution.

var $toggle = $('.your-popover-button');
$toggle.popover();

var hidePopover = function() {
    $toggle.popover('hide');
};

$toggle.on('shown', function () {
    var $popover = $toggle.next();
    $popover.on('mousedown', function(e) {
        e.stopPropagation();
    });
    $toggle.on('mousedown', function(e) {
        e.stopPropagation();
    });
    $(document).on('mousedown',hidePopover);
});

$toggle.on('hidden', function () {
    $(document).off('mousedown', hidePopover);
});
ikuegelgen
  • 11
  • 2
0

short answer insert this to bootstrap min.js

when popout onblur will hide popover
when popout more than one, older popover will be hide

$count=0;$(document).click(function(evt){if($count==0){$count++;}else{$('[data-toggle="popover"]').popover('hide');$count=0;}});$('[data-toggle="popover"]').popover();$('[data-toggle="popover"]').on('click', function(e){$('[data-toggle="popover"]').not(this).popover('hide');$count=0;});
Bear Bear
  • 9
  • 2
0

None of the above solutions worked 100% for me because I had to click twice on another, or the same, popover to open it again. I have written the solution from scratch to be simple and effective.

   $('[data-toggle="popover"]').popover({
        html:true,
        trigger: "manual",
        animation: false
    });

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

        if (e.target.hasAttribute('data-toggle') && e.target.getAttribute('data-toggle') === 'popover') {
            e.preventDefault();
            $(e.target).popover('show');
        }
        else if (e.target.parentElement.hasAttribute('data-toggle') && e.target.parentElement.getAttribute('data-toggle') === 'popover') {
            e.preventDefault();
            $(e.target.parentElement).popover('show');
        }
    });
SherylHohman
  • 16,580
  • 17
  • 88
  • 94
0

My solution, works 100%, for Bootstrap v3

$('html').on('click', function(e) {
    if(typeof $(e.target).data('original-title') !== 'undefined'){
         $('[data-original-title]').not(e.target).popover('hide');
    }

    if($(e.target).parents().is('[data-original-title]')){
         $('[data-original-title]').not($(e.target).closest('[data-original-title]')).popover('hide');
    }

    if (typeof $(e.target).data('original-title') == 'undefined' &&
!$(e.target).parents().is('.popover.in') && !$(e.target).parents().is('[data-original-title]')) {
        $('[data-original-title]').popover('hide');
    }
});