I want to be able to click anywhere inside the body except that one specific element. I can't find out what's wrong with the code I have done.
When I click on the one specific element .except inside body, I don't want it to hide but when I click on body it should hide.
HTML
<html>
<head>
<title>Click anywhere except that specific element</title>
</head>
<body id="wrapper">
<center>
<div id="except"></div>
</center>
</body>
</html>
JS
var body = document.getElementById('wrapper');
var except = document.getElementById('except');
if(body.addEventListener)
body.addEventListener("click", function(){bodyClick("yes")}, false);
else
body.attachEvent("onclick", bodyClick);
function bodyClick(clicked){
except.addEventListener("click", exceptClick,false);
function exceptClick(){
bodyClick("no");
if(clicked === "yes")
except.style.display = "none";
}
if(clicked === "yes")
except.style.display = "none";
else
except.style.display = "show";
}
Any help is appreciated. Forgive me for the incorrect formatting (it's my first post here). Thank You!
You need to stopPropagation to the outer element.
Here's a simple illustration: http://jsfiddle.net/5mhqrhwk/3/
var body = document.getElementById('wrapper');
var except = document.getElementById('except');
body.addEventListener("click", function () {
alert("wrapper");
}, false);
except.addEventListener("click", function (ev) {
alert("except");
ev.stopPropagation(); //this is important! If removed, you'll get both alerts
}, false);
HTML:
<div id="wrapper">
<center>
<div id="except"></div>
</center>
</div>
You don't really need any flags to do this. Just listen on body click and do different thing depending on the item clicked (event.target). This code should do exactly what you wanted (based on your code):
var body = document.getElementById('wrapper');
var except = document.getElementById('except');
if(body.addEventListener)
body.addEventListener("click", bodyClick, false);
else
body.attachEvent("onclick", bodyClick);
function bodyClick(event){
if(event.target != except)
except.style.display = "none";
}
Instead of stopping propagation on the except element, I'd prefer something like this:
var wrapper = document.querySelector('wrapper');
document.addEventListener('click', function(e) {
if ( !wrapper.contains(e.target) ) {
// Do something when user clicked outside of wrapper element
}
})
You are missing one piece to this. You need to stop the event from bubbling up from the except object if it is clicked
except.addEventListener("click", function(event){event.stopPropagation()}, false);
Related
I am trying to make a simple Shopping List App in which user can Add, Delete and mark the task done when completed. So far, I am able to add the task but facing problem in executing the done and delete functions. I am getting an error because when I execute it, the done and delete buttons are not there but what should I do to fix it?
var inp = document.getElementById("form");
var button = document.getElementById("click");
//Create List Function with Done and Delete Buttons
function addVal() {
var ul = document.getElementById("list");
var li = document.createElement("li");
var span = document.createElement("span");
var done = document.createElement("button");
var del = document.createElement("button");
li.appendChild(document.createTextNode(""));
span.appendChild(document.createTextNode(inp.value));
done.appendChild(document.createTextNode("Done"));
del.appendChild(document.createTextNode("Delete"));
li.appendChild(span);
li.appendChild(done);
li.appendChild(del);
done.setAttribute("class", "doneBut");
del.setAttribute("class", "delBut");
ul.appendChild(li);
inp.value = "";
}
//Get Input Length
function checkLength() {
return inp.value.length;
}
//Run function on Button Click
function onButtonClick() {
if (checkLength() > 0) {
addVal();
}
}
//Run function on Enter Keypress
function onEnter(event) {
if (checkLength() > 0 && event.which === 13) {
addVal();
}
}
//Trigger Events
button.addEventListener("click", onButtonClick);
inp.addEventListener("keypress", onEnter);
//Done and Delete Button Functions
var doneButton = document.getElementsByClassName("doneBut");
var deleteButton = document.getElementsByClassName("delBut");
function doneTask() {
doneButton.parentNode.classList.add("done");
}
function delTask() {
deleteButton.parentNode.classList.add("delete");
}
doneButton.addEventListener("click", doneTask);
deleteButton.addEventListener("click", delTask);
<input type="text" placeholder="Enter Your Task..." id="form" />
<button id="click">Add Task</button>
<h2>List:</h2>
<ul id="list"></ul>
Please Help.
Your problem is that the code tries to add events before the buttons exist. The buttons don’t exist until the addVal function gets called. Since addVal is not being called before the you try to add your event handlers, the getElementById returns null, and you attempt to add an event listener to null.
Additionally it looks like you’re planning to add multiple done and delete buttons. That wouldn’t normally be a problem, except you’re referencing them by ID, and IDs MUST be unique. You’ll need to switch this to a class or an attribute, since you’ll need one per item in the shopping cart.
You’ll probably want to look into event delegation, so that you can add your events once to the page before any buttons exist. https://javascript.info/event-delegation
It's most likely because your script is running before your code is running. Add the <script> tags just before the closing </body> tag to fix it:
<script>/* Your code here */</script>
</body>
You need to place this in a window.onload function, or run it in a function inside of the body tag's onload. Those elements don't exist yet when the script is run:
window.onload = function() {
var inp = document.getElementById("form");
var button = document.getElementById("click");
button.addEventListener("click", onButtonClick);
inp.addEventListener("keypress", onEnter);
}
First I displayed the div on screen and now I want to use the the button to create that same div(or any other action) after user input.
document.getElementById("add").addEventListener("click", function() {
document.getElementById("welcome").style.display = "block";
}
How do i make the button be able to work again after the first thing it did?
It isn't customary to reuse a UI element in different ways as it tends to confuse the end user. But if you must...
document.getElementById("add").addEventListener("click", showWelcome);
function showWelcome() {
document.getElementById("welcome").style.display = "block";
document.getElementById("add").removeEventListener("click", showWelcome);
reassignButton();
}
function resassignButton(){
// some decision logic for next button function
document.getElementById("add").addEventListener("click", doNextThing);
}
function doNextThing(){
// removes eventlistener on add button. does whatever
}
You can use removeEventListener to remove the handler and then set a new one.
var ele = document.getElementById('btn');
ele.addEventListener("click", function click1(){
//Do stuff for the first click
this.innerHTML = "Click Me Again";
alert("Hello");
//Remove the event hendler
this.removeEventListener("click", click1, true);
//Attach handler for rest of clicks
ele.addEventListener("click", function click2(){
alert("You cicked again!");
}, true);
}, true);
<button id=btn>Click Me</button>
Although it's probably more practical to re-use a single event listener, as assigning and re-assigning event listeners can sometimes lead to memory leaks..
(()=>{
var ele = document.getElementById('btn');
var clicks = 0;
ele.addEventListener("click", function(){
if(clicks){
alert("Thanks for clicking again");
}else{
alert("Hello");
this.innerHTML = "Click again";
}
clicks++;
}, true);
})();
<button id=btn>Click Me</button>
You can make several methods and use these with the onclick attribute of button.
Also I advise you to use jQuery. It's easy and faster than JS.
I would like to have an event to trigger when clicking on a box, and a different one when clicking on anyplace less that box. Here's the code:
<body>
<p id="demo"></p>
<script>
document.querySelector("#demo").addEventListener("mouseover", funcion);
document.querySelector("#demo").addEventListener("click", funcion2);
document.querySelector("#demo").addEventListener("mouseout", funcion3);
document.body.querySelector("#demo").addEventListener("click", funcion4, false);
function funcion() {
document.getElementById("demo").innerHTML += "Mouse encima!<br>";
}
function funcion2() {
document.getElementById("demo").innerHTML += "Click dentro<br>";
}
function funcion3() {
document.getElementById("demo").innerHTML += "Mouse fuera!<br>";
}
function funcion4() {
document.getElementById("demo").innerHTML += "Click fuera<br>";
}
document.querySelector("#demo").addEventListener("click", function(e) {e.stopPropagation();}, true);
</script>
</body>
"Tecnically" that should solve it, though i am getting the reverse solution I am looking for, it triggers both events (in & out) when clicking over the box, and none of the events when clicking out of it. I'm out of ideas.
You can add an event listener to the document itself and detect the real target of the event from there.
var box = document.getElementById('demo');
document.addEventListener('click', function(e) {
if (e.target === box) {
console.log('Clicking on the box');
} else {
console.log('Clicking outside of the box');
}
});
<p id="demo">This is the box!</p>
As I am learning javascript I was testing different things. I made a small function:
foo("div", "button");
function foo(divId, buttonId)
{
var flag = 1;
var button2;
document.getElementById(buttonId).onclick = function() {
if (flag) {
button2 = createButton("Ceaning Service");
var span = createSpan("I will be removed");
var div = document.getElementById(divId);
div.appendChild(span);
document.body.insertBefore(button1, div);
flag = 0;
}
if (!flag) {
button2.onclick = function() {
div.removeChild(span);
document.body.removeChild(button1);
flag = 1;
return;
}
}
}
}
function createButton(text)
{
var button = document.createElement("BUTTON");
button.innerHTML = text;
return button;
}
function createSpan(text)
{
var span = document.createElement("SPAN");
span.innerHTML = text;
return span;
}
I can click the second button without errors only when I click the first button Click Me once. When I click Click Me button more than once and try to click the second button, it shows div is undefined. (span is undefined too, but it won't show because browser reaches div first).
Html:
<!DOCTYPE html>
<html>
<head>
<title>Testing</title>
</head>
<body>
<button id="button">Click Me</button><br>
<div id="div"></div>
<script src="test3.js"></script>
</body>
</html>
I played around with this function and when I declare span and div before ...(buttonId).onclick =... it works.
So why this code only works when I click Click Me once? I think with the second click it cannot reference to the created elements, but not sure why.
And although it removes those elements, will they be removed by the garbage collection as well or is there some kind of a memory leak happening?
And please try not to reference jQuery or some other libraries/frameworks. It would only complicate things for me for now.
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.)