当前位置 : 首页 » 互动问答 » 正文

How to dismiss a Twitter Bootstrap popover by clicking outside?

分类 : 互动问答 | 发布时间 : 2012-07-29 01:15:34 | 评论 : 30 | 浏览 : 195310 | 喜欢 : 257

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. :(

回答(30)

  • 1楼
  • Update: A slightly more robust solution: http://jsfiddle.net/mattdlockyer/C5GBU/72/

    For buttons containing text only:

    $('body').on('click', function (e) {
        //did not click a popover toggle or popover
        if ($(e.target).data('toggle') !== 'popover'
            && $(e.target).parents('.popover.in').length === 0) { 
            $('[data-toggle="popover"]').popover('hide');
        }
    });
    

    For buttons containing icons use (this code has a bug in Bootstrap 3.3.6, see the fix below in this answer)

    $('body').on('click', function (e) {
            //did not click a popover toggle, or icon in popover toggle, or popover
            if ($(e.target).data('toggle') !== 'popover'
                && $(e.target).parents('[data-toggle="popover"]').length === 0
                && $(e.target).parents('.popover.in').length === 0) { 
                $('[data-toggle="popover"]').popover('hide');
            }
        });
    

    For JS Generated Popovers Use '[data-original-title]' in place of '[data-toggle="popover"]'

    Caveat: The solution above allows multiple popovers to be open at once.

    One popover at a time please:

    Update: Bootstrap 3.0.x, see code or fiddle http://jsfiddle.net/mattdlockyer/C5GBU/2/

    $('body').on('click', function (e) {
        $('[data-toggle="popover"]').each(function () {
            //the 'is' for buttons that trigger popups
            //the 'has' for icons within a button that triggers a popup
            if (!$(this).is(e.target) && $(this).has(e.target).length === 0 && $('.popover').has(e.target).length === 0) {
                $(this).popover('hide');
            }
        });
    });
    

    This handles closing of popovers already open and not clicked on or their links have not been clicked.


    Update: Bootstrap 3.3.6, see fiddle

    Fixes issue where after closing, takes 2 clicks to re-open

    $(document).on('click', function (e) {
        $('[data-toggle="popover"],[data-original-title]').each(function () {
            //the 'is' for buttons that trigger popups
            //the 'has' for icons within a button that triggers a popup
            if (!$(this).is(e.target) && $(this).has(e.target).length === 0 && $('.popover').has(e.target).length === 0) {                
                (($(this).popover('hide').data('bs.popover')||{}).inState||{}).click = false  // fix for BS 3.3.6
            }
    
        });
    });
    
  • 2楼
  • $('html').on('mouseup', function(e) {
        if(!$(e.target).closest('.popover').length) {
            $('.popover').each(function(){
                $(this.previousSibling).popover('hide');
            });
        }
    });
    

    This closes all popovers if you click anywhere except on a popover

  • 3楼
  • Most simple, most fail safe version, works with any bootstrap version.

    Demo: http://jsfiddle.net/guya/24mmM/

    Demo 2: Not dismissing when clicking inside the popover content http://jsfiddle.net/guya/fjZja/

    Demo 3: Multiple popovers: http://jsfiddle.net/guya/6YCjW/


    Simply calling this line will dismiss all popovers:

    $('[data-original-title]').popover('hide');
    

    Dismiss all popovers when clicking outside with this code:

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

    The snippet above attach a click event on the body. When the user click on a popover, it'll behave as normal. When the user click on something that is not a popover it'll close all popovers.

    It'll also work with popovers that are initiated with Javascript, as opposed to some other examples that will not work. (see the demo)

    If you don't want to dismiss when clicking inside the popover content, use this code (see link to 2nd demo):

    $('html').on('click', function(e) {
      if (typeof $(e.target).data('original-title') == 'undefined' && !$(e.target).parents().is('.popover.in')) {
        $('[data-original-title]').popover('hide');
      }
    });
    
  • 4楼
  • This is basically not very complex, but there is some checking to do to avoid glitches.

    Demo (jsfiddle)

    var $poped = $('someselector');
    
    // Trigger for the popover
    $poped.each(function() {
        var $this = $(this);
        $this.on('hover',function() {
                var popover = $this.data('popover');
                var shown = popover && popover.tip().is(':visible');
                if(shown) return;        // Avoids flashing
                $this.popover('show');
        });
    });
    
    // Trigger for the hiding
     $('html').on('click.popover.data-api',function() {
        $poped.popover('hide');
    });
    
  • 5楼
  • With bootstrap 2.3.2 you can set the trigger to 'focus' and it just works:

    $('#el').popover({trigger:'focus'});
    
  • 6楼
  • None of supposed high-voted solutions worked for me correctly. Each leads to a bug when after opening and closing (by clicking on other elements) the popover for the first time, it doesn't open again, until you make two clicks on the triggering link instead of one.

    So i modified it slightly:

    $(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-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');
            } else {
                //fixes issue described above
                $popover.popover('toggle');
            }
        });
    })
    
  • 7楼
  • I made a jsfiddle to show you how to do it:

    http://jsfiddle.net/3yHTH/

    The idea is to show the popover when you click the button and to hide the popover when you click outside the button.

    HTML

    <a id="button" href="#" class="btn btn-danger">Click for popover</a>
    

    JS

    $('#button').popover({
        trigger: 'manual',
        position: 'bottom',
        title: 'Example',
        content: 'Popover example for SO'
    }).click(function(evt) {
        evt.stopPropagation();
        $(this).popover('show');
    });
    
    $('html').click(function() {
        $('#button').popover('hide');
    });
    
  • 9楼
  • This is late to the party... but I thought I'd share it. I love the popover but it has so little built-in functionality. I wrote a bootstrap extension .bubble() that is everything I'd like popover to be. Four ways to dismiss. Click outside, toggle on the link, click the X, and hit escape.

    It positions automatically so it never goes off the page.

    https://github.com/Itumac/bootstrap-bubble

    This is not a gratuitous self promo...I've grabbed other people's code so many times in my life, I wanted to offer my own efforts. Give it a whirl and see if it works for you.

  • 10楼
  • According to http://getbootstrap.com/javascript/#popovers,

    <button type="button" class="popover-dismiss" data-toggle="popover" title="Dismissible popover" data-content="Popover Content">Dismissible popover</button>
    

    Use the focus trigger to dismiss popovers on the next click that the user makes.

    $('.popover-dismiss').popover({
        trigger: 'focus'
    })
    
  • 11楼
  • Modified accepted solution. What I've experienced was that after some popovers were hidden, they would have to be clicked twice to show up again. Here's what I did to ensure that popover('hide') wasn't being called on already hidden popovers.

    $('body').on('click', function (e) {
        $('[data-original-title]').each(function () {
            //the 'is' for buttons that trigger popups
            //the 'has' for icons within a button that triggers a popup
            if (!$(this).is(e.target) && $(this).has(e.target).length === 0 && $('.popover').has(e.target).length === 0) {
                var popoverElement = $(this).data('bs.popover').tip();
                var popoverWasVisible = popoverElement.is(':visible');
    
                if (popoverWasVisible) {
                    $(this).popover('hide');
                    $(this).click(); // double clicking required to reshow the popover if it was open, so perform one click now
                }
            }
        });
    });
    
  • 12楼
  • jQuery("#menu").click(function(){ return false; });
    jQuery(document).one("click", function() { jQuery("#menu").fadeOut(); });
    
  • 13楼
  • You can also use event bubbling to remove the popup from the DOM. It is a bit dirty, but works fine.

    $('body').on('click touchstart', '.popover-close', function(e) {
      return $(this).parents('.popover').remove();
    });
    

    In your html add the .popover-close class to the content inside the popover that should close the popover.

  • 14楼
  • It seems the 'hide' method does not work if you create the popover with selector delegation, instead 'destroy' must be used.

    I made it work like that:

    $('body').popover({
        selector: '[data-toggle="popover"]'
    });
    
    $('body').on('click', function (e) {
        $('[data-toggle="popover"]').each(function () {
            //the 'is' for buttons that trigger popups
            //the 'has' for icons within a button that triggers a popup
            if (!$(this).is(e.target) && $(this).has(e.target).length === 0 && $('.popover').has(e.target).length === 0) {
                $(this).popover('destroy');
            }
        });
    });
    

    JSfiddle here

  • 15楼
  • We found out we had an issue with the solution from @mattdlockyer (thanks for the solution!). When using the selector property for the popover constructor like this...

    $(document.body').popover({selector: '[data-toggle=popover]'});
    

    ...the proposed solution for BS3 won't work. Instead it creates a second popover instance local to its $(this). Here is our solution to prevent that:

    $(document.body).on('click', function (e) {
        $('[data-toggle="popover"]').each(function () {
            //the 'is' for buttons that trigger popups
            //the 'has' for icons within a button that triggers a popup
            if (!$(this).is(e.target) && $(this).has(e.target).length === 0 && $('.popover').has(e.target).length === 0) {
                var bsPopover = $(this).data('bs.popover'); // Here's where the magic happens
                if (bsPopover) bsPopover.hide();
            }
        });
    });
    

    As mentioned the $(this).popover('hide'); will create a second instance due to the delegated listener. The solution provided only hides popovers which are already instanciated.

    I hope I could save you guys some time.

  • 16楼
  • Bootstrap natively supports this:

    JS Bin Demo

    Specific markup required for dismiss-on-next-click

    For proper cross-browser and cross-platform behavior, you must use the <a> tag, not the <button> tag, and you also must include the role="button" and tabindex attributes.

  • 17楼
  • this solution gets rid of the pesky 2nd click when showing the popover for the second time

    tested with with Bootstrap v3.3.7

    $('body').on('click', function (e) {
        $('.popover').each(function () {
            var popover = $(this).data('bs.popover');
            if (!popover.$element.is(e.target)) {
                popover.inState.click = false;
                popover.hide();                
            }
        });
    });
    
  • 18楼
  • simply add this attribute with the element

    data-trigger="focus"
    
  • 19楼
  • I just remove other active popovers before the new popover is shown (bootstrap 3):

    $(".my-popover").popover();
    
    $(".my-popover").on('show.bs.popover',function () {
        $('.popover.in').remove();
    });              
    
  • 20楼
  • tested with 3.3.6 and second click is ok

            $('[data-toggle="popover"]').popover()
                .click(function () {
                $(this).popover('toggle');
            });;
    
            $(document).on('click', function (e) {
                $('[data-toggle="popover"]').each(function () {
                    //the 'is' for buttons that trigger popups
                    //the 'has' for icons within a button that triggers a popup
                    if (!$(this).is(e.target) && $(this).has(e.target).length === 0 && $('.popover').has(e.target).length === 0) {
                        $(this).popover('hide');
                    }
                });
            });
    
  • 22楼
  • I've tried many of the previous answers, really nothing works for me but this solution did:

    https://getbootstrap.com/docs/3.3/javascript/#dismiss-on-next-click

    They recommend to use anchor tag not button and take care of role="button" + data-trigger="focus" + tabindex="0" attributes.

    Ex:

    <a tabindex="0" class="btn btn-lg btn-danger" role="button" data-toggle="popover" 
    data-trigger="focus" title="Dismissible popover" data-content="amazing content">
    Dismissible popover</a>
    
  • 23楼
  • demo: http://jsfiddle.net/nessajtr/yxpM5/1/

    var clickOver = clickOver || {};
    clickOver.uniqueId = $.now();
    
    clickOver.ClickOver = function (selector, options) {
        var self = this;
    
        //default values
        var isVisible, clickedAway = false;
    
        var callbackMethod = options.content;
    var uniqueDiv = document.createElement("div");
    var divId = uniqueDiv.id = ++clickOver.uniqueId;
    uniqueDiv.innerHTML = options.loadingContent();
    
    options.trigger = 'manual';
    options.animation = false;
    options.content = uniqueDiv;
    
    self.onClose = function () {
        $("#" + divId).html(options.loadingContent());
        $(selector).popover('hide')
        isVisible = clickedAway = false;
    };
    self.onCallback = function (result) {
        $("#" + divId).html(result);
    };
    
    $(selector).popover(options);
    
    //events
    $(selector).bind("click", function (e) {
        $(selector).filter(function (f) {
            return $(selector)[f] != e.target;
        }).popover('hide');
    
        $(selector).popover("show");
        callbackMethod(self.onCallback);
    
        isVisible = !(clickedAway = false);
    });
    
    $(document).bind("click", function (e) {
        if (isVisible && clickedAway && $(e.target).parents(".popover").length == 0) {
            self.onClose();
            isVisible = clickedAway = false;
        } else clickedAway = true;
    });
    

    }

    this is my solution for it.

  • 24楼
  • This approach ensures that you can close a popover by clicking anywhere on the page. If you click on another clickable entity, it hides all other popovers. The animation:false is required else you will get a jquery .remove error in your console.

    $('.clickable').popover({
     trigger: 'manual',
     animation: false
     }).click (evt) ->
      $('.clickable').popover('hide')
      evt.stopPropagation()
      $(this).popover('show')
    
    $('html').on 'click', (evt) ->
      $('.clickable').popover('hide')
    
  • 25楼
  • Ok this is my first attempt at actually answering something on stackoverflow so here goes nothing :P

    It appears that it isn't quite clear that this functionality actually works out of the box on the latest bootstrap (well, if you're willing to compromise where the user can click. I'm not sure if you have to put 'click hover' per-se but on an iPad, click works as a toggle.

    The end result is, on a desktop you can hover or click (most users will hover). On a touch device, touching the element will bring it up, and touching it again will take it down. Of course, this is a slight compromise from your original requirement but at least your code is now cleaner :)

    $(".my-popover").popover({ trigger: 'click hover' });

  • 26楼
  • I came up with this: My scenario included more popovers on the same page, and hiding them just made them invisible and because of that, clicking on items behind the popover was not possible. The idea is to mark the specific popover-link as 'active' and then you can simply 'toggle' the active popover. Doing so will close the popover completely $('.popover-link').popover({ html : true, container: 'body' })

    $('.popover-link').popover().on 'shown.bs.popover', ->
      $(this).addClass('toggled')
    
    $('.popover-link').popover().on 'hidden.bs.popover', ->
      $(this).removeClass('toggled')
    
    $("body").on "click", (e) ->
      $openedPopoverLink = $(".popover-link.toggled")
      if $openedPopoverLink.has(e.target).length == 0
        $openedPopoverLink.popover "toggle"
        $openedPopoverLink.removeClass "toggled"
    
  • 27楼
  • Taking Matt Lockyer's code, I've done a simple reset so the dom doesn't get covered by the element on hide.

    Matt's code: http://mattlockyer.com/2013/04/08/close-a-twitter-bootstrap-popover-when-clicking-outside/

    Fiddle: http://jsfiddle.net/mrsmith/Wd2qS/

        $('body').on('click', function (e) {
        //hide popover from dom to prevent covering elements
        $('.popover').css('display', 'none');
        //bring popover back if trigger element is clicked
        $('[data-toggle="popover"]').each(function () {
            if ($(this).is(e.target) && $(this).has(e.target).length === 0 && $('.popover').has(e.target).length === 0) {
                $('.popover').css('display', 'block');
            }
        });
        //hide popover with .popover method
        $('[data-toggle="popover"]').each(function () {
            //the 'is' for buttons that trigger popups
            //the 'has' for icons within a button that triggers a popup
            if (!$(this).is(e.target) && $(this).has(e.target).length === 0 && $('.popover').has(e.target).length === 0) {
                $(this).popover('hide');
            }
        });
    });
    
  • 28楼
  • Try this, this will hide by clicking outside.

    $('body').on('click', function (e) {
        $('[data-toggle="popover"]').each(function () {
        //the 'is' for buttons that trigger popups
        //the 'has' for icons within a button that triggers a popup
        if (!$(this).is(e.target) && $(this).has(e.target).length === 0 && $('.popover').has(e.target).length === 0) {
        $(this).popover('hide');
        }
        });
    });
    
  • 29楼
  • I was having issues with mattdlockyer's solution because I was setting up popover links dynamically using code like this:

    $('body').popover({
            selector : '[rel="popover"]'
    });
    

    So I had to modify it like so. It fixed a lot of issues for me:

    $('html').on('click', function (e) {
      $('[data-toggle="popover"]').each(function () {
        //the 'is' for buttons that trigger popups
        //the 'has' for icons within a button that triggers a popup
        if (!$(this).is(e.target) && $(this).has(e.target).length === 0 && $('.popover').has(e.target).length === 0) {
            $(this).popover('destroy');
        }
      });
    });
    

    Remember that destroy gets rid of the element, so the selector part is important on initializing the popovers.

  • 30楼
  • The answer from @guya works, unless you have something like a datepicker or timepicker in the popover. To fix that, this is what I have done.

    if (typeof $(e.target).data('original-title') === 'undefined' && 
        !$(e.target).parents().is('.popover.in')) {
            var x = $(this).parents().context;
            if(!$(x).hasClass("datepicker") && !$(x).hasClass("ui-timepicker-wrapper")){
                $('[data-original-title]').popover('hide');
            }
    }
    

相关阅读:

How to get a responsive button in bootstrap 3

vertical-align with Bootstrap 3

Bootstrap - Text-align class for inside table

Add icon to submit button in twitter bootstrap 2

Font-awesome, input type 'submit'

Twitter Bootstrap - top nav bar blocking top content of the page

Best way to combine AngularJS and Twitter Bootstrap

Center a column using Twitter Bootstrap

How to open a Bootstrap modal window using jQuery?

Change navbar color in Twitter Bootstrap