Why event listeners execute the following code?

Viewed 26

I built this code as part of udemy AJAX course. I'd appreciate your takeaway about why my event listeners execute the console.log code before they get clicked, and the console logs the correct answer 4 times (named a) as part of ansChecker function.

<!DOCTYPE html>

<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Ajax</title>
  <style>
  </style>
</head>

<body>
  <h1>Trivia api</h1>
  <div>Score: <span id="score"></span></div>
  <div id="output"></div>
  <div id="selAnswers"></div>
  <input type="button" id="btn" value="Next Question">
  <script>
    var btn = document.getElementById('btn').addEventListener('click', getItem);

    var answers = {
      'correct': 0,
      'incorrect': 0
    };

    function getItem() {
      var url = "https://opentdb.com/api.php?amount=1";
      var output = document.getElementById('output');

      requestAjax(url, function(data) {
        var info = data.results[0];
        console.log(info);

        output.innerHTML = '<div>Category: ' + info.category + '</div>';
        output.innerHTML += '<div>Question: ' + info.question + '</div>';

        questionBuilder(info.correct_answer, info.incorrect_answers);
      });
    }

    function questionBuilder(corr, incorr) {
      /*console.clear();*/

      var holder = incorr;
      
      holder.push(corr);

      holder.sort();

      var selAnswers = document.getElementById('selAnswers');

      for (i in holder) {
        //                selAnswers.innerHTML ='';
        selAnswers.innerHTML += '<input type="button" id="answer" value="' + holder[i] + '">';

        document.getElementById('answer').addEventListener('click', ansChecker(corr));
      }
    }

    function ansChecker(a) {
      console.log(a);
    }

    function requestAjax(url, callback) {
      var xhr = new XMLHttpRequest();

      xhr.onreadystatechange = function() {
        if (xhr.readyState == 4 && xhr.status == 200) {
          callback(JSON.parse(xhr.responseText));
        }
      }

      xhr.open('GET', url, true);

      xhr.send();
    }
  </script>
</body>
</html>

1 Answers

The following line executes the ansChecker function, instead of passing it through to the event listener as a callback:

document.getElementById('answer').addEventListener('click', ansChecker(corr));

The parameter should be binded or called within the callback function, like so:

// Within callback example
document.getElementById('answer').addEventListener('click', () => ansChecker(corr));

// Binded example
document.getElementById('answer').addEventListener('click', ansChecker.bind(this, corr);

In general, MDN is a good place to read on the parameters expected and has good examples to get you going:

AddEventListener:
https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener

Bind:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_objects/Function/bind


For your second question, it consoles Counter-Strike 4 times due to the target the event listener is attached to:

document.getElementById('answer');

This will attach the event listener on the first element it finds with the id answer and in this case it would always be the Counter Strike input. Also, an id should be unique for the whole page DOM, so don't re-use the same one. There are different ways to target the individual elements for each answer. One way would be to do the following:

holder.forEach(value => {
    let inputEl = document.createElement('input');
    inputEl.setAttribute('type', 'button');
    inputEl.setAttribute('value', value);
    selAnswers.appendChild(inputEl);
    inputEl.addEventListener('click', () => ansChecker(corr));
});
Related