I've made a plugin for Tether (back in the day) for tooltips and I pass an option (Boolean)
for tooltips (only ones which are opening on "click/tap/manual" event) and it listens
to clicking outside of the tooltip area and then closes them.
I guess it will look like this:
var tooltip = new Tooltip(referenceElement, {
title: "Hey there",
trigger: "click",
options: {
clickOutside : true
}
});
Here's a snippet of my code which will give some idea to what I did (modified for simplicity purposes):
````
if( checkClickOutside ){
timer = setTimeout(function(){
// bind a "click outside" event on the document itself
// (this event MUST be removed when the tooltip is
// removed to prevent it from being created again and
// not to pollute the document with events)
$(document.body).off('click.tooltip_outside')
.on('click.tooltip_outside', onClickOutside.bind(that));
}, 50);
}
function onClickOutside(e){
if( !$(e.target).closest('.ttip').length ){
$(document).trigger('clickOutside', [e.target]);
$(document.body).off('click.tooltip_outside');
this.hide();
}
}
````
I've been using Tether for YEARS and only today I've found out about your lib. great work!
I was always sad how Tether got so shitty and abandoned... this seems so much better in terms of code.
Tether's source was so horrible..OMG.. super bad.
BTW - would be nice if the tooltip example page would show an example where some options are passed
Tooltip.js mimics the Bootstrap Tooltip API.
Do you know if the BS tooltip supports this feature? I'd like to copy its API for this as well if they do support it.
I don't use bootstrap.. so I don't know
Even if they don't have this option, I think this is a must-have feature because it's very common UX request to have a tooltip disappear when clicking outside and if it's not included in bootstrap, it shouldn't stop you from adding a few extra stuff, if they are important enough, no?
wouldn't you say this is an important feature?
Yes I'm just saying that if BS has it, I want to mimic its API, if they don't have it, I'll add it with custom API
btw, how do I destroy all the tooltips instances on the page? clean up everything.
This is highly useful when you have a tooltip which opens on click, and that tooltip is "attached" to some element which is in a modal window, and now the user closed that modal window, or perhaps it was a carousel and the user changed slide. this would mean I would need to remove all the tooltips from the screen (but do a real cleanup and not simply remove items by class from the DOM)
.dipose()
it's not showing in any of the documentation pages :) just saying
Right...
+1
+1, but would like it for popper.js too!
This is how it is done for bootstrap popovers:
http://jsfiddle.net/moyarich/adzsp0L6/
$(function () {
var element = '.example';
$(element).popover({
});
$('body').on('click', function (e) {
//Use each to hide Popovers with the same class
$(element).each(function(index, elm) {
hidePopover(elm, e);
});
});
// hide any open popovers when anywhere else in the body is clicked
var hidePopover = function(element, e){
if (!$(element).is(e.target) && $(element).has(e.target).length === 0 && $('.popover').has(e.target).length === 0){
$(element).popover('hide');
}
}
});
@moyarich is it configurable?
I assume the bootstrap example above wouldn't work if an event is stopPropagation()ed? Say you click on the next H1 tag and that has a click event that stops propagation. It would never then close the dialog.
Just evaluating my use case.
I think this is the part of the Bootstrap documentation where it talks about this feature.
https://getbootstrap.com/docs/4.0/components/popovers/#dismiss-on-next-click
I don't think I understand how it is supposed to work.
@FezVrasta bootstrap uses focusin to show the popper and and focusout to hide it.
when you click the button it creates a tooltip, when the button loses focus the tooltip disappears. it doesn't work very well because when you try to click inside the tooltip, it disappears
@moyarich Thanks man, this function is awesome!
I've started using it in my projects with tooltip.js
However just a few notes about that code:
I've replaced
$('body').on('click', function (e) {
with:
$('body').on('click touchend', function (e) {
for better mobile user experience, and
about the long _if_, when you broke it to three lines:
!$(element).is(e.target)
&& $(element).has(e.target).length === 0
&& $('.your-custom-tooltip').has(e.target).length === 0 // <-- "tooltip interaction"
That "tooltip interaction" line is also really interesting and useful.
If you don't want the user to interact with your tooltip (so close it if user clicks is), you can comment it out, but when you want the user to be able to click links/buttons in it, you leave it uncommented. So if @FezVrasta you want to implement this code once, it would be great to have this optional with a tooltip.js option.
!$(element).is(e.target)
&& $(element).has(e.target).length === 0
can be simplified to
!$(e.target).closest(element).length;
bootstrap uses focusin to show the popper and and focusout to hide it. when you click the button it creates a tooltip, when the button loses focus the tooltip disappears. it doesn't work very well because when you try to click inside the tooltip, it disappears
This can be fixed, though, by adding tabIndex="-1" attr to the tooltip/popover itself, thus making it focusable, and subscribing to focusout event on both the button and the tooltip/popover and doing something along the lines of:
onFocusOut = event => {
const newTarget = event.relatedTarget;
if ( !this.button.contains( newTarget ) &&
!this.tooltip.contains( newTarget ) ) {
// dismiss the tooltip/popover here
}
}
Any news about this implementation? If not, someone can provide a clear and working js script to use? thanks
@FezVrasta
more than a year has passed and a simple click event can't be added.. amazing guys!!
just noticed priority: low... LOL... such an important usage for most users.
click button to open tooltip, click outside to close...
abandoned?
I mean, any of you could read the issue, understand the requirements for such feature to exists, and send a PR to implement it. The issue is open rather than closed for this very reason.
So
, yes? Then how about this:
new Tooltip(document.querySelector('#anchor'), {
trigger: 'click',
closeOnDocumentClick: true,
});
_Not a quality gif_ ;(
In my opinion, I'm not really sure if tooltip.js should have this, because _it's very easy to write_ and might require further options (keep showing two tooltips at a same moment, for example). What do you think? @FezVrasta
I may think of a way to define a "group" name to a set of tooltips, so that if you click on any of them, the other don't get closed. But if you click on any other tooltip with a different group or without group, the tooltips close.
I think that would give enough flexibility.
Might be true. The spec will go something like:
new Tooltip(ref, {
trigger: 'click',
closeOnDocClick: true, // false by default
closeOnDocClickExcept: 'group1', // 'default' by default
});
Suggestion: How about starting the closeOnDocClick option without the "group" idea? Not too late after someone actually wants to have it.
Yes sure, I'm not quite sold on the closeOnDocClick actually, I think it would be easier to understand if we called it closeOnClickOutside
Sure :) Let me give it a try.
are we merging?
Is this functionality possible to add to popper and react-popper?
@PsiXdev Yes, but only if someone develop it. I don't think it's a good idea for popper.js to have it, since it's a core and better not to have such function that each users expect different depending on their needs.
Besides, there is a lot of tickets saying "popper.js is heavy". I don't think it's true, but some deliberation not to have a new option would be needed I think.
This doesn't work with a touch event outside the element, correct?
I recommend using this library for this functionality:
https://www.npmjs.com/package/react-popper-tooltip
For those using React, I created a gist of an HOC that you can attach to any component to close it when clicked outside:
https://gist.github.com/elie222/850bc4adede99650508aba2090cd5da1
I can confirm that closeOnClickOutside does not work with touches on iOS/iPhone. The name of the parameter explicitly suggests, that it handles clicks only, but I think touches should also be handled.
It seems that here only a click listener ist registered: https://github.com/FezVrasta/popper.js/blob/b4aae4b6cffffd3f61458c384b999a86c27b16a7/packages/tooltip/src/index.js#L361
@toovy the problem is iOS doesn't fire click events unless it was initiated on a "clickable" element (one that can receive focus)
A fix is to use document.body.style.cursor = 'pointer' if detecting iOS. touchend doesn't have the same behavior as a regular click and I wouldn't recommend it, because unintentional touches outside the popper can accidentally close it. It should be an intentional tap which is handled by the "click" event.
A side effect of using the pointer cursor on iOS is can create is tap highlights if the height of the document is smaller than the screen (I think?), but it can fixed by using -webkit-tap-highlight-color: transparent; on the body.
All problems will go away after this - it makes it behave like Android then (which I think is the correct behavior anyway -- iOS's quirks are incredibly annoying).
@atomiks thanks for the tips, I'll try that out.
Stimulus.js solution if anybody interesting here is;
import { Controller } from "stimulus";
import { createPopper } from '@popperjs/core';
export default class extends Controller {
static targets = ['tooltip'];
open(event) {
this.tooltipTarget.hidden = false;
createPopper(event.currentTarget, this.tooltipTarget, {
placement: 'top',
modifiers: [
{
name: 'offset',
options: {
offset: [0, 15],
},
},
]
});
this.hide(event);
}
hide() {
const clicker = (event) => {
if (!this.tooltipTarget.contains(event.target)) {
this.tooltipTarget.hidden = true;
}
};
document.addEventListener('touchstart', clicker, false);
}
}
%section.mb-3{'aria-label' => 'Bar chart', 'data-controller' => 'chart'}
%a.tooltip{'aria-label' => 'Tooltip', 'data-target' => 'chart.tooltip', hidden: '', href: '#'}
Most helpful comment
I mean, any of you could read the issue, understand the requirements for such feature to exists, and send a PR to implement it. The issue is open rather than closed for this very reason.