Hide Angular UI Bootstrap popover when clicking outside of it

51,639

Solution 1

EDITED:

Plunker Demo

Here's how it works (the still long and exhaustive explanation):

  1. Create a custom directive that allows you to target the trigger element.
  2. Create a custom directive that is added to the body and will find the trigger element and fire the custom event when it is clicked.

Create a custom directive to target the trigger element:

You need to trigger the custom event handler from the element that opened the popover (in the demo this is the button). The challenge is that the popover is appended as a sibling to this element and I always think that things have greater potential to break when you are traversing the DOM and expecting it to have a specific structure. There are several ways you can target the trigger element, but my approach is to add a unique classname to the element (I choose 'trigger') when you click on it. Only one popover can be opened at a time in this scenario, so it's safe to use a classname, but you can modify to suit your preference.

Custom Directive

app.directive('popoverElem', function(){
  return{
    link: function(scope, element, attrs) {
      element.on('click', function(){
        element.addClass('trigger');
      });
    }
  }
});

Applied to button

<button popover-template="dynamicPopover.templateUrl" popover-title="{{dynamicPopover.title}}" class="btn btn-default" popover-elem>Popover With Template</button>

Create a custom directive for the document body (or any other element) to trigger the popover close:

The last piece is to create a custom directive that will locate the triggering element and fire the custom event to close the popover when the element it is applied to is clicked. Of course, you have to exclude the initial click event from the 'trigger' element, and any elements you want to interact with on the inside of your popover. Therefore, I added an attribute called exclude-class so you can define a class that you can add to elements whose click events should be ignored (not causing the popover to close).

To clean things up, when the event handler is triggered, we remove the trigger class that was added to the trigger element.

app.directive('popoverClose', function($timeout){
  return{
    scope: {
      excludeClass: '@'
    },
    link: function(scope, element, attrs) {
      var trigger = document.getElementsByClassName('trigger');

      function closeTrigger(i) {
        $timeout(function(){ 
          angular.element(trigger[0]).triggerHandler('click').removeClass('trigger'); 
        });
      }

      element.on('click', function(event){
        var etarget = angular.element(event.target);
        var tlength = trigger.length;
        if(!etarget.hasClass('trigger') && !etarget.hasClass(scope.excludeClass)) {
          for(var i=0; i<tlength; i++) {
            closeTrigger(i)
          }
        }
      });
    }
  };
});

I added this to the body tag so that the entire page* acts as a dismissible backdrop for the popover:

<body popover-close exclude-class="exclude">

And, I added the exclude class to the input in the popover:

<input type="text" ng-model="dynamicPopover.title" class="form-control exclude">

So, there are some tweaks and gotchas, but I'll leave that to you:

  1. You should set a default exclude class in the link function of the popover-close directive, in case one is not defined.
  2. You need to be aware that the popover-close directive is element bound, so if you remove the styles I set on the html and body elements to give them 100% height, you could have 'dead areas' within your viewport if your content doesn't fill it.

Tested in Chrome, Firefox and Safari.

Solution 2

UPDATE: With the 1.0 release, we've added a new trigger called outsideClick that will automatically close the popover or tooltip when the user clicks outside the popover or tooltip.

Starting with the 0.14.0 release, we've added the ability to programmatically control when your tooltip/popover is open or closed via the tooltip-is-open or popover-is-open attributes.

Solution 3

Since Angular UI Bootstrap 1.0.0, there is a new outsideClick trigger for tooltips and popovers (introduced in this pull request. In Angular UI Bootstrap 2.0.0, the popover-trigger has been modified to use angular expressions (Changelog), so the value has to be put in quotes. This code will work with current versions of angular-ui:

<div id="new_button" uib-popover-template="plusButtonURL" popover-trigger="'outsideClick'"
    popover-placement="right" popover-append-to-body="true" popover-animation="false">+</div>

This code will work with old versions of Angular UI Bootstrap (before 2.0.0):

<div id="new_button" uib-popover-template="plusButtonURL" popover-trigger="outsideClick"
    popover-placement="right" popover-append-to-body="true" popover-animation="false">+</div>

Solution 4

popover-trigger="'outsideClick'" This will work perfectly.

popover-trigger="outsideClick" This will not.

I took 1 day to sort it out why it was not working for me.

It is because they checking this using this code, "if (trigger === 'outsideClick')"

This is due to strong type check where we need to pass it as String

Solution 5

There is a property called popover-trigger that you can assign the property focus to.

<button 
      popover-placement="right" 
      popover="On the Right!" 
      popover-trigger="focus" 
      class="btn btn-default">
   Right
</button>

This does the trick! :)

Edit: To allow for the tooltip to be clicked on and not trigger focus lost, consider an approach similar to this

If you want it to work in angular, try creating your own trigger definition. Suggestions on how to do that can be found here.

Share:
51,639

Related videos on Youtube

bryan
Author by

bryan

Updated on July 09, 2022

Comments

  • bryan
    bryan almost 2 years

    I am trying to manually close a bootstrap popover to get it to close when I click anywhere on the document or body that isn't the popover.

    The closest thing I have found to accomplishing this is to create a directive (found this answer) but this is for a manual trigger if a variable is true or false.

    Could anyone help me figure out how to to get it to close if I click on anything that isn't the popover?

    I don't mind using jQuery $(document).click(function(e){}); I just have no clue how to call a close.

    <div id="new_button" popover-template="plusButtonURL" popover-placement="right" popover-append-to-body="true" popover-animation="false">+</div>
    

    Normally popover-trigger="focus" would do the trick, however my popover contains content that needs to be clicked on. I have an ng-click inside my popover that get's ignored if I use the focus trigger so I am looking for a not-so-conventional way to get around that.

    • Patrick Motard
      Patrick Motard almost 9 years
      Could you please provide a plunkr or jsfiddle for us to plug-and-play?
    • bryan
      bryan almost 9 years
      @PatrickMotard here you go
    • Petr Averyanov
      Petr Averyanov almost 9 years
      angular-ui.github.io/bootstrap << watch here. There is one you are looking for -- name 'Click me'. (ist says: I appeared on focus! Click away and I'll vanish... (c))
  • bryan
    bryan almost 9 years
    It is what I want, but it doesn't seem to work in the browser I'm using (latest Safari OS X). Focus seems to only work cross browser on input text
  • Patrick Motard
    Patrick Motard almost 9 years
    I have a browserStack account. Let me try it out in Safari OS X (Yosemite?) to confirm.
  • bryan
    bryan almost 9 years
    I'd appreciate that, yes Yosemite. I heard firefox also has this issue.
  • Patrick Motard
    Patrick Motard almost 9 years
    Yes there was a ticket submitted regarding this issue in both firefox and safari. I'm looking for a fix. The fix noted in the closed ticket is related to it not working if you don't include class="btn" which isnt the case for us.
  • bryan
    bryan almost 9 years
    Using tabindex="0" fixes it. However, in my popover, I have html with an ng-click which is ignored when using focus trigger. :(
  • Patrick Motard
    Patrick Motard almost 9 years
    Yes to add to your comment @bryan, the following works as far as getting a popup to show, but it will not hide on lose focus: <button tabindex="0" data-trigger="focus" popover-placement="right" popover="On the Right!" class="btn btn-default">Right</button>
  • bryan
    bryan almost 9 years
    Yea @PatrickMotard I need an unconventional approach to hiding the popover since my popover content that needs to be actionable (i.e., links)
  • Patrick Motard
    Patrick Motard almost 9 years
    See edit in original solution. Let me know if it works for you.
  • bryan
    bryan almost 9 years
    If you look in the previous comments, tabindex="0" does fix it. But please look at the bottom of my original question that explains why I need to create an unconventional approach to my problem.
  • Patrick Motard
    Patrick Motard almost 9 years
    Ah... In that case you can create an event that closes sets a bool in scope equal to false when anything other than the tooltip element is clicked on. Then assign that bool to be the flag that the tooltip watches to determine whether or not it's visible. I can look into that further if you would like help.
  • bryan
    bryan almost 9 years
    That's the kind of approach I am looking for, but .popover('hide') does not work with angular bootstrap
  • bryan
    bryan almost 9 years
    WOW, this is very comprehensive. Thank you for this. I have a LOT of buttons on my page, so it kind of sucks to have to hide the popover (click on the backdrop) before I can interact with any other elements on the page. Is there anyway to avoid the backdrop? The UX would be a whole lot nicer to just hide on a mouse click anywhere but the popover. Regardless though, this is a helluva answer and I REALLY appreciate it man! Thank you!
  • jme11
    jme11 almost 9 years
    Totally valid point. Yeah, I can tweak this so that you can add the popoverBackdrop to the body tag. Give me a few minutes and I will update.
  • bryan
    bryan almost 9 years
    This seems to want to run on ANY link on my site. And I keep getting a random error that Error: undefined is not an object (evaluating 'angular.element(trigger[0]).triggerHandler('click').removeC‌​lass'). Not sure why
  • bryan
    bryan almost 9 years
    I had to change my code to this because of dynamic loading of the popovers I'm assuming. But this ended up working. Thank you @jme11
  • bryan
    bryan almost 9 years
    do you have any suggestions on getting this to work with 2 popovers? It keeps glitching up on me and I can't figure out how to get it to work.
  • jme11
    jme11 almost 9 years
    Any chance you can reproduce the problem in a Fiddle or Plunker?
  • bryan
    bryan almost 9 years
    here you go. When opening both up and closing them using the button, sometimes when I click on the body they re-appear because they aren't being "correctly" closed or something.
  • jme11
    jme11 almost 9 years
    Okay, I'm not getting that error. But, to address the multiple buttons issue, it's simply a matter of looping through the triggers. I should have changed that when I changed the code anyway. Will update the answer, but you'll have to test because of the error thing.
  • bryan
    bryan almost 9 years
    Yea I wasn't receiving the error in the plnkr just in my own code. But that would be awesome man if you could help me with the multiple buttons issue. I really appreciate it.
  • bryan
    bryan almost 9 years
    I don't see i being used in closeTrigger(i). Is that supposed to do something? Because I'm loading content dynamically, I'm getting the same error I had. So I'll have to do some tests. Thanks for the update I'll let you know if I can figure it out
  • bryan
    bryan almost 9 years
    Here are the changes I made and it works great! Thank you! I ended up having to separate triggerHandler('click') and removeClass('popovr-trigger') to be separate actions or else I would get that error for some reason. Also had to use jQuery to get the elements because of the dynamic loading. For some reason I had to move the var trigger to be declared and sent through the function because it wasn't getting any of my elements.
  • jme11
    jme11 almost 9 years
    Glad you were able to make it work for your specific implementation!
  • Jack Malkovich
    Jack Malkovich over 8 years
    doesnt work with ui-bootstrap-tpls-0.13.4.js plnkr.co/edit/bMZeNenmvpZ4hs0pmnGy?p=preview
  • icfantv
    icfantv over 8 years
    Just a note for folks reading this chain. We've added a new feature in 0.14.0 that lets you programmatically open/close both tooltips and popovers. See my answer in this SO item for the same. @JackMalkovich
  • MattDionis
    MattDionis over 8 years
    is there an example of how to use popover-is-open to close popover when clicking outside it anywhere?
  • Martin van Driel
    Martin van Driel over 8 years
    @MattDionis If the value of popover-is-open evaluates to true, then the dialog will be open. You can control this by a scope variable for example.
  • maljukan
    maljukan over 8 years
    When you click the button to open and then again you click the button to close the popover, clicking anywhere else after that will open the popover. In this Plunk i've provided a simple fix, the only change is (element.hasClass('trigger'))? element.removeClass('trigger'): element.addClass('trigger'); in the popoverDirective
  • icfantv
    icfantv over 8 years
    @MattDionis, we've added this ability. it's currently available in master and is targeted for the 1.0 release. There's a new trigger called outsideClick.
  • Admin
    Admin over 8 years
    This is not working for me pakprivatetutors.com/staging in the website i have hooked the concept for Ask Question Button in header
  • icfantv
    icfantv about 8 years
    @Niyaz, please see my comments elsewhere in this article - we've added this feature in UIBS which is currently at 1.1.0.
  • Vinay
    Vinay almost 8 years
    Works fine :) Thanks.
  • kernel
    kernel almost 8 years
    This is now the correct answer since angular-ui implemented this natively in their library. No hacks and workarounds needed anymore.
  • Senthilkumar Annadurai
    Senthilkumar Annadurai over 7 years
    popover-trigger="'outsideClick'" single quote is required
  • r0m4n
    r0m4n over 7 years
    Someone should add this to the documentation... I finally found it in the source :(
  • icfantv
    icfantv over 7 years
    @r0m4n, what do you mean by "this"? It is in our documentation. Both in angular-ui.github.io/bootstrap/#/popover and angular-ui.github.io/bootstrap/#/tooltip.
  • r0m4n
    r0m4n over 7 years
    ah thanks, I must have missed that... I read through all the settings (apparently not close enough). Triggers apparently have its own section outside of the settings but anyhow, I see it now! Also, the docs say "What should trigger a show of the popover?" Which isn't exactly true for this... This is what triggers a hide of the popover
  • kernel
    kernel over 7 years
    This answer is underrated and deserves more upvotes.
  • Kroltan
    Kroltan over 7 years
    Not sure as of 2015, but now the trigger is an angular expression, so it has to be popover-trigger="'outsideClick'".
  • Sarah Bailey
    Sarah Bailey almost 7 years
    This "gotcha" had me... I was assuming a mystery event handler was eating the event before it could propagate. Added the single quote and works