I am building an etch-a-sketch browser version for the odin project.
There is a prompt message that asks input from the user and creates a grid based on the input.
If the input is 15 that should give a 15x15 grid.
Unfortunately the higher the input the more time it takes for the page to load. Any ideas why?
const container = document.querySelector('#container');
const btn = document.querySelector('#btn');
/*
btn.addEventListener('click', () => {
squares.forEach(square => {
square.style.backgroundColor = 'white';
});
});
*/
btn.addEventListener('click', () => addGrid());
function addGrid() {
let content = document.createElement('div');
let input = prompt("Please enter the number of squares per side (2 - 100)");
while ((input == null || input > 100 || input < 2 || isNaN(input))) {
input = prompt("Please enter the number of squares per side (2 - 100)");
}
for (let i = 0; i <= input * input; i++) {
container.style.cssText = 'grid-template-rows: repeat(' + input + ' , 1fr); grid-template-columns: repeat(' + input + ', 1fr)';
content = document.createElement('div');
content.classList.add('square');
container.appendChild(content);
squares = container.querySelectorAll('.square')
squares.forEach(square => {
square.addEventListener('mouseenter', () => {
square.style.backgroundColor = 'black';
});
});
squares.forEach(square => {
square.addEventListener('mouseleave', () => {
square.style.backgroundColor = 'black';
});
});
}
return content;
}
<button id="btn">Click</button>
<div id="container"></div>
You need to move the event handlers outside the loop.
Also you need to use < instead of <= in the loop
Even better, delegate from container
Here is a working version - you could use calc() in the css to size the individual squares
Using delegated event handling
const container = document.getElementById('container');
const btn = document.getElementById('btn');
const reset = document.getElementById('reset');
const mclick = e => {
const tgt = e.target;
if (tgt.matches(".square")) tgt.classList.toggle("clicked");
}
const mhover = e => {
const tgt = e.target;
if (!tgt.matches(".square")) return; // not a square
tgt.classList.toggle("active",e.type==="mouseover")
}
const resetGrid = () => container.querySelectorAll(".square")
.forEach(sq => {
["clicked","active"]
.forEach(cls => sq.classList.remove(cls))
});
const addGrid = () => {
let content = document.createElement('div');
let input = prompt("Please enter the number of squares per side (2 - 100)");
while ((input == null || input > 100 || input < 2 || isNaN(input))) {
input = prompt("Please enter the number of squares per side (2 - 100)");
}
for (let i = 0; i < input * input; i++) {
container.style.cssText = 'grid-template-rows: repeat(' + input + ' , 1fr); grid-template-columns: repeat(' + input + ', 1fr)';
let content = document.createElement('div');
content.classList.add('square');
content.textContent = i
container.appendChild(content);
}
};
btn.addEventListener('click',addGrid);
container.addEventListener("mouseover",mhover);
container.addEventListener("mouseout",mhover);
container.addEventListener("click",mclick);
reset.addEventListener("click",resetGrid);
#container { display:grid; }
div.square { height: 50px; width: 50px; border:1px solid black;}
div.square.active { background-color: black;}
div.square.clicked { background-color: red;}
<button id="btn">Start</button><button id="reset">Reset</button>
<div id="container"></div>
Related
I have simple chrome extension which adds 1 button to the page and this button has event Listener for click, when clicked it calls function (should get some strings + change it and then input the changed value to the input field) which doesn't work. But when I take the code of the function and paste it into console it works as it should.
Here is my function code:
function priceItems(){
var discount = 90
let skins = []
let nodes = document.getElementsByClassName('className')
for (i = 0; i<nodes.length;i++){
if(!nodes[i].innerText == ''){
skins.push(nodes[i])
}
}
for (let i = 0; i < skins.length; i++) {
let base = skins[i].getElementsByClassName('className2')[0].innerHTML
let editBase = base.substring(0,base.indexOf(' ')).replace(',','.')
let newPrice = (editBase - (editBase * discount/100)).toFixed(2)
var inputs = skins[i].getElementsByTagName('input')
inputs[0].value = newPrice
}
}
Event Listener:
priceItemsBtn.addEventListener('click', function(){
// price the skins
console.log('Pricing items=======')
priceItems()
})
Button:
const createButtonPriceItems = () => {
const button = document.createElement('button')
button.style.fontFamily = "Flama,Roboto,Helvetica Neue,sans-serif;"
button.style.color = "#000000"
button.style.background = "#00"
button.style.margin = "10px"
button.style.padding = "0 20px"
button.style.lineHeight = "42px"
button.style.fontWeight = "bold"
button.style.border = "solid #21252b 3px"
button.style.borderRadius = "10px"
const buttonText = document.createTextNode('text')
button.appendChild(buttonText)
return button
}
i have created an input button which will add more squares to the grid but i need the grid to be of the same size regardless of how many squares i add to it while the squares become smaller to account for themselves inside the grid
const grid =
document.querySelector('.grid');
const blackButton =
document.querySelector('.blackbutton');
blackButton.addEventListener('click',
blkButton);
const clear =
document.querySelector('.clearbutton')
clear.addEventListener('click',
clearGrid);
const eraserButton =
document.querySelector('.eraserbutton')
eraserButton.addEventListener('click',
ersButton)
const rainbowButton =document.querySelector('.rainbowbutton')
rainbowButton.addEventListener('click',
rbwButton)
//DOM handling
function getGrid(size) {
const grid =
document.querySelector('.grid');
const boxs =
grid.querySelectorAll(".box");
boxs.forEach((div) => div.remove());
grid.style.gridTemplateColumns =
`repeat( ${size} , 1fr)`;
grid.style.gridTemplateRows = `repeat(
${size} , 1fr)`;
let amount = size * size
for (let i = 0; i < amount; i++) {
const box =
document.createElement('div');
box.classList.add('box');
grid.appendChild(box);
}
}
getGrid(16)
function changeSize(input){
if (input >= 2 && input <= 100) {
getGrid(input);
}
else {
console.log("Only between 2 to 100 Fam!")
}
}
JS code below catches words from an input as hashtags and turn them to HTML span tag. I just need to put limitation at the amount of words and span tags that are generated by user. For example 10 words. Moreover, by pressing space button the user can delete tags.
const BACKSPACE = 8;
const ENTER = 32;
document.getElementById('tag-input').addEventListener('keydown', adjust);
function adjust(e) {
const val = e.target.value;
if (e.keyCode === BACKSPACE && !val) {
deleteTag();
}
if (e.keyCode === ENTER && val) {
e.target.value = '';
addTag(val);
}
const textLength = textLengthToPx(val);
const inputWidth = e.target.offsetWidth;
const minThreshold = 1;
const maxThreshold = 200;
const delta = inputWidth - textLength;
const shouldGrow = delta < minThreshold;
const shouldShrink = delta > maxThreshold;
if (shouldGrow) {
setStyle(e.target, 'width', '90%');
} else if (shouldShrink) {
e.target.style = '';
}
}
function deleteTag() {
document.querySelectorAll('#tags > span')[document.querySelectorAll('#tags > span').length - 1].remove();
}
function addTag(val) {
const input = document.getElementById('tag-input');
const tag = document.createElement('span');
const tagsContainer = document.getElementById('tags');
tag.className = 'tag';
tag.innerHTML = val;
let counter = 0;
while (counter <= 5) {
tagsContainer.insertBefore(tag, input);
counter++;
}
}
function setStyle(node, rule, value) {
node.style = `${rule}:${value}`;
}
function textLengthToPx(text) {
const span = document.createElement('span');
span.innerHTML = text;
span.className = 'invisible';
return span.offsetWidth;
}
<textarea id="tag-input"></textarea>
Solved the problem by adding a counter.
I'm trying to check if all the input fields are filled in this form, the inputs are created based on the number of players, so I dynamically created them in JavaScript, how can I do that ?
Here is what I tried
const x = localStorage.getItem('playersNum');
for (let i = 0; i < x; i++) {
const newInput = document.createElement("INPUT");
newInput.setAttribute("type", "text");
newInput.setAttribute("class", "form-control");
newInput.setAttribute("id", `player${i}`);
newInput.setAttribute("placeholder", "Player's Name");
const parentDiv = document.getElementById('player-list');
parentDiv.appendChild(newInput);
const input = document.getElementById(`player${i}`);
const btn = document.getElementById('startGame');
btn.addEventListener('click', function() {
if (input === "") {
alert('Please fill in the players names');
}
});
}
You can try the following way:
const parentDiv = document.getElementById('player-list');
for (let i = 0; i < 2; i++) {
const newInput = document.createElement("INPUT");
newInput.setAttribute("type", "text");
newInput.setAttribute("class", "form-control");
newInput.setAttribute("id", `player${i}`);
newInput.setAttribute("placeholder", "Player's Name");
parentDiv.appendChild(newInput);
}
//get all input elements of type text and starting id with player
const input = document.querySelectorAll("[type='text'][id^='player']");
const btn = document.getElementById('startGame');
btn.addEventListener('click', function() {
//reset border style of all input elements
[...input].forEach(el => el.style.border = '');
//get all empty input elements
let empty = [...input].filter(el => el.value.trim() == "");
//check length
if (empty.length) {
//show alert
alert('Please fill in the players names');
//set border style to empty input elements
empty.forEach(el => el.style.border = '1px solid red');
}
});
<button id="startGame">Start Game</button>
<div id="player-list"></div>
You should check if the inputs are empty and attach an event listener to the start game button only after you've created them: this means after the for loop.
As an improvement you could add a required attribute on the inputs and check in CSS if they are invalid (using the :invalid pseudoclass)
const x = 3;
const parentDiv = document.getElementById('player-list');
const btn = document.getElementById('startGame');
let players;
for (let i = 0; i < x; i++) {
const newInput = document.createElement("input");
newInput.setAttribute("type", "text");
newInput.setAttribute("required", "required");
newInput.setAttribute("class", "form-control");
newInput.setAttribute("id", `player${i}`);
newInput.setAttribute("placeholder", "Player's Name");
parentDiv.appendChild(newInput);
}
/*
* Get all the inputs you have just injected
*/
players = [...document.querySelectorAll('.form-control')];
/*
* Check if there are some inputs not filled
*/
btn.addEventListener('click', function() {
if (players.some((p) => p.value === '')) {
alert('Please fill in ALL the players names');
}
else {
alert('Ok');
}
});
input {
display: block;
margin: 1rem;
border: 1px #ccc solid;
padding: .25rem;
}
input:invalid {
outline: 1px #aa0020 solid;
outline-offset: 1px;
}
<div id="player-list"></div>
<button id="startGame">Start Game</button>
How can I remove divs with class name "grid-item" in the grid? How to iterate through them and remove them? I want to use the reset() function to delete every element with class name "grid-item.
const container = document.getElementById("container");
let canvasSize = Number(prompt("Enter amount of squares per side to make the new grid"));
let resetButton = document.createElement("button");
resetButton.innerHTML = "Reset Grid";
document.body.appendChild(resetButton);
function makeRows(_canvasSize) {
const rows = canvasSize
const cols = canvasSize
container.style.setProperty('--grid-rows', rows);
container.style.setProperty('--grid-cols', cols);
for (c = 0; c < (rows * cols); c++) {
let cell = document.createElement("div");
container.appendChild(cell).className = "grid-item";
cell.addEventListener('mouseover',
e => e.target.style.backgroundColor = "black"
)
};
};
resetButton.addEventListener('click', (e) => {
reset();
});
var list= document.getElementsByClassName("events");
function reset() {
container.classList.remove("grid-item");
makeRows(canvasSize)
}
makeRows(canvasSize);
You can use querySelectorAll() to find all elements with the class "grid-item" and then for each of them find their parent node use removeChild() to remove the element, something like:
function reset() {
document
.querySelectorAll(".grid-item")
.forEach((e) => e.parentNode.removeChild(e));
}
const container = document.getElementById("container");
let canvasSize = Number(
prompt("Enter amount of squares per side to make the new grid")
);
let resetButton = document.createElement("button");
resetButton.innerHTML = "Reset Grid";
document.body.appendChild(resetButton);
function makeRows(_canvasSize) {
const rows = canvasSize;
const cols = canvasSize;
container.style.setProperty("--grid-rows", rows);
container.style.setProperty("--grid-cols", cols);
for (let c = 0; c < rows * cols; c++) {
let cell = document.createElement("div");
container.appendChild(cell).className = "grid-item";
cell.addEventListener(
"mouseover",
(e) => (e.target.style.backgroundColor = "black")
);
}
}
resetButton.addEventListener("click", (e) => {
reset();
});
var list = document.getElementsByClassName("events");
function reset() {
document
.querySelectorAll(".grid-item")
.forEach((e) => e.parentNode.removeChild(e));
}
makeRows(canvasSize);
.grid-item {
border: 1px solid black;
width: 10px;
padding: 10px;
margin: 5px
}
<div id="container"></div>