Welcome to the Treehouse Community

Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.

Start your free trial

JavaScript

M Glasser
M Glasser
10,868 Points

How to iterate over an array with event handlers?

Trying to make my own hamburger menu and I'm having trouble creating a hover effect on each individual list item without creating separate event handlers for each index item. I know there are better ways to do what I'm trying to achieve (different event handler or simple css maybe) but I'm trying to get this to work as an exercise. I can't seem to iterate over the array returned by getElementsByTagName. When I try to create a loop I get the error message "addEventListener isn't a function". I'm sure this is something obvious, but maybe someone can help me understand why the second example doesn't work?

Works

var listItems = document.getElementsByTagName('li');
var i=0;
listItems[0].addEventListener('mouseover', function(){
    listItems[0].style.backgroundColor="blue";
});

listItems[0].addEventListener('mouseout', function(){
    listItems[0].style.backgroundColor="grey";
});

Doesn't Work

var listItems = document.getElementsByTagName('li');
var i=0;

for (i=0; i<listItems.length; i++){
listItems[i].addEventListener('mouseover', function(){
    listItems[i].style.backgroundColor="blue";
});

listItems[1].addEventListener('mouseout', function(){
    listItems[1].style.backgroundColor="grey";
});
};

3 Answers

Steven Parker
Steven Parker
229,644 Points

To make sure your "i" variable is the same in the handler as it was in the loop, assign it using "let" to give it block scope:

Also, you had the number "1" instead of the letter "i" as the index in the "mouseout" function.

for (let i = 0; i < listItems.length; i++) {              // <- notice: "let" used here
  listItems[i].addEventListener("mouseover", function() {
    listItems[i].style.backgroundColor = "blue";
  });

  listItems[i].addEventListener("mouseout", function() {  // <- "i" instead of "1"
    listItems[i].style.backgroundColor = "grey";
  });
}

But perhaps an even better way to do the same job is with a single delegated handler for each event. One handler then performs the same function for every list item:

var list = document.getElementsByTagName("ul")[0];

list.addEventListener("mouseover", function(e) {  // <- one delegated handler for the entire list
  if (e.target.tagName == "LI")
    e.target.style.backgroundColor = "blue";
});

list.addEventListener("mouseout", function(e) {
  if (e.target.tagName == "LI")
    e.target.style.backgroundColor = "grey";
});
Shayne Laufenberg
Shayne Laufenberg
4,213 Points

So the problem with your code here is that you are trying to access a variable out of the scope of your function. Scopes are very important in functions and since they limit the amount of data you have access to, it will cause the variable to be undefined. You can use the keyword 'this' to directly access the item of the event listener and then apply the styles you wish like I've shown below.

Solution:

var listItems = document.getElementsByTagName('li');
var i = 0;

for (i=0; i < listItems.length; i++){

  listItems[i].addEventListener('mouseover', function(){
      this.style.backgroundColor="blue";
  });

  listItems[i].addEventListener('mouseout', function(){
      this.style.backgroundColor="grey";
  });

};
M Glasser
M Glasser
10,868 Points

Awesome! Thanks for the help! I was actually closer than I thought :) I keep forgetting about scope... still a bit confused by it, and think I better spend more time understanding exactly how "this" works.

M Glasser
M Glasser
10,868 Points

Thanks Steven. This also works! So this addresses the same scoping issue, but in a different way than using 'this.'? I'll also trying working some more with delegated handlers as well. Seem a bit more logical than looping. Appreciate it!

Steven Parker
Steven Parker
229,644 Points

The scoping issue was regarding the index value "i". The use of the event target is unrelated to scope, and gives you the same reference as "this"; but I like it better because it will work with arrow functions and "this" does not.

list.addEventListener("mouseout", function(e) {  // instead of the conventional function syntax
list.addEventListener("mouseout", e => {         // you could use the "arrow" syntax