I am building a quiz app with Vue 3 and Bootstrap 4.
I have this method for checking if the clicked answer is the (same as the) correct answer:
checkAnswer(answer) {
return answer == this.results[this.questionCount]["correct_answer"];
}
It should be executed upon clicking a list item, as seen below:
<li v-for="answer in answers" @click="checkAnswer(answer)" :class="{'text-white bg-success' : checkAnswer(answer)}">{{answer}}</li>
const quizApp = {
data() {
return {
questionCount: 0,
results: [
{
question: "The book "The Little Prince" was written by...",
correct_answer: "Antoine de Saint-Exupéry",
incorrect_answers: [
"Miguel de Cervantes Saavedra",
"Jane Austen",
"F. Scott Fitzgerald"
]
},
{
question:
"Which novel by John Grisham was conceived on a road trip to Florida while thinking about stolen books with his wife?",
correct_answer: "Camino Island",
incorrect_answers: ["Rogue Lawyer", "Gray Mountain", "The Litigators"]
},
{
question:
"In Terry Pratchett's Discworld novel 'Wyrd Sisters', which of these are not one of the three main witches?",
correct_answer: "Winny Hathersham",
incorrect_answers: [
"Granny Weatherwax",
"Nanny Ogg",
"Magrat Garlick"
]
}
]
};
},
methods: {
nextQuestion() {
if (this.questionCount < this.results.length - 1) {
this.questionCount ;
}
},
prevQuestion() {
if (this.questionCount >= 1) {
this.questionCount--;
}
},
checkAnswer(answer) {
// check if the clicked anwser is equal to the correct answer
return answer == this.results[this.questionCount]["correct_answer"];
},
shuffle(arr) {
var len = arr.length;
var d = len;
var array = [];
var k, i;
for (i = 0; i < d; i ) {
k = Math.floor(Math.random() * len);
array.push(arr[k]);
arr.splice(k, 1);
len = arr.length;
}
for (i = 0; i < d; i ) {
arr[i] = array[i];
}
return arr;
}
},
computed: {
answers() {
let incorrectAnswers = this.results[this.questionCount][
"incorrect_answers"
];
let correctAnswer = this.results[this.questionCount]["correct_answer"];
// return all answers, shuffled
return this.shuffle(incorrectAnswers.concat(correctAnswer));
}
}
};
Vue.createApp(quizApp).mount("#quiz_app");
#quiz_app {
height: 100vh;
}
.container {
flex: 1;
}
.quetions .card-header {
padding-top: 1.25rem;
padding-bottom: 1.25rem;
}
.quetions .card-footer {
padding-top: 0.7rem;
padding-bottom: 0.7rem;
}
.answers li {
cursor: pointer;
display: block;
padding: 7px 15px;
margin-bottom: 5px;
border-radius: 6px;
border: 1px solid rgba(0, 0, 0, 0.1);
background: #fff;
}
.answers li:last-child {
margin-bottom: 0;
}
.answers li:hover {
background: #fafafa;
}
.pager {
list-style-type: none;
margin: 0;
padding: 0;
display: flex;
justify-content: space-between;
}
.pager li > a {
display: inline-block;
padding: 5px 10px;
text-align: center;
width: 100px;
background-color: #fff;
border: 1px solid rgba(0, 0, 0, 0.1);
border-radius: 999px;
text-decoration: none !important;
color: #fff;
}
.pager li > a.disabled {
pointer-events: none;
background-color: #9d9d9d !important;
}
.logo {
width: 30px;
}
.nav-item {
width: 100%;
}
.card {
width: 100%;
}
@media (min-width: 768px) {
.nav-item {
width: auto;
}
.card {
width: 67%;
}
}
@media (min-width: 992px) {
.card {
width: 50%;
}
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.4.1/css/bootstrap.min.css" rel="stylesheet" />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.16.0/umd/popper.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
<script src="https://unpkg.com/vue@next"></script>
<div id="quiz_app" class="container quetions d-flex align-items-center justify-content-center my-3">
<div v-if="results.length" class="card shadow-sm">
<div class="card-header bg-light h6">
{{results[questionCount]['question']}}
</div>
<div class="card-body">
<ul class="answers list-unstyled m-0">
<li v-for="answer in answers" @click="checkAnswer(answer)" :class="{'text-white bg-success' : checkAnswer(answer)}">{{answer}}</li>
</ul>
</div>
<div class="card-footer bg-white">
<ul class="pager">
<li><a href="#" @click="prevQuestion" class="bg-dark" :class="{'disabled' : questionCount == 0}">Previous</a></li>
<li class="d-flex align-items-center text-secondary font-weight-bold small">Question {{questionCount 1}} of {{results.length}}</li>
<li><a href="#" class="bg-dark" :class="{'disabled' : questionCount == results.length - 1}" @click="nextQuestion">Next</a></li>
</ul>
</div>
</div>
</div>
<iframe name="sif1" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>
The problem
To my surprise, the checkAnswer(answer)
method is executed before (and in the absence of) any click.
Question
What am I doing wrong?
CodePudding user response:
This is executing it before the click:
:
.
You'll need to keep the state in a variable for each answer and update it within the method.
And as a side node, it is recommended to use :key
for looped elements.
CodePudding user response:
The issue is that the checkAnswer
is referenced in the html template so it will execute immediately on render. You might want to try instead setting the class to some computed property instead of the function.
<li v-for="answer in answers" @click="checkAnswer(answer)" :class="{'text-white bg-success' : checkAnswer(answer)}">{{answer}}</li>
CodePudding user response:
it will invoke the method immediately if used in template, remove it.
when checkAnswer()
is called, store the selected answer only if it is correct, and compare the selected answer with the looped answers.
<li v-for="answer in answers" :key="answer" @click="checkAnswer(answer)" :class="{'text-white bg-success' : selectedAnswer === answer}">{{answer}}</li>
data() {
return {
selectedAnswer: ''
...
}
},
methods: {
checkAnswer(answer) {
// check if the clicked anwser is equal to the correct answer
if (answer == this.results[this.questionCount]["correct_answer"]) {
this.selectedAnswer = answer
return true
}
},
}