I'm curious why this simple code does not run indefinitely. After reading Eloquent JavaScript, it says that the immediate nodes will execute first, and then the outer nodes will, in turn, execute their click event listeners. However, the code below only gives these 7 lines as output. It seems as if lines 5 and 6 would fire the click listener on the container again. Perhaps I am not understanding something about propagation. I've tried debugging using Visual Studio and it does not enter the function, but I cannot find a way to determine why this is using the debugger. Thanks for the help.
"body clicked"
"container clicked"
"item clicked"
"container clicked"
"body clicked"
"body clicked"
const container = document.getElementById("container");
const item = document.getElementById("item");
const body = document.getElementById("body");
item.addEventListener("click", function () {
console.log("item clicked");
});
container.addEventListener("click", function () {
console.log("container clicked");
item.click();
});
body.addEventListener("click", function () {
console.log("body clicked");
container.click();
});
body {
background: yellow;
}
div.container {
width: 200px;
height: 200px;
background: aqua;
border: 1px solid black;
}
div.item {
width: 100px;
height: 100px;
background: gray;
border: 1px solid red;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>My website</title>
<link rel="stylesheet" href="site.css">
</head>
<body id="body">body
<div class="container" id="container">container
<div class="item" id="item">item</div>
</div>
<script src="site.js"></script>
</body>
</html>
As #pilchard pointed out in the comments, calling the click() method of an element will only invoke that element's click event handling function (no different than directly calling the function by its name), but it won't trigger an actual click event.
Below, I've commented out those calls and you are left with being able to clearly see that a mouse click does trigger the event and how that event will bubble up through the clicked element's ancestor elements.
const container = document.getElementById("container");
const item = document.getElementById("item");
const body = document.getElementById("body");
item.addEventListener("click", function () {
console.log("item clicked");
});
container.addEventListener("click", function () {
console.log("container clicked");
//item.click();
});
body.addEventListener("click", function () {
console.log("body clicked");
//container.click();
});
body {
background: yellow;
}
div.container {
width: 200px;
height: 200px;
background: aqua;
border: 1px solid black;
}
div.item {
width: 100px;
height: 100px;
background: gray;
border: 1px solid red;
}
<body id="body">body
<div class="container" id="container">container
<div class="item" id="item">item</div>
</div>
</body>
Related
I am currently using a scene using A-frame (https://aframe.io) where I am hiding the mouse pointer in my scene. How can I create something where when a function is issued, my mouse pointer will show and when another function occurs, my mouse pointer will hide.
Currently the dfeault is that my mouse pointer is hidden. I want it so that when a function called "showPointer" occurs, my mouse pointer will show again and when a function called hidePointer occurs, my mouse pointer will hide again. How can I acheive this. My functions:
<script>
function hidePointer() {
//hide mouse pointer
}
function showPointer() {
//show mouse pointer
}
</script>
<script>
function hidePointer() {
$('a-scene').canvas.style.cursor='none'
}
function showPointer() {
$('a-scene').canvas.style.cursor='pointer'
// replace "pointer" with other style keyword
}
</script>
more detail about cursor style, check here
please make sure canvas element rm class a-grab-cursor from canvas
remove with this $('a-frame').classList.remove("a-grab-cursor")
check detail here
if you using 'cursor' component, please disable mouse cursor styles enabled
const fullBrowserWindow = document.querySelector(`body`);
const popupElement = document.querySelector(`div.popup`);
function hidePointer() {
fullBrowserWindow.style.cursor = 'none';
}
function showPointer() {
fullBrowserWindow.style.cursor = 'default';
}
popupElement.onmouseenter = (event) => {
showPointer();
console.log('Mouse entered the div, Pointer Shown!');
};
popupElement.onmouseleave = (event) => {
hidePointer();
console.log('Mouse left the div, Pointer Removed!');
};
body {
width: 100%;
height: 500px;
padding: 0;
margin: 0;
cursor: none;
background-color: #ff0000;
}
body div.wegbl {
width: 480px;
height: 312px;
background-color: #000000;
}
body div.popup {
width: 200px;
height: 35px;
background-color: #ffffff;
position: absolute;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>No Cursor - on when mouse is over popup</title>
</head>
<body>
<div class="webgl"></div>
<div class="popup">Popup Promt? [Y/N]</div>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>3 Circle</title>
<style>
body {background: black;}
.container {display: flex;}
.circle {
width: 500px;
height: 500px;
-webkit-border-radius: 250px;
-moz-border-radius: 250px;
border-radius: 250px;
background: white;
}
.active {
background: yellow !important;
color: red;
}
</style>
</head>
<body>
<section class="container">
<button class="circle circle1">Circle1</button>
<button class="circle circle2">Circle2</button>
<button class="circle circle3">Circle3</button>
</section>
<script>
let cir1 = document.querySelector('.circle1')
let cir2 = document.querySelector('.circle2')
let cir3 = document.querySelector('.circle3')
let allCircle = document.querySelectorAll('.circle');
cir1.addEventListener('onClick', onButton1Click);
cir2.addEventListener('onClick', onButton2Click);
cir3.addEventListener('onClick', onButton3Click);
function onButton1Click() {
if (cir1.classList.contains("active")) {
allCircle.classList.remove('active');
} else {
allCircle.classList.remove('active');
cir1.classList.add('active');
}
}
function onButton2Click() {
if (cir2.classList.contains("active")) {
allCircle.classList.remove('active');
} else {
allCircle.classList.remove('active');
cir2.classList.add('active');
}
}
function onButton3Click() {
if (cir3.classList.contains("active")) {
allCircle.classList.remove('active');
} else {
allCircle.classList.remove('active');
cir3.classList.add('active');
}
}
</script>
</body>
</html>
I am trying to make 3 light bulbs represented by circles using HTML & CSS.
So if I turn one light bulb on using the button, the other ones should turn off using the addeventlistener. I can't find ways to make the light bulb turn yellow. Is there anything I am doing wrong? I looked for typos but I can't find any.
A few small things need to changed here.
The event type to be passed to the addEventListener is 'click' rather than 'onClick'.
The variable allCircle returns a list of dom nodes and not a single dom node. So it is essentially a []. Hence properties and methods that are available on a dom node are not accessible on the variable. What you can rather do is write a loop to access each element of the array and then modify their classes one by one
Might also suggest you to put debugger inside your code to see what is happening line by line. This article by Google should help you on using the Chrome dev tools.
This is my first answer on Stack Overflow.
let cir1 = document.querySelector('.circle1')
let cir2 = document.querySelector('.circle2')
let cir3 = document.querySelector('.circle3')
cir1.addEventListener('click', onButton1Click);
cir2.addEventListener('click', onButton2Click);
cir3.addEventListener('click', onButton3Click);
function removeActive() {
cir1.classList.remove('active');
cir2.classList.remove('active');
cir3.classList.remove('active');
}
function onButton1Click() {
removeActive();
cir1.classList.add('active');
}
function onButton2Click() {
removeActive();
cir2.classList.add('active');
}
function onButton3Click() {
removeActive();
cir3.classList.add('active');
}
body {
background: black;
}
.container {
display: flex;
align-items: flex-start;
}
.circle {
width: 100px;
height: 100px;
max-height: 100px;
-webkit-border-radius: 250px;
-moz-border-radius: 250px;
border-radius: 250px;
background: white;
}
.active {
background: yellow !important;
color: red;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>3 Circle</title>
</head>
<body>
<section class="container">
<button class="circle circle1">Circle1</button>
<button class="circle circle2">Circle2</button>
<button class="circle circle3">Circle3</button>
</section>
</body>
</html>
There seem to be two issues here.
When adding an event listener for a click event, it must be called with click that is to be passed as the first parameter to the listener, but you've added onClick
querySelectorAll returns a HTMLCollection. So classList will not be a valid property on it. You might want to loop through the elements from allCircles to remove the class.
I've modified the listener and corrected the classist related fix for the first button here https://jsfiddle.net/gr33nw1zard/y7f5wnda/
should be click event, not 'onClick'.
cir1.addEventListener('click', onButton1Click);
Created one common function for all 3 buttons. onClick event is not available in plain javascript, it's the click that is the correct keyword here. Also, you have to iterate over allCircle's object or use getElementsByClass. This will work for you!
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>3 Circle</title>
<style>
body {
background: black;
}
.container {
display: flex;
}
.circle {
width: 500px;
height: 500px;
-webkit-border-radius: 250px;
-moz-border-radius: 250px;
border-radius: 250px;
background: white;
}
.active {
background: yellow !important;
color: red;
}
</style>
</head>
<body>
<section class="container">
<button class="circle circle1">Circle1</button>
<button class="circle circle2">Circle2</button>
<button class="circle circle3">Circle3</button>
</section>
<script>
let cir1 = document.querySelector('.circle1')
let cir2 = document.querySelector('.circle2')
let cir3 = document.querySelector('.circle3')
let allCircle = document.querySelectorAll('.circle');
cir1.addEventListener('click', onButtonClick);
cir2.addEventListener('click', onButtonClick);
cir3.addEventListener('click', onButtonClick);
function onButtonClick(e) {
const cir = e.toElement;
if (cir.classList.contains("active")) {
Object.keys(allCircle).map(circle => allCircle[circle].classList.remove('active'));
} else {
Object.keys(allCircle).map(circle => allCircle[circle].classList.remove('active'));
cir.classList.add('active');
}
}
</script>
</body>
</html>
The onClick should be edited to click
I have written this code to get the squares of a grid to change their background color to black upon a mouseover event. It works when the page initially loads, but if I create a new grid the mouseover event no longer works.
I updated the original post with a snippet. Sorry I didn't do that from the beginning.
let number = 16;
makeGrid(number);
function makeGrid(number) {
for (let i=0; i < number; i++) {
for (let j=0; j < number; j++) {
const rows = document.createElement('div');
const container = document.getElementById('container')
rows.setAttribute('class', 'rows');
container.appendChild(rows);
}
}
container.style.gridTemplateColumns = `repeat(${number}, 1fr)`;
container.style.gridTemplateRows = `repeat(${number}, 1fr)`;
}
//create new grid with on button
let newGrid = document.getElementById('newGrid');
newGrid.addEventListener('click', () => {
let number = prompt('Enter a number');
let container = document.getElementById('container');
container.textContent = '';
makeGrid(number);
})
//change background color to black
let changeClass = document.querySelectorAll('.rows');
changeClass.forEach((item) => {
item.addEventListener('mouseover', e => {
item.style.backgroundColor = 'black';
})
})
body {
background-color: rgb(5, 51, 5) ;
}
#container {
margin: auto;
width: 500px;
height: 500px;
display: grid;
border-style: solid;
border-width: thin;
border-color: lightslategray;
background-color: white;
}
.rows{
}
.black { background-color: black;
}
#header {
text-align: center;
}
#button {
text-align: center;
}
#footer {
text-align: center;
}
#newGrid {
background-color: lightgray;
color: darkcyan;
font-size: 20px;
padding: 12px 28px;
border-radius: 0px;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Etch-a-Sketch</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1 id='header'>Etch-a-Sketch</h1>
<br>
<div id='button'>
<button id='newGrid' class='button'>New Grid</button>
</div>
<br>
<br>
<div id='container'></div>
<br>
<footer id='footer'>Made by: Joe Maniaci</footer>
<script src="main.js"></script>
</body>
</html>
When you query the DOM with document.querySelectorAll('.rows') and add the event listeners, there is only one "grid" in the DOM at that time. When a "grid" is subsequently added to the DOM, as triggered by the user's click event, you must instantiate event listeners on the newly added DOM nodes too.
A way to avoid this problem and a better approach overall in your situation is to use delegated event listeners. For example:
document.addEventListener('mouseover', e=>{
if(e.target.matches(‘.myClickableItemClass’){
e.target.style.backgroundColor = 'black';
}
}
Learn more about event delegation here: https://medium.com/#bretdoucette/part-4-what-is-event-delegation-in-javascript-f5c8c0de2983
Why clicking on parent div element does the job, but clicking on the child div text Click Me! returns undefined and the page doesn't load in iframe? I used event.stopPropagation to stop bubbling but it is still the same. What is the correct way to do that, when addEvenLisneter is preferred over inline onclick ?
var panel = document.getElementsByClassName('panel');
var iframe = document.getElementById('frame');
[].forEach.call(panel, function(each) {
each.addEventListener('click', linkClick);
});
function linkClick ( event ) {
event.stopPropagation();
event.preventDefault();
iframe.src = event.target.dataset.url;
}
.panel {
padding: 20px;
margin: 10px;
border: 1px solid yellow;
}
.panel div {
display: inline;
}
<div class="panel" data-url="https://www.w3schools.com/jsref/event_preventdefault.asp">
<div>Click Me!</div>
</div>
<iframe id="frame"></iframe>
When I console.log(event) bubble property is still set to true.
If you prevented the click event from bubbling from the inner div, then it would never reach the one to which you bound the event handler and your function would not fire at all.
The problem here is that event.target matches the element that was actually clicked on. You want event.currentTarget if you want to get the element that the event handler was bound to.
var panel = document.getElementsByClassName('panel');
var iframe = document.getElementById('frame');
[].forEach.call(panel, function(each) {
each.addEventListener('click', linkClick);
});
function linkClick ( event ) {
event.stopPropagation();
event.preventDefault();
iframe.src = event.currentTarget.dataset.url;
}
.panel {
padding: 20px;
margin: 10px;
border: 1px solid yellow;
}
.panel div {
display: inline;
}
<div class="panel" data-url="https://www.w3schools.com/jsref/event_preventdefault.asp">
<div>Click Me!</div>
</div>
<iframe id="frame"></iframe>
Well, I am not actually solving the problem as a such, more like a workaround.
Maybe you could try using a button instead, as this is slightly more valid HTML.
I modified the CSS a bit, so it matches your example.
<!DOCTYPE html>
<html>
<style>
.panel {
padding: 20px;
margin: 10px;
border: 1px solid yellow;
display:block;
width:100%;
text-align:left
}
</style>
<body>
<button class="panel" data-url="https://www.w3schools.com/jsref/event_preventdefault.asp" type="button">Click Me!</button>
<iframe id="frame"></iframe>
</body>
<script>
var panel = document.getElementsByClassName('panel');
var iframe = document.getElementById('frame');
[].forEach.call(panel, function(each) {
each.addEventListener('click', linkClick);
});
function linkClick ( event ) {
event.stopPropagation();
event.preventDefault();
iframe.src = event.target.dataset.url;
}
</script>
</html>
I was learning about bubbling and capturing and although this situation does not apply to either case, I wanted to know if there is a way to do it with the event handling in JS. The problem:
Here I make a few boxes and a trigger on the same level of the DOM. I provide an event listener to each for a custom event(avalanche) that can be triggered on clicking the bottom box. I want the trigger to be emitted to the other elements changing their background color. See code below.
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="./styles.css">
<script src="./actions.js"></script>
</head>
<body>
<div class="one block"></div>
<div class="two block"></div>
<div class="three block"></div>
<div class="four trigger"> Fire When Ready </div>
</body>
</html>
CSS
.block, .trigger{
height: 200px;
width: 200px;
}
.one{
background-color: red;
}
.two{
background-color: yellow;
}
.three{
background-color: green;
}
.four{
background-color: orange;
text-align: center;
vertical-align: middle;
line-height: 200px;
}
JS
document.addEventListener("DOMContentLoaded", function(event){
console.log("content loaded");
var blocks = document.getElementsByClassName("block");
for(var i = 0; i<blocks.length; i++){
(function(){
var target = blocks[i];
target.addEventListener("avalanche", function(){
this.style.backgroundColor = "brown";
}, true);
})()
}
var beacon = document.getElementsByClassName("four")[0];
beacon.addEventListener("click", function(){
console.log("fired!");
// trigger the event and watch the cascade
beacon.dispatchEvent(cascade);
});
// create a custom event
var cascade = new CustomEvent('avalanche', {});
console.log("handlers set");
});
So, the idea would be to click the bottom box and have the event launch and be seen by the other divs in the body. These divs would all be triggered to change the background color of the boxes. Can this be done? There are obvious other ways to do this with JS but I wanted to do it by listening for the avalanche event.
The dispatch needs to be run on the target elements (see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/dispatchEvent) and not on the beacon, so you will need to iterate over the matching elements (the siblings in this case).
document.addEventListener("DOMContentLoaded", function(event){
console.log("content loaded");
var blocks = document.getElementsByClassName("block");
for(var i = 0; i<blocks.length; i++){
(function(){
var target = blocks[i];
target.addEventListener("avalanche", function(){
this.style.backgroundColor = "brown";
}, true);
})()
}
var beacon = document.getElementsByClassName("four")[0];
beacon.addEventListener("click", function(){
console.log("fired!");
// trigger the event and watch the cascade
var sameLevel = beacon.parentNode.children; // get siblings (and self)
Array.from(sameLevel).forEach(function(node){
node.dispatchEvent(cascade)
});
});
// create a custom event
var cascade = new CustomEvent('avalanche', {});
console.log("handlers set");
});
.block, .trigger{
height: 200px;
width: 200px;
}
.one{
background-color: red;
}
.two{
background-color: yellow;
}
.three{
background-color: green;
}
.four{
background-color: orange;
text-align: center;
vertical-align: middle;
line-height: 200px;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="./styles.css">
<script src="./actions.js"></script>
</head>
<body>
<div class="one block"></div>
<div class="two block"></div>
<div class="three block"></div>
<div class="four trigger"> Fire When Ready </div>
</body>
</html>