I've come accross something quite odd which I can't really figure out how to solve.
I'm working on a project at the university where we are tasked with making a game to meassure the intelligence of the user. Each game is it's own module.
The one I'm working on now basically displays a bunch of flags for a second before turning them over using window.setTimeout
. This works absolutely fine, the transition and everything looks smooth. During the time I'm setting up the board (displaying the flags) I'm also giving each flag an eventListener
, that listens to clicks
, which flips a flag over on its Y-axis.
However the last part of the assignment is to create and display a list of the flags. I'm using a separate function called set_list
which simply adds an ordered list to the innerHTML of the gameboard.
But whenever I add the list the eventListener breaks (ie. it simply wont active when clicking the overturned flags).
Here is a JSFiddle that might help describe the issue
The game itself is started when a button is clicked. So basically there is a button with its own eventListener
which needs to be pressed for the gameboard to initiate:
document.getElementById('start').addEventListener('click', function(){
setup_cards();
create_cards();
set_list();
});
And these are the two functions that seem to be making a mess of things:
function create_cards() {
for (i = 0; i < cards_arr.length; i++) {
if (i == 3 || i == 6) {
flag_divs += "</div><div class='row flag-buffer'>"
}
if (flags[cards_arr[i]].name == 'Colombia' || flags[cards_arr[i]].name == 'Chile' || flags[cards_arr[i]].name == 'Japan') {
flag_divs += "<div id='flag_" + i + "' class='flip-container col-xs-2 col-xs-offset-1'>" +
"<div class='front'>" +
"</div>" +
"<div class='back'>" +
specialflag(flags[cards_arr[i]].name) +
"</div>" +
"</div>";
} else {
flag_divs += "<div id='flag_" + i + "' class='flip-container col-xs-2 col-xs-offset-1'>" +
"<div class='front'>" +
"</div>" +
"<div class='back'>" +
"<div id='" + flags[cards_arr[i]].class + "' class='flag'> </div>" +
"</div>" +
"</div>";
}
}
board.innerHTML = '<h3 class="text-center"> Memorize the flags </h3><div class="container-fluid"> <div class="row">' + flag_divs + '</div></div>';
var myNodeList = document.getElementsByClassName('flip-container');
for (var iterator = 0; iterator < myNodeList.length; iterator++) {
var el = document.getElementById(myNodeList["flag_" + iterator].id);
el.id = iterator;
//console.log(el);
el.style.transform = 'rotateY(180deg)';
el.classList.add('flipper');
el.addEventListener('click', function () {
animate(this);
});
var animate = function (sender) {
sender.style.transform = "rotateY(0deg)";
console.log("animating");
sender.style.transform = "rotateY(180deg)";
sender.classList.add('flipper');
};
}
window.setTimeout(() => {
for (var iterator = 0; iterator < myNodeList.length; iterator++) {
var el = document.getElementById(myNodeList[iterator].id);
el.id = iterator;
el.style.transform = 'rotateY(0deg)';
console.log("I'm done!");
}
}, 1000);
}
function set_list() {
console.log("I'm in");
var flag_list = "<ol>";
for (var i = 0; i < cards_arr.length; i++) {
flag_list += "<li>" + flags[cards_arr[i]].name + "</li>";
}
flag_list += "</ol>";
board.innerHTML += flag_list;
console.log(board.innerHTML);
}
Your problem is here:
What this is doing is asking for the contents of the
board
element as a HTML string, adding some more HTML to the end of that string, then deleting the contents ofboard
and replacing them with the parsed string. The newly created start-button node doesn't have any event listener attached, so nothing happens.Using
element.innerHTML += some_html
is a bad idea for many reasons, this being one of them. It is best to avoid using.innerHTML
whenever you can.document.createElement
andelement.appendChild
which work in terms of the DOM tree instead of text..innerHTML
is to set the initial contents of a newly created element.That said, the simplest fix for your existing code is to change the order in which things happen so that you do the
addEventListener
after the last time you useinnerHTML
. That is, take the event listener code out ofcreate_cards
and put it (in a function or not, as you like) where it will be called afterset_list()
.Also, constructing HTML by string concatenation, like you're doing
is very bad practice because — not in this case but in others — it leads to XSS security bugs. Consider what happens if the string you're inserting contains a quote character.
Using the DOM methods (
createTextNode
,setAttribute
or properties, etc) to construct nodes is a good way to avoid XSS risks because you don't need to make sure text is quoted or sanitized.