Search code examples
javascripthtmlonclickwhile-loopgetelementsbytagname

JavaScript: Attach onclick to a link inside "while" loop


I'm having trouble with attaching an onclick to each link object inside a loop, when you click the link, it seems to always return data relevant to the first item in the loop, regardless what was clicked. Where as i need each link clicked to have the relevant href to that link

In the example below, regardless what link was clicked, the console.log would always show "http://example.com/link1/"

HTML Example

  <li>
    <h2><a class="t" href="http://example.com/link1/"><i>Link 1 text</i></a></h2>
    <div class="panel" >Content</div>
  </li>

  <li>
    <h2><a class="t" href="http://example.com/link2/"><i>Link 2 text</i></a></h2>
    <div class="panel" >Content</div>
  </li>

  <li>
    <h2><a class="t" href="http://example.com/link3/"><i>Link 3 text</i></a></h2>
    <div class="panel" >Content</div>
  </li>

</ul>

JavaScript:

(function(){
  var theh2s = document.getElementById("panelList").getElementsByTagName("h2"), 
  i = theh2s.length;

  while (i--) {

      var getTheLinks = theh2s[i].getElementsByTagName("a");

      if (getTheLinks){
        if (getTheLinks[0].href){

          console.log(getTheLinks[0].href);

          getTheLinks[0].onclick = function() {
            console.log(getTheLinks[0].href);
            _gaq.push(['_trackEvent', 'Homepage', 'AB Click - Test B', getTheLinks[0].href]);
          };


        }
      }

  }
})();

Solution

  • The problem is that when a click occured, getTheLinks has already been set to the last h2 in the list. To prevent each loop to override the previous one, you have to use a closure create a new context using this pattern : (function(i){...})(i).

    As Felix Kling mentionned, a closure actually is the source of the problem. The following article could enlighten you about this concept. There is a paragraph concerning this common pitfall you just have encountered : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Closures.

    (function () {
        var theh2s = document.getElementById("panelList").getElementsByTagName("h2"),
            i = theh2s.length;
        while (i--) {
            (function (i) {
                var getTheLinks = theh2s[i].getElementsByTagName("a");
                if (getTheLinks) {
                    if (getTheLinks[0].href) {
                        console.log(getTheLinks[0].href);
                        getTheLinks[0].onclick = function () {
                            console.log(getTheLinks[0].href);
                            _gaq.push(['_trackEvent', 'Homepage', 'AB Click - Test B', getTheLinks[0].href]);
                        };
                    }
                }
            })(i);
        }
    })();
    

    I'm not familiar with JSLint. If you need to be JSLint valid, I guess you'll have to move the function definition outside the loop like this :

    while (i--) {
        f(i)
    }
    function f(i) {
        // same as above
    }