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>
Related
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>
My goal is to have text change onmouseover from "hello" (without a link) to "Google" and provide an 'href' on the resulting "Google" text, and then revert to "hello" onmouseout without a link.
The code below works in changing the text from "hello" to "Google" but,
the link on "Google" does not work (even though I can right-click on "Google" and open the link on another tab)
the text does not change back to "hello" onmouseout.
Thanks for your help in advance!
Here is my code:
<style>
.container {
margin-top: 6vw;
margin-left: 40%;
margin-right: 40%;
}
</style>
<div class="container">
<h1>
<div class="hello" id="hello1" onmouseover="changeText()" onmouseout="changeText(this,'Hello.')">Hello.</div>
</h1>
</div>
<script>
function changeText() {
if (document.getElementById("hello1")) {
a = document.getElementById("hello1")
a.innerHTML = 'Google'
}
}
</script>
try this way onmouseout="this.innerHTML='Hello.';"
function changeText() {
if (document.getElementById("hello1")) {
a = document.getElementById("hello1")
a.innerHTML = 'Google'
}
}
.container {
margin-top: 6vw;
margin-left: 40%;
margin-right: 40%;
}
<div class="container">
<h1>
<div class="hello" id="hello1" onmouseover="changeText()" onmouseout="this.innerHTML='Hello.';">Hello.</div>
</h1>
</div>
By changing a class of a parent tag, any and all child tags can be affected via CSS. Having the HTML ready when the page loads and then hiding it is better than constantly creating and destroying HTML for trivial effects.
The events "mouseenter" and "mouselrave" are handled by a property event handler and an event listener. Either one is sufficient, but avoid using attribute event handlers:
<div onmouselame="lameAttributeEventHandler()">...</div>
Details are commented in the example below
// Reference the <header>
const hdr = document.querySelector('.title');
/* This is a property event handler
// Whenever the mouse enters within the borders of
// the <header>:
// '.google' class is added
// '.hello' class is removed
*/
hdr.onmouseenter = function(event) {
this.classList.add('google');
this.classList.remove('hello');
};
/* This is an even listener
// Whenever the mouse exits the <header> the
// opposite behavior of the previous handler
// happens
*/
hdr.addEventListener("mouseleave", function(event) {
this.classList.add('hello');
this.classList.remove('google');
});
.title {
height: 50px;
margin-top: 3vh;
border: 3px solid black;
text-align: center;
}
h1 {
margin: auto 0;
}
.hello span {
display: inline-block;
}
.hello a {
display: none;
}
.google a {
display: inline-block;
}
.google span {
display: none;
}
<header class="title hello">
<h1>
<span>Hello</span>
Google
</h1>
</header>
You can try this, May it help u to solve the problem
<!DOCTYPE html>
<head>
<title>change text on mouse over and change back on mouse out
</title>
<style>
#box {
float: left;
width: 150px;
height: 150px;
margin-left: 20px;
margin-top: 20px;
padding: 15px;
border: 5px solid black;
}
</style>
</head>
<html>
<body>
<div id="box" onmouseover="changeText('Yes, this is Onmouseover Text')" onmouseout="changeback('any thing')" >
<div id="text-display" >
any thing
</div>
</div>
<script type="text/javascript">
function changeText(text)
{
var display = document.getElementById('text-display');
display.innerHTML = "";
display.innerHTML = text;
}
function changeback(text)
{
var display = document.getElementById('text-display');
display.innerHTML = "";
display.innerHTML = text;
}
</script>
</body>
</html>
I cannot manage to reproduce the problem in fiddle.
The problem occurs when is exactly 1286px wide (in the inspector), but it might also happen at other widths.
Clicking the elements changes the height of the body, from 86 to 85.9833.
https://i.imgur.com/rGJPFyW.png
https://gfycat.com/acidicmarvelousabyssiniancat
<html>
<head>
<meta charset="utf-8">
<style type="text/css">
body {font-family: monospace; background: #222; color:#aaa;}
html{scrollbar-color: grey black;}
#ui_tags{
column-width: 80px;
margin: 1em;
padding: 1em;
background-color: #181818;
/*width: 90%;*/
}
.tag_select {
cursor:pointer;
color:#aaa;
}
.tag_select:hover {
background: #222;
}
.tag_select:active {
background: #f00;
}
p {margin-bottom:1px;margin-top:1px;}
</style>
<body>
<div id="ui">
<div id="ui_tags"><p class="tag_select">4ch:114</p><p class="tag_select">avtr:54</p><p class="tag_select">awe:53</p><p class="tag_select">btfl:211</p><p class="tag_select">cat:319</p><p class="tag_select">charc:19</p><p class="tag_select">cmc:145</p><p class="tag_select">dung:15</p><p class="tag_select">frnc:53</p><p class="tag_select">fun:5</p><p class="tag_select">inspr:192</p><p class="tag_select">lego:16</p><p class="tag_select">lndsc:63</p><p class="tag_select">mchn:5</p><p class="tag_select">meh:42</p><p class="tag_select">mnstr:87</p><p class="tag_select">pgrmh:62</p><p class="tag_select">pltc:239</p><p class="tag_select">ppl:22</p><p class="tag_select">pxlr:72</p><p class="tag_select">shrlt:79</p><p class="tag_select">txl:145</p><p class="tag_select">urbn:6</p><p class="tag_select">vlgr:135</p><p class="tag_select">wrd:23</p>
</div>
</div>
<div id="gallery">
</div>
<script type="text/javascript">
window.onload = function(){
var tag_selects = document.getElementsByClassName("tag_select");
for(var i = 0, size = tag_selects.length; i < size ; i++){
// var elem = tag_selects[i].children[1];
var elem = tag_selects[i];
elem.addEventListener("click", function(event){
char = this.innerHTML;
document.getElementById('gallery').innerHTML = char;
});
}
}
</script>
</body>
</html>
I don't know if I'm using bad practices, or how to avoid this problem. I think column-width is not really stable and should be avoided but I'm not sure.
I modified the <p> to <span> and the problem went away.
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
I have a click event binded to the body element but I don't want it to fire for when the user clicks on certain elements, that being when the element has an attribute of data-dropdown-target, however what I have tried isn't working, it always fires.
CodePen: http://codepen.io/anon/pen/ORQkrb
HTML:
<body>
<div class="foo">foo</div>
<div class="bar" data-dropdown-target="something">bar</div>
<div class="moo">moo</div>
</body>
CSS:
.foo, .bar, .moo {
padding: 10px;
margin: 5px;
}
.foo {
background-color: gray;
}
.bar {
background-color: teal;
}
.moo {
background-color: green;
}
JS:
$('body').not('[data-dropdown-target]').on('click', function(e) {
console.log('Hi!');
});
I assume this is because it is trying to remove body elements that have this attribute, rather than it's children - correct?
How do I go about stopping it from firing on children elements that have this attribute - do I have to loop through everything, as I would like to avoid that because of performance reasons, especially since it's on the body.
Actually your code try to bind event click on every <body> without data-dropdown-target attribute.
This could solve your problem :
$('body').on('click', function(e) {
if($(e.target).data('dropdown-target') || $(e.target).parents('[data-dropdown-target]').length !== 0) return false;
console.log('Hi!');
});
.foo, .bar, .moo {
padding: 10px;
margin: 5px;
}
.foo {
background-color: gray;
}
.bar {
background-color: teal;
}
.moo {
background-color: green;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<body>
<div class="foo">foo</div>
<div class="bar" data-dropdown-target="something">bar</div>
<div data-dropdown-target="something">
<div class="moo">moo</div>
</div>
</body>
The not selector just remove the body element if it has [data-dropdown-target] attribute.
Remove elements from the set of matched elements.
$('body').on('click', function(e) {
console.log('Hi!');
});
$('[data-dropdown-target]').on('click',function(e){
return false;
});