I created a quiz by using VueJS. However, there is something that blocks me from clicking on radio buttons. It will check another radio button rather than the one that I want.
HTML code
<div id="app">
<h1>{{ quiz.title }}</h1>
<div v-for="(question, index) in quiz.questions" class="question-content">
<div v-show="index === questionIndex" class="question-box">
<h4>{{ question.text }}</h4>
<ol>
<li v-for="response in question.responses">
<label>
<input type="radio"
v-bind:value="response.correct"
v-bind:name="index"
v-model="userResponses[index]"> {{response.text}}
</label>
</li>
</ol>
<button v-if="questionIndex > 0" v-on:click="prev">
Prev
</button>
<button v-on:click="next">
Next
</button>
</div>
</div>
<div v-show="questionIndex === quiz.questions.length">
<h2>
Quiz finished
</h2>
<p>
Total score: {{ score() }} / {{ quiz.questions.length }}
</p>
<button v-on:click="start">
Restart
</button>
</div>
</div>
VueJS code
// A question has one or more answer, and one or more is valid.
var quiz = {
title: 'Quiz',
questions: [
{
text: "1. Which of the following is a category or element of the balance sheet?",
responses: [
{text: 'Expenses'},
{text: 'Gains'},
{text: 'Liabilities', correct: true},
{text: 'Losses'},
]
}, {
text: "2. Which of the following is an asset account?",
responses: [
{text: 'Accounts Payable'},
{text: 'Prepaid Insurance', correct: true},
{text: 'Unearned Revenue'}
]
}
]
};
new Vue({
el: '#app',
data: {
quiz: quiz,
// Store current question index
questionIndex: 0,
// An array initialized with "false" values for each question
// It means: "did the user answered correctly to the question n?" "no".
userResponses: Array(quiz.questions.length).fill(false)
},
// The view will trigger these methods on click
methods: {
// Go to next question
next: function() {
this.questionIndex++;
},
// Go to previous question
prev: function() {
this.questionIndex--;
},
// Return "true" count in userResponses
score: function() {
return this.userResponses.filter(function(val) { return val }).length;
},
// Restart quiz
start: function() {
this.questionIndex = 0;
}
}
});
You can check the live code at : https://jsfiddle.net/dalenguyen/z4rj62c6/1/
Use Nested index for name attribute.
HTML
<div id="app">
<h1>{{ quiz.title }}</h1>
<div v-for="(question, index) in quiz.questions" class="question-content">
<div v-show="index === questionIndex" class="question-box">
<h4>{{ question.text }}</h4>
<ol>
<li v-for="(response, child_index) in question.responses">
<label>
<input type="radio" v-bind:value="response.text" v-bind:name="response.text" v-model="userResponses[index]"> {{response.text}}
</label>
</li>
</ol>
<button v-if="questionIndex > 0" v-on:click="prev">
Prev
</button>
<button v-on:click="next">
Next
</button>
</div>
</div>
<div v-show="questionIndex === quiz.questions.length">
<h2>
Quiz finished
</h2>
<p>
Total score: {{ score() }} / {{ quiz.questions.length }}
</p>
<button v-on:click="start">
Restart
</button>
</div>
</div>
JS: Calculate Correct Ans
score: function() {
correctCount = 0;
that = this;
this.quiz.questions.filter(function(val, i) {
val.userAnswerCorrect = false;
val.userAnswer = that.userResponses[i];
val.responses.filter(function(ans, j) {
if (ans.correct == true && val.userAnswer == ans.text) {
correctCount++;
}
})
});
return correctCount;
}
Demo: https://jsfiddle.net/sumitridhal/esshqr25/
Just doing the following change will make it work:
v-bind:value="response.text"
response.correct was making all other values undefined, so the radio was getting confused which is the correct value!
I hope that helps!
Related
First Image without clicking on Edit
Second Image when i click on Edit
here when i click on which ever edit button all the task which is in loop plus it is in if part will be hidden and else part will be shown but i want to hide particular task when i click on edit button. can anyone help me with that?.
<script>
export default {
data(){
return{
newTaskTitle: "",
isEditing : false
}
},
props:{
Task:{
type:Array,
required: true
},
},
methods:{
removeTask: function(idx){
this.Index = idx;
this.$emit('remove',this.Index);
},
EditTaskI(tsk){
this.task = tsk;
console.log(this.task);
this.isEditing = this.isEditing == true ? false : true;
this.newTaskTitle = this.task;
},
TaskUpdated(indx){
this.Index = indx
this.$emit('update',this.Index,this.newTaskTitle);
this.isEditing = this.isEditing == true ? false : true;
},
taskContentChange(e){
this.newTaskTitle = e.target.value;
}
}
}
</script>
<template>
<section v-if="Task.length > 0" class="taskMainSection">
<section v-for="(tasks,index) in Task" :key="index" class="sectionTask" >
<section class="TaskBox" v-if="!isEditing">
<div class="TaskTitleList" >
<div class="TaskSection">
<p class="listTask">{{ tasks.Task }}</p>
</div>
</div>
<div class="OptionSectionMain">
<div class="OptionSection">
<p class="removeTask fa fa-close" #click="removeTask(index)"></p>
<p class="editTask fa fa-edit" #click="EditTaskI(tasks.Task,index)"></p>
</div>
</div>
</section>
<section class="TaskBoxEdit" v-else>
<div class="TaskTitleList" >
<div class="TaskSection">
<input type="text" class="form-control" :value="newTaskTitle" #change="taskContentChange">
</div>
</div>
<div class="OptionSectionMain">
<div class="OptionSection">
<p class="removeTask fa fa-check" #click="TaskUpdated(index)"></p>
</div>
</div>
</section>
</section>
</section>
</template>
Instead of boolean use index for isEditing:
Vue.component('child', {
template: `
<section v-if="Task.length > 0" class="taskMainSection">
<section v-for="(tasks,index) in Task" :key="index" class="sectionTask" >
<section class="TaskBox" >
<div class="TaskTitleList" >
<div class="TaskSection">
<p class="listTask">{{ tasks.Task }}</p>
</div>
</div>
<div class="OptionSectionMain">
<div class="OptionSection">
<p class="removeTask fa fa-close" #click="removeTask(index)"></p>
<p class="editTask fa fa-edit" #click="EditTaskI(tasks.Task,index)"></p>
</div>
</div>
</section>
<section class="TaskBoxEdit" v-if="isEditing === index">
<div class="TaskTitleList" >
<div class="TaskSection">
<input type="text" class="form-control" :value="newTaskTitle" #change="taskContentChange">
</div>
</div>
<div class="OptionSectionMain">
<div class="OptionSection">
<p class="removeTask fa fa-check" #click="TaskUpdated(index)"></p>
</div>
</div>
</section>
</section>
</section>
`,
data(){
return{
newTaskTitle: "",
isEditing : null
}
},
props:{
Task:{
type:Array,
required: true
},
},
methods:{
removeTask(idx){
console.log(idx)
this.$emit('remove', idx);
},
EditTaskI(tsk, i){
this.task = tsk;
this.isEditing = i;
this.newTaskTitle = this.task;
},
TaskUpdated(indx){
this.Index = indx
this.$emit('update',this.Index,this.newTaskTitle);
this.isEditing = null;
},
taskContentChange(e){
this.newTaskTitle = e.target.value;
}
}
})
new Vue({
el: "#demo",
data(){
return{
tasks: [{Task: 'aaa'}, {Task: 'bbb'}, {Task: 'ccc'}],
}
},
methods: {
updateTasks(i, name) {
this.tasks[i].Task = name
},
removeTask(i) {
this.tasks.splice(i, 1)
}
}
})
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.1/css/all.min.css" integrity="sha512-KfkfwYDsLkIlwQp6LFnl8zNdLGxu9YAA1QvwINks4PhcElQSvqcyVLLD9aMhXd13uQjoXtEKNosOWaZqXgel0g==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="demo">
<child :task="tasks" #update="updateTasks" #remove="removeTask"></child>
</div>
Observation : isEditing is a culprit in your code. As isEditing is a global variable containing boolean value. Hence, On edit you are updating the value of isEditing which impact for all the tasks.
Solution : Instead of defining isEditing globally, You can add isEditing in each object of Task array. So that you can just update the value of clicked task not for every task.
Your template code will be :
<section class="TaskBox" v-if="!tasks.isEditing">
instead of
<section class="TaskBox" v-if="!isEditing">
const app = new Vue({
el: "#app",
name: 'aselect',
data: {
value: 'Select a Fruit',
list: ["Orange", "Apple", "Kiwi", "Lemon", "Pineapple"],
visible: false
},
methods: {
toggle() {
this.visible = !this.visible;
},
select(option) {
this.value = option;
}
}
})
<div id="app">
<h1>Custom Select Dropdown</h1>
<div class="aselect" :data-value="value" :data-list="list">
<div class="selector" #click="toggle()">
<div class="label">
<span>{{ value }}</span>
</div>
<div class="arrow" :class="{ expanded : visible }"></div>
<div :class="{ hidden : !visible, visible }">
<ul>
<li :class="{ current : item === value }" v-for="item in list" #click="select(item)">{{ item }}</li>
</ul>
</div>
</div>
</div>
</div>
I am using the reference from https://www.npmjs.com/package/vue-click-outside
This is my codepen link https://codepen.io/santoshch/pen/gOmvvmN In the codepen link, i have a dropdown, where after toggling down the dropdown i am unable to close the dropdown list.
So i have searched for some reference i vuejs, And finally found npm package called vue-click-outside, Itried to place event but not sure how to proceed.
I found out a solution to your problem. Follow below steps
At first Add box class to every element that lies inside the box that toggle the dropdown
<div id="app">
<h1>Custom Select Dropdown</h1>
<div class="aselect" :data-value="value" :data-list="list">
<div class="selector box" #click="toggle()">
<div class="label box">
<span class="box">{{ value }}</span>
</div>
<div class="arrow box" :class="{ expanded : visible }"></div>
<div :class="{ hidden : !visible, visible }">
<ul>
<li :class="{ current : item === value }" v-for="item in list" #click="select(item)">{{ item }}</li>
</ul>
</div>
</div>
</div>
</div>
Then add click listener to window in vue.js
const app = new Vue({
el: "#app",
name: 'aselect',
data: {
value: 'Select a Fruit',
list: ["Orange","Apple","Kiwi", "Lemon", "Pineapple"],
visible: false
},
methods: {
toggle() {
this.visible = !this.visible;
},
select(option) {
this.value = option;
},
handleClick(e){
const classname = e.target.className;
if(this.visible && !classname.includes("box")){
this.visible = false;
}
}
},
created () {
window.addEventListener('click', this.handleClick);
},
destroyed () {
window.removeEventListener('click', this.handleClick);
},
})
Check this pen: https://codepen.io/salim-hosen/pen/xxqYYMQ
I am working on a to-do web application and I wanted to make it look nicer with some animations from UIKit. Right now, I have an animation that toggles when the todo-items gets put into view:
<div class="todo-items uk-animation-toggle uk-animation-scale-up" tabindex="0" :id="style.id">
Where style.id changes based on the todo item.
I also have a checkmark that moves the todo item to the completed section of the webpage:
<input type="checkbox" uk-toggle :target="style.target" animation="uk-animation-scale-up uk-animation-reverse" mode="click" #click="completedTask">
However, the animation for that checkmark does not seem to work when there is already an animation for the block when it initially appears. The topmost toggle animation takes precedence and the checkmark animation does not work. If I remove the topmost animation, the checkmark works as intended, it is when there are two when it does not work.
This is a template from a Vue.js component:
<div class="todo-items uk-animation-toggle uk-animation-scale-up" tabindex="0" :id="style.id">
<div class="row">
<div class="col-1-10">
<div class="center">
<input type="checkbox" uk-toggle :target="style.target" animation="uk-animation-scale-up uk-animation-reverse" mode="click" #click="completedTask">
</div>
</div>
<div class="col-7-10">
<h3>Task {{ count + 1 }}</h3>
<p v-show="!edit" #click="editTask">{{ description }}</p>
<input v-show="edit" v-model="description" #keyup.enter="editTask">
</div>
<div class="col-2-10">
<div class="center">
<button #click="editTask">{{ edit ? "Done" : "Edit" }}</button>
<button #click="deleteTask" id="deleteButton">Delete</button>
</div>
</div>
</div>
</div>
Here is the whole component if needed:
Vue.component('todo-item', {
props: {
count: {
type: Number,
required: true
},
description: {
type: String,
required: true
},
index: {
type: Number,
required: true
}
},
template:
`
<div class="todo-items uk-animation-toggle uk-animation-scale-up" tabindex="0" :id="style.id">
<div class="row">
<div class="col-1-10">
<div class="center">
<input type="checkbox" uk-toggle :target="style.target" animation="uk-animation-scale-up uk-animation-reverse" mode="click" #click="completedTask">
</div>
</div>
<div class="col-7-10">
<h3>Task {{ count + 1 }}</h3>
<p v-show="!edit" #click="editTask">{{ description }}</p>
<input v-show="edit" v-model="description" #keyup.enter="editTask">
</div>
<div class="col-2-10">
<div class="center">
<button #click="editTask">{{ edit ? "Done" : "Edit" }}</button>
<button #click="deleteTask" id="deleteButton">Delete</button>
</div>
</div>
</div>
</div>
`,
data() {
return {
edit: false,
style: {
id: 'task' + this.index,
'target': '#task' + this.index,
'animation': 'uk-animation-scale-up uk-animation-reverse',
'slideRight': 'uk-animation-slide-right uk-animation-reverse',
'mode': 'click',
'duration': 400
}
}
},
methods: {
completedTask() {
setTimeout(() => this.$emit('complete-task', this.index), this.style.duration);
},
deleteTask() {
this.$emit('remove-task', this.index);
},
editTask() {
this.edit = !this.edit;
if (!this.edit) {
this.$emit('edit-task', this.description, this.index);
}
}
}
})
I think it's impossible. The alternative is remove the previous animation when it's finished.
<div
class="todo-items uk-animation-toggle"
:class="animationEnter ? 'uk-animation-scale-up' : ''"
:id="style.id"
tabindex="0">
...
</div>
...
mounted () {
setTimeout(() => {
this.animationEnter = false
}, 500)
}
...
Example
Or use Vue List-Transitions which is reliable and easier:
<transition-group
name='list'
tag='div'
appear
enter-active-class='uk-animation-scale-up'
leave-active-class='uk-animation-scale-up uk-animation-reverse'>
<todo-item
v-for='item in todoItems'
:key='item.id'
...>
</todo-item>
</transition-group>
Example
How can I access and show/hide the exact button in second repeat?
<div class="row" ng-repeat="person in people">
<div class="row" ng-repeat="ticket in tickets">
<button type="button" ng-if="!ticket.added" ng-click="add(ticket)">+</button>
<button type="button" ng-if="ticket.added" ng-click="remove(ticket)">-</button>
</div>
</div>
For example I have 3 persons and 4 different tickets. When someone click on button I want to add clicked ticket for that person.
Now when I click on add button, it's adding clicked ticket for all persons :(
Thanks in advance!
I'm not sure (your question is not too clear) but, maybe you can pass the person to your functions too?
Example:
$scope.people = [{
name: 'Jhon Snow',
tickets: [{
name: 'ticket1',
added: false
}, {
name: 'ticket2',
added: false
}]
}, {
name: 'Peter Parker',
tickets: [{
name: 'ticket3',
added: false
}, {
name: 'ticket4',
added: false
}]
}];
$scope.add = function (ticket) {
ticket.added = true;
}
$scope.remove = function (ticket) {
ticket.added = false;
}
And the html:
<div class="row" ng-repeat="person in people">
{{ person.name}}
<div class="row" ng-repeat="ticket in person.tickets">
- {{ ticket.name }}
<button type="button" ng-if="!ticket.added" ng-click="add(ticket)">+</button>
<button type="button" ng-if="ticket.added" ng-click="remove(ticket)">-</button>
</div>
</div>
You can check a working example here
I'm using Vue v2
I'm trying to change only the properties of the selected element. See, when the response is marked after the click, it should change to a red color with a text that says 'Unmark'. And vice versa: if the button is clicked again (which now would say 'Unmark'), it should change to a green color and the text would be 'Mark'. Alas, it works.... Nevertheless, my code applies the change to every element inside the v-for; I only want that to happen to the selected element.
I've thought about using a Component to check if somethings changes, but first I'd like to see if there's a solutions for this. ANy help will be appreciated
Here's my code:
<div class="search-results">
<div class="activity-box-w" v-for="user in users">
<div class="box">
<div class="avatar" :style="{ 'background-image': 'url(' + user.customer.profile_picture + ')' }">
</div>
<div class="info">
<div class="role">
#{{ '#' + user.username }}
</div>
<div>
<div>
<p class="title">#{{ user.customer.name }}
#{{user.customer.lastname}}
</p>
</div>
</div>
</div>
<div class="time">
<input type="button" class="btn btn-sm btn-primary" v-on:click.prevent="markUser(user)" v-model="text"
v-bind:class="[{'green-border':notMarked}, {'red-border':marked}]" v-cloak v-if="!action"
:disabled="action"></input>
</div>
</div>
</div>
Now for the script:
new Vue({
el: '#engage-panel',
data: {
users: [],
mark: {'id' : '', 'marks' : ''},
text: 'Mark', //Migth change to Unmark later on
action: false,
marked: null,
notMarked: null,
},
methods:
{
markUser: function(user){
this.mark.id = user.id;
this.action= true;
Vue.http.headers.common['X-CSRF-TOKEN'] = $('meta[name="csrf-token"]').attr('content');
this.$http.put('/mark/user', this.mark).then(response => {
if(response.body === "marked")
{
this.mark.marks="true";
this.text = 'Unmark';
this.marked= true;
this.notMarked= false;
this.action= false;
}else{
this.mark.marks="false";
this.text = 'Mark';
this.marked= false;
this.notMarked= true;
this.action= false;
}
}).catch(e => {
this.action= false;
});
}
}
You can use $event.target on click if you just need to toggle css class.
fiddle here
But it's true that it's easier if a user has a status like marked = true/false for example, you just need to bind class like :
<input :class="{ 'green-border': user.marked, 'red-border': !user.marked }">
The issue my code applies the change to every element you met is caused by every user in v-for="user in users" uses one same object to indicates it is marked or not.
If your users data has one property like status to save current status (like unmark, mark etc), it is very simple, just change to next status when click mark button.
If your users data doesn't have that property, you need to create one dictionary, then save the users already clicked as key, the status for the user will be the value.
Below is one demo:
app = new Vue({
el: "#app",
data: {
users1: [{'name':'abc', 'status':'none'},
{'name':'xyz', 'status':'none'}],
users2: [{'name':'abc'}, {'name':'xyz'}],
selectedUsers: {}
},
methods: {
getNextStatusBaseOnRoute: function (status) {
if(status ==='marked') return 'marked'
let routes = {'none':'unmark', 'unmark':'marked'}
return routes[status]
},
markUser1: function (item) {
item.status = this.getNextStatusBaseOnRoute(item.status)
},
markUser2: function (item) {
let status = item.name in this.selectedUsers ? this.selectedUsers[item.name] : 'none'
// remember to use vue.$set when adding new property to one object
this.$set(this.selectedUsers, item.name, this.getNextStatusBaseOnRoute(status))
}
}
})
.marked {
background-color:green;
}
.unmark {
background-color:yellow;
}
<script src="https://unpkg.com/vue#2.5.16/dist/vue.js"></script>
<div id="app">
<h2>Case 1: </h2>
<div v-for="(item, index1) in users1" :key="'1'+index1">
<span>{{item.name}}:</span><span :class="[item.status]">{{item.status}}</span><button #click="markUser1(item)">Mark</button>
</div>
<h2>Case 2: </h2>
<div v-for="(item, index2) in users2" :key="'2'+index2">
<span>{{item.name}}:</span><span :class="[item.name in selectedUsers ? selectedUsers[item.name] : 'none']">{{item.name in selectedUsers ? selectedUsers[item.name] : 'none'}}</span><button #click="markUser2(item)">Mark</button>
</div>
</div>
For Vue3, you can also store the index of the selected element
<ul role="list" class="">
<li class="relative" v-for="(image, index) of images" :class="selectedImage == index? 'border-indigo-500 border-2': 'border-transparent'" >
<div #click="selectedImage = index" class="">
<img :src="image" alt="" class="object-cover pointer-events-none group-hover:opacity-75">
</div>
</li>
</ul>