2

For some reason I need to loop through various elements and change their style when they are clicked. This is the code(https://jsfiddle.net/wd4z1kvs/1/) I am trying:

var myscroll = {};

myscroll.list = document.getElementsByClassName("navbar-right")[0].getElementsByTagName("li");

for( var j=0; j < 5; j++) {
 myscroll.list[j].anchor = document.getElementsByClassName("navbar-right")[0].getElementsByTagName("li")[j].getElementsByTagName("a")[0];
 myscroll.list[j].anchor.addEventListener("click", function() {
  alert(j);
  myscroll.list[1].anchor.innerHTML = "hello" + j;
                myscroll.list[j].anchor.innerHTML = "yellow" + j;  
 });
}
 <div id="navbar" class="navbar-collapse collapse">
            <ul class="nav navbar-nav navbar-right">
              <li class="active"><a href="#">Home</a></li>
              <li><a href="#artists">Artists</a></li>
              <li><a href="#songs">Songs</a></li>
              <li><a href="#beats">Beats</a></li>
              <li><a href="#contact">Contact</a></li>
            </ul>
          </div>

The problem seems to be that the event works with j's present value. It doesn't take the value of the parameter when the code was actually run.

user31782
  • 7,087
  • 14
  • 68
  • 143
  • Possible duplicate of [JavaScript closure inside loops – simple practical example](http://stackoverflow.com/questions/750486/javascript-closure-inside-loops-simple-practical-example) – Andreas May 18 '16 at 06:59
  • Possible duplicate of [Assign click handlers in for loop](http://stackoverflow.com/questions/4091765/assign-click-handlers-in-for-loop) – 4castle May 18 '16 at 07:01
  • Change `var j = 0` to `let j = 0` – Starfish May 18 '16 at 07:03
  • @Patrick2607 That won't fix anything. That will just keep `j` from being defined outside of the `for` loop. – 4castle May 18 '16 at 07:11

3 Answers3

1

Try this:

var myscroll = {};

myscroll.list = document.getElementsByClassName("navbar-right")[0].getElementsByTagName("li");

for( var j=0; j < 5; j++) {

  (function(j) {
  
    /* j variable is just a local copy of the j variable from your loop */

    myscroll.list[j].anchor = document.getElementsByClassName("navbar-right")[0].getElementsByTagName("li")[j].getElementsByTagName("a")[0];
 myscroll.list[j].anchor.addEventListener("click", function() {
 
    alert(j);
 
    myscroll.list[1].anchor.innerHTML = "hello" + j;
                myscroll.list[j].anchor.innerHTML = "yellow" + j;  
 });
    
  }(j));
  

  
  
}
<div id="navbar" class="navbar-collapse collapse">
            <ul class="nav navbar-nav navbar-right">
              <li class="active"><a href="#">Home</a></li>
              <li><a href="#artists">Artists</a></li>
              <li><a href="#songs">Songs</a></li>
              <li><a href="#beats">Beats</a></li>
              <li><a href="#contact">Contact</a></li>
            </ul>
          </div>

As noticed @Andreas there is a closure in a loop.

The events don't remember values, they only keep a link (reference) to the environment where they were created. In this case, the variable j happens to live in the environment where the three events were defined. So, all events, when they need to access the value, reach back to the environment and find the most current value of j. After the loop, the j variable's value is 5. So, all five events point to the same value.

Ali Mamedov
  • 5,116
  • 3
  • 33
  • 47
  • 1
    You should read [How do JavaScript closures work?](http://stackoverflow.com/q/111102/5743988) – 4castle May 18 '16 at 07:10
  • I have updated my answer. Here, instead of just creating an event that use `j`, you pass the `j` variable's current value to immediate function. In this function, `j` becomes the local value `j`, and j has a different value every time. – Ali Mamedov May 18 '16 at 07:10
  • The IIFE (immediately invoked function expression) creates a new closure for each value of j, so when the event handler executes, it will have access to its own copy of j – devnull69 May 18 '16 at 07:11
  • @devnull69, exactly. – Ali Mamedov May 18 '16 at 07:12
1

The simplest solution is to use a forEach, or some other looping callback function:

myscroll.list.forEach(function(item, j) {
    item.anchor = document.getElementsByClassName("navbar-right")[0]
                          .getElementsByTagName("li")[j]
                          .getElementsByTagName("a")[0];
    item.anchor.addEventListener("click", function() {
        alert(j);
        myscroll.list[1].anchor.innerHTML = "hello" + j;
        item.anchor.innerHTML = "yellow" + j;
    });
});
4castle
  • 32,613
  • 11
  • 69
  • 106
0

use this: var myscroll = {};

var list = document.getElementsByClassName("navbar-right")[0].getElementsByTagName("li");
myscroll.list = list;

for (var j = 0; j < 5; j++) {
    var anchor = list[j].getElementsByTagName("a")[0];
    anchor.setAttribute("index",j);
    myscroll.list[j].anchor = anchor;
    anchor.addEventListener("click", function () {
        alert(this.getAttribute("index"));
        this.innerHTML = "hello" + this.getAttribute("index");
        //this.innerHTML = "yellow" + this.getAttribute("index");
        this.style.backgroundColor = "yellow";

    });
}