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 using the bootstrap radio buttons and would like to allow deselection of a radio group. This can be done using an extra button (Fiddle). Instead of an extra button, however, I would like to deselect a selected radio option if the option is clicked when it's active.
I have tried this
$(".btn-group label").on("click", function(e) {
var clickedLabel = $(this);
if ($(clickedLabel).hasClass("active"))
{
// an active option was clicked => deselect it
$(clickedLabel).children("input:radio").prop("checked", false)
$(clickedLabel).removeClass("active");
}
}
)
but there seems to be a race condition: the event of clicking the label that I use seems to be used by bootstrap.js to set the clicked label option to "active". If I introduce a timeout, the class "active" is removed successfully:
$(".btn-group label").on("click", function(e) {
var clickedLabel = $(this);
if ($(clickedLabel).hasClass("active"))
{
setTimeout(function() {
// an active option was clicked => deselect it
$(clickedLabel).children("input:radio").prop("checked", false)
$(clickedLabel).removeClass("active");
}, 500)
}
}
)
How can I toggle a selected option successfully without using a timeout?? Thank you for help.
Instead of using two method's preventDefault & stopPropagation, use return false, will work same.
The difference is that return false; takes things a bit further in
that it also prevents that event from propagating (or "bubbling up")
the DOM. The you-may-not-know-this bit is that whenever an event
happens on an element, that event is triggered on every single parent
element as well.
$(".btn-group label").on("click", function(e) {
var clickedLabel = $(this);
if ($(clickedLabel).hasClass("active"))
{
// an active option was clicked => deselect it
$(clickedLabel).children("input:radio").prop("checked", false)
$(clickedLabel).removeClass("active");
return false;
}
});
After messing with your code in jsfiddle for a while I figured out that a combination of preventDefault() and stopPropagation() does the trick.
Here's a fiddle
and the code:
$(".btn-group label").on("click", function(e) {
var clickedLabel = $(this);
if ($(clickedLabel).hasClass("active"))
{
// an active option was clicked => deselect it
$(clickedLabel).children("input:radio").prop("checked", false)
$(clickedLabel).removeClass("active");
e.preventDefault();
e.stopPropagation();
}
}
);
I have a page that uses KendoUI controls with knockout binding, and I need to use Enter instead of Tab to navigate through controls.
I managed to make it work great by using the solution posted here by Damien Sawyer and enhancing it with Shift-Enter as suggested by Andre Van Zuydam
ko.bindingHandlers.nextFieldOnEnter = {
init: function (element, valueAccessor, allBindingsAccessor) {
j$(element).on('keydown', 'input, select', function (e) {
var self = j$(this)
, form = j$(element)
, focusable
, next
;
var tabElements = 'input,a,select,button,textarea';
if (e.shiftKey) {
if (e.keyCode === 13) {
focusable = form.find(tabElements).filter(':visible');
prev = focusable.eq(focusable.index(this) - 1);
if (prev.length) {
prev.focus();
} else {
form.submit();
}
}
}
else
if (e.keyCode === 13) {
focusable = form.find(tabElements).filter(':visible');
var nextIndex = focusable.index(this) === focusable.length - 1 ? 0 : focusable.index(this) + 1;
next = focusable.eq(nextIndex);
next.focus();
return false;
}
});
} };
(my code uses j$ instead of $ for jquery because the project uses also mootools and I redefined jquery as j$)
However, I have a problem with kendoUI DropDown lists. The problem it is not or element, so it does not get focus (instead it is a span with special classes and unselecteable="on" attribute).
How should I update the ko binding code to set focus to dropdown on Enter? It works with Tab
Thanks
Doing the best I can without having a Kendo sample I can test this out on, but I think you should be able to achieve this. When Kendo creates a dropdown, as you said it adds a bunch of other elements and isn't given focus the same way as a regular select element. You can find a kendo select, however, by first finding its parent span with the class of k-dropdown.
Try adding k-dropdown to tabElements as a class selector:
var tabElements = 'input,a,select,button,textarea,.k-dropdown';
Then, adjust the part where you give focus by adding a condition to check for Kendo dropdown. So instead of just this:
prev.focus();
Try something like this:
if (prev.hasClass('k-dropdown')) {
prev.children('select').first().data("kendoDropDownList").focus();
} else {
prev.focus();
}
I found a related post which did not help:
Twitter bootstrap:Popovers are not showing up on first click but show up on second click
The difference is in my page I have several elements which require popover (several tips-icon), so I need to loop over them..
My markup:
<img class="help_icon" src="http://media.mysite.com/pub/images/help/tips-icon.png">
This is my javascript:
var h=document.getElementsByName("click_help_container");
for (i=0;i<h.length;i++)
{
$('#'+h[i]['id']).click(
function ()
{
var id=$(this).attr("id");
getHelp(id,$(this),function(t,elem)
{
var isVisible = false;
var clickedAway = false;
$(elem).unbind('click');
$(elem).popover(
{
"title":t.title,
"content":"<p class='popover_body_text'>"+t.content+"</p>",
"html":true,
"animation":true,
"placement":"bottom",
"trigger":"manual"
}).click(function(e)
{
$(this).popover('show');
clickedAway = false;
isVisible = true;
e.preventDefault();
});
$(document).click(function(e) {
if(isVisible & clickedAway)
{
$(elem).popover('hide')
isVisible = false;
clickedAway = false;
}else
{
clickedAway = true;
}
});
//$(elem).popover('show');
});
});
}
The problem is when I click on the tips-icon.png button, the popover doesn't show up on first click (I guess it's because I have 2 .click() calls When I click on the button the second time popover shows up and it then maintains it's toggle behavior from there onwards.
You don't need to loop through all elements and initialize popovers one by one, you can apply popover to all items with this name at once (same as you're doing in loop).
And you don't need to show/hide popovers manually by yourself, bootstrap can do it for you.
I think this should work for you:
$("a[name='click_help_container']").popover(
{
"title":t.title,
"content":"<p class='popover_body_text'>"+t.content+"</p>",
"html":true,
"animation":true,
"placement":"bottom",
"trigger":"click"
});