2

I am new to javascript. I am trying to add "click" eventlistener to <div> elements in the cycle, which are nested in the parent <div> element (id="cont").

But the problem is, that if I pass argument cont.children[i] in the "ClickOnMe" function, the reference points to the cont.children[cont.length] after completition of the cycle (which does not exist in reality). Here is the problematic piece of code:

cont.children[i].addEventListener("click", function() {ClickOnMe(cont.children[i]);});

Passing constant 0 instead of i variable is the only way, how to make it "work": cont.children[i].addEventListener("click", function() {ClickOnMe(cont.children[0]);});

But it fix only specific code below, what is obviously non-sense in the other case. May somebody tell me, how to correct it?

here is example. Clicking on the text "I am Anna" throws an exception:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Problem</title>
</head>
<script>
var girlFriend;
var cont;

function ClickOnMe(sender){
    alert(sender.innerHTML);
}

function init(){
    cont = document.getElementById("container");
    for (i=0; i < cont.children.length; i++){
        var el = cont.children[i];
        var elid = el.attributes.getNamedItem("id").value;
        switch (elid) {
            case "anna":
                cont.children[i].addEventListener("click", function() {ClickOnMe(cont.children[i]);});  // looses reference
                // cont.children[i].addEventListener("click", function() {ClickOnMe(cont.children[0]);});  // this works
                break;
            case "betty":
                girlFriend = el;
                el.addEventListener("click", function() {ClickOnMe(girlFriend);}); // works well
                break;
        }
        }
    }


</script>

<body onload="init()">
<div id="container">
    <div id="anna">I am Anna </div>
    <div id="betty">I am  Betty </div>
</div>
</body>
</html>
User0123456789
  • 760
  • 2
  • 10
  • 25
lyborko
  • 2,571
  • 3
  • 26
  • 54

2 Answers2

1

If all you need is to access the DOM element the handler is bound to, you don't need to access the loop variable and can completely avoid the closure problem. Just use this or event.currentTarget to access the DOM element:

for (i=0; i < cont.children.length; i++){
  var el = cont.children[i];
  el.addEventListener("click", function() {ClickOnMe(this);});
  // or  el.addEventListener("click", function(event) {ClickOnMe(event.currentTarget);});
}

For a general solution to the problem, see JavaScript closure inside loops – simple practical example .

Felix Kling
  • 795,719
  • 175
  • 1,089
  • 1,143
  • your example really works, but what I do not understand the difference between "this" and "el" itself, because they look equivalent for me. Note: I did not read your link yet... – lyborko Mar 15 '16 at 20:38
  • The value of `el` changes in every iteration. There is only one variable `el` in the scope (JavaScript has function scope only, not block scope). After the loop finished, `el` will point to the last element in the list (`cont.children[con.children.length - 1]`), so every event handler will refer to the last element. On the other hand, `this` has a different value in every function. In event handlers, `this` refers to the element that handler was bound to. See also http://www.quirksmode.org/js/this.html . See http://www.quirksmode.org/js/introevents.html for more info about event handling. – Felix Kling Mar 15 '16 at 20:55
0

Add one function which use immediately-invoked-function-expression(IIFE) so,one can bind value of i immediately.

function addListner(i) {

    (function(i) {
        cont.children[i].addEventListener("click", function() {
            ClickOnMe(cont.children[i]);
        });
    })(i)

}

Switch case should be like this one

case "anna":
        addListner(i)
break;
RIYAJ KHAN
  • 15,032
  • 5
  • 31
  • 53
  • yes, it works nice, thank you a lot. And now I will have to look very close at the closures... :-) – lyborko Mar 15 '16 at 19:00
  • There is no need to use an IIFE in this case. This solution is unnecessarily complex and misguiding. Either have a a dedicated function (`addListener`) **or** use an IIFE inline, but not both. – Felix Kling Mar 15 '16 at 19:53
  • agreed.But i given solution there is already array of htmlCollection – RIYAJ KHAN Mar 15 '16 at 20:00
  • Not sure I understand your comment. What is outside of `addListner` is completely irrelevant. Again, there is no benefit whatsoever to use `(function(i) {... })(i)` here. It just demonstrate a lack of understanding... – Felix Kling Mar 15 '16 at 20:19