I would like to get an html element without its children, to set an event addEventListener("click) on it, so that the function will only be executed when it is clicked, not on its children. I can only use Javascript. Is this possible?
const divs = document.querySelectorAll("div");
const body = document.querySelector("body");
const myFunction = function() {
this.classList.add("clicked")
}
divs.forEach(function(element) {
element.addEventListener("click", myFunction)
});
.grandparent {
padding: 20px;
border: 5px solid black;
}
.parent {
padding: 20px;
border: 5px solid blue;
}
.child {
padding: 20px;
height: 100px;
width: 100px;
border: 5px solid yellow;
}
.clicked {
background-color: red;
}
<div data-time="3000" class="grandparent">
<div data-time="2000" class="parent">
<div data-time="1000" class="child"></div>
</div>
</div>
this function adds a class to each div, now I would like clicking outside of div to remove that class, however the body variable contains including its children.
If you want to ignore all and any child clicks, then check if the currentTarget is different than the target of the event.
target is the element the event originated from (the deepest child that received the event)
currentTarget is the element on which the event handler is attached
const divs = document.querySelectorAll("div");
const body = document.querySelector("body");
const myFunction = function(event) {
if (event.target === event.currentTarget) {
this.classList.add("clicked")
}
}
divs.forEach(function(element) {
element.addEventListener("click", myFunction)
});
.grandparent {
padding: 20px;
border: 5px solid black;
}
.parent {
padding: 20px;
border: 5px solid blue;
}
.child {
padding: 20px;
height: 100px;
width: 100px;
border: 5px solid yellow;
}
.clicked {
background-color: red;
}
<div data-time="3000" class="grandparent">
<div data-time="2000" class="parent">
<div data-time="1000" class="child"></div>
</div>
</div>
Related
This code adds a class (a border in this case) to an single element (a square) when the user clicks on that element. This part of the code is working fine.
I would then like to be able to remove however many borders were added (1-3 in this example), with the click of a single button, using a for loop.
I was able to do remove the borders by just repeating item1.classList.remove('.bigBorder'); (for example with item1) but that certainly does not scale well.
const item = document.querySelector('.item');
const item1 = document.querySelector('.item1');
const item2 = document.querySelector('.item2');
const item3 = document.querySelector('.item3');
const clearBordersButton = document.querySelector('.clearBorders');
const bigBorder = document.querySelector('.bigBorder');
item1.addEventListener('click', function() {
item1.classList.add('bigBorder');
});
item2.addEventListener('click', function() {
item2.classList.add('bigBorder');
});
item3.addEventListener('click', function() {
item3.classList.add('bigBorder');
});
clearBordersButton.addEventListener('click', clearBorders);
function clearBorders() {
for (let i = 0; i < item.length; i++) {
item[i].classList.remove('bigBorder');
}
};
* {
box-sizing: border-box;
}
.container {
width: 960px;
margin: 20px auto;
border: 1px solid black;
}
.boxes {
display: flex;
justify-content: space-around;
border: 1px solid black;
}
.item1,
.item2,
.item3 {
border: 2px solid blue;
margin: 20px 0;
width: 100px;
height: 100px;
}
.bigBorder {
border: 10px solid black;
}
<div class="container">
<div class="boxes">
<div class="item item1">item</div>
<div class="item item2">item</div>
<div class="item item3">item</div>
</div>
<button class="clearBorders">clear borders</button>
</div>
Instead of attaching a listener to each item you can use event delegation - attach one listener to the parent component (.boxes) and have that listen to events as they "bubble up" the DOM from its children.
If you also select all the item elements with querySelectorAll it's a simple process to iterate over them and remove the class.
// Cache the elements
const boxes = document.querySelector('.boxes');
const items = document.querySelectorAll('.item');
const clear = document.querySelector('.clear');
// Add listeners to the container, and the button
boxes.addEventListener('click', handleClick, false);
clear.addEventListener('click', clearBorders, false);
// Because we're using event delegation
// check that the child element that was clicked
// on was has a `.item` class, and then add the new class
function handleClick(e) {
if (e.target.matches('.item')) {
e.target.classList.add('bigBorder');
}
}
function clearBorders() {
items.forEach(item => {
item.classList.remove('bigBorder');
});
};
.boxes{display:flex;justify-content:space-around;width:250px}
.item{display:flex;justify-content:center;align-items:center;background-color:#efefef;border:10px solid white;margin:20px 0;width:50px;height:50px}
.item:hover{cursor:pointer;background-color:#cdcdcd;}
.bigBorder{border:10px solid #000}
<div class="boxes">
<div class="item">1</div>
<div class="item">2</div>
<div class="item">3</div>
</div>
<button class="clear">clear borders</button>
It's important to note that querySelector returns the first matching element where as querySelectorAll returns a node list of matching elements.
We can use the node list to assign the event lister and remove the class.
//Selects multimple itmes
const items = document.querySelectorAll('.item');
//selects first matching itme
const clearBordersButton = document.querySelector('.clearBorders');
const bigBorder = document.querySelector('.bigBorder');
//Iterate the items
items.forEach(function(element){
//add an event listern
element.addEventListener("click", function(){
//"this" is the item clicked
this.classList.add("bigBorder");
})
})
clearBordersButton.addEventListener('click', clearBorders);
function clearBorders() {
//find the elements
document.querySelectorAll(".item.bigBorder").forEach(function(element){
//remove the class
element.classList.remove("bigBorder");
})
};
* {
box-sizing: border-box;
}
.container {
width: 960px;
margin: 20px auto;
border: 1px solid black;
}
.boxes {
display: flex;
justify-content: space-around;
border: 1px solid black;
}
.item1,
.item2,
.item3 {
border: 2px solid blue;
margin: 20px 0;
width: 100px;
height: 100px;
}
.bigBorder {
border: 10px solid black;
}
<div>
<div class="container">
<div class="boxes">
<div class="item item1">item</div>
<div class="item item2">item</div>
<div class="item item3">item</div>
</div>
<button class="clearBorders">clear borders</button>
</div>
</div>
I am trying to store the previous node of the event.currentTarget in a variable to apply some styling to the previous node, and another to the current node but so far I haven't find a way.
Bellow you'll find the code I am trying to write but doesn't seem to store the variable as the previous target
questionsArray.map((question) => {
if (Object.values(question).includes(true) == true) {
let previousTarget = e.currentTarget
console.log(previousTarget)
e.previousTarget.className = "qgroup red";
e.currentTarget.className = "qgroup blue";
}
})
Calling a variable previousTarget doesn't make it so. As is, this is just another name for the currentTarget. Move previousTarget outside the handler and only assign the currentTarget to it at the very end of the handler.
previousTarget will be undefined on first click, so be sure to handle that.
let prevTarget = null;
for (const div of [...document.querySelectorAll(".box")]) {
div.addEventListener("click", event => {
if (prevTarget) {
prevTarget.classList.remove("blue");
prevTarget.classList.add("red");
}
event.currentTarget.classList.add("blue");
prevTarget = event.currentTarget;
});
}
.box {
width: 50px;
height: 50px;
border: 1px solid black;
display: inline-block;
cursor: pointer;
}
.red {
background-color: red;
}
.blue {
background-color: blue;
}
<div class="box"></div>
<div class="box"></div>
<div class="box"></div>
<div class="box"></div>
If you want the previous previous to be returned to the default state, you can do that too:
let prevTarget = null;
let prevPrevTarget = null;
for (const div of [...document.querySelectorAll(".box")]) {
div.addEventListener("click", event => {
if (prevPrevTarget) {
prevPrevTarget.classList.remove("red");
}
if (prevTarget) {
prevTarget.classList.remove("blue");
prevTarget.classList.add("red");
}
event.currentTarget.classList.add("blue");
prevPrevTarget = prevTarget;
prevTarget = event.currentTarget;
});
}
.box {
width: 50px;
height: 50px;
border: 1px solid black;
display: inline-block;
cursor: pointer;
}
.red {
background-color: red;
}
.blue {
background-color: blue;
}
<div class="box"></div>
<div class="box"></div>
<div class="box"></div>
<div class="box"></div>
I have an array of elements. I want when I hover on any element from this array to add a class to said element. How can I do that?
Loop through each item in the array and an event listener for mouseover which adds the class.
const array = document.querySelectorAll('div');
const classToAdd = 'red';
array.forEach(e => e.addEventListener('mouseover', () => {
e.classList.add(classToAdd);
}))
div {
height: 100px;
width: 100px;
border: 1px solid;
}
.red {
background-color: red;
}
<div></div>
<div></div>
<div></div>
<div></div>
If you have multiple elements with different ids or class you can try this.
let elements = ['#hud-menu', '#hud-intro', '.hud-shop', '.hud-settings'];
document.querySelectorAll(elements).forEach(element => {
element.addEventListener('mouseover', () => {
element.classList.add('blue');
});
});
div {
height: 100px;
width: 100px;
border: 2px solid;
border-radius: 10px;
text-align: center;
}
.blue {
background-color: blue;
}
<div id="hud-menu">Hud Menu</div><br>
<div id="hud-intro">Hud Intro</div><br>
<div class="hud-shop">Hud Shop</div><br>
<div class="hud-settings">Hud Settings</div>
And just in case if you want to remove the class when you stop hovering over an element you can use mouseout like the example below.
let elements = ['#hud-menu', '#hud-intro', '.hud-shop', '.hud-settings'];
// Add class on mouseover
document.querySelectorAll(elements).forEach(element => {
element.addEventListener('mouseover', () => {
element.classList.add('blue');
});
});
// Remove class on mouseout
document.querySelectorAll(elements).forEach(element => {
element.addEventListener('mouseout', () => {
element.classList.remove('blue');
});
});
div {
height: 100px;
width: 100px;
border: 2px solid;
border-radius: 10px;
text-align: center;
}
.blue {
background-color: blue;
}
<div id="hud-menu">Hud Menu</div><br>
<div id="hud-intro">Hud Intro</div><br>
<div class="hud-shop">Hud Shop</div><br>
<div class="hud-settings">Hud Settings</div>
I am having problem of traversing through each HTML element one by one.There are two buttons #up and #down.On click of #up the id #myID should move to the next element upwards and vice versa for #down.The problem is I am able to move through the siblings but not through the child elements.
For example if I click on #down the id #myID should have moved to p tag which is the child of that div on next click to span which is child of p then on next click to div.But in my code it is directly jumping to div ignoring the children.
JSFIDDLE
Here is the code:
$("#up").click(function() {
$("#startHere").find("#myID").next().attr('id', 'myID');
$('#startHere').find("#myID").removeAttr('id');
});
$("#down").click(function() {
$("#startHere").find("#myID").prev().attr('id', 'myID');
$('#startHere').find("#myID").next().removeAttr('id');
})
#myID {
border: 2px solid yellow;
}
#startHere {
height: 100%;
width: 50%;
}
div {
height: 100px;
width: 100px;
border: 2px solid;
margin: 10px;
}
p {
height: 50px;
width: 50px;
border: 2px solid blue;
margin: 10px;
}
h1 {
height: 40px;
width: 40px;
border: 2px solid red;
margin: 10px;
}
span {
display: block;
height: 25px;
width: 25px;
border: 2px solid green;
margin: 10px;
}
button {
height: 25px;
width: 100px;
text-align: center;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button id="up">GO DOWN</button>
<button id="down">GO UP</button>
<div id="startHere">
<div id="myID">
<p><span></span></p>
</div>
<div><span></span></div>
<div>
<h1></h1>
</div>
<p></p>
<h1></h1>
<p><span></span></p>
</div>
I think you can just find all the elements first, jQuery returns them in DOM order, which is what you want. No need to search for the next/prev element on-the-fly.
var allElements = $("#startHere").find('*');
var currentIndex = allElements.index('#myID');
function move(delta) {
// Find the new index
var index = currentIndex + delta;
// Clamp to 0…lengh of list
// Here we could also make it wrap instead
index = Math.max(Math.min(index, allElements.length - 1), 0);
// Remove the ID from the old element
allElements.eq(currentIndex).removeAttr('id');
// Add the ID to the new element
allElements.eq(index).attr('id', 'myID');
// Update the index
currentIndex = index;
}
$("#up").click(function() {
move(1);
});
$("#down").click(function() {
move(-1);
})
var allElements = $("#startHere").find('*');
var currentIndex = allElements.index('#myID');
function move(delta) {
// Find the new index
var index = currentIndex + delta;
// Clamp to 0…lengh of list
// Here we could also make it wrap instead
index = Math.max(Math.min(index, allElements.length - 1), 0);
// Remove the ID from the old element
allElements.eq(currentIndex).removeAttr('id');
// Add the ID to the new element
allElements.eq(index).attr('id', 'myID');
// Update the index
currentIndex = index;
}
$("#up").click(function() {
move(-1);
});
$("#down").click(function() {
move(1);
})
#myID {
border: 2px solid yellow;
}
#startHere {
height: 100%;
width: 50%;
}
div {
height: 100px;
width: 100px;
border: 2px solid;
margin: 10px;
}
p {
height: 50px;
width: 50px;
border: 2px solid blue;
margin: 10px;
}
h1 {
height: 40px;
width: 40px;
border: 2px solid red;
margin: 10px;
}
span {
display: block;
height: 25px;
width: 25px;
border: 2px solid green;
margin: 10px;
}
button {
height: 25px;
width: 100px;
text-align: center;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button id="down">GO DOWN</button>
<button id="up">GO UP</button>
<div id="startHere">
<div id="myID">
<p><span></span></p>
</div>
<div><span></span></div>
<div>
<h1></h1>
</div>
<p></p>
<h1></h1>
<p><span></span></p>
</div>
If you do need the elements on-the-fly (because they might have changed), you can still use the same tactic (and simply build up the allElements list in the move function and get the index using allElements.index('#myID')) but it might be more performant to update the list only when you know it changed (after an Ajax request, after modification on event handlers, etc.).
Edit:
The code for searching the next/prev element on-the-fly is a bit more work because it has to recurse when traversing up but makes it possible to have a different set of rules for up vs. down movement.
var boundary = $("#startHere");
function findNext(node, anchor) {
if(!anchor && node.children(':first-child').length) {
return node.children(':first-child');
}
if(node.next().length) {
return node.next();
}
if(!boundary.find(node.parent()).length) {
// Out of boundary. Stick to the last node
return anchor||node;
}
return findNext(node.parent(), anchor||node);
}
function findPrev(node, anchor) {
if(!anchor && node.children(':last-child').length) {
return node.children(':last-child');
}
if(node.prev().length) {
return node.prev();
}
if(!boundary.find(node.parent()).length) {
// Out of boundary. Stick to the last node
return anchor||node;
}
return findPrev(node.parent(), anchor||node);
}
function move(finder) {
// Find the current item
var current = boundary.find('#myID');
// Find the next item
var next = finder(current);
// Remove the ID from the old element
current.removeAttr('id');
// Add the ID to the new element
next.attr('id', 'myID');
}
$("#up").click(function() {
move(findPrev);
});
$("#down").click(function() {
move(findNext);
})
var boundary = $("#startHere");
function findNext(node, anchor) {
if(!anchor && node.children(':first-child').length) {
return node.children(':first-child');
}
if(node.next().length) {
return node.next();
}
if(!boundary.find(node.parent()).length) {
// Out of boundary. Stick to the last node
return anchor||node;
}
return findNext(node.parent(), anchor||node);
}
function findPrev(node, anchor) {
if(!anchor && node.children(':last-child').length) {
return node.children(':last-child');
}
if(node.prev().length) {
return node.prev();
}
if(!boundary.find(node.parent()).length) {
// Out of boundary. Stick to the last node
return anchor||node;
}
return findPrev(node.parent(), anchor||node);
}
function move(finder) {
// Find the current item
var current = boundary.find('#myID');
// Find the next item
var next = finder(current);
// Remove the ID from the old element
current.removeAttr('id');
// Add the ID to the new element
next.attr('id', 'myID');
}
$("#up").click(function() {
move(findPrev);
});
$("#down").click(function() {
move(findNext);
})
#myID {
border: 2px solid yellow;
}
#startHere {
height: 100%;
width: 50%;
}
div {
height: 100px;
width: 100px;
border: 2px solid;
margin: 10px;
}
p {
height: 50px;
width: 50px;
border: 2px solid blue;
margin: 10px;
}
h1 {
height: 40px;
width: 40px;
border: 2px solid red;
margin: 10px;
}
span {
display: block;
height: 25px;
width: 25px;
border: 2px solid green;
margin: 10px;
}
button {
height: 25px;
width: 100px;
text-align: center;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button id="down">GO DOWN</button>
<button id="up">GO UP</button>
<div id="startHere">
<div id="myID">
<p><span></span></p>
</div>
<div><span></span></div>
<div>
<h1></h1>
</div>
<p></p>
<h1></h1>
<p><span></span></p>
</div>
This is really bad UI. To select some nodes in some states, you first have to navigate “UP” and then “DOWN” again. But it seems to do what you ask for.
I was tinkering with event bubbling and I created the typical 3 divs each other exercise for myself and I was able to get the code working and event to stop "bubbling" where I wanted.
My code was clunky so I tried to make it a little more simplified. By having all my <divs> point to a single function, that had a statement that said if this <div> was clicked, run the function and then stop propagation, but I believe the way I am "checking" the click is wrong.
Link: https://jsfiddle.net/theodore_steiner/t5r5kov0/3/
var d1 = document.getElementById("d1");
var d2 = document.getElementById("d2");
var d3 = document.getElementById("d3");
function showme(event) {
if (d3.onclick == true) {
alert("hello")
event.stopPropagation();
} else {
alert("hello");
}
};
d1.addEventListener("click", showme);
d2.addEventListener("click", showme);
d3.addEventListener("click", showme);
#d1 {
height: 300px;
width: 300px;
border: 1px solid black;
}
#d2 {
height: 200px;
width: 200px;
border: 1px solid black;
}
#d3 {
height: 100px;
width: 100px;
border: 1px solid black;
}
<div id="d1">
<div id="d2">
<div id="d3">
</div>
</div>
</div>
Use event.target.id as event.target will return element on which event is invoked and id is a property of the DOMElement
Event.target, A reference to the object that dispatched the event.
var d1 = document.getElementById("d1");
var d2 = document.getElementById("d2");
var d3 = document.getElementById("d3");
function showme(event) {
if (event.target.id == 'd3') {
alert("hello")
event.stopPropagation();
} else {
alert("hello");
}
};
d1.addEventListener("click", showme);
d2.addEventListener("click", showme);
d3.addEventListener("click", showme);
#d1 {
height: 300px;
width: 300px;
border: 1px solid black;
}
#d2 {
height: 200px;
width: 200px;
border: 1px solid black;
}
#d3 {
height: 100px;
width: 100px;
border: 1px solid black;
}
<div id="d1">
<div id="d2">
<div id="d3">
</div>
</div>
</div>