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
Related
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
const divArr = [];
for (let i = 0; i < 5; i++) {
document.getElementById("h").innerHTML += `<div id=${i}>hello</div>`;
divArr.push(document.getElementById(`${i}`));
}
console.log(divArr);
let divArrIndex = 0;
setInterval(() => {
document.getElementById(`${divArrIndex}`).style.backgroundColor = 'green';
if (divArrIndex > 4) {
divArrIndex = 0;
}
divArrIndex += 1;
}, 1000 / 1);
<div id="h">alsdjf;lkajsdf</div>
The code above successfully turns all the divs green.
But, when I use
divArr[divArrIndex].style.backgroundColor = "green"
instead of
document.getElementById(`${divArrIndex}`).style.backgroundColor='green';
I only get the last div green.
Why?
codepen: https://codepen.io/sai-nallani/pen/KKopOXZ?editors=1111
By reassignment to innerHTML, you are destroying and recreating the contents of #h in each iteration of the loop. You create #0, then discard it and create #0 and #1, then discard those and create #0, #1, #2... So the elements you push into the array don't exist any more in your document (though references to them in divArr still keep the garbage collector from destroying them outright).
When you change the colour of divArr[0], you are changing the colour of an element that only exists in memory, but is not in DOM any more. However, #4 is still the original #4, it has not been discarded, since you have performed no further assignments to innerHTML.
One solution is to gather all the divs after you have constructed them all. You can use another loop, but the easiest way would be:
const divArr = Array.from(document.querySelectorAll('#h > div'));
(Depending on what you are doing with it next, you may not need Array.from since NodeList should suffice for many purposes.)
Another solution is to construct elements in their own right, not by changing the parent's innerHTML:
const hEl = document.querySelector('#h');
for (let i = 0; i < 5; i++) {
const divEl = document.createElement('div');
divEl.textContent = 'Hello';
divEl.id = i;
hEl.appendChild(divEl);
divArr.push(divEl);
}
This way, every element is created and added to #h without affecting any other elements already there.
I'm trying to create a function that uses the number input I got from users to create the same amount of division inside of a container division.However, no matter what the number is, it always creates only 1 div. It seems like the for loop inside my function is being avoided.
I've tried to alter the function, checked number input whether it is defined or undefined.
function createGrid(parameter) {
for (i = 0; i < parameter * parameter; i++); {
const div = document.createElement('div');
newDiv = container.appendChild(div);
newDiv.setAttribute('class', 'newDiv');
}
return newDiv;
}
You have semicolon ; after for loop which is essentially an empty statement.
That is the reason the for loop is not working as expected, and rest of your code is just creating one divider.
Remove the semicolon ; to fix the issue.
Additional to Nikhil's answer, here is how I would write it (without using global variables, which is considered to be bad practice in most cases):
function createGrid(parameter) {
let newDiv;
for (let i = 0; i < parameter * parameter; i++) {
newDiv = document.createElement('div');
newDiv.setAttribute('class', 'newDiv');
container.appendChild(newDiv);
}
return newDiv;
}
If you don't need to return the last div added, just remove the let newDiv; line and put the const keyword back into the first line of the for loop. Also remove the return value then.
function createGrid(parameter) {
for (let i = 0; i < parameter * parameter; i++) {
const newDiv = document.createElement('div');
newDiv.setAttribute('class', 'newDiv');
container.appendChild(newDiv);
}
}
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.
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");
}
}
}