Conditionally render a div with vue.js - javascript

I am trying to build a quiz which when the user chooses a selection and submits the answer the notification div displays whether choice is correct or not. I have got this logic working however what is happening is the div is disappearing each time the next question is answered. I want to keep the previous question result on the screen and lock the user out of answering the question again and show the correct answer.
Here is what I have so far:
<div
class="container"
v-for="options in quiz"
v-bind:key="options.quizId"
>
<h3>{{ options.question }}</h3>
<div
class="control"
>
<label class="radio">
<input
type="radio"
:value="options.op1"
v-model="selectedAnswer"
/>
{{ options.op1 }}
</label>
</div>
<div class="control">
<label class="radio">
<input
type="radio"
:value="options.op2"
v-model="selectedAnswer"
/>
{{ options.op2 }}
</label>
</div>
<div class="control">
<label class="radio">
<input
type="radio"
:value="options.op3"
v-model="selectedAnswer"
/>
{{ options.op3 }}
</label>
</div>
<div class="control">
<label class="radio">
<input
type="radio"
:value="options.op4"
v-model="selectedAnswer"
/>
{{ options.op4 }}
</label>
</div>
<div class="control mt-4" >
<button class="button is-link" #click="call(options.id)">
Submit
</button>
</div>
<br />
<template v-if="options.id === quizId">
<template v-if="quizResult == 'correct'">
<template v-if="correctAnswer">
<div class="notification is-success mt-4">
Correct. Well done!
</div>
</template>
<br />
</template>
<template v-if="quizResult == 'incorrect'">
<div class="notification is-danger mt-4">
Incorrect. Please try again.
</div>
<br />
</template>
</template>
</div>
</div>
</div>
</div> <br>
</section>
</div>
</div>
<!-- <hr /> -->
</template>
<script>
import axios from "axios";
export default {
data() {
return {
course: {},
lessons: [],
comments: [],
activeLesson: null,
errors: [],
showModalFlag1: false,
okPressed1: false,
quiz: {},
questionIndex: 0,
loading: true,
quizId: null,
selectedAnswer: "",
quizResult: null,
correctAnswer: false,
userScore: {
username: '',
lesson_id: '',
lesson_score: '',
},
images: [
{
photo: "",
},
],
comment: {
name: "",
content: "",
},
};
},
methods: {
call(id){
this.submitQuiz(id);
console.log('checkid#call',id)
},
submitQuiz(e) {
console.log('check-id#submit', e)
const quizArray = this.quiz;
const choice = this.selectedAnswer;
console.log('chosen: ', choice)
const result = quizArray.filter( obj => obj.op1 === choice || obj.op2 === choice ||
obj.op3 === choice || obj.op4 === choice)[0];
console.log('result',result.id);
for (const prop in result) {
if ( result.hasOwnProperty(prop) ) {
if (result.id == e) {
this.quizId = result.id;
if (choice == result.answer) {
this.quizResult = "correct";
this.correctAnswer = true;
}
if(choice !== result.answer){
this.quizId = result.id;
this.quizResult = "incorrect";
}
}
}
}
},

You could have another div or in your notification div to display last answered question.
You can either have a new object that gets overwritten each time you answer correctly or track the index of your loop and use the value of index - 1 to get last answered question. Add a check if index !== 0.
To get index of your loop in html do: v-for="(options, ) in quiz

I'm seeing conditions with inner conditions for the correct answer notification. So if your quizId is the next one, the outer condition already fails.
It's likely you have an array with questions and thus have an index for the current question. You could save a property a property on that index that shows if the question is correct, so your condition can be extended to also look back for the previous question
Something like this (pseudo code)
<template v-if="showCorrectMsg">
Good!
...
and then have a computed showCorrectMsg method something like this (pseudo code):
if ( questions[currentIndex].quizResult == 'correct' ||
( currentIndex > 0 && questions[currentIndex - 1].quizResult == 'correct' )
) return true;
return false;
So for this to work you will have to store that flag on each answer given (again, pseudo code):
questions[currentIndex].quizResult = [ theAnswer == theRightAnswer ] ? 'correct' : 'wrong';

Related

How to implement form validation using a for loop and an array for errors

I want to check inputs for emptiness on button click. I am filtering an array if one of the inputs is empty. I'm trying to add an error to the array for errors, My task is that I need to add an error only for those inputs that are empty, but the problem is that the error is added even for those inputs that are not empty.
<template>
<form>
<div v-for="(learning, i) in general.learnings" :key="i">
<input
type="text"
v-model="general.learnings[i]"
maxlength="120"
/>
</div>
<p
style="background: red"
v-for="(i, index) in errorList"
:key="'A' + index"
>
{{ i }}
</p>
<button #click="save">Save</button>
</form>
</template>
<script>
export default {
methods: {
save(e) {
this.general.learnings.filter((e, index) => {
if (!e[index]) {
this.errorList.push("Error")
} else if (e[index] !== "") {
this.errorList = [""];
}
});
e.preventDefault();
},
},
data() {
return {
errorList: [],
general: {
learnings: ["", ""],
},
};
},
};
</script>
I think that the problem lies in this.errorList.push("Error") you can look at this code in codesandbox you can write something in the input press the button after pressing delete and press again you will see that everything works awfully, I will be very glad if help with this
I applied #flaxon code where the error will only show for my index then I slightly changed the validation check inside the save method
<div v-for="(learning, i) in general.learnings" :key="i">
<input type="text" v-model="general.learnings[i]" maxlength="120" />
<p style="background: red" :key="'A' + index">
{{ errorList[i] }}
</p>
</div>
save(e) {
this.errorList = [];
this.general.learnings.filter((e, index) => {
if (e === "") {
this.errorList.push("Error");
} else {
return true;
}
});
e.preventDefault();
},
https://codesandbox.io/s/happy-sun-8z8xg
I changed the p to be in the div, and only display the error of that index
<div v-for="(learning, i) in general.learnings" :key="i">
<input type="text" v-model="general.learnings[i]" maxlength="120" />
<p style="background: red" :key="'A' + index">
{{ errorList[i] }}
</p>
</div>

How to checked radio button in Vue.js using V-Model?

Im new to laravel and vue.js. I'm developing a simple online examination and Im having a hard time in showing the answer of the user if he/she will click the previous button.
This my template I used v-for to loop the questions:
<div class="myQuestion" v-for="(question, index) in questions" :key="index + uuid">
<div class="row">
<div class="col-md-6">
<blockquote >
Total Questions {{ index+1 }} / {{questions.length}}
</blockquote>
<h2 class="question">Q. {{question.question}}</h2>
<form class="myForm" action="/quiz_start" v-on:submit.prevent="createQuestion(question.question_id, question.answer, auth.id, question.topic_id)" method="post">
<input class="radioBtn" v-bind:id="'radio'+ index" type="radio" v-model="result.user_answer" value="A" aria-checked="false" > <span>{{question.a}}</span><br>
<input class="radioBtn" v-bind:id="'radio'+ index+1" type="radio" v-model="result.user_answer" value="B" aria-checked="false"> <span>{{question.b}}</span><br>
<input class="radioBtn" v-bind:id="'radio'+ index+2" type="radio" v-model="result.user_answer" value="C" aria-checked="false"> <span>{{question.c}}</span><br>
<input class="radioBtn" v-bind:id="'radio'+ index+3" type="radio" v-model="result.user_answer" value="D" aria-checked="false"> <span>{{question.d}}</span><br>
<div class="row">
<div class="col-md-6 col-xs-8">
<button type="submit" class="btn btn-wave btn-block nextbtn">Next</button>
</div>
</div>
<div class="row">
<div class="col-md-6 col-xs-8">
<button type="submit" class="btn btn-wave btn-block prebtn">Previous</button>
</div>
</div>
</form>
</div>
</div>
</div>
This is my script to fetch the data and insert the data array to questions variable.
<script>
import { v4 as uuidv4 } from 'uuid';
export default {
props: ['topic_id'],
data () {
return {
questions: [],
answers: [],
uuid:0,
result: {
question_id: '',
answer: '',
user_id: '',
user_answer:0,
topic_id: '',
},
auth: [],
}
},
created () {
this.fetchQuestions();
},
methods: {
fetchQuestions() {
this.$http.get(`${this.$props.topic_id}/quiz/${this.$props.topic_id}`).then(response => {
this.questions = response.data.questions;
this.auth = response.data.auth;
this.uuid=uuidv4();
console.log(this.questions);
}).catch((e) => {
console.log(e)
});
},
createQuestion(id, ans, user_id, topic_id) {
this.result.question_id = id;
this.result.answer = ans;
this.result.user_id = user_id;
this.result.topic_id = this.$props.topic_id;
this.$http.post(`${this.$props.topic_id}/quiz`, this.result).then((response) => {
console.log(response.data.message);
let newdata=response.data.newdata;
this.questions.splice(newdata[0]["index"],1,newdata[0]);
}).catch((e) => {
console.log(e);
});
this.result.topic_id = '';
this.result.user_answer =0;
}
}
}
</script>
I used jQuery next() and prev() for the next and previous buttons. In questions variable, I store my array of objects from database which contains the questions and choices so after the user click next it will update the element of questions to insert the answer chosen by the user. My problem is how can I checked the answer by default chosen by the user if the question showed was answered already by the user. This is the time when the user wants to review his/her answers before finishing the exam.

Vuejs generating click event in next button after pressing enter input

I'm encountering a very strange case.
I've build a very simple example in order to present my problem.
I've 3 files : App.vue, todo2.vue, todoI.vue.
App.vue has 1 component (todo2.vue). todo2.vue has 1 component (todoI.vue).
You'll find under the code of todo2 and todoI.
The problem I'm facing is that when i press enter in the input text id #checkboxAdd, it triggers an event on the next button.
In the code below when pressing enter in the input text #checkboxAdd, it triggers the click event on the first iteration of my todoI button, which in my example calls the function del (#click="del()"), which console.log(this), logging the first iteration of the component todoI.
What is even stranger is that when I add a button just after the input text, add a #click to console.log the event, it is indeed called (instead of calling the button of the first iteration of todoI).
Does anyone understand why this happens ? Is this something I'm doing wrong ?
todo2.vue:
<template>
<div class="d-flex flex-column">
<div>
<form #submit.prevent="">
<div class="mb-3 input-group">
<div class="input-group-text">
<input type="checkbox" class="form-check-input" id="checkboxAdd" aria-describedby="checkboxAdd">
</div>
<input type="text" class="form-control" id="inputAdd" aria-describedby="inputAdd" v-model="tempI">
</div>
<ul class="list-group">
<todoI v-for="(it, k) in todos" v-model="it.v" :key="k" #delItem="del(it)"></todoI>
</ul>
<br>
</form>
</div>
</div>
</template>
<script>
import todoI from './todoI.vue'
export default {
name:"todo2",
components: {todoI},
data: function(){
return {
todos: [
{v:{
val: "Bread",
checked: false
}},
{v:{
val: "Fruits",
checked: false
}},
{v:{
val: "Ironing",
checked: false
}}
],
tempI: ''
}
},
methods:{
del (it){
this.todos = this.todos.filter(i => i!==it)
}
}
}
</script>
todoI.vue:
<template>
<li class="list-group-item d-flex align-items-center" #mouseover="btnShow=true" #mouseleave="btnShow=false">
<input type="checkbox" class="me-4" v-model="value.checked">
<div class="w-100" :class="value.checked ? checkedClass : '' ">{{ value.val }}</div>
<div class="flex-shrink-1">
<button class="btn btn-sm btn-close" v-show="btnShow" #click="del()"></button>
</div>
</li>
</template>
<script>
export default {
name:"todoI",
props:{
value: Object
},
data: function(){
return {
checkedClass:['text-decoration-line-through', 'text-muted'],
btnShow: false,
}
},
methods:{
del(){
console.log(this)
}
}
}
</script>
you can simple use #keypress or #keydown
<input type="text" class="form-control" id="inputAdd" v-model="tempI" #keypress.enter.prevent />
or
<input type="text" class="form-control" id="inputAdd" v-model="tempI" #keydown.enter.prevent = "">

Vue displays validation text on multiple fields

There are two buttons called ADD and REMOVE. If the user clicks on ADD it will add one more input field for FULL NAME. I am using validationText to display text as PLEASE ENTER MORE THAN 5 CHARACTERS for full name. If I ADD two fields and insert only two characters in second one then it displays validationText on both input fields as
Is there a way to display validationText message to the particular field which consists of less than 5 characters?
View
<div id="app">
<div class="work-experiences">
<div class="form-row" v-for="(minordatabase, index) in minorsDetail" :key="index">
<div class="col">
<br>
<label id="minorHeading">FULL NAME</label>
<input v-model="minordatabase.full_name" type="text" class="form-control" placeholder="FULL NAME" size="lg" #input="checkValidation"/>
<p v-show="!validationText" style="color:red;">
Please enter than 5 characters
</p>
</div>
</div>
</div>
<br>
<div class="form-group">
<button #click="addExperience" type="button" class="btn btn-info" style="margin-right:1.5%;">Add</button>
<button #click="removeExperience" type="button" class="btn btn-outline-info">Remove Last Field</button>
</div>
</div>
Script
new Vue({
el: "#app",
data: {
minorsDetail: [
{
full_name: "",
date_of_birth: "",
}
],
validationText: true
},
methods: {
checkValidation(){
console.log("SAN");
var minorsDetailLastElement = this.minorsDetail[this.minorsDetail.length-1].full_name.length;
console.log(minorsDetailLastElement);
if(minorsDetailLastElement > 2){
this.validationText = false;
}
if(minorsDetailLastElement > 5){
this.validationText = true;
}
},
addExperience(){
this.minorsDetail.push({
full_name: ''
})
},
removeExperience: function(todo){
var index = this.minorsDetail.indexOf(todo)
this.minorsDetail.splice(index, 1)
this.removeMinorFieldFunction();
},
}
})
Below is the code on JSFIDDLE
https://jsfiddle.net/ujjumaki/5mqp1bag/28/
You only have one validationText for all fields. So, if you set it for one field, it's going to show up in the other field too.
I recommend doing something like this instead to show the validation:
<p v-if="minordatabase.full_name.length > 2 && minordatabase.full_name.length < 5" style="color: red;">
Please enter more than 5 characters
</p>

Update nested parent classes in Vue.js

I'm trying to add a class to a parent container each time an "advanced" link is clicked.
So with jQuery I would just write something like..
$(this).closest('.row').addClass('overlay');
or
$(this).closest('section').addClass('overlay');
But it seems to be getting a little complex with Vue to just add a class to the parent container of the item that is clicked. I'm sure there is a more simple way to go about it.
Here is an example of my code.
<div id="app">
<section v-bind:class="{ overlay: i == sectionActive && rowActive == null }" v-for="(section, i) in sections">
Advanced
<div class="advanced-fields" v-bind:class="{ overlay: i == sectionActive && rowActive == null }">
<fieldset>
<label>
ID
<input type="text" name="section[id]" v-model="section.id">
</label>
</fieldset>
<fieldset>
<label>
Class
<input type="text" name="section[css_class]" v-model="section.css_class">
</label>
</fieldset>
</div>
<div class="row" v-bind:class="{ overlay: i == sectionActive && row_i == rowActive }" v-for="(row, row_i) in section.rows">
Advanced
<div class="advanced-fields" v-bind:class="{ overlay: i == sectionActive && row_i == rowActive }">
<fieldset>
<label>ID</label>
<input type="text" name="" v-model="row.id">
</fieldset>
<fieldset>
<label>CSS Class</label>
<input type="text" name="" v-model="row.css_class">
</fieldset>
</div>
</div>
</section>
<pre>{{ $data }}</pre>
</div>
<script>
new Vue({
el: "#app",
data: {
"sections": [{
"id": "section-1",
"css_class": "",
"rows": [{
"id": "",
"css_class": ""
}, {
"id": "",
"css_class": ""
}]
}, {
"id": "section-2",
"css_class": '',
"rows": [{
"id": "",
"css_class": ""
}]
}],
sectionActive: null,
rowActive: null,
columnActive: null
},
methods: {
toggleAdvanced: function(index) {
this.sectionActive = this.sectionActive === index ? null : index;
this.rowActive = null;
this.columnActive = null;
},
toggleRowAdvanced: function(section, row) {
var sectionIndex = this.sections.indexOf(section);
var rowIndex = section.rows.indexOf(row);
this.sectionActive = this.sectionActive === sectionIndex ? null : sectionIndex;
this.rowActive = this.rowActive === rowIndex ? null : rowIndex;
}
}
});
</script>
I need to do the same thing for columns but as you can see, it is getting too overly complicated. Any ideas on how to simplify this?
I know it would be easier to add a data attribute to each row, but I am saving the hash to a database and don't want to add in unnecessary data just for a UI toggle.
https://jsfiddle.net/ferne97/4jbutbkz/
I took a different approach and built several re-usable components. This removes all the complicated logic that you are putting into your Vue.
Vue.component("expand-link",{
template:`Advanced`,
data(){
return {
expanded: false
}
},
methods:{
toggle(){
this.expanded = !this.expanded
this.$emit('toggled', this.expanded)
}
}
})
Vue.component("expanded-fields",{
props:["details", "expanded"],
template:`
<div class="advanced-fields" :class="{overlay: expanded}">
<fieldset>
<label>
ID
<input type="text" name="section[id]" v-model="details.id">
</label>
</fieldset>
<fieldset>
<label>
Class
<input type="text" name="section[css_class]" v-model="details.css_class">
</label>
</fieldset>
</div>
`
})
Vue.component("expandable-section", {
props:["section"],
template:`
<section>
<expand-link #toggled="onToggle"></expand-link>
<expanded-fields :details="section" :expanded="expanded"></expanded-fields>
<expandable-row v-for="row in section.rows" :key="row" :row="row"></expandable-row>
</section>
`,
data(){
return {
expanded: false
}
},
methods:{
onToggle(val){
this.expanded = val
}
}
})
Vue.component("expandable-row",{
props:["row"],
template:`
<div class="row">
<h3>Row</h3>
<expand-link #toggled="onToggle"></expand-link>
<expanded-fields :details="row" :expanded="expanded"></expanded-fields>
</div>
`,
data(){
return {
expanded: false
}
},
methods:{
onToggle(val){
this.expanded = val
}
}
})
And the template simply becomes
<div id="app">
<expandable-section v-for="section in sections" :key="section" :section="section"></expandable-section>
<pre>{{ $data }}</pre>
</div>
Here is your fiddle updated.

Categories