Creating and Inserting DOM Nodes - Exercise

Contact Us or call 1-877-932-8228
Creating and Inserting DOM Nodes - Exercise

Creating and Inserting DOM Nodes

Duration: 45 to 60 minutes.

In this exercise, you will modify our soccer game so that it adds new balls when the user misses.

  1. Open HTMLDOM/Solutions/soccer-game-multiball.html in your browser and play for a bit. Notice that when you miss the ball, a new one appears. Also notice that when you hit a ball, it gets removed from the field and added to the cage using appendChild().
  2. Open HTMLDOM/Exercises/soccer-game-multiball.html in your editor. Notice we have changed the HTML some:
    1. Added a "msg" div to hold messages (no more alerts).
    2. Added a "cage" div to hold balls that have been removed from the field.
    3. Added a "level" span to show what level the user is on. This is for the challenge.
    4. Removed the "click-count" span. We will show the click-count messages in the "msg" div.
    5. Removed the img. We'll add it using JavaScript when the game starts.
  3. We have also changed the JavaScript some:
    1. Added a msg() function to display messages in our new "msg" div.
    2. Modified the moveBall() function to put a red border around the ball that escapes the field and to use the msg() function to report the end of the game.
    3. Added a removeBall() function, which we'll discuss later.
  4. We're going to have multiple balls and associated timers now, so the first thing you need to do is change the scalar global variable timer to an empty timers array and add an empty balls array.
  5. Also add a global variable clickCount and set it to 0.
  6. When the user missed the ball, we used to just alert "Miss!". Now we want to do much more. Write an addBall() function that does the following:
    1. Create a "ball" img element and append it to the global balls array.
    2. Set a ballNum variable to hold the length of the balls array.
    3. Set the following properties of the new img element:
      • src: "../Images/ball.gif"
      • alt: "Ball " + ballNum
      • title: "Ball " + ballNum
      • className: "ball"
      • style.top: "92px"
      • style.left: "92px"
    4. Append the new "ball" img to the "field" div so the ball appears on the field.
    5. Use our observeEvent() function to attach the hit() function mousedown events on the new ball.
    6. Append an Interval object to the timers array to set the ball moving: timers.push( setInterval(function() { moveBall(ball) },20) );
  7. In the miss() function, remove the alert() and replace it with a call to addBall().
  8. Replace the existing incrementClickCount() function with this one:
    function incrementClickCount() {
    	clickCount++;
    	msg(clickCount + " clicks");
    }
  9. Replace the existing start() function with this one:
    function start() {
    	clickCount=0;
    	removeBalls();
    	addBall();
    	document.getElementById("start").disabled=true;
    	observeEvent(document.getElementById("field"),"click",miss,false);
    }
    1. Notice we call the addBall() function we just wrote.
    2. We call removeBalls() before adding a ball. That's because there might be left over balls from the previous game. You'll need to write that function.
    3. Remember that we put code in the addBall() function to observe events on each new ball we add. Therefore, we don't need to observe events on the ball in the start() function.
  10. Write a removeBalls() function that removes all balls from the "field" div and empties the global balls array.
  11. We are all set for starting the game and handling misses. Now we need to add code to capture hits. In our simple game, there was only one ball. When the user hit it the game ended. But now, the game is more complicated. There can be many balls on the field at once. When the user clicks on one it must be moved into the cage. The game is won only when the last ball on the field is hit.
  12. The first thing you need to do in the hit() function is figure out which ball was hit. In other words, which ball was the target of the mousedown event. Hint: remember the getTarget() function in ClassFiles/lib.js.
  13. You then need to move that ball to the cage. Call the removeBall() function, which is already included in your code, to do so.
    1. The actual removal of the ball to the cage is pretty straightforward:
      var cage=document.getElementById("cage");
      cage.appendChild(ball);
      An element can only be in one place in the DOM, so when we we append it to the "cage" div, it gets removed from the "field" div.
    2. The stopping of the timer is a bit messy. We need to stop the timer that is associated with moving the ball that gets removed, but there is no direct relationship between the ball and the timer. We have stored both balls and timers in arrays, such that the nth timer calls the code that moves the nth ball. That kind of code is precarious at best. We'll learn how to make it better when we get to object-oriented JavaScript. For the time being, we need to find out which ball in the balls array is being removed so we can clear the timer at the same index in the timers array. We use the alt value to do this:
      var timerNum = ball.alt.split(' ')[1]-1;
      clearInterval(timers[timerNum]);
      We have to subtract 1 because JavaScript arrays are base 0. Note that if we didn't clear the timer, the ball would continue to move around in the cage.
    3. Don't worry about the style properties. We'll address that sort of thing when we get to Dynamic HTML.
  14. Next, back in the hit() function, increment the click count and stop the event from propagating to the field.
  15. The last thing to do in the hit() function is to determine if the "field" div has child nodes.
    • If it does not have child nodes, output a message (using msg()) that reads "Hit! Congrats! You got it in " + clickCount + " clicks." And call endGame().
    • If it does have child nodes, output a message (again using msg()) that reads "Congrats! You got one."
  16. Finally, you must modify the endGame() function as follows:
    1. Clear all the timers and empty the timers array.
    2. Stop observing events for all the balls remaining on the field and empty the balls array.

Code Sample:

HTMLDOM/Exercises/soccer-game-multiball.html
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<link rel="stylesheet" type="text/css" href="../Styles/soccer-game.css">
<script type="text/javascript" src="../../lib.js"></script>
<script type="text/javascript">
	var timer = null;
	observeEvent(window,"load",function() {
		observeEvent(document.getElementById("start"),"click",start,false);
	});
	
	function miss() {
		incrementClickCount();
		alert("Miss!");
	}
	
	function hit(e) {
		incrementClickCount();
		alert("Hit! Congrats! You got it in " + document.getElementById("click-count").innerHTML + " clicks.");
		stopPropagation(e);
		clearInterval(timer);
		endGame();
	}
	
	function removeBall(ball) {
		var timerNum = ball.alt.split(' ')[1]-1;
		var cage=document.getElementById("cage");
		clearInterval(timers[timerNum]);
		cage.appendChild(ball);
		ball.style.position="relative";
		ball.style.left="0px";
		ball.style.top="0px";
	}
	
	function start() {
		var ball = document.getElementById("ball");
		document.getElementById("start").disabled=true;
		ball.style.top="92px";
		ball.style.left="92px";	
		document.getElementById("click-count").innerHTML="0";
		timer=setInterval(function() { moveBall(ball) },20);
		
		observeEvent(document.getElementById("field"),"click",miss,false);
		observeEvent(document.getElementById("ball"),"mousedown",hit,false);
	}
	
	function msg(msg) {
		document.getElementById("msg").innerHTML=msg;	
	}
	
	function moveBall(ball) {
		var x=2,y=2;
		var left, top;
		if (Math.floor(Math.random()*2)==0) {
			x = -x;
		}
		if (Math.floor(Math.random()*2)==0) {
			y = -y;
		}
		left = parseInt(ball.style.left);
		top = parseInt(ball.style.top);
		if (top < 0 || top > 184 || left < 0 || left > 184) {
			ball.style.border = "1px solid red";
			msg("Game over!<br>" + ball.alt + " is at pos x:" + left + ", y:" + + top);
			endGame();
		}
		ball.style.left = (left + x) + "px";
		ball.style.top = (top + y) + "px";
	}
	
	function incrementClickCount() {
		var clickCount = document.getElementById("click-count").innerHTML;
		clickCount++;
		document.getElementById("click-count").innerHTML = clickCount;
	}
	
	function endGame() {
		unObserveEvent(document.getElementById("field"),"click",miss,false);
		unObserveEvent(document.getElementById("ball"),"mousedown",hit,false);
		document.getElementById("start").disabled=false;
	}
</script>
<title>Click the Ball</title>
</head>
<body>
	<div id="container">
		<h1>Click the Ball</h1>
		<div>
			<button id="start">Start</button>
			<span id="level">Level 1</span>
		</div>
		<div id="msg">Click the ball to make it disappear. If you miss, another ball will be added.</div>
		<div id="field"></div>
		<div id="cage">
			<h3>CAGE</h3>
		</div>
	</div>
</body>
</html>

Challenge

Just in case this exercise isn't challenging enough, try adding levels so that when the user succeeds at removing the balls from the field, he or she moves to the next level, in which two balls are added at the start and with each miss. Then three balls, then four, etc. Open HTMLDOM/Solutions/soccer-game-multiball-challenge.html in your browser to see how the game should work.

Solution:

HTMLDOM/Solutions/soccer-game-multiball.html
---- C O D E   O M I T T E D ----

<script type="text/javascript">
	var timers = [];
	var balls = [];
	var clickCount=0;
	observeEvent(window,"load",function() {
		observeEvent(document.getElementById("start"),"click",start,false);
	});
	
	function miss() {
		incrementClickCount();
		addBall();
	}
	
	function hit(e) {
		e = e || window.event;
		var ball = getTarget(e);
		var field = ball.parentNode;
		removeBall(ball);
		incrementClickCount();
		stopPropagation(e);
		if (!field.hasChildNodes()) {
			msg("Hit! Congrats! You got it in " + clickCount + " clicks.");
			endGame();
		} else {
			msg("Congrats! You got one.");	
		}
	}

	function removeBall(ball) {
		var timerNum = ball.alt.split(' ')[1]-1;
		var cage=document.getElementById("cage");
		clearInterval(timers[timerNum]);
		cage.appendChild(ball);
		unObserveEvent(ball,"mousedown",hit,false);
		ball.style.position="relative";
		ball.style.left="0px";
		ball.style.top="0px";
	}
	
	function start() {
		clickCount=0;
		removeBalls();
		addBall();
		document.getElementById("start").disabled=true;
		observeEvent(document.getElementById("field"),"click",miss,false);
	}
	
	function addBall() {
		var field=document.getElementById("field");
		var ball=document.createElement("img");
		balls.push(ball);
		var ballNum = balls.length;
		ball.src="../Images/ball.gif";
		ball.alt="Ball " + ballNum;
		ball.title="Ball " + ballNum;
		ball.className="ball";
		ball.style.top="92px";
		ball.style.left="92px";
		field.appendChild(ball);
		observeEvent(ball,"mousedown",hit,false);
		timers.push( setInterval(function() { moveBall(ball) },20) );
	}
	
	function msg(msg) {
		document.getElementById("msg").innerHTML=msg;	
	}
	
	function moveBall(ball) {
		var x=2,y=2;
		var left, top;
		if (Math.floor(Math.random()*2)==0) {
			x = -x;
		}
		if (Math.floor(Math.random()*2)==0) {
			y = -y;
		}
		left = parseInt(ball.style.left);
		top = parseInt(ball.style.top);
		if (top < 0 || top > 184 || left < 0 || left > 184) {
			ball.style.border = "1px solid red";
			msg("Game over!<br>" + ball.alt + " is at pos x:" + left + ", y:" + + top);
			endGame();
		}
		ball.style.left = (left + x) + "px";
		ball.style.top = (top + y) + "px";
	}
	
	function removeBalls() {
		var field=document.getElementById("field");		
		removeAllChildren(field);
		balls=[];
	}
	
	function incrementClickCount() {
		clickCount++;
		msg(clickCount + " clicks");
	}
	
	function endGame() {
		var field=document.getElementById("field");
		var remainingBalls=field.childNodes;
		unObserveEvent(document.getElementById("field"),"click",miss,false);
		for (var t=0; t<timers.length; ++t) {
			clearInterval(timers[t]);
		}
		timers=[];
		for (var b=0; b<remainingBalls.length; b++) {
			unObserveEvent(remainingBalls[b],"mousedown",hit,false);
		}
		balls=[];
		document.getElementById("start").disabled=false;
	}
</script>
<title>Click the Ball</title>
</head>
<body>
	<div id="container">
		<h1>Click the Ball</h1>
		<div>
			<button id="start">Start</button>
			<span id="level">Level 1</span>
		</div>
		<div id="msg">Click the ball to make it disappear. If you miss, another ball will be added.</div>
		<div id="field"></div>
		<div id="cage">
			<h3>CAGE</h3>
		</div>
	</div>
</body>
</html>

Solution:

HTMLDOM/Solutions/soccer-game-multiball-challenge.html
---- C O D E   O M I T T E D ----

<script type="text/javascript">
	var timers = [];
	var balls = [];
	var clickCount=0;
	var level=1;
	observeEvent(window,"load",function() {
		observeEvent(document.getElementById("start"),"click",start,false);
	});
	
	function miss() {
		incrementClickCount();
		for (var i=0; i<level; i++) {
			addBall();
		}
	}
	
	function hit(e) {
		e = e || window.event;
		var ball = getTarget(e);
		var field = ball.parentNode;
		removeBall(ball);
		incrementClickCount();
		stopPropagation(e);
		if (!field.hasChildNodes()) {
			msg("Hit! Congrats! You got it in " + clickCount + " clicks.");
			incrementLevel();
			endGame();
		} else {
			msg("Congrats! You got one.");	
		}
	}
	
	function incrementLevel() {
		level++;
		document.getElementById("level").innerHTML="Level " + level;	
	}
	
	function removeBall(ball) {
		var timerNum = ball.alt.split(' ')[1]-1;
		var cage=document.getElementById("cage");
		clearInterval(timers[timerNum]);
		cage.appendChild(ball);
		unObserveEvent(ball,"mousedown",hit,false);
		ball.style.position="relative";
		ball.style.left="0px";
		ball.style.top="0px";
	}
	
	function start() {
		document.getElementById("level").innerHTML="Level " + level;
		clickCount=0;
		removeBalls();
		for (var i=0; i<level; i++) {
			addBall();
		}
		document.getElementById("start").disabled=true;
		observeEvent(document.getElementById("field"),"click",miss,false);
	}
	
	function addBall() {
		var field=document.getElementById("field");
		var ball=document.createElement("img");
		balls.push(ball);
		var ballNum = balls.length;
		ball.src="../Images/ball.gif";
		ball.alt="Ball " + ballNum;
		ball.title="Ball " + ballNum;
		ball.className="ball";
		ball.style.top="92px";
		ball.style.left="92px";
		field.appendChild(ball);
		observeEvent(ball,"mousedown",hit,false);
		timers.push( setInterval(function() { moveBall(ball) },20) );
	}
	
	function msg(msg) {
		document.getElementById("msg").innerHTML=msg;	
	}
	
	function moveBall(ball) {
		if(ball.parentNode.id=="cage") {
			ball.style.border="1px green solid";
			return;	
		}
		var x=2,y=2;
		var left, top;
		if (Math.floor(Math.random()*2)==0) {
			x = -x;
		}
		if (Math.floor(Math.random()*2)==0) {
			y = -y;
		}
		left = parseInt(ball.style.left);
		top = parseInt(ball.style.top);
		if (top < 0 || top > 184 || left < 0 || left > 184) {
			ball.style.border = "1px solid red";
			msg("Game over!<br>" + ball.alt + " is at pos x:" + left + ", y:" + + top);
			level=1;
			endGame();
		}
		ball.style.left = (left + x) + "px";
		ball.style.top = (top + y) + "px";
		
	}

---- C O D E   O M I T T E D ----

		<div>
			<button id="start">Start</button>
			<span id="level">Level 1</span>
		</div>
---- C O D E   O M I T T E D ----
Next