点击 Angular-bootstrap 弹出框外如何关闭?

17
我试图关闭 Angular-bootstrap 的 popover,当单击弹出框外部时。根据这个问题的一个答案,现在可以通过使用新的 popover-is-open 属性来实现(在版本 0.13.4 中):Hide Angular UI Bootstrap popover when clicking outside of it
当前我的 HTML 如下所示:
<div
  ng-click="level.openTogglePopover()"
  popover-template="level.changeLevelTemplate"
  popover-trigger="none"
  popover-placement="right"
  popover-is-open="level.togglePopover">
  <button class="btn btn-default btn-xs" type="button">
    <span class="glyphicon glyphicon-sort"></span>
  </button>
</div>

...以及我相关的控制器代码:

vm.togglePopover = false;

vm.openTogglePopover = function() {
  vm.togglePopover = !vm.togglePopover;
};

点击上述按钮时,打开/关闭气泡窗口非常有效。我的问题是,如何扩展此功能以在单击气泡窗口之外的任何位置时关闭气泡窗口?我应该如何设置事件处理程序来完成此操作?


你有一个plunker可以让我们试试吗? - sp00m
5个回答

14

首先,如果你想让弹出框在任何点击时关闭,而不仅仅是在弹出框外部点击时关闭,你可以使用现有的 UI-Bootstrap 代码实现:

首先,如果您希望气泡弹出窗口在任何点击时关闭,而不仅仅是在窗口外部的点击,则可以使用现有的UI-Bootstrap代码:

<button class="btn btn-default btn-xs" type="button"
        popover-template="level.changeLevelTemplate"
        popover-trigger="focus"
        popover-placement="right">
  <span class="glyphicon glyphicon-sort"></span>
</button>

这里的技巧是删除周围的<div>,并将popover-trigger="focus"直接放在按钮上。


如果您需要仅通过在弹出窗口内容外单击才实际关闭弹出窗口,则会更加困难。 您需要一个新的指令,就像这样:

app.directive('clickOutside', function ($parse, $timeout) {
  return {
    link: function (scope, element, attrs) {
      function handler(event) {
        if(!$(event.target).closest(element).length) {
          scope.$apply(function () {
            $parse(attrs.clickOutside)(scope);
          });
        }
      }

      $timeout(function () {
        // Timeout is to prevent the click handler from immediately
        // firing upon opening the popover.
        $(document).on("click", handler);
      });
      scope.$on("$destroy", function () {
        $(document).off("click", handler);
      });
    }
  }
});

然后,在您的弹出框模板中,将该指令用于最外层元素:

Translated text:

然后,在您的弹出框模板中,将该指令用于最外层元素:

<div click-outside="level.closePopover()">
   ... (actual popover content goes here)
</div>

最后,在您的控制器中实现closePopover函数:

vm.closePopover = function () {
  vm.togglePopover = false;
};

我们在这里所做的是:

  • 监听文档上的任何点击事件,如果点击事件发生在未添加我们的close-popover指令的元素之外:
    • 我们会调用close-popover的值所对应的代码。
  • 当指令的作用域被销毁时(即弹出窗口关闭时),我们还会进行清理,以便不再处理这些点击事件。

这并不是最干净的解决方案,因为您必须从弹出窗口模板内部调用控制器方法,但这是我想到的最好的解决方案。


非常感谢。我现在非常接近了,但是当我在弹出窗口内单击时,它仍然会关闭。这是因为我没有使用jQuery,所以我需要调整一些链接函数代码。您有任何想法如何在不使用jQuery的情况下编写此行:if(!$(event.target).closest(element).length) - MattDionis
我认为你可以通过例如 if (!element.contains(event.target)) {... 来模拟这个。 - Dzinx
这就是我尝试过的,但是我得到了一个“element.contains不是一个函数”的错误。 - MattDionis
奇怪,元素应该始终是一个节点... 你能调试并检查它到底是什么类型吗? - Dzinx
最终检查元素是否包含目标的HTML:if (element.context.outerHTML.indexOf($event.target.outerHTML) === -1) - MattDionis
1
不要那样做,这非常低效。我刚想起来:element是一个jqLite实例,你需要调用element[0]来获取DOM元素。所以你的if语句应该是:if (!element[0].contains(event.target)) {... - Dzinx

13
自从 angular-ui 1.0.0 版本以来,工具提示和弹出框新增了一个名为 outsideClick 的触发器(在 此 pull request 中引入)。
<div
  uib-popover-template="level.changeLevelTemplate"
  popover-trigger="outsideClick"
  popover-placement="right">
  <button class="btn btn-default btn-xs" type="button">
    <span class="glyphicon glyphicon-sort"></span>
  </button>
</div>

超棒。popover-trigger 就是我需要的。 - Yogesh Sanchihar

2

如果我理解正确,您希望当用户点击弹出窗口内部以外的任何地方(除了实际的关闭按钮)时,弹出窗口都会关闭。这可以通过事件监听器来实现:

$('html').click(function() {
    if(!$(event.target).is('#foo')) {
        // Code to hide/remove popovers
    }
});

检查一下这个plunkr
或者,在你的具体场景中:
$('html').click(function() {
    if(!$(event.target).is('.my-popover-class')) {
        vm.togglePopover = false;
    }
})

1

当您使用新的*-is-open属性时,您需要自己处理事件,因为没有事件处理。

如果您不需要对弹出窗口的打开/关闭进行编程控制,则可以使用内置的focus触发器来实现您想要的效果。


我把触发器改成了 popover-trigger="focus",但现在弹出框甚至都不打开。 - MattDionis

1

在单击弹出框外部的任何位置时关闭弹出框


我之前发现这个回答很有用: 如何通过点击外部来关闭Twitter Bootstrap弹出框?


我在其中一个演示中使用的代码(混合了angularjQuery事件处理,这可能不是推荐的做法)是根据我的需求特定编写,但可能会提供一些想法:


  app.directive("eventlistener", function($rootScope) {
    $(window).resize($rootScope.closeAllPopovers); // because Bootstrap popovers don't look good when misplaced

    return {
      link: function(scope, element, attrs) {
        $('body').on('mouseup touchend', $rootScope.closeAllPopovers);
      }
    };
  });

  $rootScope.closeAllPopovers = function (e) {
    $('[data-toggle="popover"]').each(function () {
      if (e) {
        if (!$(this).is(e.target) && $(this).has(e.target).length === 0 && $('.popover').has(e.target).length === 0) {
          $(this).popover('hide');
        }
      } else {
        // No event passed - closing all popovers programmatically
        $(this).popover('hide');
      }
    });
  };

我建议您也可以看一下以下内容的区别:



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