如何通过页面上的任何其他位置点击关闭Twitter Bootstrap弹出框?

156

我目前正在使用 Twitter Bootstrap 中的弹出窗口,可以通过以下方式初始化:

$('.popup-marker').popover({
        html: true,
        trigger: 'manual'
    }).click(function(e) {
        $(this).popover('toggle');
        e.preventDefault();
    });

如您所见,它们是手动触发的,单击.popup-marker(这是带有背景图像的div)会切换弹出框。这很好用,但我希望还能够通过单击页面上其他任何地方(但不是弹出框本身!)来关闭弹出框。

我尝试了几种不同的方法,包括以下方法,但没有结果可以展示:

$('body').click(function(e) {
    $('.popup-marker').popover('hide');
});

如何使弹出框在页面的任意其他位置单击时关闭,但不是在弹出框本身上单击?


嗯,我认为那应该可以运行...你有可能有在线链接吗? - thatryan
31个回答

106
假设一次只能显示一个弹出窗口,您可以使用一组标志来标记何时有弹出窗口可见,并仅在此时隐藏它们。

如果在文档主体上设置事件侦听器,则在单击标记为“popup-marker”的元素时触发。因此,您必须在事件对象上调用 stopPropagation()。并且在单击弹出窗口本身时应用相同的技巧。

以下是一个可行的JavaScript代码。它使用jQuery >= 1.7

jQuery(function() {
    var isVisible = false;

    var hideAllPopovers = function() {
       $('.popup-marker').each(function() {
            $(this).popover('hide');
        });  
    };

    $('.popup-marker').popover({
        html: true,
        trigger: 'manual'
    }).on('click', function(e) {
        // if any other popovers are visible, hide them
        if(isVisible) {
            hideAllPopovers();
        }

        $(this).popover('show');

        // handle clicking on the popover itself
        $('.popover').off('click').on('click', function(e) {
            e.stopPropagation(); // prevent event for bubbling up => will not get caught with document.onclick
        });

        isVisible = true;
        e.stopPropagation();
    });


    $(document).on('click', function(e) {
        hideAllPopovers();
        isVisible = false;
    });
});

http://jsfiddle.net/AFffL/539/

唯一的注意事项是您无法同时打开两个弹出窗口。但我认为这对用户来说可能会很困惑 :-)

1
单击 jsfiddle 中的弹出窗口本身会导致弹出窗口隐藏 - 不完全符合 tnorthcutt 的要求。 - Jonathon Hill
3
在 $(this).popover('show') 之前运行 $('.popup-marker').popover('hide')(隐藏它们),这样可以避免使用任何 isVisible 和 clickedAway 变量,您觉得这样做如何?注意不要改变原本的意思。 - Nathan Hangen
1
这个解决方案给了我一些问题(点击打开的气泡窗口中的“.popup-marker”元素会导致之后的气泡窗口无法工作)。我想出了另一个解决方案(如下所示),它对我有用并且看起来更简单(我正在使用Bootstrap 2.3.1)。 - RayOnAir
1
具有弹出窗口的元素已经通过 data-toggle="popover" 属性进行了标识,因此请将 $ ('.popup-marker') 选择器替换为 $('[data-toggle="popover"]'),您无需将 popup-marker 类添加到您的元素中。 - Tim Partridge
1
此功能应该是默认设置。 - lewis
显示剩余11条评论

77

这就更容易了:

$('html').click(function(e) {
    $('.popup-marker').popover('hide');
});

$('.popup-marker').popover({
    html: true,
    trigger: 'manual'
}).click(function(e) {
    $(this).popover('toggle');
    e.stopPropagation();
});

4
希望使用这个,但由于某些原因无法实现。由于e.stopPropagation();的作用,点击事件无法到达html。我使用了类似下面这样的代码来代替:$('.popup-marker').on('show', function(event) { $('.popup-marker').filter(function(index, element) { return element != event.target; }).popover('hide'); }); 这也能很好地完成工作(不知道是否有性能差异)。 - Cornelis
1
这在我看来是最好的答案。 - Loolooii
1
@pbaron和@Cornelis的答案编译效果最好。 我添加的是Cornelis代码在第二个“click”函数中(在$(this).popover('toggle');之前)。 然后,如果您有多个“popup-marker”对象,则单击每个对象都会关闭其他对象。 - alekwisnia
2
这个方法的一个问题是弹出框仍然存在,只是被隐藏了。因此,例如,如果您在弹出框中有链接,您可以将光标悬停在它原来的位置上,并仍然可以在那些链接上看到光标的变化。 - Benjamin Carlsson
为什么这些事件的顺序似乎很重要?在示例中,它们的顺序是有效的。如果我交换顺序,它们就无法工作了。 - Pseudonymous
显示剩余4条评论

48

我有类似的需求,发现了这个名为BootstrapX-clickover的Twitter Bootstrap Popover小扩展,由Lee Carmichael编写,您可以在此处找到它。他还提供了一些使用示例,您可以在这里查看。基本上,它将把弹出框变成一个交互式组件,当你在页面其他地方或者在弹出框内部的关闭按钮上单击时,它会自动关闭。这也允许同时打开多个弹出框以及一堆其他很棒的功能。

插件可以在这里找到

使用示例

<button rel="clickover" data-content="Show something here. 
    <button data-dismiss='clickover'
    >Close Clickover</button>"
>Show clickover</button>

javascript:

的翻译是:

JavaScript:

// load click overs using 'rel' attribute
$('[rel="clickover"]').clickover();

1
这真的很棒。超级简单。 - Doug
优秀的插件!感谢提供链接。 - Matt Wilson
1
刚试了一下,效果很棒。只需要将现有的弹出框的 rel 从“popover”改为“clickover”就可以了,非常简单。 - Dmase05
运行在Bootstrap v2.3.1上,没有问题。 - Kevin Dewalt

39

这个已被接受的解决方案给了我一些问题(点击打开的弹出框中的'.popup-marker'元素后,弹出框之后就无法工作)。我想出了这个其他的解决方案,对我非常有效,并且非常简单(我使用的是Bootstrap 2.3.1):

$('.popup-marker').popover({
    html: true,
    trigger: 'manual'
}).click(function(e) {
    $('.popup-marker').not(this).popover('hide');
    $(this).popover('toggle');
});
$(document).click(function(e) {
    if (!$(e.target).is('.popup-marker, .popover-title, .popover-content')) {
        $('.popup-marker').popover('hide');
    }
});

更新:这段代码也适用于Bootstrap 3!


1
这是一个很棒的解决方案。谢谢。 - Gavin
1
好的解决方案。为什么不使用以下代码: if (!$(e.target).is('.popup-marker') && !$(e.target).parents('.popover').length > 0) 这样即使弹出窗口有HTML内容,它也不会关闭。 - ykay says Reinstate Monica
3
更好的写法是 if (!$(e.target).is('.popup-marker') && !$(e.target).closest('.popover').length)。其意思是如果事件源不是类名为“popup-marker”的元素,并且最近的祖先元素中没有类名为“popover”的元素,则条件成立。 - fdaugan

20

在这里阅读“下次单击关闭”http://getbootstrap.com/javascript/#popovers

您可以使用焦点触发器在下次单击时关闭弹出窗口,但必须使用<a>标签而不是<button>标签,并且还必须包括一个tabindex属性...

示例:

<a href="#" tabindex="0" class="btn btn-lg btn-danger"
  data-toggle="popover" data-trigger="focus" title="Dismissible popover"
  data-content="And here's some amazing content. It's very engaging. Right?">
  Dismissible popover
</a>

2
问题说明他不希望在单击弹出窗口时将其关闭。这会在任何地方的单击上关闭它。 - Fred
1
添加 data-trigger="focus" 后,我的弹出框不再弹出,直到我阅读了这篇文章并添加了 tabindex 属性。现在它可以正常工作了! - PixelGraph
2
请注意,这也适用于“tooltip”,即使实际文档上没有明确提到。 - AlexB

7

所有现有的答案都相当不够强大,因为它们依赖于捕获所有文档事件,然后找到活动的弹出窗口,或修改对.popover()的调用。

一个更好的方法是在文档的body上监听show.bs.popover事件,然后根据情况做出反应。以下是关闭弹出窗口的代码,当单击文档或按下esc键时,只会绑定事件侦听器当弹出窗口显示时:

function closePopoversOnDocumentEvents() {
  var visiblePopovers = [];

  var $body = $("body");

  function hideVisiblePopovers() {
    $.each(visiblePopovers, function() {
      $(this).popover("hide");
    });
  }

  function onBodyClick(event) {
    if (event.isDefaultPrevented())
      return;

    var $target = $(event.target);
    if ($target.data("bs.popover"))
      return;

    if ($target.parents(".popover").length)
      return;

    hideVisiblePopovers();
  }

  function onBodyKeyup(event) {
    if (event.isDefaultPrevented())
      return;

    if (event.keyCode != 27) // esc
      return;

    hideVisiblePopovers();
    event.preventDefault();
  }

  function onPopoverShow(event) {
    if (!visiblePopovers.length) {
      $body.on("click", onBodyClick);
      $body.on("keyup", onBodyKeyup);
    }
    visiblePopovers.push(event.target);
  }

  function onPopoverHide(event) {
    var target = event.target;
    var index = visiblePopovers.indexOf(target);
    if (index > -1) {
      visiblePopovers.splice(index, 1);
    }
    if (visiblePopovers.length == 0) {
      $body.off("click", onBodyClick);
      $body.off("keyup", onBodyKeyup);
    }
  }

  $body.on("show.bs.popover", onPopoverShow);
  $body.on("hide.bs.popover", onPopoverHide);
}

+1 这是最干净、最易扩展的解决方案。如果您也使用像backbone这样的框架,只需将其放入初始化代码中即可,它会处理弹出窗口。 - JohnP
此答案还添加了性能问题,并允许在弹出窗口中处理更复杂的HTML。 - Ricardo
非常好的解决方案;能够轻松地将其放入React方法中。一个建议是,在onPopoverHide函数中添加$(event.target).data("bs.popover").inState.click = false;,这样在关闭后重新打开时就不需要点击两次了。 - sco_tt
你能否制作一个包含两个弹出框的fiddle呢?在我的应用程序中,当我实现了你的代码后,我可以点击弹出框并且多个弹出框会出现,然后只需点击“body”即可移除最后一个显示的弹出框。 - Terry

5

2
我会将焦点设置在新创建的弹出窗口上,并在失去焦点时删除它。这样就不需要检查DOM中被点击的元素,弹出窗口可以被点击和选择:它不会失去焦点,也不会消失。
代码如下:
    $('.popup-marker').popover({
       html: true,
       trigger: 'manual'
    }).click(function(e) {
       $(this).popover('toggle');
       // set the focus on the popover itself 
       jQuery(".popover").attr("tabindex",-1).focus();
       e.preventDefault();
    });

    // live event, will delete the popover by clicking any part of the page
    $('body').on('blur','.popover',function(){
       $('.popup-marker').popover('hide');
    });

2
尽管这里有很多解决方案,但我也想提出我的解决方案。我不知道是否已经有一个可以解决所有问题的解决方案,但我尝试了其中的3个,它们都有问题,比如点击弹出框本身会使其隐藏,其他解决方案中如果我点击了其他弹出框按钮,所有弹出框仍然会出现(就像在所选解决方案中一样)。然而,这个解决方案解决了所有问题
var curr_popover_btn = null;
// Hide popovers function
function hide_popovers(e)
{
    var container = $(".popover.in");
    if (!container.is(e.target) // if the target of the click isn't the container...
        && container.has(e.target).length === 0) // ... nor a descendant of the container
    {
        if( curr_popover_btn != null )
        {
            $(curr_popover_btn).popover('hide');
            curr_popover_btn = null;
        }
        container.hide();
    }
}
// Hide popovers when out of focus
$('html').click(function(e) {
    hide_popovers(e);
});
$('.popover-marker').popover({
    trigger: 'manual'
}).click(function(e) {
    hide_popovers(e);
    var $popover_btns = $('.popover-marker');
    curr_popover_btn = this;
    var $other_popover_btns = jQuery.grep($($popover_btns), function(popover_btn){
                return ( popover_btn !== curr_popover_btn );
            });
    $($other_popover_btns).popover('hide');
    $(this).popover('toggle');
    e.stopPropagation();
});

2

由于某些原因,这里的其他解决方案都对我无效。然而,在经过大量疑难解答后,我终于找到了这个完美运行的方法(至少对我来说是这样)。

$('html').click(function(e) {
  if( !$(e.target).parents().hasClass('popover') ) {
    $('#popover_parent').popover('destroy');
  }
});

在我的情况下,我要在表格中添加一个弹出窗口,并将其绝对定位在被点击的 td 上方/下方。表格选择是由 jQuery-UI Selectable 处理的,因此我不确定是否会产生干扰。然而,每当我在弹出窗口内点击时,我的点击处理程序指向$('.popover')从未起作用,事件处理总是委托给了$(html) 点击处理程序。我对 JS 还比较新,也许我只是遗漏了什么?无论如何,我希望这能帮助到某些人!

顺便说一句,我不确定这是否重要,但我在Bootstrap 2中使用了这种方法。我认为它也适用于Bootstrap 3,但尚未确认。 - moollaza

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接