This question already has answers here:
How do I detect a click outside an element?
(91 answers)
Closed 1 year ago.
The community reviewed whether to reopen this question last year and left it closed:
Original close reason(s) were not resolved
I have a div with id="content-area", when a user clicks outside of this div, I would like to alert them to the fact that they clicked outside of it. How would I use JavaScript to solve this issue?
<div id = "outer-container">
<div id = "content-area">
Display Conents
</div>
</div>
In pure Javascript
Check out this fiddle and see if that's what you're after!
document.getElementById('outer-container').onclick = function(e) {
if(e.target != document.getElementById('content-area')) {
document.getElementById('content-area').innerHTML = 'You clicked outside.';
} else {
document.getElementById('content-area').innerHTML = 'Display Contents';
}
}
http://jsfiddle.net/DUhP6/2/
The Node.contains() method returns a Boolean value indicating whether a node is a descendant of a given node or not
You can catch events using
document.addEventListener("click", clickOutside, false);
function clickOutside(e) {
const inside = document.getElementById('content-area').contains(e.target);
}
Remember to remove the event listened in the right place
document.removeEventListener("click", clickOutside, false)
Bind the onClick-Event to an element that is outside your content area, e.g. the body. Then, inside the event, check whether the target is the content area or a direct or indirect child of the content area. If not, then alert.
I made a function that checks whether it's a child or not. It returns true if the parent of a node is the searched parent. If not, then it checks whether it actually has a parent. If not, then it returns false. If it has a parent, but it's not the searched one, that it checks whether the parent's parent is the searched parent.
function isChildOf(child, parent) {
if (child.parentNode === parent) {
return true;
} else if (child.parentNode === null) {
return false;
} else {
return isChildOf(child.parentNode, parent);
}
}
Also check out the Live Example (content-area = gray)!
I made a simple and small js library to do this for you:
It hijacks the native addEventListener, to create a outclick event and also has a setter on the prototype for .onoutclick
Basic Usage
Using outclick you can register event listeners on DOM elements to detect whether another element that was that element or another element inside it was clicked. The most common use of this is in menus.
var menu = document.getElementById('menu')
menu.onoutclick = function () {
hide(menu)
}
this can also be done using the addEventListener method
var menu = document.getElementById('menu')
menu.addEventListener('outclick', function (e) {
hide(menu)
})
Alternatively, you can also use the html attribute outclick to trigger an event. This does not handle dynamic HTML, and we have no plans to add that, yet
<div outclick="someFunc()"></div>
Have fun!
Use document.activeElement to see which of your html elements is active.
Here is a reference:
document.activeElement in MDN
$('#outer-container').on('click', function (e) {
if (e.target === this) {
alert('clicked outside');
}
});
This is for the case that you click inside the outer-container but outside of the content-area.
Here is the fiddle : http://jsfiddle.net/uQAMm/1/
$('#outercontainer:not(#contentarea)').on('click', function(event){df(event)} );
function df(evenement)
{
var xstart = $('#contentarea').offset().left;
var xend = $('#contentarea').offset().left + $('#contentarea').width();
var ystart = $('#contentarea').offset().top;
var yend = $('#contentarea').offset().top + $('#contentarea').height();
var xx = evenement.clientX;
var yy = evenement.clientY;
if ( !( ( xx >= xstart && xx <= xend ) && ( yy >= ystart && yy <= yend )) )
{
alert('out');
}
}
use jquery as its best for DOM access
$(document).click(function(e){
if($(e.target).is("#content-area") || $(e.target).closest("#content-area").length)
alert("inside content area");
else alert("you clicked out side content area");
});
Put this into your document:
<script>
document.onclick = function(e) {
if(e.target.id != 'content-area') alert('you clicked outside of content area');
}
</script>
Here is a simple eventListener that checks all parent elements if any contain the id of the element. Otherwise, the click was outside the element
html
<div id="element-id"></div>
js
const handleMouseDown = (ev) => {
let clickOutside = true
let el = ev.target
while (el.parentElement) {
if (el.id === "element-id") clickOutside = false
el = el.parentElement
}
if (clickOutside) {
// do whatever you wanna do if clicking outside
}
}
document.addEventListener("mousedown", handleMouseDown)
Related
I have searched for a good solution everywhere, yet I can't find one which does not use jQuery.
Is there a cross-browser, normal way (without weird hacks or easy to break code), to detect a click outside of an element (which may or may not have children)?
Add an event listener to document and use Node.contains() to find whether the target of the event (which is the inner-most clicked element) is inside your specified element. It works even in IE5
const specifiedElement = document.getElementById('a')
// I'm using "click" but it works with any event
document.addEventListener('click', event => {
const isClickInside = specifiedElement.contains(event.target)
if (!isClickInside) {
// The click was OUTSIDE the specifiedElement, do something
}
})
var specifiedElement = document.getElementById('a');
//I'm using "click" but it works with any event
document.addEventListener('click', function(event) {
var isClickInside = specifiedElement.contains(event.target);
if (isClickInside) {
alert('You clicked inside A')
} else {
alert('You clicked outside A')
}
});
div {
margin: auto;
padding: 1em;
max-width: 6em;
background: rgba(0, 0, 0, .2);
text-align: center;
}
Is the click inside A or outside?
<div id="a">A
<div id="b">B
<div id="c">C</div>
</div>
</div>
You need to handle the click event on document level. In the event object, you have a target property, the inner-most DOM element that was clicked. With this you check itself and walk up its parents until the document element, if one of them is your watched element.
See the example on jsFiddle
document.addEventListener("click", function (e) {
var level = 0;
for (var element = e.target; element; element = element.parentNode) {
if (element.id === 'x') {
document.getElementById("out").innerHTML = (level ? "inner " : "") + "x clicked";
return;
}
level++;
}
document.getElementById("out").innerHTML = "not x clicked";
});
As always, this isn't cross-bad-browser compatible because of addEventListener/attachEvent, but it works like this.
A child is clicked, when not event.target, but one of it's parents is the watched element (i'm simply counting level for this). You may also have a boolean var, if the element is found or not, to not return the handler from inside the for clause. My example is limiting to that the handler only finishes, when nothing matches.
Adding cross-browser compatability, I'm usually doing it like this:
var addEvent = function (element, eventName, fn, useCapture) {
if (element.addEventListener) {
element.addEventListener(eventName, fn, useCapture);
}
else if (element.attachEvent) {
element.attachEvent(eventName, function (e) {
fn.apply(element, arguments);
}, useCapture);
}
};
This is cross-browser compatible code for attaching an event listener/handler, inclusive rewriting this in IE, to be the element, as like jQuery does for its event handlers. There are plenty of arguments to have some bits of jQuery in mind ;)
How about this:
jsBin demo
document.onclick = function(event){
var hasParent = false;
for(var node = event.target; node != document.body; node = node.parentNode)
{
if(node.id == 'div1'){
hasParent = true;
break;
}
}
if(hasParent)
alert('inside');
else
alert('outside');
}
you can use composePath() to check if the click happened outside or inside of a target div that may or may not have children:
const targetDiv = document.querySelector('#targetDiv')
document.addEventListener('click', (e) => {
const isClickedInsideDiv = e.composedPath().includes(targetDiv)
if (isClickedInsideDiv) {
console.log('clicked inside of div')
} else {
console.log('clicked outside of div')
}
})
I did a lot of research on it to find a better method. JavaScript method .contains go recursively in DOM to check whether it contains target or not. I used it in one of react project but when react DOM changes on set state, .contains method does not work. SO i came up with this solution
//Basic Html snippet
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id="mydiv">
<h2>
click outside this div to test
</h2>
Check click outside
</div>
</body>
</html>
//Implementation in Vanilla javaScript
const node = document.getElementById('mydiv')
//minor css to make div more obvious
node.style.width = '300px'
node.style.height = '100px'
node.style.background = 'red'
let isCursorInside = false
//Attach mouseover event listener and update in variable
node.addEventListener('mouseover', function() {
isCursorInside = true
console.log('cursor inside')
})
/Attach mouseout event listener and update in variable
node.addEventListener('mouseout', function() {
isCursorInside = false
console.log('cursor outside')
})
document.addEventListener('click', function() {
//And if isCursorInside = false it means cursor is outside
if(!isCursorInside) {
alert('Outside div click detected')
}
})
WORKING DEMO jsfiddle
using the js Element.closest() method:
let popup = document.querySelector('.parent-element')
popup.addEventListener('click', (e) => {
if (!e.target.closest('.child-element')) {
// clicked outside
}
});
To hide element by click outside of it I usually apply such simple code:
var bodyTag = document.getElementsByTagName('body');
var element = document.getElementById('element');
function clickedOrNot(e) {
if (e.target !== element) {
// action in the case of click outside
bodyTag[0].removeEventListener('click', clickedOrNot, true);
}
}
bodyTag[0].addEventListener('click', clickedOrNot, true);
Another very simple and quick approach to this problem is to map the array of path into the event object returned by the listener. If the id or class name of your element matches one of those in the array, the click is inside your element.
(This solution can be useful if you don't want to get the element directly (e.g: document.getElementById('...'), for example in a reactjs/nextjs app, in ssr..).
Here is an example:
document.addEventListener('click', e => {
let clickedOutside = true;
e.path.forEach(item => {
if (!clickedOutside)
return;
if (item.className === 'your-element-class')
clickedOutside = false;
});
if (clickedOutside)
// Make an action if it's clicked outside..
});
I hope this answer will help you !
(Let me know if my solution is not a good solution or if you see something to improve.)
I am in the process of converting a large script from jQuery to JavaScript. This was code that I didn't write myself but that I forked from a project on GitHub.
I've consulted W3Schools, the official documentation and this website as a reference.
http://youmightnotneedjquery.com/
One of the parts I'm trying to convert into JavaScript is the following.
$('body').on('click','.vb',function(){
exportVB(this.value);
});
According to the aforementioned link,
$(document).on(eventName, elementSelector, handler);
converts to this
document.addEventListener(eventName, function(e) {
// loop parent nodes from the target to the delegation node
for (var target = e.target; target && target != this; target = target.parentNode) {
if (target.matches(elementSelector)) {
handler.call(target, e);
break;
}
}
}, false);
My attempt is as follows
/*document.addEventListener('click',function(e) {
for (var target = e.target; target && target != this; target = target.parentNode) {
if (target.matches('.vb')) {
exportVB.call(target,e);
break;
}
}
}, false);*/
That evidently didn't work so I did a Google search that brought me to this StackOverflow solution
Attach event to dynamic elements in javascript
document.addEventListener('click',function(e){
if(e.target && e.target.id== 'brnPrepend'){
//do something
}
});
//$(document).on('click','#btnPrepend',function(){//do something})
Testing that gave me this idea. I commented it out because that apparently didn't work either.
/*document.addEventListener('click',function(e) {
if (e.target && e.target.className == 'vb') {
exportVB(this.value);
}
});*/
Just for reference, the original jQuery function works well.
I solved it.
document.body.addEventListener('click',function(e) {
for (var target = e.target; target && target != this; target = target.parentNode) {
if (target.matches('.vb')) {
exportVB(target.value);
break;
}
}
});
I can't explain how it worked because I didn't write the original code in the first place. But there were two things I change.
exportVB.call(target.e) to exportVB(target.value)
Removing the false as the last argument.
Rather than iterating over each parent element manually, consider using .closest instead, which will return the ancestor element (or the current element) which matches a selector:
document.querySelector('button').addEventListener('click', () => {
document.body.insertAdjacentHTML('beforeend', '<span class="vb">some span </span>');
});
document.body.addEventListener('click', (e) => {
if (e.target.closest('.vb')) {
console.log('vb clicked');
}
});
<button>add span</button>
I created a button(dupe of existed) onclick of existed button. But the newly created button is not going to create another button when I click.
Here is my code
$('.add-more, .dropdown button').click(function(event){
var elementToBeAdded;
if(event.target.nodeName === "IMG"){
elementToBeAdded = event.target.parentElement.parentElement.parentElement.parentElement;
}
else if(event.target.nodeName === "BUTTON"){
elementToBeAdded = event.target.parentElement.parentElement.parentElement;
}
else if(event.target.nodeName === "SPAN"){
elementToBeAdded = event.target.parentElement.parentElement;
}
var newElement = elementToBeAdded.outerHTML;
newElement = newElement.slice(0, 5) + "style='margin-top:25px' " + newElement.slice(5, newElement.length);
newElement = $(newElement)
$(elementToBeAdded).parent().append(newElement);
})
The above code working fine and creates the dupe button, But the dupe is unable to run the code on click. Please help me.
Add the click handler to the new element.
It's probably easier to move the main logic into a separate function so you can easily attach that function as the click handler.
if the element has the same class ie .add-more and .dropdown and its a button then this is the solution
$('.add-more, .dropdown button').on('click', function(event){
var elementToBeAdded;
if(event.target.nodeName === "IMG"){
elementToBeAdded = event.target.parentElement.parentElement.parentElement.parentElement;
}
else if(event.target.nodeName === "BUTTON"){
elementToBeAdded = event.target.parentElement.parentElement.parentElement;
}
else if(event.target.nodeName === "SPAN"){
elementToBeAdded = event.target.parentElement.parentElement;
}
var newElement = elementToBeAdded.outerHTML;
newElement = newElement.slice(0, 5) + "style='margin-top:25px' " + newElement.slice(5, newElement.length);
newElement = $(newElement)
$(elementToBeAdded).parent().append(newElement);
})
As shown in this same, already answered, question you'll have to use event delegation like this:
$(document).on('click', '.add-more, .dropdown button', function(event){
// ...
})
Because dynamically created elemenets dosen't have any event handler unless they are attached to them after they were created. So instead of making an event handler on the elements themselves, you can have it on an element (a parent of those elements) that you know for sure it will be there always (here I used document, it could be any other element, the condition is it have to be there always). You attach the event handler to that fixed element (document) and telling it that when ever an event occur (first argument), check if the target element match the selector (second argument '.add-more, .dropdown button'), if so then call the function (third argument) on that element.
WHY DO DYNAMICALLY CREATED ELEMENT NOT HAVE EVENT LISTENER?:
Because, this code right here:
$('selector').click(function(){
// ...
})
selects all the elements that match the selector ('selector') and loop through them (THE SELECTED ELEMENTS) one by one assigning the function passed as an event listener using basic JS function (addEventListener, attachEvent...). At this point when this code is run, your future dynamically created elements do not exist so they don't get attached to that event (because they do not exist yet). And by the time they do exist, this line of code $('selector').click(...) is already been executed (because javascript execute code instruction after the other, there's no comming back to a previously executed instruction). So another check to see if there is new elements that match will not happen. To understand here is a plain java script example:
function handleClick() {
alert("Yaay! A Click Happened!");
}
// consider we have three .btn elements in DOM at this point
var btns = document.querySelectorAll('.btn'); // three elements are selected
btns.forEach(function(btn){
btn.addEventListener('click', handleClick); // here too, just three elements get the event listener attached to them
});
// now we create another .btn
var div = document.creatElement('div');
div.className = '.btn':
// we have never ever ever ever ... called .addEventListener on the last element so clicking it will have no effect at all.
I have done this on my own. It's working.
var addBtns = document.querySelectorAll('.add-more, .dropdown button');
addClick(addBtns);
function addClick(addBtns){
Array.prototype.forEach.call(addBtns, function(addBtn) {
addBtn.addEventListener('click', addClickEvent);
});
}
function addClickEvent(e){
var elementToBeAdded;
if(event.target.nodeName === "IMG"){
elementToBeAdded = event.target.parentElement.parentElement.parentElement.parentElement;
}
else if(event.target.nodeName === "BUTTON"){
elementToBeAdded = event.target.parentElement.parentElement.parentElement;
}
else if(event.target.nodeName === "SPAN"){
elementToBeAdded = event.target.parentElement.parentElement;
} else{
return false;
}
var newElement = elementToBeAdded.outerHTML;
newElement = newElement.slice(0, 5) + "style='margin-top:25px' " + newElement.slice(5, newElement.length);
newElement = $(newElement)
$(elementToBeAdded).parent().append(newElement);
addClick(newElement);
}
I am using the Angular directives for bootstrap.
I have a popover as in their example:
<button popover="Hello, World!" popover-title="Title" class="btn btn-default ng-scope">Dynamic Popover</button>
It closes when you click on the button again. I'd like to close it -- and any other open popovers -- when the user clicks anywhere.
I don't see a built-in way to do this.
angular.element(document.body).bind('click', function (e) {
var popups = document.querySelectorAll('.popover');
if(popups) {
for(var i=0; i<popups.length; i++) {
var popup = popups[i];
var popupElement = angular.element(popup);
if(popupElement[0].previousSibling!=e.target){
popupElement.scope().$parent.isOpen=false;
popupElement.remove();
}
}
}
});
This feature request is being tracked (https://github.com/angular-ui/bootstrap/issues/618). Similar to aet's answer, you can do what is recommended in the feature request as a work-around:
$('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;
}
});
});
(source: vchatterji's comment in feature request mentioned above)
The feature request also has a non-jQuery solution as well as this plnkr: http://plnkr.co/edit/fhsy4V
angular.element(document.body).bind('click', function (e) {
var popups = document.querySelectorAll('.popover');
if (popups) {
for (var i = 0; i < popups.length; i++) {
var popup = popups[i];
var popupElement = angular.element(popup);
console.log(2);
if (popupElement[0].previousSibling != e.target) {
popupElement.scope().$parent.isOpen = false;
popupElement.scope().$parent.$apply();
}
}
}
});
What you say it's a default settings of the popover, but you can control it with the triggers function, by putting blur in the second argument of the trigger like this popover-trigger="{mouseenter:blur}"
One idea is you can change the trigger to use mouse enter and exit, which would ensure that only one popover shows at once. The following is an example of that:
<button popover="I appeared on mouse enter!"
popover-trigger="mouseenter" class="btn btn-default"
popover-placement="bottom" >Hello World</button>
You can see this working in this plunker. You can find the entire list of tooltip triggers on the angular bootstrap site (tooltips and popovers have the same trigger options). Best of luck!
Had the same requirement, and this is how we did it:
First, we modified bootstrap, in the link function of the tooltip:
if (prefix === "popover") {
element.addClass('popover-link');
}
Then, we run a click handler on the body like so:
$('body').on('click', function(e) {
var clickedOutside = true;
// popover-link comes from our modified ui-bootstrap-tpls
$('.popover-link').each(function() {
if ($(this).is(e.target) || $(this).has(e.target).length) {
clickedOutside = false;
return false;
}
});
if ($('.popover').has(e.target).length) {
clickedOutside = false;
}
if (clickedOutside) {
$('.popover').prev().click();
}
});
I am using below code for same
angular.element(document.body).popover({
selector: '[rel=popover]',
trigger: "click"
}).on("show.bs.popover", function(e){
angular.element("[rel=popover]").not(e.target).popover("destroy");
angular.element(".popover").remove();
});
Thank you Lauren Campregher, this is worked.
Your code is the only one that also runs the state change on the scope.
Only configured so that if you click on the popover, the latter closes.
I've mixed your code, and now also it works if you click inside the popover.
Whether the system, whether done through popover-template,
To make it recognizable pop up done with popover-template, I used classes popover- body and popover-title, corresponding to the header and the body of the popover made with the template, and making sure it is pointing directly at them place in the code:
angular.element(document.body).bind('click', function (e) {
var popups = document.querySelectorAll('.popover');
if(popups) {
for(var i=0; i<popups.length; i++) {
var popup = popups[i];
var popupElement = angular.element(popup);
var content;
var arrow;
if(popupElement.next()) {
//The following is the content child in the popovers first sibling
// For the classic popover with Angularjs Ui Bootstrap
content = popupElement[0].querySelector('.popover-content');
// For the templating popover (popover-template attrib) with Angularjs Ui Bootstrap
bodytempl = popupElement[0].querySelector('.popover-body');
headertempl= popupElement[0].querySelector('.popover-title');
//The following is the arrow child in the popovers first sibling
// For both cases.
arrow = popupElement[0].querySelector('.arrow');
}
if(popupElement[0].previousSibling!=e.target && e.target != content && e.target != arrow && e.target != bodytempl && e.target != headertempl){
popupElement.scope().$parent.isOpen=false;
popupElement.remove();
}
}
}
});
Have ever a good day, thank you Lauren, thank you AngularJS, Thank You So Much Stack Family!
Updated:
I updated all adding extra control.
The elements within the popover were excluded from the control (for example, a picture inserted into the body of the popover.). Then clicking on the same closed.
I used to solve the command of API Node.contains, integrated in a function that returns true or false.
Now with any element placed inside, run the control, and keeps the popover open if you click inside :
// function for checkparent with Node.contains
function check(parentNode, childNode) { if('contains' in parentNode) { return parentNode.contains(childNode); } else { return parentNode.compareDocumentPosition(childNode) % 16; }}
angular.element(document.body).bind('click', function (e) {
var popups = document.querySelectorAll('.popover');
if(popups) {
for(var i=0; i<popups.length; i++) {
var popup = popups[i];
var popupElement = angular.element(popup);
var content;
var arrow;
if(popupElement.next()) {
//The following is the content child in the popovers first sibling
// For the classic popover with Angularjs Ui Bootstrap
content = popupElement[0].querySelector('.popover-content');
// For the templating popover (popover-template attrib) with Angularjs Ui Bootstrap
bodytempl = popupElement[0].querySelector('.popover-body');
headertempl= popupElement[0].querySelector('.popover-title');
//The following is the arrow child in the popovers first sibling
// For both cases.
arrow = popupElement[0].querySelector('.arrow');
}
var checkel= check(content,e.target);
if(popupElement[0].previousSibling!=e.target && e.target != content && e.target != arrow && e.target != bodytempl && e.target != headertempl&& checkel == false){
popupElement.scope().$parent.isOpen=false;
popupElement.remove();
}
}
}
});
I am using elemntFromPoint in order to return onclick some element. I would like to check if the returned element ('span' div' whatever...) is part of a link or if it is a button etc. How should I start? Thanks.
To check if the element "is part of a link" you'll want to traverse up the DOM tree until you hit an anchor - if you don't hit an anchor then the element isn't in a link. E.g.
var el = document.elementFromPoint(x,y),
cur = el,
isInAnchor = false;
do {
if (cur.nodeName.toLowerCase() === 'a') {
isInAnchor = true;
break;
}
} while (cur = cur.parentNode);
alert(isInAnchor); // either true or false