2

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>
Damon
  • 4,216
  • 2
  • 17
  • 27
  • 1
    @StephenP not OP, but I'd imagine OP wants the hover state to persist , as the html is titled "Etch-a-Sketch Assignment" – Damon Mar 20 '20 at 21:05

6 Answers6

3

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>
Damon
  • 4,216
  • 2
  • 17
  • 27
  • Nice work! I _do_ suggest you call out the difference between `querySelector` and `querySelectorAll` — I also have a question: You and Leonid both use the spread operator `[...smallBoxes].forEach(…)` -- but `smallBoxes.forEach(…)` works just as well, as PatrykBuniX used (and as I was playing with when thinking of writing an answer) -- so **why** use spread? – Stephen P Mar 20 '20 at 21:23
  • 1
    Hey @StephenP , good question. `NodeList.prototype.forEach` may have browser compatibility issues: https://caniuse.com/#feat=mdn-api_nodelist_foreach and require a polyfill. I suppose you could say the same for ES6 spread syntax (though it's more standard to use Babel for that). A better "ES5 compliant" approach would be `[].forEach.cal(nodeList, callback)`. It's more of a habit than a relevant part of the answer though. I doubt it's much of a concern for someone trying to learn the basics (for now). – Damon Mar 20 '20 at 21:34
  • Edited to add more details on `querySelector` vs `querySelectorAll` though, good suggestion. – Damon Mar 20 '20 at 21:39
  • 1
    Ah! I get it... I'm fortunate that I don't have to support "old" browsers or _any_ version of IE since I work on internal web applications (where we can require up-to-date browsers), and support for NodeList.forEach dates back to 2016-2017 so I never had to resort to that. – Stephen P Mar 20 '20 at 21:43
  • @Damon This is it! The second approach is what I was going for - I was almost there in some of my non-functioning attempts, but didn't have quite the right knowledge to complete it. Thank you very much for your thorough explanations! – Carl D'Oleo-Lundgren Mar 20 '20 at 23:27
2

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>
Wax
  • 605
  • 1
  • 5
  • 15
1

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!

PatrykBuniX
  • 152
  • 5
  • I didn't use this as the solution to add the permahover effect, but I did build off of this methodology to create a reset button for the grid. Thank you for your help! – Carl D'Oleo-Lundgren Mar 22 '20 at 15:57
1
let smallBoxes = document.querySelectorAll('.smallBox');
[...smallBoxes].forEach(el => {
    el.addEventListener('mouseover', e => e.target.classList.add('permahover'));
});
Leonid
  • 766
  • 4
  • 8
1

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

Samet Conrad
  • 201
  • 1
  • 5
0

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

  • This will not work, the style will be gone when the div will be not hovered anymore – Wax Mar 20 '20 at 21:30