I'm trying to create a trivia. When a user clicks on an option (a button) within the MCQ portion, i want the site to respond immediately, by responding to the user's click (by changing color either to red or green depending on whether the answer is correct). It will also reset color of all buttons to its original color when the user clicks a button, before responding with the respective response of the button that was clicked
I have tried looking through the code but cannot determine my error - whether its a syntax one or a problem with the logic of my code. Appreciate it
Here is the code:
<!DOCTYPE html>
<html lang="en">
<head>
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@500&display=swap" rel="stylesheet">
<link href="styles.css" rel="stylesheet">
<title>Trivia!</title>
</head>
<body>
<div >
<h1>Trivia!</h1>
</div>
<script>
// Run script once DOM is loaded
document.addEventListener('DOMContentLoaded', function() {
// When correct button is clicked, turns green
let correct = document.querySelectorAll('.correct');
let feedbackmcq = document.querySelector('.feedbackmcq');
let feedbackfrq = document.querySelector('.feedbackfrq');
let wrongs = document.querySelectorAll('.wrong');
// Reset colors of the button (1st question)
function resetColor()
{
for (let i = 0; i < correct.length; i ) {
correct[i].style.backgroundColor = '#d9edff';
for (let i = 0; i < wrongs.length; i ) {
wrongs[i].style.backgroundColor = '#d9edff';
}
}
// When correct button is clicked, turns green
for (let i = 0; i < correct.length; i ) {
correct[i].addEventListener('click', function() {
resetColor();
correct[i].style.backgroundColor = 'green';
});
}
// When wrong button is clicked, turns red
for (let i = 0; i < wrongs.length; i ) {
wrongs[i].addEventListener('click', function() {
resetColor();
wrongs[i].style.backgroundColor = '#red';
});
}}
});
</script>
<div >
<div >
<h2>Part 1: Multiple Choice </h2>
<hr>
<div>
<h3>Who sent you this link?</h3>
<button >Jonathan</button>
<button >Thomas</button>
<button >Kiara</button>
<button >James</button>
<p id="feedback1"></p>
</div>
<div>
<h3>Who created this form?</h3>
<button >Joel</button>
<button >Jonathan</button>
<button >Kiara</button>
<button >James</button>
<p id="feedback2"></p>
</div>
</div>
</div>
</body>
</html>
After creating my script, I checked my syntax to ensure that there was not any semicolon / spelling error. I also thought through the logic, but it really seems logical to me.
Here is my pseudocode:
- For loop looping through all correct buttons, constantly checking for a click on any one of its buttons.
- If a click is identified, colors of button is resetted
- Then, I will change the style of the respective button that was clicked
(same for the wrong buttons when pressed)
CodePudding user response:
The approach your taking is incorrect - don't set eventListeners on the fly based on conditions - rather set them up in the beginning and then use logic to determine what they should or shouldn't do.
- use a single loop on all buttons
- when they're clicked, test if they are 'correct' or not based on the data-is property (You had these as classes, but really they are more appropriate as datasets
- Then we remove any right/wrong classes that may have been applied from that group of buttons. We do that using closest() and querySelectorAll() together
- finally, we apply the correct/incorrect class based on the dataset of the clicked button
- you had styles being applied directly, but using classes is a better, more extendable and easier to maintain approach.
** Further help with the code:
buttons.forEach(b => b.addEventListener('click', e => {
Here, we are looping through buttons
which we already defined as 'all the buttons on the page' with buttons = document.querySelectorAll('button')
. This is arrow function syntax, which isn't too important here. For each button (assigned as b
in the loop) we add an event listener which also uses arrow syntax. Again, not important here but I find it easier to read and code. The event it is listening to is click
and the task it will perform on click is (as you mentioned) an anonymous function. Unless you need to reuse that code in other parts of the site, it's fine to leave it as an anonymous function.
e.target.closest('div').querySelectorAll('button')
which says 'Find the closest containing <div>
tag to event.target
(the button that was clicked) - and then find all the <button>
elements inside that div. That becomes an HTML collection that we can iterate through with forEach
like before.
Then you'll see ....forEach(bb => bb.classList.remove("isCorrect", "isWrong"))
. Again, the bb
inside is a placeholder for the thing you're iterating through. In this case each of the buttons inside the div.
At that point, we are just adding or removing CSS classes for the effect
// Run script once DOM is loaded
document.addEventListener('DOMContentLoaded', function() {
let buttons = document.querySelectorAll('button');
buttons.forEach(b => b.addEventListener('click', e => {
// first lets clear out any existing colorations
e.target.closest('div').querySelectorAll('button').forEach(bb => bb.classList.remove("isCorrect", "isWrong"))
if (e.target.dataset.is=='correct') e.target.classList.add('isCorrect')
else e.target.classList.add('isWrong');
}))
});
button {
background: #d9edff;
}
button.isCorrect {
background: green;
color: #fff;
}
button.isWrong {
background: red;
color: #fff;
}
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@500&display=swap" rel="stylesheet">
<link href="styles.css" rel="stylesheet">
<div >
<h1>Trivia!</h1>
</div>
<div >
<div >
<h2>Part 1: Multiple Choice </h2>
<hr>
<div>
<h3>Who sent you this link?</h3>
<button data-is="correct">Jonathan</button>
<button data-is="wrong">Thomas</button>
<button data-is="wrong">Kiara</button>
<button data-is="wrong">James</button>
<p id="feedback1"></p>
</div>
<div>
<h3>Who created this form?</h3>
<button data-is="wrong">Joel</button>
<button data-is="correct">Jonathan</button>
<button data-is="wrong">Kiara</button>
<button data-is="wrong">James</button>
<p id="feedback2"></p>
</div>
</div>
</div>