0
votes

So I am trying to flip a card when it is being clicked. I did a console.log(card[i]) just before addEventListener and it works fine but error appeared on the console.log after that addEventListener.

The error I got is: Uncaught TypeError: Cannot read property 'style' of undefined

These are my code

var card = document.querySelectorAll('.card');
var cardInner = document.querySelectorAll('.card-inner');
var cardBack = document.querySelectorAll('.card-back');


for (var i=0; i < card.length; i++) {
    console.log(card[i]);      // works fine
    // when a card is clicked
    card[i].addEventListener('click', function(){
        // flip THAT card
        console.log(card[i]);    // undefined value 
        card[i].style.perspective = "500px";
        cardInner[i].style.transform = "rotateY(180deg)";
        cardInner[i].style.transition = "all 0.5s"; 
        cardBack[i].style.transform = "rotateY(180deg)";

    });
}
<!DOCTYPE html>
<html lang="en">
<head>
    
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Memory Game</title>
    <link rel="stylesheet" href="style.css">

</head>
<body>

    <section class="box">

        <div class="card">
            <div class="card-inner">
                <div class="card-front"></div>
                <div class="card-back"></div>
            </div>
        </div>
        <div class="card">
            <div class="card-inner">
                <div class="card-front"></div>
                <div class="card-back"></div>
            </div>
        </div>


        
        
        

    </section>
    
    <script src="app.js"></script>
</body>
</html>
2
Do you have this function wrapped in DOMContentLoaded listener? (window.onload) It might be because your divs are not created when you try to assign them listeners and styles. DOMContentLoaded listener is fired after all content is created.Fide

2 Answers

1
votes

The problem here is that you are creating a closure, and you are closing over the wrong variable.

Each of the inner functions you are creating will have 'closed' over the local variable i. This means that this local variable will be kept alive after the loop finishes and the inner functions will be able to access it. However, it is important to note that the inner functions have access to the variable i, not the value i had when the function was created. The on-click event listeners won't fire until after the loop has finished, but once the loop finishes, i will have value card.length, and of course card[i] will be undefined because i points to an element off the end of your card array.

What you need to do instead is to 'close' over each value of i separately. One way to do this is to create another function and pass the value of i to this function:

function addEventListenerToCard(index) {
    card.addEventListener('click', function(){
        // flip THAT card
        console.log(card[index]);
        card.style.perspective = "500px";
        cardInner[index].style.transform = "rotateY(180deg)";
        cardInner[index].style.transition = "all 0.5s"; 
        cardBack[index].style.transform = "rotateY(180deg)";

    });
}

for (var i=0; i < card.length; i++) {
    console.log(card[i]);      // works fine
    // when a card is clicked
    addEventListenerToCard(i);
}

In this case, each call to addEventListenerToCard creates a new scope for the parameter index, and this parameter doesn't get changed, so each event listener keeps track of which card it is associated to.

You could also try using Array.forEach instead of a loop:

card.forEach(function (cardElement, index) {
    console.log(card[index]);      // can also write "console.log(cardElement);" here
    // when a card is clicked
    card[index].addEventListener('click', function(){
        // flip THAT card
        console.log(card[index]);    // should no longer be an undefined value 
        card[index].style.perspective = "500px";
        cardInner[index].style.transform = "rotateY(180deg)";
        cardInner[index].style.transition = "all 0.5s"; 
        cardBack[index].style.transform = "rotateY(180deg)";
    });
});
0
votes

Luke answered perfectly an alternative solution is use event.target in your addEventListener

var card = document.querySelectorAll('.card');    

for (var i=0; i < card.length; i++) {
       // console.log(card[i]);      // works fine
    // when a card is clicked
    card[i].addEventListener('click', function(event){
        var elem=event.currentTarget;
        var cardInner = elem.querySelector('.card-inner');
        var cardBack = elem.querySelector('.card-back');
        

        // flip THAT card
        elem.style.perspective = "500px";
        cardInner.style.transform = "rotateY(180deg)";
        cardInner.style.transition = "all 0.5s"; 
        cardBack.style.transform = "rotateY(180deg)";

        
    });
}
.card{
width:100px;
height:100px;
background-color:red;
margin:5px;
}

.card-inner{
  width:90px;
  height:90px;
  background-color:yellow;
}

.card-front{
margin:5px;
  width:50%;
  height:50%;
  background-color:green;
}

.card-back{
margin:5px;
  width:50%;
  height:50%;
  background-color:orange;
}
<!DOCTYPE html>
<html lang="en">
<head>
    
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Memory Game</title>
    <link rel="stylesheet" href="style.css">

</head>
<body>

    <section class="box">

        <div class="card">
            <div class="card-inner">
                <div class="card-front"></div>
                <div class="card-back"></div>
            </div>
        </div>
        <div class="card">
            <div class="card-inner">
                <div class="card-front"></div>
                <div class="card-back"></div>
            </div>
        </div>
        

    </section>

</body>
</html>

Hope this helps.