Applying a mouseover event listener inside of a loop

I have a button (<a class="gmButton"></a>) and a <span id="gmToolTip"></span>, and I’d like the span to display certain text when the relevant link is mouseovered.

The text to display is an array of strings called toolTips.

gmButtons[i].addEventListener("mouseover", function(){
  clearTimeout(t);
  t = setTimeout(function() {
    gmToolTip.textContent = toolTips[i];
  }, 500);
});
gmButtons[i].addEventListener("mouseout", function(){
  gmToolTip.textContent = null;
  clearTimeout(t);
});

When applied to the links one-by-one, the code seems to perform as expected. It’s not working when applied in a loop like this. What have I screwed up?

Here’s the fiddle: http://jsfiddle.net/d5tpqt5h/1/


solution

The issue is that the event listener functions are fired after you have already looped over all of the element. This means that when they are called, i is equal to 9 (and toolTips[9] is undefined because the last element’s index in an array is one less than its length).

One option is to wrap the logic in an IIFE in order to capture the current value of i:

Updated Example

for (var i = 0; i < gmButtons.length; i++) {
  (function(i) {
    gmButtons[i].addEventListener("mouseover", function() {
      clearTimeout(t);
      t = setTimeout(function() {
        gmToolTip.textContent = toolTips[i];
      }, 500);
    });
    gmButtons[i].addEventListener("mouseout", function() {
      gmToolTip.textContent = null;
      clearTimeout(t);
    });
  })(i);
}

Alternately, you could also utilize the .bind() method in order to pass the current value of i to the function:

Updated Example

for (var i = 0; i < gmButtons.length; i++) {
  gmButtons[i].addEventListener("mouseover", function(i) {
      clearTimeout(t);
      t = setTimeout(function() {
      gmToolTip.textContent = toolTips[i];
    }, 500);
  }.bind(this, i));
  gmButtons[i].addEventListener("mouseout", function() {
    gmToolTip.textContent = null;
    clearTimeout(t);
  });
}