How to close Angular-bootstrap popover when clicking outside [duplicate]

How to close Angular-bootstrap popover when clicking outside [duplicate]

This question already has an answer here:

Hide Angular UI Bootstrap popover when clicking outside of it

12 answers

I am attempting to close my Angular-bootstrap popovers when clicking anywhere outside the popovers. According to an answer to this question this can now be accomplished (in version 0.13.4) by utilizing the new popover-is-open attribute: Hide Angular UI Bootstrap popover when clicking outside of it
Currently my HTML looks like so:


…and my relevant controller code:
vm.togglePopover = false;

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

This works great for opening/closing the popover when clicking on the button referenced above. My question is, how would I extend this functionality to close the popover when clicking anywhere outside of the popover? How would I set up my event handling to accomplish this?

Solutions/Answers:

Solution 1:

First of all, if you want the popover to close on any click, not only the one outside of your popover, you can do it using existing UI-Bootstrap code:

<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>

The trick here is to drop the surrounding <div> and put the popover-trigger="focus" right on the button.

Related:  How to integrate a Wrap Bootstrap theme into an Rails application?

If you need to actually close the popover only for clicks outside the popover content, then it will be more difficult. You need a new directive, like this one:

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);
      });
    }
  }
});

Then, in your popover template, use the directive on the outermost element:

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

Finally, in your controller, implement the closePopover function:

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

What we’ve done here is:

  • we’re listening on any clicks on the document, and, if the click is outside of the element to which we added our close-popover directive:
    • we invoke whatever code was the value of close-popover
  • we also clean up after ourselves when the directive’s scope is destroyed (i.e. when the popover is closed) so that we don’t handle the clicks anymore.
Related:  Bootstrap fixed header and footer with scrolling body-content area in fluid-container

It’s not the cleanest solution, as you have to invoke the controller method from within the popover template, but it’s the best I came up with.

Solution 2:

Since angular-ui 1.0.0, there is a new outsideClick trigger for tooltips and popovers (introduced in this 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>

Solution 3:

If I understood correctly, you want the popover to close when a user clicks pretty much anywhere except on the inside of the popover itself, barring the actual close button. This could be accomplished with an event listener:

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

Check out this plunkr.

Or, in your specific scenario:

$('html').click(function() {
    if(!$(event.target).is('.my-popover-class')) {
        vm.togglePopover = false;
    }
})

Solution 4:

You’re going to need to do the event handling yourself as when you use the new *-is-open attributes, there is no event handling.

If you don’t need the programmatic control over opening/closing the popover, then you can use the built in focus trigger to give you what you want.

Related:  How do I know which button is clicked when the bootstrap modal closes?

Solution 5:

close the popover when clicking anywhere outside of the popover

Some time ago I’ve found this answer useful: How to dismiss a Twitter Bootstrap popover by clicking outside?

Code I used in one of my demos (mixing angular and jQueryevent handling which is probably not recommended) is specific to my needs but may give some idea:

  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');
      }
    });
  };

I’d also suggest looking at the difference between:

References