-4

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>
Brian Pham
  • 551
  • 9
  • 23
  • 5
    Well the element `left_side` has no children so it is null. Placing it inside a IIFE should make zero difference. – epascarello Dec 26 '15 at 15:42
  • I know it has no children so that it's null. My question is WHY IIFE can solve this, just like putting inside $(document).ready – Brian Pham Dec 26 '15 at 15:44
  • No it should not make a difference... – epascarello Dec 26 '15 at 15:44
  • _"and also see that the problem is fixed"_ Can describe "fixed" , create stacksnippets to demonstrate ? – guest271314 Dec 26 '15 at 15:46
  • But please explain why :(. I understand that document.ready is triggered when the page is ready and everything is loaded. But how I can make sure that everything is loaded when code inside IIFE is run ? – Brian Pham Dec 26 '15 at 15:47
  • [That IIFE doesn't solve the problem.](https://jsfiddle.net/e23d3mkj/9/): _TypeError: theLeftSide.lastChild is null_ – Andy Dec 26 '15 at 15:48
  • The IIFE runs when you are at it and has nothing to do with document ready. Document ready listens for different documnet state. See http://stackoverflow.com/questions/9899372/pure-javascript-equivalent-to-jquerys-ready-how-to-call-a-function-when-the – epascarello Dec 26 '15 at 15:49
  • @gues271314 I mean lastChild is no longer null – Brian Pham Dec 26 '15 at 15:49
  • 2
    @PQThắng, it _is_ still null. – Andy Dec 26 '15 at 15:49
  • @PQThắng — It is still null and it will still error. – Quentin Dec 26 '15 at 15:50
  • Your JSFiddle runs on window load. Click on the cog icon beside the word JavaScript to see that. Your code is actually `window.addEventListener("load", function () { /* YOUR CODE IS BEING RUN HERE */ });` – epascarello Dec 26 '15 at 15:51
  • I updated my code with IIFE – Brian Pham Dec 26 '15 at 15:54
  • 1
    @PQThắng — In the JS Fiddle example you still have it configured to run the JavaScript ONLOAD. The IIFE makes no difference whatsoever. It's the onload wrapper that does. This would be obvious had you provided the complete code **in the question itself** instead of depending on an external service (which added side effects) to host half your question. – Quentin Dec 26 '15 at 15:55
  • 1
    I don't see any difference with or without an IIFE. –  Dec 26 '15 at 15:55
  • @procrastinator sorry. Updated. I'm too hurry – Brian Pham Dec 26 '15 at 15:57
  • You should put your code into a local file of your computer and open it into a browser. As epascarello said, jsfiddle has an extra onload behind the scene. It's not the most reliable place to test your program then. –  Dec 26 '15 at 16:01
  • I posted my full code – Brian Pham Dec 26 '15 at 16:02
  • @PQThắng See stacksnippets https://blog.stackoverflow.com/2014/09/introducing-runnable-javascript-css-and-html-code-snippets/ – guest271314 Dec 26 '15 at 16:02
  • This is a simple game which requires player to click on an extra smile face on the left. and theLeftside.lastChild is that extra face. onclick is to move user to next level @guest271314 – Brian Pham Dec 26 '15 at 16:06
  • @PQThắng Initial Question did not appear to call `generateFace()` before `.onclick` event attached to `.lastChild` ? – guest271314 Dec 26 '15 at 16:07
  • I didn't think it's related to my question so did not include it – Brian Pham Dec 26 '15 at 16:09
  • @PQThắng _"didn't think it's related to my question so did not include it"_ Yes, related to Question; appears to be portion of `js` where `.lastChild` of `theLeftSide`, `theRightSide` defined – guest271314 Dec 26 '15 at 16:21

1 Answers1

2

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>