Search code examples
javascriptiife

Why IIFE can fix "cannot set property 'onclick' of null"


I have a block of code as the following

<body id="main">
<h1>Matching Game</h1>
<p>Click on the extra smile face on the left</p>

<div id="left_side"></div>
<div id="right_side"></div>

<script>    
        var theLeftSide = document.getElementById("left_side");
        var theRightSide = document.getElementById("right_side");

         theLeftSide.lastChild.onclick = function nextLevel(event){            
            event.stopPropagation();
            quan+=5;
            dele();
            generateFace();
            }     

</script>
</body>

This line theLeftSide.lastChild.onclickwill throw an error, saying that "cannot set property 'onclick' of null", because at the time javascript engine scans through it, it hasn't has any child yet. This problem can be solved by putting this code inside $(document).ready, I know.

However, I also tried IIFE, which means that wrapping all those code inside

(function(){
  var theLeftSide = document.getElementById("left_side");
  var theRightSide = document.getElementById("right_side");

     theLeftSide.lastChild.onclick = function nextLevel(event){            
        event.stopPropagation();
        quan+=5;
        dele();
        generateFace();
        }     
})()

and also see that the problem is fixed, but cannot figure out why. Any one can explain me ?

Here's the full original code:

     <!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>part4</title>
<style>
div{
    position:absolute;
    }
#left_side{
    width:500px; height: 500px; border-right:#000 thin inset;
    float: left;
    }
#right_side{
    width:500px; height: 500px;
    float:left; left:500px;
    }
img{
    position: absolute;
    }
body{
    margin:0px;
    }

</style>
<script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.11.3.min.js"></script>
</head>

<body onload="generateFace()" id="main">
<h1>Matching Game</h1>
<p>Click on the extra smile face on the left</p>

<div id="left_side"></div>
<div id="right_side"></div>

<script>
var theLeftSide = document.getElementById("left_side");
var theRightSide = document.getElementById("right_side");
var e = event;
var theBody= document.getElementById("main");

var post_left =0;
var post_top=0;
var quan = 4;
function generateFace(){
    var i =0;
    for(i=0; i<= quan; i++){
        var img = document.createElement("img");
        img.src="smile.png"; post_top=getRandom(); post_left=getRandom();
        img.style.top = post_top + "px";
        img.style.left = post_left +"px";
        theRightSide.appendChild(img);
        var clone = img.cloneNode(true);
        theLeftSide.appendChild(clone);
        }
        //var clone = theRightSide.cloneNode(true);
        //theLeftSide.appendChild(clone);
        var extra = document.createElement("img");
        extra.src="smile.png"; post_top=getRandom(); post_left=getRandom();
        extra.style.top = post_top + "px";
        extra.style.left = post_left +"px";
        theLeftSide.appendChild(extra);
    };

function getRandom(){
    var i = Math.random() * 400 +1;
    return Math.floor(i);
    };
function dele(){
    while(theLeftSide.firstChild != null){
        theLeftSide.removeChild(theLeftSide.firstChild);
        }
    while(theRightSide.firstChild != null){
        theRightSide.removeChild(theRightSide.firstChild);
        }
    };
theBody.onclick = function gameOver() {
    alert("Game Over!");
    dele();
    theBody.onclick = null;
    theLeftSide.lastChild.onclick = null;
};
function nextLevel(event){
    event.stopPropagation();
    quan+=5;
    delse();
    generateFace();
    }
</script>
</body>
</html>

and with IIFE:

<!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>part4</title>
<style>
div{
    position:absolute;
    }
#left_side{
    width:500px; height: 500px; border-right:#000 thin inset;
    float: left;
    }
#right_side{
    width:500px; height: 500px;
    float:left; left:500px;
    }
img{
    position: absolute;
    }
body{
    margin:0px;
    }

</style>
<script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.11.3.min.js"></script>
</head>

<body id="main">
<h1>Matching Game</h1>
<p>Click on the extra smile face on the left</p>

<div id="left_side"></div>
<div id="right_side"></div>

<script>
    (function(){
        var theLeftSide = document.getElementById("left_side");
        var theRightSide = document.getElementById("right_side");
        var e = event;
        var theBody= document.getElementById("main");

        var post_left =0;
        var post_top=0;
        var quan = 4;

        generateFace();

        function generateFace(){
            var i =0;
            for(i=0; i<= quan; i++){
                var img = document.createElement("img");
                img.src="smile.png"; post_top=getRandom(); post_left=getRandom();
                img.style.top = post_top + "px";
                img.style.left = post_left +"px";
                theRightSide.appendChild(img);
                var clone = img.cloneNode(true);
                theLeftSide.appendChild(clone);
                }
                //var clone = theRightSide.cloneNode(true);
                //theLeftSide.appendChild(clone);
                var extra = document.createElement("img");
                extra.src="smile.png"; post_top=getRandom(); post_left=getRandom();
                extra.style.top = post_top + "px";
                extra.style.left = post_left +"px";
                theLeftSide.appendChild(extra);
        };

        function getRandom(){
        var i = Math.random() * 400 +1;
        return Math.floor(i);
        };

        function dele(){
            while(theLeftSide.firstChild != null){
                theLeftSide.removeChild(theLeftSide.firstChild);
                }
            while(theRightSide.firstChild != null){
                theRightSide.removeChild(theRightSide.firstChild);
                }
            };
        theBody.onclick = function gameOver() {
            alert("Game Over!");
            dele();
            //theBody.onclick = undefined;
            //theLeftSide.lastChild.onclick = undefined;
        };

        theLeftSide.lastChild.onclick = function nextLevel(event){            
            event.stopPropagation();
            quan+=5;
            dele();
            generateFace();
            } 
    })();

</script>
</body>
</html>

Solution

  • In the original piece of code, generateFace() is called on page load. In the second piece of code, generateFace() is called directly. There is no relation with the IIFE. Please forget it! :-P Here is a simplified version of what you are doing in each case (open your browser console before running the snippets):

    Original code (throws a TypeError)

    console.log('attempting to initialize onclick');
    document.getElementById('clickable').onclick = function () {
      alert(this.id);
    };
    console.log('onclick initialized successfully');
    
    function gendiv () {
      console.log('gendiv was called');
      document.body.innerHTML = '<div id="clickable">click me</div>';
    }
    <body onload="gendiv()"></body>

    Modified code

    gendiv();
    
    console.log('trying to initialize onclick');
    document.getElementById('clickable').onclick = function () {
      alert(this.id);
    };
    console.log('onclick initialized successfully');
    
    function gendiv () {
      console.log('gendiv was called');
      document.body.innerHTML = '<div id="clickable">click me</div>';
    }
    <body></body>