0👍
There are a few issues here. Firstly, with a radio list you need to bind v-model
to one underlying variable so Vue selects the correct option, in your example you are binding your radio buttons to an array index, but you need to do:
<input type="radio" value="1" v-model="selected">
<input type="radio" value="2" v-model="selected">
The problem is that you have given each of your radio buttons a value of either true
or null
based on their correct
value, so you need to set the value to something unique, such as index
. Also, because you are going to be reusing this piece of code you should wrap it in it’s own component and pass the question as a prop
so:
Vue.component('question', {
template: '#question',
props: ['question', 'number'],
methods: {
hideOption: function() {
this.disabled = true;
}
},
data() {
return {
selected: ''
}
}
})
Then your markup would be (I’ve removed some properties for clarity):
<template id="question">
<div>
<h5>{{question.instruction}} {{selected}}</h5>
<h5>{{number}}. {{question.text}}</h5>
<ul >
<li v-for="(option, optionindex) in question.options">
<div :class="{correct: option['correct'], incorrect: !option['correct']}">
<input type="radio" @click="hideOption" :value="optionindex" :name="number" v-model="selected">
<label>{{option.text}}</label>
</div>
</li>
</ul>
</div>
</template>
Then in you main Vue instance you can do:
<div id="app">
<div class="col-sm-8 gpquiz">
<question :question="question" :number="index+1" :key="index" v-for="(question, index) in questions"></question>
</div>
</div>
Here’s the JSFiddle for that: https://jsfiddle.net/8ocmun6s/
You now have a second problem, your user can select a correct or incorrect answer but your main Vue instance doesn’t know anything about what was selected, to do that we need to $emit
that event back to the parent and we can do that by using a watcher
to watch our selected
value and $emit
the event when it changes:
watch: {
selected(answer) {
let correct = this.question.options[answer].correct
// Emit the question number and whether it was correct back to the parent
this.$emit('user-selected', this.number, correct)
}
},
And, we can now listen to that on the component and trigger a method (in this case selectAnswer
):
<question :question="question" :number="index+1" :key="index" @user-selected="selectAnswer" v-for="(question, index) in quiz.questions"></question>
Now we just need to add that method:
methods: {
selectAnswer(number, correct) {
// use $set here otherwise view won't detect the change
this.$set(this.userResponses, number-1, correct)
}
},
Now we have an array of true or false answers we can present the result, I’m just going to use a computed
:
computed:{
correctAnswers(){
// Return the number of correct answers
return this.userResponses.filter(x => x).length
}
},
Now we can just show and hide that when the user submits the form, so we can add the following to methods:
submitAnswers() {
this.submitted = true;
}
And also make sure you declare submitted
in data
, then you can bind that to the button and add the conditional to your markup, which ends up as:
<div id="app">
<h1>{{quiz.title}}</h1>
<hr>
<div class="col-sm-8 gpquiz" v-if="!submitted">
<question :question="question" :number="index+1" :key="index" @user-selected="selectAnswer" v-for="(question, index) in quiz.questions"></question>
<button @click="submitAnswers">
Submit
</button>
</div>
<h4 v-else>
You got {{correctAnswers}} out of {{userResponses.length}} Correct!
</h4>
</div>
And here’s the final JSFiddle: https://jsfiddle.net/fvk4L326/