Change global variable from event function - javascript

I am trying to learn Javascript.
I watched some Javascript course on Youtube and am trying to create my first project.
Project should be simple tic-tac-toe game.
Functionality:
First click on box should fill with "X"
Second click on another box should fill with "Y"
Third click on another box should fill with "X" again and so on until all boxes will be filled with character.
there is my codes
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="main">
<table>
<tr>
<td class="b1-1"></td>
<td class="b1-2"></td>
<td class="b1-3"></td>
</tr>
<tr>
<td class="b2-1"></td>
<td class="b2-2"></td>
<td class="b2-3"></td>
</tr>
<tr>
<td class="b3-1"></td>
<td class="b3-2"></td>
<td class="b3-3"></td>
</tr>
</table>
</div>
<script src="script.js" type="text/javascript"></script>
</body>
</html>
CSS
.main {
padding: 100px 0;
width: 360px;
margin: 0 auto;
}
table, tbody {
margin: 0;
padding: 0;
width: 360px;
height: 360px;
}
tr {
width:360px;
height: 120px;
margin: 0;
padding: 0;
}
td {
text-align: center;
width:120px;
height: 120px;
border: 1px solid #333;
margin: 0;
padding: 0;
font-size: 50px;
}
Javascript
var action = document.querySelectorAll('td');
var gameState = 0;
for (var i = 0; i <= action.length - 1; i++) {
getClassName = "." + action[i].classList.value;
if (gameState === 0) {
document.querySelector(getClassName).addEventListener("click", chasviX(i));
} else {
document.querySelector(getClassName).addEventListener("click", chasviO(i));
}
}
function chasviX(cord) {
document.querySelector("." + action[cord].classList.value).addEventListener("click", event => {
document.querySelector("." + action[cord].classList.value).textContent = "X";
gameState = 1;
});
};
function chasviO(cord) {
document.querySelector("." + action[cord].classList.value).addEventListener("click", event => {
document.querySelector("." + action[cord].classList.value).textContent = "O";
gameState = 0;
});
};
There is project link also - https://jsfiddle.net/qwy2k571/
At the moment each box is filling with "X".
I know i didn't understand closures and scope chains exactly but please give me an correct code to understand it by example.
Thanks in advance and best regards.

Since you are using querySelectorAll which will give a collection, you can iterate that and directly add event listener to it. Aslo you are adding multiple eventlistener which is not required
var action = document.querySelectorAll('td');
var gameState = 0;
action.forEach((item) => {
item.addEventListener('click', (e) => {
if (gameState === 0) {
e.target.textContent = "X";
gameState = 1;
} else {
e.target.textContent = "0";
gameState = 0;
}
})
})
.main {
padding: 100px 0;
width: 360px;
margin: 0 auto;
}
table,
tbody {
margin: 0;
padding: 0;
width: 360px;
height: 360px;
}
tr {
width: 360px;
height: 120px;
margin: 0;
padding: 0;
}
td {
text-align: center;
width: 120px;
height: 120px;
border: 1px solid #333;
margin: 0;
padding: 0;
font-size: 50px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="main">
<table>
<tr>
<td class="b1-1"></td>
<td class="b1-2"></td>
<td class="b1-3"></td>
</tr>
<tr>
<td class="b2-1"></td>
<td class="b2-2"></td>
<td class="b2-3"></td>
</tr>
<tr>
<td class="b3-1"></td>
<td class="b3-2"></td>
<td class="b3-3"></td>
</tr>
</table>
</div>

Here I made a working jsfiddle: https://jsfiddle.net/1jnkepLg/1/
I simplified the JS part to:
var gameState = 0; // Holds the current game state
var cells = document.getElementsByTagName("td"); // list of cells of the game board
// attach an event listener at each cell.
for (var i=0;i<cells.length;i++)
{
cells[i].addEventListener("click", chasvi);
}
function chasvi()
{
// in event callbacks "this" refers to the element that triggered the event. In this case, it is the TD element.
this.textContent = gameState ? "X" : "0";
// switch the game state
gameState = !gameState;
}
In this case, one event listener is enough, you can determine what X|0 to draw at the callback function.

In your first loop : for (var i = 0; i <= action.length - 1; i++) { ... } you are adding events listeners to each cells. Since the variable gameState is initialized to 0, the condition if (gameState === 0) { ... } will always be true within that loop.
Instead of checking the status of the game during the initialization, just add an event listener as you did, to each cells.
To make sure you pass the correct parameter to the event, you need to wrap the callback within function() { chasvi(param); }, and the whole body of the loop inside another anonymous function, setting another variable to the coordinate i :
for (var i = 0; i <= action.length - 1; i++) {
(function(){
let coor = i;
let getClassName = "." + action[i].classList.value;
document.querySelector(getClassName).addEventListener("click", function() { chasvi(coor); });
}());
}
Note that I changed the name of the function to chasvi because you can manage the case X or O in that function.
In each of the functions chasviX and chasviO you are again adding an event listener. This is not good, because on each clicks, you'll add one more event.
Before clicking, there's already an event.
After clicking once, there are 2 events. Another click, and there's 3 events, and so on...
I suggest you to change thoses functions to 1 function that handles both cases :
function chasvi(cord)
{
let TextToDisplay;
if (gameState === 0)
{
TextToDisplay = "X";
gameState = 1;
}
else
{
TextToDisplay = "O";
gameState = 0;
}
document.querySelector("." + action[cord].classList.value).textContent = TextToDisplay;
}
Since there's only 2 states for gameState, you can use a boolean value. You can initialize it to var gameState = false; I randomly choose false for the X.
And then, the function can be simplified to :
function chasvi(cord)
{
let TextToDisplay;
if (gameState === false)
{
TextToDisplay = "X";
}
else
{
TextToDisplay = "O";
}
gameState = !gameState; // This swaps the state true/false
document.querySelector("." + action[cord].classList.value).textContent = TextToDisplay;
}
This kind of notation :
let TextToDisplay;
if (gameState === false)
{
TextToDisplay = "X";
}
else
{
TextToDisplay = "O";
}
Can be simplified using ternary expressions :
let TextToDisplay = gameState ? "O" : "X"; // This means if gameState is true, then "O" is returned, else "X"
Final code can looks like this :
var action = document.querySelectorAll('td');
var gameState = false;
for (var i = 0; i <= action.length - 1; i++) {
(function(){
let coor = i;
getClassName = "." + action[i].classList.value;
document.querySelector(getClassName).addEventListener("click", function() { chasvi(coor); });
}());
}
function chasvi(cord) {
let TextToDisplay = gameState ? "O" : "X";
gameState = !gameState;
document.querySelector("." + action[cord].classList.value).textContent = TextToDisplay;
}
.main {
padding: 100px 0;
width: 360px;
margin: 0 auto;
}
table, tbody {
margin: 0;
padding: 0;
width: 360px;
height: 360px;
}
tr {
width:360px;
height: 120px;
margin: 0;
padding: 0;
}
td {
text-align: center;
width:120px;
height: 120px;
border: 1px solid #333;
margin: 0;
padding: 0;
font-size: 50px;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="main">
<table>
<tr>
<td class="b1-1"></td>
<td class="b1-2"></td>
<td class="b1-3"></td>
</tr>
<tr>
<td class="b2-1"></td>
<td class="b2-2"></td>
<td class="b2-3"></td>
</tr>
<tr>
<td class="b3-1"></td>
<td class="b3-2"></td>
<td class="b3-3"></td>
</tr>
</table>
</div>
<script src="script.js" type="text/javascript"></script>
</body>
</html>

The problem with your solution is that the for loop you have written assigns event listeners for setting an 'X' on the board to every single square BEFORE there is even a single click on the board. This means your if logic will never assign an 'O', since all clicks will call chasviX()
You should instead use a forEach() function of an NodeList like your action variable:
var gameState = false;
action.forEach(function(act) {
act.addEventListener("click", function(event) {
// If gameState is false(logical 0), place an 'X' on the board
// Otherwise, place a 'O'
event.target.textContent = gameState ? 'X' : 'O';
// Invert state at the end
gameState = !gameState;
});
})

Your mistake is that you create the events in such a manner that they depend only on the current value of gameState, before any moves were made. You need to check and change the value of gameState inside the event:
var action = document.querySelectorAll('td');
var gameState = 0;
for (var i = 0; i <= action.length - 1; i++) {
action[i].addEventListener("click", function() {
this.textContent = (gameState = (gameState + 1) % 2) ? 'x' : '0';
})
}
https://jsfiddle.net/fby3g12v/

Related

Is there a way to have an add event listener first execute a mousedown, then mouseover, and finally mouseup?

So right now I have a 20 by 20 grid and I want the user to be able to click and select multiple cells in the grid. There is a method I was able to find online but the problem is that mouseover takes over and highlights the cells right when the mouse is over the cells and this is not what I want. I want the user click on a cells then basically drag their mouse and highlight the cells that they want then execute mouseup once they let go.
These are my files.
let graph = document.getElementById("container");
graph.style.display = "flex";
function createGraph() {
let j = 0;
for (let i = 0; i < 20; i++) {
let row = document.createElement("div");
row.id = "row" + i;
row.style.height = "50px";
row.style.width = "50px";
graph.appendChild(row);
let currentRow = document.getElementById("row" + i);
j++;
for (let j = 0; j < 20; j++) {
let cell = document.createElement("div");
cell.classList.add("cells");
///id's are used later in the project
cell.id = "index" + j + i;
cell.style.border = "1px solid black";
cell.style.height = "50px";
cell.style.width = "50px";
currentRow.appendChild(cell);
}
}
}
createGraph();
function main() {
document.querySelectorAll(".cells").forEach(item => {
["mousedown", "mouseover", "mouseup"].forEach(function(e) {
item.addEventListener(e, function() {
item.style.backgroundColor = "red";
})
})
})
}
main();
<!DOCTYPE html>
<html lang="en">
<head>
<title>Document</title>
</head>
<body>
<div id="container"></div>
</body>
</html>
So in the main function I added an even listener to all the cells and I am trying to change their color to red. The problem is that the mouseover event takes over the mousedown which is what I want to happen first. How can I make it so the user is able to first click down on a cell then drag their mouse and keep highlighting cells and once they let go of the mouse the highlighting stops. Is there away to first execute the mousedown, then mouseover and finally the mouseup?
I refactored your code a little. Here is a simple example how you can use toggle state:
let graph = document.getElementById('container');
function createGraph() {
for (let i = 0; i < 20; i++) {
let row = document.createElement('div');
row.id = 'row' + i;
row.className = 'rows';
for (let j = 0; j < 20; j++) {
let cell = document.createElement('div');
cell.className = 'cells';
cell.id = 'index' + j + i;
row.appendChild(cell);
}
graph.appendChild(row);
}
}
createGraph();
function main() {
let canSelect = false;
document.addEventListener('mousedown', () => canSelect = true);
document.addEventListener('mouseup', () => canSelect = false);
document.querySelectorAll('.cells').forEach(item => {
['mousedown', 'mouseover'].forEach(function(e) {
item.addEventListener(e, () => {
if (!canSelect && e !== 'mousedown') return;
item.style.backgroundColor = 'red';
})
})
})
}
main();
#container {
display: flex;
}
.rows, .cells {
width: 50px;
height: 50px;
}
.cells {
border: 1px solid black;
}
<!DOCTYPE html>
<html lang="en">
<head>
<title>Document</title>
</head>
<body>
<div id="container"></div>
</body>
</html>
The trick with things like this is to only add the mouseover event on mousedown to begin with. mouseover is generally an expensive event to have anyways (because it fires a lot), so you only "turn it on" when you want and remove it when you don't.
Also, if you're hooking the same event to multiple elements in the same parent, it is far better to assign the event to the parent and then check the target and act when it is one of the children you want (usually using the .matches() method).
Then, you don't have to worry about mousemove firing first, because it'll always fire second. Just be aware it'll probably fire MANY times per cell, so you need to write your code to handle that.
let targetElements = [];
const parent = document.querySelector('.parent');
const mouseoverHandler = ({ target }) => {
if (!target.matches('.parent span')
|| targetElements.includes(target)) {
return;
}
targetElements.push(target);
};
parent.addEventListener('mousedown', ({ target }) => {
// use whatever selector makes sense for your children
if (!target.matches('.parent span')) return;
// reset the list here in case they mouseup-ed outside of the parent
targetElements = [];
// turn mouseover "on"
parent.addEventListener('mouseover', mouseoverHandler);
targetElements.push(target);
console.log('mouseover on');
});
parent.addEventListener('mouseup', ({ target }) => {
// use whatever selector makes sense for your children
if (!event.target.matches('.parent span')) return;
// turn mouseover "off"
parent.removeEventListener('mouseover', mouseoverHandler);
// do something with them
targetElements.forEach(el => el.classList.toggle('on'));
console.log('mouseover off');
});
.parent {
border: 2px solid #333;
width: 150px;
display: flex;
flex-wrap: wrap;
}
.parent span {
flex: 0 0 50px;
flex-wrap: wrap;
height: 50px;
border: 1px solid #CCC;
margin: -1px;
height: 50px;
display: -block;
}
.parent span:hover {
/* doesn't seem to work in the demo window */
background: #EEC;
cursor: pointer;
}
.parent span.on {
background: #F00;
}
<div class="parent">
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
</div>

How to make a custom confirmation dialog for critical actions

I was creating a dialog to confirm action by user.
We can achieve that by confirm() dialog like this:
function btnChangeBodyColorFunc() {
let text = "You are going to change body color.Are you sure";
if (confirm(text) == true) {
document.body.style.backgroundColor = "red";
console.log("You changed!");
} else {
document.body.style.backgroundColor = "";
console.log("You canceled!");
}
}
<button onclick="btnChangeBodyColorFunc()">Change body color to red</button>
But can it be sort of custom like this:
let iConfirmToGoAhead = ""
let confirmDialog = document.querySelector(".confirmDialog");
function btnChangeBodyColorFunc() {
confirmDialog.style.display = "flex";
if (iConfirmToGoAhead === "true") {
document.body.style.backgroundColor = "red";
console.log("You changed!");
iConfirmToGoAhead = ""
} else if (iConfirmToGoAhead === "false") {
document.body.style.backgroundColor = "";
console.log("You canceled!");
iConfirmToGoAhead = ""
}
}
let confirmDialogBtn = document.querySelectorAll(".confirmDialogContent button");
for (let i = 0; i < confirmDialogBtn.length; i++) {
confirmDialogBtn[i].addEventListener('click', confirmDialogBtnFunc)
}
function confirmDialogBtnFunc() {
confirmDialog.style.display = "none";
iConfirmToGoAhead = this.dataset.confirmationValue;
}
.confirmDialog {
position: fixed;
display: none;
top: 0;
left: 0;
background-color: rgba(0, 0, 0, 0.7);
height: 100vh;
width: 100vw;
}
.confirmDialogContent {
margin: auto;
}
<button onclick="btnChangeBodyColorFunc()">Change body color to red</button>
<div class="confirmDialog">
<div class="confirmDialogContent">
You are going to change body color.Are you sure
<button data-confirmation-value="true">Yes sure</button>
<button data-confirmation-value="false">No don't change</button>
</div>
</div>
But this way it don't wait for iConfirmToGoAhead value to be confirmed and execute the function.
Know this is how JS works that execute the codes which comes first. I tried use of async and await but that also don't work and throw error(might not applied it right).
Can do required functionality by adding conditions to second function but thought a common way for all critical actions through 1 dialog and change according to it.
Is it possible to have a custom confirmation dialog like this or have to use conditions in 2nd function. Any ideas are most welcome
Thanks for help in advance
As you mentioned in this line, any button click action inside the class confirmDialogContent, should trigger an event & call this function confirmDialogBtnFunc. But you have written all functionality inside the function btnChangeBodyColorFunc.
This function will call only when you click the button Change body color to red. If you make any click event inside the confirmation popup, it will call the confirmDialogBtnFunc function as you mentioned inside the for loop
let confirmDialogBtn = document.querySelectorAll(".confirmDialogContent button");
for (let i = 0; i < confirmDialogBtn.length; i++) {
confirmDialogBtn[i].addEventListener('click', confirmDialogBtnFunc)
}
I have rewritten the code, Please look into it.
I hope that would be useful for you :)
let iConfirmToGoAhead = ""
let confirmDialog = document.querySelector(".confirmDialog");
function btnChangeBodyColorFunc() {
confirmDialog.style.display = "flex";
}
let confirmDialogBtn = document.querySelectorAll(".confirmDialogContent button");
for (let i = 0; i < confirmDialogBtn.length; i++) {
confirmDialogBtn[i].addEventListener('click', confirmDialogBtnFunc)
}
function confirmDialogBtnFunc() {
iConfirmToGoAhead = this.dataset.confirmationValue;
confirmDialog.style.display = "flex";
if (iConfirmToGoAhead === "true") {
document.body.style.backgroundColor = "red";
console.log("You changed!");
iConfirmToGoAhead = ""
} else if (iConfirmToGoAhead === "false") {
document.body.style.backgroundColor = "";
console.log("You canceled!");
iConfirmToGoAhead = ""
}
confirmDialog.style.display = "none";
}
.confirmDialog {
position: fixed;
display: none;
top: 0;
left: 0;
background-color: rgba(0, 0, 0, 0.7);
height: 100vh;
width: 100vw;
}
.confirmDialogContent {
margin: auto;
}
<!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>Document</title>
</head>
<body>
<button onclick="btnChangeBodyColorFunc()">Change body color to red</button>
<div class="confirmDialog">
<div class="confirmDialogContent">
You are going to change body color.Are you sure
<button data-confirmation-value="true">Yes sure</button>
<button data-confirmation-value="false">No don't change</button>
</div>
</div>
</body>
</html>

Vanilla JavaScript Checkbox Doesn't Work When Checked

I try to make changes with color saturation of div element every time some checkbox is checked.
allCheckBoxes.forEach(box => {
if(box.checked) {
satura -= 51;
console.log('checked')
}
})
It doesn't work, even can not log into console. According all tutorials everything seems right. I tried successfully almost the same things with checkbox before. So I can not find any mistakes comparing with my own code and any similar questions in Stackoverflow. I use Chrome, but tried in Firefox as well.
I tried to put checkboxes out of tags, doesn't work either.
Please help me to find the solution. Any suggestions will very much appreciated. Thanks in advance.
<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">
<style>
* {
box-sizing: border-box;
}
#schedule td {
width: 70px;
height: 30px;
text-align: center;
border: 1px solid blue;
}
#schedule td:nth-child(2) {
width: 100px;
}
.date {
display: flex;
justify-content: center;
align-items: center;
font-family: Verdana, Geneva, Tahoma, sans-serif;
font-size: 1rem;
padding: 3px;
--color: 255;
background-color: rgb(var(--color), 255, var(--color));
}
.date {
width: 40px;
height: 40px;
border: 1px solid red;
}
</style>
</head>
<body>
<div id="calendar">
<div class="date 19">19</div>
</div>
<div id="today"></div>
<div id="schedule"></div>
<script>
"use strict";
const schedule = document.getElementById('schedule');
let tasks = ['Code', 'Study', 'Tests', 'Sport', 'English'];
let headers = ['Number', 'Task', 'Hours', 'Check'];
let table = '<table>';
let tr = '<tr>';
for (let head of headers) {
tr += `<th>${head}</th>`;
}
tr += '</tr>';
table += tr;
for (let i = 0; i < tasks.length; i++) {
let tr = '<tr>';
tr += `<td>${i+1}</td><td>${tasks[i]}</td><td></td><td></td>`;
tr += '</tr>';
table += tr;
}
table += '</table>'
schedule.innerHTML = table;
document.getElementsByTagName("table")[0].style.borderCollapse = "collapse";
//add checkboxes to last column in a table
let allRows = Array.from(document.getElementsByTagName('tr'));
allRows.forEach(row => {
//remove checkbox from <th>
if (allRows.indexOf(row) === 0) return
let input = document.createElement('input');
input.setAttribute('type', 'checkbox');
row.childNodes[3].appendChild(input);
//add input of hours
let inputHours = document.createElement('input');
inputHours.style.width = '100%';
inputHours.style.height = '100%';
inputHours.style.border = 'none';
inputHours.style.textAlign = 'center';
row.childNodes[2].appendChild(inputHours);
})
//fill calendar in accordance with checked inputs
let dates = document.querySelectorAll('.date');
let today;
setInterval(() => {
const todayDiv = document.getElementById('today');
today = new Date().toUTCString();
todayDiv.innerHTML = today;
}, 1000);
//filter all checkboxes
let allCheckBoxes = [];
let allInputs = document.querySelectorAll('input');
allInputs.forEach(inp => {
if (inp.getAttribute('type') === 'checkbox') {
allCheckBoxes.push(inp);
}
});
console.log(allCheckBoxes)
//bind current date and calendar's day. color saturation
dates.forEach(date => {
let day = today = new Date().getDate();
if (date.classList.contains(day)) {
let satura = 255;
allCheckBoxes.forEach(box => {
if (box.checked) {
satura -= 51;
console.log('checked')
}
})
date.style.setProperty("--color", satura);
}
})
</script>
</body>
</html>
added a button and eventListener. now it works
let colorize = document.querySelector('.colorize')
colorize.addEventListener('click', () => {
dates.forEach(date => {
let day = today = new Date().getDate();
if(date.classList.contains(day)) {
let satura = 255;
allCheckBoxes.forEach(box => {
if(box.checked) {
satura -= 51;
console.log('checked')
}
})
date.style.setProperty("--color", satura);
}
})
})

How to add hover effect upon mouseover to all divs on a page?

I have a 16x16 grid of small squares. I have added a permanent "hover" effect to make the very first box turn red when I put my mouse over it. However, I want to add the same effect to all of the boxes on the page. I can't figure out how to do it - I have tried to add an event listener to the whole page and used target.nodeName and target.NodeValue, but to no avail. I have included the working version where the fix box turns red on mouseover.
var n=16; //take grid column value as you want
const bigContainer = document.querySelector('.bigContainer')
for(var i = 1; i < n; i++) {
bigContainer.innerHTML+='<div class="row">';
for(j = 0; j < n; j++) {
bigContainer.innerHTML+='<div class="smallBox">';
}
}
const smallBox = document.querySelector('.smallBox');
smallBox.addEventListener('mouseover', () => {
smallBox.classList.add('permahover');
});
.smallBox {
border: 1px solid black;
width: 20px;
height: 20px;
display: inline-block;
}
.permahover {
background: red;
}
h1 {
text-align: center;
}
.bigContainer {
text-align: center;
}
<h1>Etch-a-Sketch Assignment - The Odin Project</h1>
<div class="bigContainer">
</div>
The immediate problem you are having is that this is only querying, and subsequently adding an event listener to, one element.
const smallBox = document.querySelector('.smallBox');
smallBox.addEventListener('mouseover', () => {
smallBox.classList.add('permahover');
});
In the above portion of your code, querySelector only returns the first matching element. You may be looking for querySelectorAll here which returns a NodeList of matching elements.
You have two options (perhaps others if you want to restructure your code further). The naive approach is to, in fact, query for all of the cells and add event listeners to each of them.
var n=16; //take grid column value as you want
const bigContainer = document.querySelector('.bigContainer')
for(var i = 1; i < n; i++) {
bigContainer.innerHTML+='<div class="row">';
for(j = 0; j < n; j++) {
bigContainer.innerHTML+='<div class="smallBox">';
}
}
const smallBoxes = document.querySelectorAll('.smallBox');
[...smallBoxes].forEach(smallBox => {
smallBox.addEventListener('mouseover', () => {
smallBox.classList.add('permahover');
});
})
.smallBox {
border: 1px solid black;
width: 20px;
height: 20px;
display: inline-block;
}
.permahover {
background: red;
}
h1 {
text-align: center;
}
.bigContainer {
text-align: center;
}
<h1>Etch-a-Sketch Assignment - The Odin Project</h1>
<div class="bigContainer">
</div>
Another option is to use event delegation as you identified. Here is how you can leverage that. Note: this approach is a bit tricker for an aggressive event like "mouseover" as you may get false positive targets (like the outer container for example).
var n=16; //take grid column value as you want
const bigContainer = document.querySelector('.bigContainer')
for(var i = 1; i < n; i++) {
bigContainer.innerHTML+='<div class="row">';
for(j = 0; j < n; j++) {
bigContainer.innerHTML+='<div class="smallBox">';
}
}
bigContainer.addEventListener('mouseover', e => {
var target = e.target
if (target !== bigContainer) {
target.classList.add('permahover')
}
})
.smallBox {
border: 1px solid black;
width: 20px;
height: 20px;
display: inline-block;
}
.permahover {
background: red;
}
h1 {
text-align: center;
}
.bigContainer {
text-align: center;
}
<h1>Etch-a-Sketch Assignment - The Odin Project</h1>
<div class="bigContainer">
</div>
You need to use a delegation event, because all the small boxes don't exist on the page when the page is loaded (You can figure out in the inspector element that only your first box has the event listener).
So you listen the whole container (because it is always on the page on load)
bigContainer.addEventListener('mouseover', () => {
// Code for checking if we hovered a small div & if yes applying the style
});
...and then do a comparaison with the event.target (which will be the small div hovered)
if (event.target.matches('.smallBox')) {
event.target.classList.add('permahover');
}
var n=16; //take grid column value as you want
const bigContainer = document.querySelector('.bigContainer')
for(var i = 1; i < n; i++) {
bigContainer.innerHTML+='<div class="row">';
for(j = 0; j < n; j++) {
bigContainer.innerHTML+='<div class="smallBox">';
}
}
const smallBox = document.querySelector('.smallBox');
bigContainer.addEventListener('mouseover', () => {
if (event.target.matches('.smallBox')) {
event.target.classList.add('permahover');
}
});
.smallBox {
border: 1px solid black;
width: 20px;
height: 20px;
display: inline-block;
}
.permahover {
background: red;
}
h1 {
text-align: center;
}
.bigContainer {
text-align: center;
}
<h1>Etch-a-Sketch Assignment - The Odin Project</h1>
<div class="bigContainer">
</div>
You can use forEach method to loop through all boxes and add eventListener on each one.
If all of them have .smallBox class you can do it like this:
const smallBoxes = document.querySelectorAll('.smallBox');
smallBoxes.forEach(box => box.addEventListener('mouseover', () => {
smallBox.classList.add('permahover');
}))
I hope it helped you!
let smallBoxes = document.querySelectorAll('.smallBox');
[...smallBoxes].forEach(el => {
el.addEventListener('mouseover', e => e.target.classList.add('permahover'));
});
you should set the eventlistener to your DOM and ask if the trigger element are one of your elements which are that specific class. So you can handle every element with that class.
var n = 16; //take grid column value as you want
const bigContainer = document.querySelector('.bigContainer')
for (var i = 1; i < n; i++) {
bigContainer.innerHTML += '<div class="row">';
for (j = 0; j < n; j++) {
bigContainer.innerHTML += '<div class="smallBox">';
}
}
document.addEventListener('mouseover', function(e) {
if (e.target && e.target.className == 'smallBox') {
var target = e.target;
target.classList.add('permahover');
}
});
Working js fiddle: https://jsfiddle.net/nwukf205/
hope i could help you :)
if you got questions just ask
Have you tried the :hover selector? Not sure if you want specify any dynamic actions here, but it's easy to do basic stuff.
https://www.w3schools.com/cssref/sel_hover.asp
a:hover {
background-color: yellow;
}
I haven't tried your example myself but something similar to this has been answered here:
Hover on element and highlight all elements with the same class

Getting different id for 24 buttons, made with a for loop

I have made 24 buttons with a for loop, and want button from number 1 to 24 to give a message like this.
"you clicked on button 1"
"you clicked on button 2" and so on.
I have been able to split the 3 first buttons so they say "button 1" "2" "3", but that is done by 3 if statements, which means i would need 23-24 ish if statements to get them all to do as I want. That's not a very efficient way to do it.
Is there a good way to get the button id to add +1 after "knapp" every time the loop runs ? something like this element.id = "knapp" + 1; < so the id become knapp1, knapp2, knapp3 as the loop keep running 24 times ?
html:
<!DOCTYPE html>
<meta charset="utf-8">
<html>
<head>
<script src="Assignment06.js"></script>
<style>
h1 {
text-align: center;
}
div {
background-color: forestgreen;
border: solid 1px #000;
display: inline-block;
width: 100px;
height: 100px;
padding: 10px
}
#panel {
width: 610px;
margin: 0 auto;
}
</style>
</head>
<body>
<h1>Assignment06</h1>
<p id = "panel"></p>
</body>
</html>
Javascript:
function dag(){
knapp = window.alert("Du trykket knapp 1");
}
function dag2(){
window.alert("Du trykket knapp 2");
}
function dag3(){
window.alert("Du trykket knapp 3");
}
function init(){
knapper();
}
function knapper(){
for (var antall = 1; antall <= 24; antall++){
if(antall == 1){
var element = document.createElement("div");
element.innerHTML = antall;
element.id = "knapp";
knapp = element.addEventListener("click", dag);
element.type = "div";
var panel = document.getElementById("panel");
panel.appendChild(element);
}
else if (antall == 2){
var element = document.createElement("div");
element.innerHTML = antall;
element.id = "knapp2";
knapp2 = element.addEventListener("click", dag2);
element.type = "div";
var panel = document.getElementById("panel");
panel.appendChild(element);
}
else{
var element = document.createElement("div");
element.innerHTML = antall;
element.id = "knapp3";
knapp3 = element.addEventListener("click", dag3);
element.type = "div";
var panel = document.getElementById("panel");
panel.appendChild(element);
}
}
}
window.onload = init;
You can save the id in the dataset of the <div /> element.
function knapper() {
var panel = document.getElementById("panel");
for (var antall = 1; antall <= 10; antall++) {
var element = document.createElement("div");
element.innerHTML = antall;
element.dataset.id = antall;
element.addEventListener("click", dag);
panel.appendChild(element);
}
}
function dag(evt) {
alert(evt.target.dataset.id);
}
window.onload = knapper;
#panel div {
width: 50px;
height: 50px;
border: solid 1px black;
float: left;
}
<div id="panel"></div>
To directly answer your question without suggestions:
You already have a counter in the for loop (antall). You can just use that variable and concatenate it on the end of the string that you're using as an id.
element.id = "knapp" + antall;

Categories

Resources