Why does the onclick event listener gets removed? - javascript

I am making a small matching game in javascript. Why does the onclick listener stop working after the first successful attempt. How do I rectify this issue.
I am expecting the listener to be attached to the last child element of the div with id "leftSide" even after I refresh all nodes by removing old nodes first.
<!DOCTYPE html>
<html>
<head>
<title>Matching Game</title>
<style>
img {
position: absolute;
}
div {
position: absolute;
width: 500px;
height: 500px;
}
#rightSide {
left: 500px;
border-left: 1px solid black;
}
</style>
</head>
<body>
<h2>Matching Game</h2>
<p>Click on the extra smile on the left</p>
<div id="leftSide"></div>
<div id="rightSide"></div>
<script>
var numberOfFaces = 5;
var theLeftSide = document.getElementById("leftSide");
var theRightSide = document.getElementById("rightSide");
function generateFaces() {
for (var i=0; i<=numberOfFaces; i++) {
new_img = document.createElement("img");
new_img.src="smile.png";
new_img.style.top=String(Math.floor(Math.random()*400))+"px";
new_img.style.left=String(Math.floor(Math.random()*400))+"px";
theLeftSide.appendChild(new_img);
}
// Clone all images to right side
leftSideImages = theLeftSide.cloneNode(true);
leftSideImages.removeChild(leftSideImages.lastChild);
theRightSide.appendChild(leftSideImages);
}
generateFaces()
var theBody = document.getElementsByTagName("body")[0];
theBody.onclick = function gameOver() {
alert("Game Over!");
theBody.onclick = null;
theLeftSide.lastChild.onclick = null;
};
theLeftSide.lastChild.onclick = function nextLevel(event) {
// remove child nodes
while (theLeftSide.firstChild) {
theLeftSide.removeChild(theLeftSide.firstChild);
}
while (theRightSide.firstChild) {
theRightSide.removeChild(theRightSide.firstChild);
}
// new nodes
event.stopPropagation();
numberOfFaces += 5;
generateFaces();
};
</script>
</body>
</html>

The problem is when you attach an event handler to an element, when that element is destroyed, the event is gone too (when the GC cleans). The theLeftSide.lastChild doesn't creates a reference to whatever last child element of theLeftSide is. It creates a reference to the current last child.
You must change the place you add the event handler to inside your generateFaces function, so everytime you change the theLeftSide children, you get again the last child, and add the event.
Take a look at the example below:
<!DOCTYPE html>
<html>
<head>
<title>Matching Game</title>
<style>
img {
position: absolute;
}
div {
position: absolute;
width: 500px;
height: 500px;
}
#rightSide {
left: 500px;
border-left: 1px solid black;
}
</style>
</head>
<body>
<h2>Matching Game</h2>
<p>Click on the extra smile on the left</p>
<div id="leftSide"></div>
<div id="rightSide"></div>
<script>
var numberOfFaces = 5;
var theLeftSide = document.getElementById("leftSide");
var theRightSide = document.getElementById("rightSide");
function generateFaces() {
for (var i = 0; i <= numberOfFaces; i++) {
new_img = document.createElement("img");
new_img.src = "http://www.webgranth.com/wp-content/uploads/2012/03/smile.png";
new_img.style.top = String(Math.floor(Math.random() * 400)) + "px";
new_img.style.left = String(Math.floor(Math.random() * 400)) + "px";
theLeftSide.appendChild(new_img);
}
// Clone all images to right side
leftSideImages = theLeftSide.cloneNode(true);
leftSideImages.removeChild(leftSideImages.lastChild);
theRightSide.appendChild(leftSideImages);
theLeftSide.lastChild.onclick = nextLevel;
}
generateFaces()
var theBody = document.getElementsByTagName("body")[0];
theBody.onclick = function gameOver() {
alert("Game Over!");
theBody.onclick = null;
theLeftSide.lastChild.onclick = null;
};
function nextLevel(event) {
// remove child nodes
while (theLeftSide.firstChild) {
theLeftSide.removeChild(theLeftSide.firstChild);
}
while (theRightSide.firstChild) {
theRightSide.removeChild(theRightSide.firstChild);
}
// new nodes
event.stopPropagation();
numberOfFaces += 5;
generateFaces();
};
</script>
</body>
</html>
Note: I've simpled changed the theLeftSide.lastChild.onclick = function nextLevel(event) {, to a simple function declaration: function nextLevel(event) {, and then added the event handler inside the generateFaces function: theLeftSide.lastChild.onclick = nextLevel;.

you could try something like this:
<!DOCTYPE html>
<html>
<head>
<title>Matching Game</title>
<style>
img {
position: absolute;
}
div {
position: absolute;
width: 500px;
height: 500px;
}
#rightSide {
left: 500px;
border-left: 1px solid black;
}
</style>
</head>
<body>
<h2>Matching Game</h2>
<p>Click on the extra smile on the left</p>
<div id="leftSide"></div>
<div id="rightSide"></div>
<script>
var numberOfFaces = 5;
var theLeftSide = document.getElementById("leftSide");
var theRightSide = document.getElementById("rightSide");
var clickLastChild = function clickLastChild() {
// remove child nodes
theLeftSide.lastChild.onclick = function (event) {
while (theLeftSide.firstChild) {
theLeftSide.removeChild(theLeftSide.firstChild);
}
while (theRightSide.firstChild) {
theRightSide.removeChild(theRightSide.firstChild);
}
// new nodes
event.stopPropagation();
numberOfFaces += 5;
generateFaces();
};
};
function generateFaces() {
for (var i = 0; i <= numberOfFaces; i++) {
new_img = document.createElement("img");
new_img.src = "smile.png";
new_img.style.top = String(Math.floor(Math.random() * 400)) + "px";
new_img.style.left = String(Math.floor(Math.random() * 400)) + "px";
theLeftSide.appendChild(new_img);
}
// Clone all images to right side
leftSideImages = theLeftSide.cloneNode(true);
leftSideImages.removeChild(leftSideImages.lastChild);
theRightSide.appendChild(leftSideImages);
clickLastChild();
}
generateFaces();
var theBody = document.getElementsByTagName("body")[0];
theBody.onclick = function gameOver() {
alert("Game Over!");
theBody.onclick = null;
theLeftSide.lastChild.onclick = null;
};
clickLastChild();
</script>
</body>
</html>

Here i made fully working jsfiddle
You have to call generateFaces() from game over scenario also.
And yes as #epascarello mentioned, you are losing click events as you are setting them to null.

Because you set the onclick to false
theBody.onclick = null;
theLeftSide.lastChild.onclick = null;
when you click the tbody.

Related

Clicking on an image not working as intended

I made a game where you need to click the image on the left side of the screen that you do not see on the opposite side to get to the next level. For some reason, when you click any image on the left side you still go to the next level. When you click the wrong image it should say game over. Right now it only does that when the whitespace is clicked. Any tips?
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Matching Game</title>
<style>
img {
position: absolute;
}
div {
position: absolute;
width: 500px;
height: 500px;
}
#rightSide {
left: 500px;
border-left: 1px solid;
}
</style>
</head>
<body onload="generateFaces()">
<h1>Matching Game</h1>
<p>Click on the extra smiling face on the left.</p>
<div id="leftSide"></div>
<div id="rightSide"></div>
<script >
let numberOfFaces = 5;
const theLeftSide = document.querySelector("#leftSide");
const theRightSide = document.querySelector("#rightSide");
function generateFaces() {
for (let i = 0; i < numberOfFaces; i++) {
let face = document.createElement("img");
face.src = 'images/smile.png';
const randomTop = Math.floor(Math.random() * 400) + 1;
const randomLeft = Math.floor(Math.random() * 400) + 1;
face.style.top = randomTop + 'px';
face.style.left = randomLeft + 'px';
theLeftSide.appendChild(face);
theLeftSide.lastChild.addEventListener('click', nextLevel);
document.body.addEventListener('click', gameOver);
}
const leftSideImages = theLeftSide.cloneNode(true);
leftSideImages.removeChild(leftSideImages.lastChild);
theRightSide.appendChild(leftSideImages);
function nextLevel(event) {
event.stopPropagation();
numberOfFaces += 5;
while (theLeftSide.firstChild) {
theLeftSide.removeChild(theLeftSide.firstChild);
}
while (theRightSide.firstChild) {
theRightSide.removeChild(theRightSide.firstChild);
}
generateFaces();
}
function gameOver() {
alert('Game Over!');
document.body.removeEventListener('click', gameOver);
theLeftSide.lastChild.removeEventListener('click', nextLevel);
}
}
</script>
</body>
</html>
notice
for (let i = 0; i < numberOfFaces; i++) {
let face = document.createElement("img");
...
theLeftSide.appendChild(face);
// you do this in every loop
theLeftSide.lastChild.addEventListener('click', nextLevel);
}
put the theLeftSide.lastChild.addEventListener('click', nextLevel); outside loop for it to work

Dom html javascript about onclick

I want to generate five image imgs first in left side, then remove the lastchild of it and copy the rest to the right side. If user clicks the extra img in left side, then clear all and generate more 5(10) img in left and copy 9 to the right side, and so on. If user clicks the wrong place, alert ("game over").Here is my code:
<html>
<head>
<style>
img{
position: absolute;
}
div{
position: absolute;
width: 500px;
height: 500px;
}
#rightSide { left: 500px;
border-left: 1px solid black;
}
</style>
<script>
var numberOfFaces = 5;
function generateFaces(){
var theLeftSide = document.getElementById("leftSide");
for (var i = 0; i < numberOfFaces; i++){
var img = document.createElement("img");
img.src = "smile.png";
var randomTop = Math.random() * 400;
var randomLeft = Math.random() * 400;
img.style.top = randomTop + "px";
img.style.left = randomLeft + "px";
theLeftSide.appendChild(img);
}
var theRightSide = document.getElementById("rightSide");
leftSideImages = theLeftSide.cloneNode(true);
leftSideImages.removeChild(leftSideImages.lastChild);
document.getElementById("rightSide").appendChild(leftSideImages);
var theBody = document.getElementByTagName("body")[0];
theLeftSide.lastChild.onclick = function nextLevel(event){
event.stopPropagation();
while (theBody.firstChild){
theBody.removeChild(theBody.firstChild);
}
numberOfFaces += 5;
generateFaces();
}
theBody.onclick = function gameover(){
alert("Game Over");
thBody.onclick = null;
theLeftSide.lastChild.onclick = null;
}
}
window.onload = generateFaces;
</script>
</head>
<body>
<h1>Matching Game</h1>
<p>click on the extra smiling face on the left</p>
<div id = "leftSide">
</div>
<div id = "rightSide">
</div>
</body>
</html>
I could generate and clone at first time, but when I click, nothing happens, so what is the problem?
This may be a typing error.
var theBody = document.getElementsByTagName("body")[0];
your code: var theBody = document.getElementByTagName("body")[0];
I suggest to use a good editor for html. I fixed the error with Jetbrain phpStorm but better ones may exist.
Bellow is a modified version that worked for me. Notice that in this version I added the events on all children instead of the whole document body. I don't know but for me it feels more accurate not to end the game on a user clicks on a white space for example.
<html>
<head>
<style>
img{
position: absolute;
}
div{
position: absolute;
width: 500px;
height: 500px;
}
#rightSide { left: 500px;
border-left: 1px solid black;
}
</style>
<script>
var numberOfFaces = 5;
var theBody;
var theLeftSide;
var theRightSide;
function generateFaces(){
theBody = document.getElementsByTagName("body")[0];
theLeftSide = document.getElementById("leftSide");
theRightSide = document.getElementById("rightSide");
for (var i = 0; i < numberOfFaces; i++){
var img = document.createElement("img");
img.src = "smile.png";
var randomTop = Math.random() * 400;
var randomLeft = Math.random() * 400;
img.style.top = randomTop + "px";
img.style.left = randomLeft + "px";
if(theLeftSide != null)
theLeftSide.appendChild(img);
else
{
alert("Game Over");
return;
}
}
var leftSideImages = theLeftSide.cloneNode(true);
leftSideImages.removeChild(leftSideImages.lastChild);
document.getElementById("rightSide").appendChild(leftSideImages);
addEvents();
}
function addEvents(){
for(var i=0; i < theLeftSide.childNodes.length; i++){
theLeftSide.childNodes[i].onclick = nextLevel;
}
}
function removeEvents(){
for(var i=0; i < theLeftSide.childNodes.length; i++){
theLeftSide.childNodes[i].onclick = null;
}
}
function nextLevel(event){
if(this == theLeftSide.lastChild){
event.stopPropagation();
theLeftSide.innerHTML = "";
theRightSide.innerHTML = "";
numberOfFaces += 5;
generateFaces();
}
else
{
alert("Game Over");
removeEvents();
}
}
window.onload = generateFaces;
</script>
</head>
<body>
<h1>Matching Game</h1>
<p>click on the extra smiling face on the left</p>
<div id = "leftSide">
</div>
<div id = "rightSide">
</div>
</body>
</html>

How can I generate new set of smiling faces at random position after user click on extra smiling face?

I have been trying to get the smiley faces to generate in new random position by putting the function delete_all_children() at appropriate locations, but everytime, it won't run properly. Instead, if I ignore the randomized location after finding the extra picture, the game runs properly but it is a build up from existing smiley faces, which makes the game easier.
I want the new faces to generate in random location.
Please help!
<DOCTYPE! html>
<html>
<head>
<title>Matching Game - Smiling :)</title>
<style>
div, img {position: absolute;}
div {width: 500px; height: 500px;}
#rightSide {left: 500px; border-left: 1px solid black}
</style>
<script type="text/javascript">
var numberOfFace = 5;
var count = 0;
var theBody = document.getElementsByTagName("body")[0];
function generateFaces() {
var theLeftSide = document.getElementById('leftSide');
var theRightSide = document.getElementById('rightSide');
while (count < numberOfFace) {
var top_random = (Math.random() * 400);
var left_random = (Math.random() * 400);
var smiling_face = document.createElement("img");
smiling_face.src = "http://home.cse.ust.hk/~rossiter/mooc/matching_game/smile.png";
smiling_face.style.top = Math.floor(top_random) + "px";
smiling_face.style.left = Math.floor(left_random) + "px";
theLeftSide.appendChild(smiling_face);
count ++;
};
leftSideImages = theLeftSide.cloneNode(true);
leftSideImages.removeChild(leftSideImages.lastChild);
theRightSide.appendChild(leftSideImages);
theLeftSide.lastChild.onclick = function nextLevel(event) {
event.stopPropagation();
numberOfFace += 5;
generateFaces();
};
theBody.onclick = function gameOver() {
alert("Game Over!");
theBody.onclick = null;
theLeftSide.lastChild.onclick = null;
};
function delete_all_children() {
var theNode = document.getElementById("theBody");
while (theNode.firstChild) {
theNode.removeChild(theNode.firstChild);
};
};
};
</script>
</head>
<body onload="generateFaces()">
<h1>Matching Game</h1>
<p>Click on the extra smiling face on the left!</p>
<div id="leftSide"></div>
<div id="rightSide"></div>
</body>
</html>

Why do the elements not appear in random places but they are staying aligned on top of the div?

The code is supposed to generate 5 elements one by one so that when the page is downloaded all we`ll see 5 elements (newFace) in random places.
This code generates all 5 elements but they are staying aligned one by one instead of appearing in random position as wished
function generate_faces() {
var theLeftSide = document.getElementById("leftSide");
var number_of_faces = 0;
while (number_of_faces < 5){
var top_position = Math.floor(Math.random()*300);
var left_position = Math.floor(Math.random()*300);
newFace = document.createElement("img");
newFace.setAttribute(
"src",
"http://home.cse.ust.hk/~rossiter/mooc/matching_game/smile.png"
);
newFace.style.top = top_position+"px";
newFace.style.left = left_position+"px";
theLeftSide.appendChild(newFace);
number_of_faces = number_of_faces + 1;
}
}
Try this.
<!DOCTYPE html>
<html>
<head></head>
<style>
html, body, leftSide {
width: 100%;
height: 100%;
position: relative;
}
#leftSide {
position: relative;
}
#leftSide img {
position: absolute;
}
</style>
<body onclick="generate_faces()">
<div id="leftSide">
</div>
</body>
</html>
<script>
function generate_faces() {
var theLeftSide = document.getElementById("leftSide");
var number_of_faces = 0;
while (number_of_faces < 5){
var top_position = Math.floor(Math.random()*300);
var left_position = Math.floor(Math.random()*300);
newFace = document.createElement("img");
newFace.setAttribute(
"src",
"http://home.cse.ust.hk/~rossiter/mooc/matching_game/smile.png"
);
newFace.style.top = top_position+"px";
newFace.style.left = left_position+"px";
theLeftSide.appendChild(newFace);
number_of_faces = number_of_faces + 1;
}
}
</script>

Is this a browser issue with HTML5 and javascript?

I'm doing html code in Atom IDE, but i don't get the desired result on the browser, however, when i try with the Atom's HTML preview, i can see the result i want to get.
Here are the screenshots of both the browser and the Atom's preview, please take a look on them:
Atom's HTML preview
Firefox
I've also tried to run the code on Chrome and Internet Explorer, and i get the same result.
Here is my code also:
<!DOCTYPE html>
<html>
<head>
<meta name="author" content="Carlos Cordova">
<meta charset="utf-8">
<title>Assessment 3 part 4 (Finishing the Game)</title>
<style>
img{
position: absolute;
}
div{
position: absolute;
width: 500px;
height: 500px;
}
#rightSide{
left: 500px;
border-left: 1px solid black;
}
</style>
<script>
var numberOfFaces = 5;
function generateFaces(){
for (var i = 0; i < numberOfFaces; i++) {
var newImage = document.createElement("img");
newImage.src = "http://home.cse.ust.hk/~rossiter/mooc/matching_game/smile.png";
newImage.style.top = Math.floor(Math.random()*400);
newImage.style.left = Math.floor(Math.random()*400);
theLeftSide = document.getElementById("leftSide");
theLeftSide.appendChild(newImage);
theRightSide = document.getElementById("rightSide");
leftSideImages = theLeftSide.cloneNode(true);
leftSideImages.removeChild(leftSideImages.lastChild);
theRightSide.appendChild(leftSideImages);
theBody = document.getElementsByTagName("body")[0];
theLeftSide = document.getElementById("leftSide");
theLeftSide.lastChild.onclick = function nextLevel(event){
event.stopPropagation();
numberOfFaces += 5;
deleteAllChildren();
generateFaces();
};
theBody.onclick = function gameOver(){
alert("Sorry, your game is over!");
theBody.onclick = null;
theLeftSide.lastChild.onclick = null;
};
function deleteAllChildren(){
theLeftSide = document.getElementById("leftSide");
while(theLeftSide.firstChild){
theLeftSide.removeChild(theLeftSide.firstChild);
}
theRightSide = document.getElementById("rightSide");
while(theRightSide.firstChild){
theRightSide.removeChild(theRightSide.firstChild);
}
}
}
/*console.log(theLeftSide.childNodes[0]);
console.log(theLeftSide.childNodes[1]);
console.log(theLeftSide.childNodes[2]);
console.log(theLeftSide.childNodes[3]);
console.log(theLeftSide.childNodes[4]);
*/
}
</script>
</head>
<body onload="generateFaces()">
<h1>Matching Game</h1>
<p>Click on the extra smiling face on the left.</p>
<div id="leftSide"></div>
<div id="rightSide"></div>
</body>
</html>
I will be very grateful to help me solve my problem.
The style attribute needs to be a string.
Example:
newImage.style.top = Math.floor(Math.random()*400)+"px";
newImage.style.left = Math.floor(Math.random()*400)+"px";
On the other side, you're setting the same id for different HTML elements.
This will result in in very unexpected behavior. If you need to reuse markers then make use of classes instead.
The problem with your code is that the img element needs the position by pixles:
newImage.style.top = Math.floor(Math.random() * 400) + "px";
newImage.style.left = Math.floor(Math.random() * 400) + "px";
See here: https://jsfiddle.net/wj4mx1dn/
Add px to your image position.
newImage.style.top = Math.floor(Math.random()*400) + 'px';
newImage.style.left = Math.floor(Math.random()*400) + 'px';
full code is here:
<script type="text/javascript">
'use strict';
var numberOfFaces = 5;
function generateFaces() {
function deleteAllChildren() {
theLeftSide = document.getElementById("leftSide");
while (theLeftSide.firstChild) {
theLeftSide.removeChild(theLeftSide.firstChild);
}
theRightSide = document.getElementById("rightSide");
while (theRightSide.firstChild) {
theRightSide.removeChild(theRightSide.firstChild);
}
}
var theBody = document.getElementsByTagName("body")[0];
var theLeftSide = document.getElementById("leftSide");
var theRightSide = document.getElementById("rightSide");
for (var i = 0; i < numberOfFaces; i++) {
var newImage = document.createElement("img");
theLeftSide.appendChild(newImage);
newImage.style.top = Math.floor(Math.random() * 400) + 'px';
newImage.style.left = Math.floor(Math.random() * 400) + 'px';
//theLeftSide = document.getElementById("leftSide");
newImage.src = "http://home.cse.ust.hk/~rossiter/mooc/matching_game/smile.png";
var leftSideImages = theLeftSide.cloneNode(true);
leftSideImages.removeChild(leftSideImages.lastChild);
theRightSide.appendChild(leftSideImages);
}
theLeftSide.lastChild.onclick = function nextLevel(event) {
event.stopPropagation();
numberOfFaces += 5;
deleteAllChildren();
generateFaces();
};
theBody.onclick = function gameOver() {
alert("Sorry, your game is over!");
theBody.onclick = null;
theLeftSide.lastChild.onclick = null;
};
/*console.log(theLeftSide.childNodes[0]);
console.log(theLeftSide.childNodes[1]);
console.log(theLeftSide.childNodes[2]);
console.log(theLeftSide.childNodes[3]);
console.log(theLeftSide.childNodes[4]);
*/
}
</script>

Categories