Class-wide Event is Malfunctioning [duplicate] - javascript

This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 4 years ago.
I create a 16x16 grid of divs in JavaScript.
Each div has a mouse enter event (inherited based on common class) that should change its black background to white.
Here's my problem: whereas each div should change color when it's hovered over, all divs (when hovered over) cause the same bottom-right div to change color, rather than themselves.
I believe all of the events reference the final div under the className "squares" (bottom right div). How do I make each event reference its own div?
SNAPSHOT
CODE
//GRID CREATION (30x30)
var container = document.getElementById('container');
var size = 30;
//Row Creation (30)
for(j=0; j < size; j++) {
var row = document.createElement('div');
row.classList.add("row");
//Square Creation (30 per Row)
for(i=0; i<size; i++) {
var square = document.createElement('div')
square.classList.add("square");
//div background-color changes on mouse-enter
square.addEventListener('mouseenter', () => {
this.square.style.background = 'white';
});
row.appendChild(square);
}
container.append(row);
}

It’s strange to find this.square in your event listener when nothing else refers to this.square. Perhaps you originally tried square.style.background, which didn’t work for the reason described in JavaScript closure inside loops – simple practical example, then tried to fix it with this.style.background = 'white';, which didn’t work because it was an arrow function, then tried to make this.square work…?
Anyway, use either a non-arrow function, to get this inside to be the element the event listener was attached to instead of the same as the this outside:
square.addEventListener('mouseenter', function () {
this.style.background = 'white';
});
or better, declare your variables with let – which must be available, since you were using an arrow function – and refer to square:
//GRID CREATION (30x30)
let container = document.getElementById('container');
let size = 30;
//Row Creation (30)
for (let j = 0; j < size; j++) {
let row = document.createElement('div');
row.classList.add("row");
//Square Creation (30 per Row)
for (let i = 0; i < size; i++) {
let square = document.createElement('div');
square.classList.add("square");
//div background-color changes on mouse-enter
square.addEventListener('mouseenter', () => {
square.style.background = 'white';
});
row.appendChild(square);
}
container.append(row);
}
Declarations for j and i were missing, too. You should enable strict mode and never use var to avoid these types of problems.

Related

How does this repeat() function work? Help me understand

I'm learning Javascript and found this solution on how to make a 16X16 grid. so far i've used example.repeat(number) with a an integer value. I somewhat get the flow of the code but I cant grasp how repeat works here exactly, kindly help.
Result on codepen: https://codepen.io/shogunhermit15/pen/mdxyqMN?editors=1010
function buildGrid(x, y, cellSize, gridElement) {
gridElement.style.display = "grid";
gridElement.style.gridTemplateColumns = `repeat(${x}, ${cellSize}px)`;
gridElement.style.gridTemplateRows = `repeat(${y}, ${cellSize}px)`;
let squares = new DocumentFragment();
for (let i = 0; i < x * y; i++) {
let square = document.createElement('div');
square.className = 'square';
squares.appendChild(square);
}
gridElement.appendChild(squares);
}
buildGrid(16, 16, 25, document.querySelector(".grid"));
I think your question is not related to javascript.
It is related to the CSS repeat function.
The repeat() CSS function represents a repeated fragment of the track list, allowing a large number of columns or rows that exhibit a recurring pattern to be written in a more compact form.
Here is Mdn refrence where You can learn more:
https://developer.mozilla.org/en-US/docs/Web/CSS/repeat
You will find here your code with each line commented, hoping that it helps you to understand your code correctly.
function buildGrid(x, y, cellSize, gridElement) { // declaration of the buildGrid function which takes as parameters the location of the grid, the size of the cell, and the element of the grid
gridElement.style.display = "grid"; // grid display is set to grid
gridElement.style.gridTemplateColumns = `repeat(${x}, ${cellSize}px)`; // set grid size based on cell size
gridElement.style.gridTemplateRows = `repeat(${y}, ${cellSize}px)`; // set grid size based on cell size
let squares = new DocumentFragment(); // creating the squares object which contains the grid elements
for (let i = 0; i < x * y; i++) { // loop that creates grid cells
let square = document.createElement('div'); // creating a div element
square.className = 'square'; // adding square class to cell
squares.appendChild(square); // adding the cell to the grid
}
gridElement.appendChild(squares); // adding grid to page element
}
buildGrid(16, 16, 25, document.querySelector(".grid")); // call buildGrid function with defined parameters

scroll function inside a loop not working because of undefined property

I want to change the style of items while scrolling.
My code is working if I target the ID, but I have to target many items.
So I changed it for class name and add a "for" loop to get through every items.
It ended with the error "Cannot read property 'style' of undefined".
Can someone explain me where I am wrong ?
var gear = document.getElementsByClassName("rotate-block");
for (var i = 0; i < gear.length; i++) {
window.addEventListener("scroll", function() {
gear[i].style.transform = "rotate("+window.pageYOffset/2+"deg)";
});
};
Your code is using a closure-based access to i inside the scroll listeners.
Because you defined your index using var rather than let, all these closures reference the same i, which is evaluated when the listener is executed, not when it is defined.
After your last iteration of the for-loop, i is equal to gear.length, which means any of the listeners is trying to access gear[gear.length]. The highest index available on any array is length - 1 though.
To fix your issue, simply switch from
for (var i = 0; i < gear.length; i++)
to
for (let i = 0; i < gear.length; i++)
So this is the basis of the error you are describing...
...but
Why are you adding more than one scroll listener in the first place?
You probably instead want to iterate over gear inside the listener, at which point using var is perfectly fine since it's no longer accessed as a closure.
var gear = document.getElementsByClassName("rotate-block");
window.addEventListener("scroll", function() {
for (var i = 0; i < gear.length; i++) {
gear[i].style.transform = "rotate("+window.pageYOffset/2+"deg)";
}
});
For the future, I highly recommend to switch to using for...of to iterate over iterables:
window.addEventListener("scroll", function() {
for (const gear of document.getElementsByClassName("rotate-block")) {
gear.style.transform = "rotate("+window.pageYOffset/2+"deg)";
}
});

Why isn't this div node the child to the container?

Developing an Etch-A-Sketch - I created a 16*16 grid using a 2 for loops - one nested in the other.
const div = document.createElement('div');
div.style.cssText = 'flex: 1';
container.appendChild(div);
// div.addEventListener('mouseover', changeBackground);
for (let j = 0; j < 16; j++){
const div2 = document.createElement('div');
div2.classList.add('square');
div.style.display = 'flex';
div.appendChild(div2);
In this for loop I create a div and assign it to const div then that gets appended as a CHILD node to the container - const container = document.querySelector('.container');. In the inner for loop const div2 = document.createElement('div'); is assigned as a child to const div in the outer loop because div2 are the squares on the grid. So the DOM tree looks like this:
-
At the moment what I want to do is set up an event listener which will fire when the user clicks on a button to give them the option to set a grid of their choice - (i.e. prompt which asks them to enter a number between 2 and 64, and then in similar fashion I want to iterate loop variables i and j - as many times as the user's input suggested and then generate a input * input grid. However, the first thing I need to do is clear the grid of its existing 16 * 16 squares - so that's where I decided to make use of the removeChild() method and remove const div from container and remove const div2 from const div. I am able to successfully call and remove div.removeChild(div2);. However I run into an error - Uncaught DOMException: Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node , when I run container.removeChild(div);. I have already looked up the error somewhat and have seen that you can't remove descendants using the removeChild() method however from the DOM tree above I'm pretty much sure that div is a child to the container element. So my question is: what is the cause of this error and how can I go about fixing it? Does it have something to do with container having a parent of its own - containerBorder?
//sets grid depending on what number the user specifies.
container.removeChild(div);
// let userPrompt = prompt('Generate a grid: Enter a number between 2 and 100: ');
// let input = Number.parseInt(userPrompt, 10);
// container.removeChild(div);
// div.removeChild(div2);
// console.log(typeof input);
// for(let i = 0; i < 16; i++){
// let div = document.createElement('div');
// div.style.cssText = 'border: 1px solid black; flex: 1';
// container.appendChild(div);
// for (let j = 0; j < 16; j++){
// let div2 = document.createElement('div');
// div2.classList.add('square');
// div2.style.display = 'flex';
// div.appendChild(div2);
// }
// }
});
The full code is here - https://codepen.io/safdari/pen/oNXJBGY
container.innerHTML = '' will clear the container and allow me to re-render it

Logical loop confusion javascript [duplicate]

This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 5 years ago.
I have an array of songs, where each song has its index number in the array and the .source property. I wrote a script that creates a table row for each song:
var list = document.getElementsByClassName("preview")[1];
for (y = 0; y < allSongs.length; y ++) {
var row = list.insertRow(-1);
var celeft = row.insertCell(0);
var celright = row.insertCell(1);
celright.innerHTML = allSongs[y].name;
celeft.setAttribute("class", "left");
But now I want to make all those left cells (0) linked to the songs, which I tried to do this way:
x = document.getElementsByClassName("left");
var counter = 0;
while (counter < allSongs.length) {
x[counter+1].addEventListener('click', function() {
document.getElementById("audplayer").setAttribute("src", allSongs[counter].source);
});
counter++;
}
Basically, I made them clickable with the addEventListener property, but the problem is that the addEventListener function is not excecuted with the loop, so the counter value is not saved, and everytime I click one of the table cells, counter has a value of 3 at that moment. I understand where the problem comes from but I cannot solve it. How can this be solved?
More details (if the explanation wasnt clear enough):
I want each of the table cells to perform a different action (play different songs), but they all play the same one - allSongs[3], because counter value is taken when the click event happens, not every time the while loop is executed.
Try replacing your loops with map and _.zip from lodash.
Example pseudocode:
song_and_elem_pairs = _.zip(allSongs, x);
song_and_elem_pairs.map(function (song, elem) {
elem.addEventListener('click', function() {
document.getElementById("audplayer").setAttribute("src",
song.source);
}
});
You can also similarly replace the for (y = 0; y < allSongs.length; y ++) loop with a map.
This is certainly not the only way to do this, but it's how I would approach it and I think it should fix your problem.

Changing classes with setInterval

I've made a 5x5 grid of tiles, and I'd like to create a tile that changes its class every 2 seconds.
Essentially, this tile would be turning, facing Up, Left, Down, Right -- in that particular order.
Right now, I'm putting all elements with a particular class into a nodeList/array.
Then, I iterate through each element, replacing the current color/class with the new one.
This kind of works, but appears to skip certain tiles, giving me wonky performance.
What am I doing wrong here?
function rotateTile(){
var tattleTowerUpArray = document.getElementsByClassName("tattleTowerUp");
var tattleTowerLeftArray = document.getElementsByClassName("tattleTowerLeft");
var tattleTowerDownArray = document.getElementsByClassName("tattleTowerDown");
var tattleTowerRightArray = document.getElementsByClassName("tattleTowerRight");
for(var i=0; i < tattleTowerUpArray.length; i++){
document.getElementById(tattleTowerUpArray.item(i).id).style.borderTopColor = "black";
document.getElementById(tattleTowerUpArray.item(i).id).style.borderLeftColor = "red";
document.getElementById(tattleTowerUpArray.item(i).id).classList.remove("tattleTowerUp");
document.getElementById(tattleTowerUpArray.item(i).id).classList.add("tattleTowerLeft");
}
for(var j=0; j < tattleTowerLeftArray.length; j++){
document.getElementById(tattleTowerLeftArray.item(j).id).style.borderLeftColor = "black";
document.getElementById(tattleTowerLeftArray.item(j).id).style.borderBottomColor = "red";
document.getElementById(tattleTowerLeftArray.item(j).id).classList.remove("tattleTowerLeft");
document.getElementById(tattleTowerLeftArray.item(j).id).classList.add("tattleTowerDown");
}
for(var k=0; k < tattleTowerDownArray.length; k++){
document.getElementById(tattleTowerDownArray.item(k).id).style.borderBottomColor = "black";
document.getElementById(tattleTowerDownArray.item(k).id).style.borderRightColor = "red";
document.getElementById(tattleTowerDownArray.item(k).id).classList.remove("tattleTowerDown");
document.getElementById(tattleTowerDownArray.item(k).id).classList.add("tattleTowerRight");
}
for(var l=0; l < tattleTowerRightArray.length; l++){
document.getElementById(tattleTowerRightArray.item(l).id).style.borderRightColor = "black";
document.getElementById(tattleTowerRightArray.item(l).id).style.borderTopColor = "red";
document.getElementById(tattleTowerRightArray.item(l).id).classList.remove("tattleTowerRight");
document.getElementById(tattleTowerRightArray.item(l).id).classList.add("tattleTowerUp");
}
}
Fixed your code and tried to comment it. You need to add class 'tattleTower' to EVERY tile element for this to work.
Just a couple of points:
1) getElementsByClassName returns a live NodeList. That means it looks for elements every time you access it(like when you access it's length property);
2) You can avoid using 4 loops and use just 1 loop with if statements, if you add a common class to ALL elements. Like in this example you could add a class 'tattleTower'. So each element would have 2 classes. eg: class='tattleTower tattleTowerLeft'.
3) I don't quite understand why you decided to change border styles with js. You could do it in CSS in those classes. You can explicitly define what properties you want transitions to work on if you're worried about it.
4) You don't need to use id to access a particular element within a loop. You can use el[i]. Eg. el[0] will give you the first element of the array.
5) Try to cache as much as possible by using variables if you perform expensive operations.
function rotateTile(){
// set up all variables at the top of the function
// use querySelectorAll for static NodeList, instead of live nodeList
var tattleTowerArray = document.querySelectorAll(".tattleTower"),
el, i, len, elClassList, elStyle;
// use one loop instead of four
// cache array's length for performance (to avoid asking for it on each iteration)
for (i = 0, len = tattleTowerArray.length; i < len; i++){
el = tattleTowerArray[i]; // cache element for performance
elClassList = el.classList; // cache element's classList
elStyle = el.style; // cache element's style object
// use 'if-else if' statements to check for class (you can change it to switch block, but i don't think it'd be best here)
if (elClassList.contains('tattleTowerUp')) {
elStyle.borderTopColor = "black";
elStyle.borderLeftColor = "red";
elClassList.remove("tattleTowerUp");
elClassList.add("tattleTowerLeft");
} else if (elClassList.contains('tattleTowerLeft')) {
elStyle.borderLeftColor = "black";
elStyle.borderBottomColor = "red";
elClassList.remove("tattleTowerLeft");
elClassList.add("tattleTowerDown");
} else if (elClassList.contains('tattleTowerDown')) {
elStyle.borderBottomColor = "black";
elStyle.borderRightColor = "red";
elClassList.remove("tattleTowerDown");
elClassList.add("tattleTowerRight");
} else if (elClassList.contains('tattleTowerRight')) {
elStyle.borderRightColor = "black";
elStyle.borderTopColor = "red";
elClassList.remove("tattleTowerRight");
elClassList.add("tattleTowerUp");
}
}
}

Categories