Skip to content
This repository has been archived by the owner on May 29, 2019. It is now read-only.

close popover programmatically & only one popover open at the time #618

Closed
fxck opened this issue Jul 4, 2013 · 51 comments
Closed

close popover programmatically & only one popover open at the time #618

fxck opened this issue Jul 4, 2013 · 51 comments

Comments

@fxck
Copy link

fxck commented Jul 4, 2013

is there any way to do these? I would like to have a "x" button inside the popover to close it and I would want all other popover to be closed when I open a new one..

@pkozlowski-opensource
Copy link
Member

No, it is not currently possible AFAIK. There is an issue opened for this already:
#590

Let's continue the discussion in #590

@fxck
Copy link
Author

fxck commented Jul 4, 2013

neither is currently possible?

@pkozlowski-opensource
Copy link
Member

no, I'm afraid not

@fxck
Copy link
Author

fxck commented Jul 4, 2013

Darn that's kind of a bummer.. popover without those two features has a very very limited use.

@pkozlowski-opensource
Copy link
Member

@fxck If you care about this feature and care about having it in the project we would be happy to accept a pull request (of course one that has test as well as documentation / demo page update). Would be awesome to see your contribution!

@vchatterji
Copy link

@fxck Looking at the tooltip code, I was able to achieve single popover functionality and also hide popover on click elsewhere in the body using the following code (I use jQuery as well).

$('body').on('click', function (e) {
   $('*[popover]').each(function () {
                //Only do this for all popovers other than the current one that cause this event
                if (!($(this).is(e.target) || $(this).has(e.target).length > 0) 
                     && $(this).siblings('.popover').length !== 0 
                     && $(this).siblings('.popover').has(e.target).length === 0)                  
                {
                     //Remove the popover element from the DOM          
                     $(this).siblings('.popover').remove();
                     //Set the state of the popover in the scope to reflect this          
                     angular.element(this).scope().tt_isOpen = false;
                }
    });
});

If you want to do it without jQuery and using only angular jQlite, you can try the following code (maybe it could be added to $tooltipProvider.options as popover-mode, like the following:

$tooltipProvider.options(
{
placement: 'top',
animation: true,
popupDelay: 0,
appendToBody: false,
popover-mode:'single'
});

Anyway, here's the non-jQuery version and a working plnkr:

    <script>
      angular.element(document.body).bind('click', function (e) {
        //Find all elements with the popover attribute
        var popups = document.querySelectorAll('*[popover]');
        if(popups) {
          //Go through all of them
          for(var i=0; i<popups.length; i++) {
            //The following is the popover DOM elemet
            var popup = popups[i];
            //The following is the same jQuery lite element
            var popupElement = angular.element(popup);

            var content;
            var arrow;
            if(popupElement.next()) {
              //The following is the content child in the popovers first sibling
              content = popupElement.next()[0].querySelector('.popover-content');
              //The following is the arrow child in the popovers first sibling
              arrow = popupElement.next()[0].querySelector('.arrow');
            }
            //If the following condition is met, then the click does not correspond
            //to a click on the current popover in the loop or its content.
            //So, we can safely remove the current popover's content and set the
            //scope property of the popover
            if(popup != e.target && e.target != content && e.target != arrow) {
              if(popupElement.next().hasClass('popover')) {
                //Remove the popover content
                popupElement.next().remove();
                //Set the scope to reflect this
                popupElement.scope().tt_isOpen = false;
              }
            }
          }
        }
      });
    </script>

You can see the above non-jQuery code live at:
http://plnkr.co/edit/fhsy4V

@clee704
Copy link

clee704 commented Aug 14, 2013

+1 for the option. I think it's a more natural behavior for popover (in some usecases). In the meantime I will use the hack @vchatterji posted.

@tomoyuki28jp
Copy link

+1 for the option.

@msimonc
Copy link

msimonc commented Mar 16, 2014

to have only one popover open at a time: popover-trigger="focus"

@tmundt
Copy link

tmundt commented Mar 22, 2014

If using "focus" as trigger the popover window will not remain open. So I think just a simple "focus" won't do if you would like a single popup to show up which will remain open. It should be possible to set focus on the content of the popover to keep it open. Until it looses focus, it should remain open.

What do you think?

@kevinhikaruevans
Copy link

+1

3 similar comments
@jesiah-hackinglife
Copy link

+1

@rknell
Copy link

rknell commented Sep 15, 2014

+1

@Muskos
Copy link

Muskos commented Oct 14, 2014

+1

@igorbelikov
Copy link

+1

hot solution ☕
CoffeeScript

# on click popover button
$ '.btn-popover'
    .on 'click', ->
        $ '.btn-popover'
            .not @
                .popover 'hide'

@tmundt
Copy link

tmundt commented Nov 10, 2014

@igorbelikov What kind of code is that, especially the annotation?

@igorbelikov
Copy link

@tmundt CoffeeScript - http://coffeescript.org/

@jesselpalmer
Copy link

+1

@smilelizy
Copy link

@vchatterji
Thanks for the hacks there.

image

In your solution here, I found everything works fine, except that : angular.element(this).scope().tt_isOpen = false;
does not see to do anything.

when I tracing the scope(), there is no property called tt_isOpen, not sure if it because something has changed over the last year.

Now if your set trigger map as : click -> click, you open the popover by clicking, and click somewhere else on the screen does close it (removing it from the DOM I believe) , however the state of of popover remain : isOpen = true. This results that next time you'd click twice to make the popover appear again.

I'm not able to figure how to fix this.
for now I just can think of configuring the trigger as : click -> never

Hope there is a better way of doing this.

@thetaylor82
Copy link

Infuriating problem that should have been fixed long ago. On 0.12.1 I'm completely stuck as I have no access to tt_isOpen (or isOpen). Why can't I just trigger a close on the elements?

@tmundt
Copy link

tmundt commented Apr 23, 2015

+1

2015-04-23 11:46 GMT+02:00 thetaylor82 notifications@github.com:

Infuriating problem that should have been fixed long ago. On 0.12.1 I'm
completely stuck as I have no access to tt_isOpen (or isOpen). Why can't I
just trigger a close on the elements?


Reply to this email directly or view it on GitHub
#618 (comment)
.

@tompi
Copy link
Contributor

tompi commented May 5, 2015

tt_isOpen was removed, they use an "isOpen" attribute and I believe it's not in the same scope as before, anyway, lot's of trial and error leed us to this code which works unless you use the "append to body" attribute... (call hidePopovers from your global click-handler))

var hidePopover = function(element) {
    //Set the state of the popover in the scope to reflect this
    var elementScope = angular.element($(element).siblings('.popover')).scope().$parent;
    elementScope.isOpen = false;
    elementScope.$apply();
    //Remove the popover element from the DOM
    $(element).siblings('.popover').remove();
};
var hidePopovers = function(e) {
    $('*[popover]').each(function() {
        //Only do this for all popovers other than the current one that cause this event,
        if (!($(this).is(e.target) || $(this).has(e.target).length > 0) && $(this).siblings('.popover').length !==
            0 && $(this).siblings('.popover').has(e.target).length === 0) {
            hidePopover(this);
        }
    });
};

@bettysteger
Copy link

hmm I am using the new popover-template directive, how can I achieve this, my current code:
scope() is always undefined ..

  $('body').on('click', function (e) {
    $('*[popover-template]').each(function () {
      var $this = angular.element(this);
      //Only do this for all popovers other than the current one that cause this event
      if (!($this.is(e.target) || $this.has(e.target).length > 0) && $this.siblings('.popover').length !== 0 && $this.siblings('.popover').has(e.target).length === 0) {
        //Set the state of the popover in the scope to reflect this
        console.log($this.scope()); // => undefined
        console.log($this.siblings('.popover')).scope()); // => undefined
        // var elementScope = angular.element($this.siblings('.popover')).scope().$parent;
        // elementScope.isOpen = false;
        // elementScope.$apply();
        //Remove the popover element from the DOM
        $this.siblings('.popover').remove();
      }
    });
  });

@vchatterji Thank you for your code there.

@ghost
Copy link

ghost commented Jun 16, 2015

Any fixes if I use append to body? None of these seems to be working for me.

@windmaomao
Copy link

will this work ? http://stackoverflow.com/questions/20939754/good-way-to-dynamically-open-close-a-popover-or-tooltip-using-angular-based, not sure,

but this one works for me, #590, basically put a ng-click inside the template.

@bitflower
Copy link

@windmaomao it works like a charm. Unless you want to use a template. I'm investigating here right now.

@johnnncodes
Copy link

+1

1 similar comment
@ghost
Copy link

ghost commented Jun 30, 2015

+1

@Elijen
Copy link

Elijen commented Jul 12, 2015

As need for more control over popovers is quite common and there seem to be no progress on adding it to angular-ui repo I created a mini directive that can be used to open/close popovers programmatically: https://github.com/Elijen/angular-popover-toggle

Suggestions are welcome :)

@billyshena
Copy link

Hello guys, any news about closing popovers on clicking outside?
Code examples do not seems to work using popover-template.
@IpsBetty: did you find any solution to make it work with popover-template?

Best,

@Elijen
Copy link

Elijen commented Jul 20, 2015

@billyshena Closing on clicking outside is little bit more complicated as you will most likely need to add ng-click on the body element and do $event.stopPropagation where you don't want to hide the popover (e.g. use clicks inside).

Here is a plunkr: http://plnkr.co/edit/kU9D8Av0fJI76wT8YlHa?p=preview

@bettysteger
Copy link

@billyshena sorry, I use the same code as I pasted above, It just works for the first time, but not for the second time ... :/

@billyshena
Copy link

@Elijen: I've used your plunkr as an example and it seems to work (even tho it's kind of tricky). Thanks for your code sample !
@IpsBetty: Yeah I had the same issue aswell

Are there gonna be any updates on this repo about implementing additional features to the popover?
Best,

@Elijen
Copy link

Elijen commented Jul 21, 2015

@billyshena The issue #590 is without any response from angular-ui team for 1 year and half. So I doubt there will be any progress on this. That's why I created the bower package.

@shashi8shekhar
Copy link

it is solved by adding data-trigger="focus"

@billyshena
Copy link

@Elijen : Mhh I see, thanks again then !
@mikuatapex : I need to be able to click inside the opened popup, which won't work using your solution I think.

@rsmirnov90
Copy link

@Elijen Wow. You are an absolute genius! Thanks so much for this.

I spend like two days on this issue (having built the rest of my app in 5). It is mind-blowing how something this trivial could be so ridiculously complicated.

@dnshah1991
Copy link

yes this is possible
just add this code

$(".popover").each(function(index){

             $(this).not('#' + $(Element).attr('id')).popover('hide'); 

// Element is current element on which popover will open.

        });

@tkalfigo
Copy link

@Hrafnkellos
Copy link

Here is another implementation of Elijen's directive, ng-click on body was not working well for me so i had to change it a little. Here we don't have to hide the popover in a parent controller.

(function (angular) {
    'use strict';

    var POPOVER_SHOW = 'popoverToggleShow';
    var POPOVER_HIDE = 'popoverToggleHide';

    var module = angular.module('popoverToggle', ['ui.bootstrap']);

    module.config(['$tooltipProvider', function ($tooltipProvider) {
        var triggers = {};
        triggers[POPOVER_SHOW] = POPOVER_HIDE;

        $tooltipProvider.setTriggers(triggers);
    }]);

    module.directive('popoverToggle', ['$rootScope', '$timeout', '$window', function ($rootScope, $timeout, $window) {
        return {
            restrict: 'A',
            link: link
        };

        function link($scope, $element, $attrs) {
            $attrs.popoverTrigger = POPOVER_SHOW;

            // To check if we are clicking an element with a popover class
            // http://stackoverflow.com/questions/16863917/check-if-class-exists-somewhere-in-parent-vanilla-js
            var hasSomeParentTheClass = function hasSomeParentTheClass(element, classname) {
                if (element.className && element.className.split(' ').indexOf(classname) >= 0) return true;
                return element.parentNode && hasSomeParentTheClass(element.parentNode, classname);
            }

            // To hide the popover and initialise values again.
            var hidePopover = function hidePopover() {
                $scope.open = false;
                if ($window.onclick) {
                    $window.onclick = null;
                }
                $element.triggerHandler(POPOVER_HIDE);
                $scope.$apply(); //--> trigger digest cycle and make angular aware. 
            }

            $scope.$watch('open', function (newValue) {
                $timeout(function () {
                    if (newValue) {
                        $element.triggerHandler(POPOVER_SHOW);

                        $window.onclick = function ($event) {
                            var clickedElement = $event.target;
                            if (!clickedElement) return; // Element exists

                            var clickedOnTheElement = hasSomeParentTheClass(clickedElement, 'popover'); // Element parent has popover class

                            if (!clickedOnTheElement) {
                                hidePopover();
                                return;
                            }
                        };

                    } else {
                        hidePopover();
                    }
                });
            })
        }
    }]);

})(angular);

@tobiasmuecksch
Copy link

+1

@icfantv
Copy link
Contributor

icfantv commented Sep 11, 2015

Please do not +1 issues. It doesn't add anything to the discussion and clutters the thread. Thank you.

@icfantv
Copy link
Contributor

icfantv commented Feb 19, 2016

@smohamali, we have the *-is-open properties now. Instructions are in the docs.

@smohamali
Copy link

@icfantv Nope, I missed that in the documentation. Even easier. Thanks for the tip 👍
Deleted earlier comment so others don't get confused.

@icfantv
Copy link
Contributor

icfantv commented Feb 19, 2016

No worries. It's always a good idea to carefully check both that and the changelog to see what's present and what's changed. Especially in two years. That's an eternity in OSS.

@danielimatics
Copy link

To open only one popover at a time: popover-trigger="outsideClick"

@MikhailRoot
Copy link

currently popover-trigger is expression so popover-trigger="'outsideClick'" this is working with current version.

@r3wt
Copy link

r3wt commented Jun 21, 2017

@MikhailRoot thank you for that.

@ak868308
Copy link

somehow i created one example for my need. i used this code:
$('.btn').popover();

$('.btn').on('click', function (e) {
$('.btn').not(this).popover('hide');
});

@SebSob
Copy link

SebSob commented Jan 10, 2018

To programatically close the popover you can use this dirty hack:

angular.element(document.querySelector('body')).click();

@IshanFx
Copy link

IshanFx commented Apr 22, 2019

What I did was I set the tooltip-trigger="'none'" and change the tooltip-is-open="true" value based on the click.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests