franklee
franklee

Reputation: 63

addEventListener using for loop gives me an undefined result

Why does this piece of code give me a result of undefined? I have tested in the web browser. It shows me that li[i] is undefined. And I don't know why and how. I know about closure. And maybe it should pop out the string 'dataToggle' every time I click each li element; But someone could help me with more details about this specific question and what you think is important in understanding this question? Thanks

(function(){
  var innerDiv = document.getElementsByClassName('showDiv')[0];
  //var li = document.querySelectorAll('ul.idiv li'); 
var li = document.getElementsByTagName('li'); 

  for(var i=0,len=li.length; i<len; i++){
       li[i].addEventListener('click',function(e){
             e.preventDefault();
             alert(li[i]);
             var data = li[i].dataset.info;
             showDiv.innerHTML = data;
       },false);
    
  } 
}()); 
<div class="showDiv">Show data: </div>

<div class="outerDiv">
  <ul class="idiv">
     <li data-info="datashow"><a href="">111</a></li>
     <li data-info="dataHide"><a href="">222</a></li>
     <li data-info="dataToggle"><a href="">333</a></li>
  </ul> 
</div>

Upvotes: 1

Views: 1330

Answers (4)

This is because, i is a variable defined outside your event handler function, which is incremented by each iteration. So when you finish the iterations of the for loop, the value of i is equal tolen, which causes li[i] to be undefined. And if you ask me why it's value is not considered during the iteration, it's because your event handler function is only executed when the event occurs (not when you are setting the event handlers by for loop). So you can make a variable inside the function scope which won't be changed by iteration. Better use this from inside the event handler to get the same thing.

for(var i=0,len=li.length; i<len; i++){
   li[i].addEventListener('click',function(e){
         e.preventDefault();
         alert(this);
         var data = this.dataset.info;
         showDiv.innerHTML = data;
   },false);
} 

Understanding the for loop

Why the value of i is equal to len after for loop is finished? Let's have a simpler example to make you understand the situation.

var len = 2;
for(var i=0; i<len; i++; {
    //Do anything here
}
console.log("after for loop: i = " + i);

Lets's go through the iterations.

  1. i = 0, matches the condition i<len, which proceeds with executing the code block and executes i++ after that, which makes i=1;
  2. i = 1 now, matches the condition i<len, which proceeds with executing the code block and executes i++ after that, which makes i=2;
  3. i = 2 now, fails to match the condition i<len and stops the iteration.

So, you have set the i=2 before you go to step 3. So your final console.log after for loop will say, after for loop: i = 2

Upvotes: 3

Bunti
Bunti

Reputation: 1760

Another approach in addition to the answers of other posters is to use a local scope with function. In JavaScript function is the most reliable way of creating a new scope. Taking the above example,

for (var i = 0; i < li.length; i++) {
    (function(i) {
        var j = i;
        li[j].addEventListener('click', function(e) {
        e.preventDefault();
        var data = li[j].dataset.info;
        showDiv.innerHTML = data;
    }, false);
  })(i);
}

I have created a new scope that that will isolate the value of variable i. If you're using ES6 you can use let keyword to do just the same thing. All you have to do is replace var with let. You can also use let in a for loop that will give you a whole new scope that is local to for loop.

Upvotes: 1

brk
brk

Reputation: 50291

You need to refer to the particular li on which you want to attach the event

(function(){
      var innerDiv = document.getElementsByClassName('showDiv')[0];
       var li = document.getElementsByTagName('li'); 
          for(var i=0,len=li.length;i<len;i++){
           var thisLi = li[i];
           thisLi.addEventListener('click',function(e){
                 e.preventDefault();
                 alert(thisLi.innerHTML);
                 var data = thisLi.getAttribute('data-info');
                 innerDiv.innerHTML = data;
           },false);
      } 
    }());

When click will be executed i will have value as 3 (in your case) so li[i]will be different. You can check the console of THIS to check value of i on click event

Upvotes: 0

Tah
Tah

Reputation: 1536

This occurs because of scope. Inside the EventListener, you have an anonymous function, because of this, you are no longer in the scope of li. However, you can refer to it with the this keyword. See the alert that I've replaced in your code.

(function(){
  var innerDiv = document.getElementsByClassName('showDiv')[0];
  //var li = document.querySelectorAll('ul.idiv li'); 
var li = document.getElementsByTagName('li'); 

  for(var i=0,len=li.length; i<len; i++){
       li[i].addEventListener('click',function(e){ // you are now inside a different function block here.
             e.preventDefault();
             alert(this.innerHTML);
             var data = li[i].dataset.info;
             showDiv.innerHTML = data;
       },false);
    
  } 
}()); 
<div class="showDiv">Show data: </div>

<div class="outerDiv">
  <ul class="idiv">
     <li data-info="datashow"><a href="">111</a></li>
     <li data-info="dataHide"><a href="">222</a></li>
     <li data-info="dataToggle"><a href="">333</a></li>
  </ul> 
</div>

Upvotes: 1

Related Questions