Can we get popovers to be dismissable in the same way as modals, ie. make them close when user clicks somewhere outside of them?
Unfortunately I can't just use real modal instead of popover, because modal means position:fixed and that would be no popover anymore. :(
Update: A slightly more robust solution: http://jsfiddle.net/mattdlockyer/C5GBU/72/
For buttons containing text only:
$('body').on('click', function (e) {
//did not click a popover toggle or popover
if ($(e.target).data('toggle') !== 'popover'
&& $(e.target).parents('.popover.in').length === 0) {
$('[data-toggle="popover"]').popover('hide');
}
});
For buttons containing icons use (this code has a bug in Bootstrap 3.3.6, see the fix below in this answer)
$('body').on('click', function (e) {
//did not click a popover toggle, or icon in popover toggle, or popover
if ($(e.target).data('toggle') !== 'popover'
&& $(e.target).parents('[data-toggle="popover"]').length === 0
&& $(e.target).parents('.popover.in').length === 0) {
$('[data-toggle="popover"]').popover('hide');
}
});
For JS Generated Popovers Use '[data-original-title]' in place of '[data-toggle="popover"]'
Caveat: The solution above allows multiple popovers to be open at once.
One popover at a time please:
Update: Bootstrap 3.0.x, see code or fiddle http://jsfiddle.net/mattdlockyer/C5GBU/2/
$('body').on('click', function (e) {
$('[data-toggle="popover"]').each(function () {
//the 'is' for buttons that trigger popups
//the 'has' for icons within a button that triggers a popup
if (!$(this).is(e.target) && $(this).has(e.target).length === 0 && $('.popover').has(e.target).length === 0) {
$(this).popover('hide');
}
});
});
This handles closing of popovers already open and not clicked on or their links have not been clicked.
Update: Bootstrap 3.3.6, see fiddle
Fixes issue where after closing, takes 2 clicks to re-open
$(document).on('click', function (e) {
$('[data-toggle="popover"],[data-original-title]').each(function () {
//the 'is' for buttons that trigger popups
//the 'has' for icons within a button that triggers a popup
if (!$(this).is(e.target) && $(this).has(e.target).length === 0 && $('.popover').has(e.target).length === 0) {
(($(this).popover('hide').data('bs.popover')||{}).inState||{}).click = false // fix for BS 3.3.6
}
});
});
Update: Using the conditional of the previous improvement, this solution was achieved. Fix the problem of double click and ghost popover:
$(document).on("shown.bs.popover",'[data-toggle="popover"]', function(){
$(this).attr('someattr','1');
});
$(document).on("hidden.bs.popover",'[data-toggle="popover"]', function(){
$(this).attr('someattr','0');
});
$(document).on('click', function (e) {
$('[data-toggle="popover"],[data-original-title]').each(function () {
//the 'is' for buttons that trigger popups
//the 'has' for icons within a button that triggers a popup
if (!$(this).is(e.target) && $(this).has(e.target).length === 0 && $('.popover').has(e.target).length === 0) {
if($(this).attr('someattr')=="1"){
$(this).popover("toggle");
}
}
});
});
$('html').on('mouseup', function(e) {
if(!$(e.target).closest('.popover').length) {
$('.popover').each(function(){
$(this.previousSibling).popover('hide');
});
}
});
This closes all popovers if you click anywhere except on a popover
UPDATE for Bootstrap 4.1
$("html").on("mouseup", function (e) {
var l = $(e.target);
if (l[0].className.indexOf("popover") == -1) {
$(".popover").each(function () {
$(this).popover("hide");
});
}
});
Most simple, most fail safe version, works with any bootstrap version.
Demo:
http://jsfiddle.net/guya/24mmM/
Demo 2: Not dismissing when clicking inside the popover content
http://jsfiddle.net/guya/fjZja/
Demo 3: Multiple popovers:
http://jsfiddle.net/guya/6YCjW/
Simply calling this line will dismiss all popovers:
$('[data-original-title]').popover('hide');
Dismiss all popovers when clicking outside with this code:
$('html').on('click', function(e) {
if (typeof $(e.target).data('original-title') == 'undefined') {
$('[data-original-title]').popover('hide');
}
});
The snippet above attach a click event on the body.
When the user click on a popover, it'll behave as normal.
When the user click on something that is not a popover it'll close all popovers.
It'll also work with popovers that are initiated with Javascript, as opposed to some other examples that will not work. (see the demo)
If you don't want to dismiss when clicking inside the popover content, use this code (see link to 2nd demo):
$('html').on('click', function(e) {
if (typeof $(e.target).data('original-title') == 'undefined' && !$(e.target).parents().is('.popover.in')) {
$('[data-original-title]').popover('hide');
}
});
With bootstrap 2.3.2 you can set the trigger to 'focus' and it just works:
$('#el').popover({trigger:'focus'});
None of supposed high-voted solutions worked for me correctly.
Each leads to a bug when after opening and closing (by clicking on other elements) the popover for the first time, it doesn't open again, until you make two clicks on the triggering link instead of one.
So i modified it slightly:
$(document).on('click', function (e) {
var
$popover,
$target = $(e.target);
//do nothing if there was a click on popover content
if ($target.hasClass('popover') || $target.closest('.popover').length) {
return;
}
$('[data-toggle="popover"]').each(function () {
$popover = $(this);
if (!$popover.is(e.target) &&
$popover.has(e.target).length === 0 &&
$('.popover').has(e.target).length === 0)
{
$popover.popover('hide');
} else {
//fixes issue described above
$popover.popover('toggle');
}
});
})
This is basically not very complex, but there is some checking to do to avoid glitches.
Demo (jsfiddle)
var $poped = $('someselector');
// Trigger for the popover
$poped.each(function() {
var $this = $(this);
$this.on('hover',function() {
var popover = $this.data('popover');
var shown = popover && popover.tip().is(':visible');
if(shown) return; // Avoids flashing
$this.popover('show');
});
});
// Trigger for the hiding
$('html').on('click.popover.data-api',function() {
$poped.popover('hide');
});
I made a jsfiddle to show you how to do it:
http://jsfiddle.net/3yHTH/
The idea is to show the popover when you click the button and to hide the popover when you click outside the button.
HTML
<a id="button" href="#" class="btn btn-danger">Click for popover</a>
JS
$('#button').popover({
trigger: 'manual',
position: 'bottom',
title: 'Example',
content: 'Popover example for SO'
}).click(function(evt) {
evt.stopPropagation();
$(this).popover('show');
});
$('html').click(function() {
$('#button').popover('hide');
});
simply add this attribute with the element
data-trigger="focus"
Just add this attribute to html element to close popover in next click.
data-trigger="focus"
reference from https://getbootstrap.com/docs/3.3/javascript/#dismiss-on-next-click
According to http://getbootstrap.com/javascript/#popovers,
<button type="button" class="popover-dismiss" data-toggle="popover" title="Dismissible popover" data-content="Popover Content">Dismissible popover</button>
Use the focus trigger to dismiss popovers on the next click that the user makes.
$('.popover-dismiss').popover({
trigger: 'focus'
})
Bootstrap 5 UPDATE:
$(document).on('click', function (e) {
var
$popover,
$target = $(e.target);
//do nothing if there was a click on popover content
if ($target.hasClass('popover') || $target.closest('.popover').length) {
return;
}
$('[data-bs-toggle="popover"]').each(function () {
$popover = $(this);
if (!$popover.is(e.target) &&
$popover.has(e.target).length === 0 &&
$('.popover').has(e.target).length === 0)
{
$popover.popover('hide');
}
});
})
This has been asked before here. The same answer I gave then still applies:
I had a similar need, and found this great little extension of the Twitter Bootstrap Popover by Lee Carmichael, called BootstrapX - clickover. He also has some usage examples here. Basically it will change the popover into an interactive component which will close when you click elsewhere on the page, or on a close button within the popover. This will also allow multiple popovers open at once and a bunch of other nice features.
This is late to the party... but I thought I'd share it.
I love the popover but it has so little built-in functionality. I wrote a bootstrap extension .bubble() that is everything I'd like popover to be. Four ways to dismiss. Click outside, toggle on the link, click the X, and hit escape.
It positions automatically so it never goes off the page.
https://github.com/Itumac/bootstrap-bubble
This is not a gratuitous self promo...I've grabbed other people's code so many times in my life, I wanted to offer my own efforts. Give it a whirl and see if it works for you.
Modified accepted solution. What I've experienced was that after some popovers were hidden, they would have to be clicked twice to show up again. Here's what I did to ensure that popover('hide') wasn't being called on already hidden popovers.
$('body').on('click', function (e) {
$('[data-original-title]').each(function () {
//the 'is' for buttons that trigger popups
//the 'has' for icons within a button that triggers a popup
if (!$(this).is(e.target) && $(this).has(e.target).length === 0 && $('.popover').has(e.target).length === 0) {
var popoverElement = $(this).data('bs.popover').tip();
var popoverWasVisible = popoverElement.is(':visible');
if (popoverWasVisible) {
$(this).popover('hide');
$(this).click(); // double clicking required to reshow the popover if it was open, so perform one click now
}
}
});
});
This solution works fine :
$("body") .on('click' ,'[data-toggle="popover"]', function(e) {
e.stopPropagation();
});
$("body") .on('click' ,'.popover' , function(e) {
e.stopPropagation();
});
$("body") .on('click' , function(e) {
$('[data-toggle="popover"]').popover('hide');
});
For anyone looking for a solution that works with Bootstrap 5 and no jQuery, even when the popovers are dynamically generated (ie manually triggered):
document.querySelector('body').addEventListener('click', function(e) {
var in_popover = e.target.closest(".popover");
if (!in_popover) {
var popovers = document.querySelectorAll('.popover.show');
if (popovers[0]) {
var triggler_selector = `[aria-describedby=${popovers[0].id}]`;
if (!e.target.closest(triggler_selector)) {
let the_trigger = document.querySelector(triggler_selector);
if (the_trigger) {
bootstrap.Popover.getInstance(the_trigger).hide();
}
}
}
}
});
jQuery("#menu").click(function(){ return false; });
jQuery(document).one("click", function() { jQuery("#menu").fadeOut(); });
You can also use event bubbling to remove the popup from the DOM. It is a bit dirty, but works fine.
$('body').on('click touchstart', '.popover-close', function(e) {
return $(this).parents('.popover').remove();
});
In your html add the .popover-close class to the content inside the popover that should close the popover.
It seems the 'hide' method does not work if you create the popover with selector delegation, instead 'destroy' must be used.
I made it work like that:
$('body').popover({
selector: '[data-toggle="popover"]'
});
$('body').on('click', function (e) {
$('[data-toggle="popover"]').each(function () {
//the 'is' for buttons that trigger popups
//the 'has' for icons within a button that triggers a popup
if (!$(this).is(e.target) && $(this).has(e.target).length === 0 && $('.popover').has(e.target).length === 0) {
$(this).popover('destroy');
}
});
});
JSfiddle here
We found out we had an issue with the solution from #mattdlockyer (thanks for the solution!). When using the selector property for the popover constructor like this...
$(document.body').popover({selector: '[data-toggle=popover]'});
...the proposed solution for BS3 won't work. Instead it creates a second popover instance local to its $(this). Here is our solution to prevent that:
$(document.body).on('click', function (e) {
$('[data-toggle="popover"]').each(function () {
//the 'is' for buttons that trigger popups
//the 'has' for icons within a button that triggers a popup
if (!$(this).is(e.target) && $(this).has(e.target).length === 0 && $('.popover').has(e.target).length === 0) {
var bsPopover = $(this).data('bs.popover'); // Here's where the magic happens
if (bsPopover) bsPopover.hide();
}
});
});
As mentioned the $(this).popover('hide'); will create a second instance due to the delegated listener. The solution provided only hides popovers which are already instanciated.
I hope I could save you guys some time.
Bootstrap natively supports this:
JS Bin Demo
Specific markup required for dismiss-on-next-click
For proper cross-browser and cross-platform behavior, you must use the <a> tag, not the <button> tag, and you also must include the role="button" and tabindex attributes.
this solution gets rid of the pesky 2nd click when showing the popover for the second time
tested with with Bootstrap v3.3.7
$('body').on('click', function (e) {
$('.popover').each(function () {
var popover = $(this).data('bs.popover');
if (!popover.$element.is(e.target)) {
popover.inState.click = false;
popover.hide();
}
});
});
I've tried many of the previous answers, really nothing works for me but this solution did:
https://getbootstrap.com/docs/3.3/javascript/#dismiss-on-next-click
They recommend to use anchor tag not button and take care of role="button" + data-trigger="focus" + tabindex="0" attributes.
Ex:
<a tabindex="0" class="btn btn-lg btn-danger" role="button" data-toggle="popover"
data-trigger="focus" title="Dismissible popover" data-content="amazing content">
Dismissible popover</a>
I came up with this:
My scenario included more popovers on the same page, and hiding them just made them invisible and because of that, clicking on items behind the popover was not possible.
The idea is to mark the specific popover-link as 'active' and then you can simply 'toggle' the active popover. Doing so will close the popover completely
$('.popover-link').popover({ html : true, container: 'body' })
$('.popover-link').popover().on 'shown.bs.popover', ->
$(this).addClass('toggled')
$('.popover-link').popover().on 'hidden.bs.popover', ->
$(this).removeClass('toggled')
$("body").on "click", (e) ->
$openedPopoverLink = $(".popover-link.toggled")
if $openedPopoverLink.has(e.target).length == 0
$openedPopoverLink.popover "toggle"
$openedPopoverLink.removeClass "toggled"
I just remove other active popovers before the new popover is shown (bootstrap 3):
$(".my-popover").popover();
$(".my-popover").on('show.bs.popover',function () {
$('.popover.in').remove();
});
The answer from #guya works, unless you have something like a datepicker or timepicker in the popover. To fix that, this is what I have done.
if (typeof $(e.target).data('original-title') === 'undefined' &&
!$(e.target).parents().is('.popover.in')) {
var x = $(this).parents().context;
if(!$(x).hasClass("datepicker") && !$(x).hasClass("ui-timepicker-wrapper")){
$('[data-original-title]').popover('hide');
}
}
tested with 3.3.6 and second click is ok
$('[data-toggle="popover"]').popover()
.click(function () {
$(this).popover('toggle');
});;
$(document).on('click', function (e) {
$('[data-toggle="popover"]').each(function () {
//the 'is' for buttons that trigger popups
//the 'has' for icons within a button that triggers a popup
if (!$(this).is(e.target) && $(this).has(e.target).length === 0 && $('.popover').has(e.target).length === 0) {
$(this).popover('hide');
}
});
});
demo: http://jsfiddle.net/nessajtr/yxpM5/1/
var clickOver = clickOver || {};
clickOver.uniqueId = $.now();
clickOver.ClickOver = function (selector, options) {
var self = this;
//default values
var isVisible, clickedAway = false;
var callbackMethod = options.content;
var uniqueDiv = document.createElement("div");
var divId = uniqueDiv.id = ++clickOver.uniqueId;
uniqueDiv.innerHTML = options.loadingContent();
options.trigger = 'manual';
options.animation = false;
options.content = uniqueDiv;
self.onClose = function () {
$("#" + divId).html(options.loadingContent());
$(selector).popover('hide')
isVisible = clickedAway = false;
};
self.onCallback = function (result) {
$("#" + divId).html(result);
};
$(selector).popover(options);
//events
$(selector).bind("click", function (e) {
$(selector).filter(function (f) {
return $(selector)[f] != e.target;
}).popover('hide');
$(selector).popover("show");
callbackMethod(self.onCallback);
isVisible = !(clickedAway = false);
});
$(document).bind("click", function (e) {
if (isVisible && clickedAway && $(e.target).parents(".popover").length == 0) {
self.onClose();
isVisible = clickedAway = false;
} else clickedAway = true;
});
}
this is my solution for it.
This approach ensures that you can close a popover by clicking anywhere on the page. If you click on another clickable entity, it hides all other popovers. The animation:false is required else you will get a jquery .remove error in your console.
$('.clickable').popover({
trigger: 'manual',
animation: false
}).click (evt) ->
$('.clickable').popover('hide')
evt.stopPropagation()
$(this).popover('show')
$('html').on 'click', (evt) ->
$('.clickable').popover('hide')
Ok this is my first attempt at actually answering something on stackoverflow so here goes nothing :P
It appears that it isn't quite clear that this functionality actually works out of the box on the latest bootstrap (well, if you're willing to compromise where the user can click. I'm not sure if you have to put 'click hover' per-se but on an iPad, click works as a toggle.
The end result is, on a desktop you can hover or click (most users will hover). On a touch device, touching the element will bring it up, and touching it again will take it down. Of course, this is a slight compromise from your original requirement but at least your code is now cleaner :)
$(".my-popover").popover({
trigger: 'click hover'
});
I'm trying to hide the Bootstrap Popover when the user clicks anywhere outside the popover. (I'm really not sure why the creators of Bootstrap decided not to provide this functionality.)
I found the following code on the web but I really don't understand it.
// Hide popover on click anywhere on the document except itself
$(document).click(function(e) {
// Check for click on the popup itself
$('.popover').click(function() {
return false; // Do nothing
});
// Clicking on document other than popup then hide the popup
$('.pop').popover('hide');
});
The main thing I find confusing is the line $('.popover').click(function() { return false; });. Doesn't this line add an event handler for the click event? How does that prevent the call to popover('hide') that follows from hiding the popover?
And has anyone seen a better technique?
Note: I know variations of this question has been asked here before, but the answers to those questions involve code more complex than the code above. So my question is really about the code above
I made http://jsfiddle.net/BcczZ/2/, which hopefully answers your question
Example HTML
<div class="well>
<a class="btn" data-toggle="popover" data-content="content.">Popover</a>
<a class="btn btn-danger bad">Bad button</a>
</div>
JS
var $popover = $('[data-toggle=popover]').popover();
//first event handler for bad button
$('.bad').click(function () {
alert("clicked");
});
$(document).on("click", function (e) {
var $target = $(e.target),
var isPopover = $target.is('[data-toggle=popover]'),
inPopover = $target.closest('.popover').length > 0
//Does nothing, only prints on console and wastes memory. BAD CODE, REMOVE IT
$('.bad').click(function () {
console.log('clicked');
return false;
});
//hide only if clicked on button or inside popover
if (!isPopover && !inPopover) $popover.popover('hide');
});
As I mentioned in my comment, event handlers don't get overwritten, they just stack. Since there is already an event handler on the .bad button, it will be fired, along with any other event handler
Open your console in the jsfiddle, press 5 times somewhere on the page (not the popover button) and then click bad button you should see clicked printed the same amount of times you pressed
Hope it helps
P.S:
If you think about it, you already saw this happening, especially in jQuery.
Think of all the $(document).ready(...) that exist in a page using multiple jquery plugins. That line just registers an event handler on the document's ready event
I just did a more event based solution.
var $toggle = $('.your-popover-button');
$toggle.popover();
var hidePopover = function() {
$toggle.popover('hide');
};
$toggle.on('shown', function () {
var $popover = $toggle.next();
$popover.on('mousedown', function(e) {
e.stopPropagation();
});
$toggle.on('mousedown', function(e) {
e.stopPropagation();
});
$(document).on('mousedown',hidePopover);
});
$toggle.on('hidden', function () {
$(document).off('mousedown', hidePopover);
});
short answer
insert this to bootstrap min.js
when popout onblur will hide popover
when popout more than one, older popover will be hide
$count=0;$(document).click(function(evt){if($count==0){$count++;}else{$('[data-toggle="popover"]').popover('hide');$count=0;}});$('[data-toggle="popover"]').popover();$('[data-toggle="popover"]').on('click', function(e){$('[data-toggle="popover"]').not(this).popover('hide');$count=0;});
None of the above solutions worked 100% for me because I had to click twice on another, or the same, popover to open it again. I have written the solution from scratch to be simple and effective.
$('[data-toggle="popover"]').popover({
html:true,
trigger: "manual",
animation: false
});
$(document).on('click','body',function(e){
$('[data-toggle="popover"]').each(function () {
$(this).popover('hide');
});
if (e.target.hasAttribute('data-toggle') && e.target.getAttribute('data-toggle') === 'popover') {
e.preventDefault();
$(e.target).popover('show');
}
else if (e.target.parentElement.hasAttribute('data-toggle') && e.target.parentElement.getAttribute('data-toggle') === 'popover') {
e.preventDefault();
$(e.target.parentElement).popover('show');
}
});
My solution, works 100%, for Bootstrap v3
$('html').on('click', function(e) {
if(typeof $(e.target).data('original-title') !== 'undefined'){
$('[data-original-title]').not(e.target).popover('hide');
}
if($(e.target).parents().is('[data-original-title]')){
$('[data-original-title]').not($(e.target).closest('[data-original-title]')).popover('hide');
}
if (typeof $(e.target).data('original-title') == 'undefined' &&
!$(e.target).parents().is('.popover.in') && !$(e.target).parents().is('[data-original-title]')) {
$('[data-original-title]').popover('hide');
}
});
Basically I want user to click on any .editable item, which makes an input appear, copy its styles, and then if they click anywhere else, I want the input to disappear and the changes to save. I'm having difficulty making this work. I've seen a solution using event.stopPropagation, but I don't see how to include it the way I have my code structured:
$(function() {
var editObj = 0;
var editing = false;
$("html").not(editObj).click(function(){
if (editing){
$(editObj).removeAttr("style");
$("#textEdit").hide();
alert("save changes");
}
});
$(".editable").not("video, img, textarea")
.click(function(event) {
editObj = this;
editing = true;
$("#textEdit")
.copyCSS(this)
.offset($(this).offset())
.css("display", "block")
.val($(this).text())
.select();
$(this).css("color", "transparent");
});
}
copyCSS function from here
I need to distinguish between clicks on the editable object, and clicks away from it, even if that click is onto a different editable object (in which case it should call 2 events).
Try this:
$('body').click(function(event) {
var parents = $(event.target).parents().andSelf();
if (parents.filter(function(i,elem) { return $(elem).is('#textEdit'); }).length == 0) {
// click was not on #textEdit or any of its childs
}
});
$(".editable").not("video, img, textarea")
.click(function(event) {
// you need to add this, else the event will propagate to the body and close
e.preventDefault();
http://jsfiddle.net/dDFNM/1/
This works by checking if the clicked element, or any of its parents, is #textEdit.
The event.stopPropagation solution can be implemented this way:
// any click event triggered on #textEdit or any of its childs
// will not propagate to the body
$("#textEdit").click(function(event) {
event.stopPropagation();
});
// any click event that propagates to the body will close the #textEdit
$('body').click(function(event) {
if (editing) {
$("#textEdit").hide();
}
});
http://jsfiddle.net/dDFNM/2/
The problem is that you are not correctly binding to the editObj. Perhaps it will help if you move the binding to inside your .editable click handler, or even better use live() or delegate().
$("html").not(editObj)... is bound once at document ready time, and at that time editObj is false