I'm trying to get the total score by checking the checkbox on Vue.js, but I can't subtract from the total score when the same checkbox is clicked.
<template>
<div class="container">
<ul class="list-group" v-for="(data,key) in questionData" :key="key">
<li class="list-item" style="list-style-type:none;">
<h3 class="title">{{data.title}}</h3>
</li>
<ul class="list-group" >
<li class="list-question" v-for="(title,key) in data.questions" :key="key" style="list-style-type:none;"><input type="checkbox" #click="total(data.id, title.id)" :id="title.id" :value="title.question" v-model="selected">{{ title.question}} </li>
</ul>
</ul>
<div class="alert">
Toplam Puan : {{totalScore}}
</div>
</div>
</template>
<script>
import {dataMixin} from "./dataMixin"
export default {
mixins : [dataMixin],
data () {
return{
checked : false,
questionData : [],
selected : [],
totalScore : 0
}
},
methods :{
total(dataId,titleId){
this.questionData.forEach(data => {
data.questions.forEach(element => {
if(dataId == data.id && titleId == element.id){
this.checked = !this.checked;
if (this.checked == true){
this.totalScore += element.score
}
else {
this.totalScore -= element.score
}
}
});
});
}
},
created(){
this.getDataQuestions()
.then(response => {
this.questionData = response.data;
console.log(this.questionData)
})
}
}
</script>
Example JSON data
[
{
"id" : 1,
"title" : "Sözleşme Doğrulaması",
"questions" : [
{
"id" : 1,
"question" : "Geliştirici tüm isterleri karşılayabileceği güvenini vermektedir.",
"score" : 5
},
{
"id" : 2,
"question" : "İsterler tutarlı olup kullanıcı gereksinimlerini kapsamaktadır.",
"score" : 5
}
]
}
]
In my opinion using an array to store selected values is not a good design (using a checked boolean is worse). You have a very nice, functioning object structure for your questions and we can easily work with that.
Your question structure can easily be viewed as this:
[
{
"id" : 1,
"title" : "Group 1",
"questions" : [
{
"id" : 1,
"question" : "A question",
"score" : 5,
"selected" : false
},
{
"id" : 2,
"question" : "Another question",
"score" : 5,
"selected" : false
}
]
},
{ ... }
]
So it's easy to calculate the total score for each question group:
const groupScore = group.questions
.reduce((score, question) => question.selected ? score + question.score : score, 0)
And once you have the score for each question group, you can calculate the total score:
const totalScore = groupScoreArray.reduce((total, groupScore) => total + groupScore, 0)
So, given the data structure above, you can use a computed property to calculate total score.
<template>
<div class="container">
<ul class="list-group" v-for="(data, index) in questionData" :key="index">
<li class="list-item" style="list-style-type:none;">
<h3 class="title">{{data.title}}</h3>
</li>
<ul class="list-group" >
<li class="list-question" v-for="(title, qindex) in data.questions" :key="qindex" style="list-style-type:none;">
<!-- Delete click event, bind data to title.selected -->
<input type="checkbox" :id="title.id" v-model="title.selected">
{{ title.question}}
</li>
</ul>
</ul>
<div class="alert">
Toplam Puan : {{totalScore}}
</div>
</div>
</template>
export default {
data () {
return {
questionData : [{
"id" : 1,
"title" : "Sözleşme Doğrulaması",
"questions" : [{
"id" : 1,
"question" : "Geliştirici tüm isterleri karşılayabileceği güvenini vermektedir.",
"score" : 5
}, {
"id" : 2,
"question" : "İsterler tutarlı olup kullanıcı gereksinimlerini kapsamaktadır.",
"score" : 5
}]
}, {
"id" : 2,
"title" : "Sözleşme Doğrulaması",
"questions" : [{
"id" : 1,
"question" : "Geliştirici tüm isterleri karşılayabileceği güvenini vermektedir.",
"score" : 2
}, {
"id" : 2,
"question" : "İsterler tutarlı olup kullanıcı gereksinimlerini kapsamaktadır.",
"score" : 3
}]
}]
};
},
computed: {
totalScore() {
const scoreMap = this.questionData.map(questionItem =>
questionItem.questions.reduce((sum, q) => {
return q.selected ? sum + q.score : sum
}, 0));
return scoreMap.reduce((sum, score) => sum + score, 0);
}
}
}
This is a working example in my local environment (vue#2.6.10, vue-cli#4.0.0), and here is a codepen: https://codepen.io/jasminexie/pen/pooRaNr
Related
I'm trying to utilize radio buttons to make a simple accordion in vue. Everything works except I can't get the accordion to only show a single view at once.
Once the view has been expanded I seem to longer be able to close it without creating a separate v-model for the whole group and adding a conditional around it. Is it not possible to get a radio button to default to its off state after it is no longer selected?
<div v-for="(item, index) in options">
<label :for="'l' + item.name">{{ item.name }}</label>
<input type="radio" :id="'l' + item.name" name="internalFinish" v-model="item.selected" :value="true">
<div v-if="item.selected">
<p>Accordion Open</p>
</div>
</div>
https://jsfiddle.net/s5ohgvde/
Bind the radio input value to unique value like item.name then bind the v-model to another property which will be used as condition in v-if=
<div id="app">
<div v-for="(item, index) in options">
<label :for="'l' + item.name">{{ item.name }}</label>
<input type="radio" :id="'l' + item.name" name="internalFinish" v-model="selectedOption" :value="item.name" >
<div v-if="item.name===selectedOption">
<p>Accordion Open</p>
</div>
</div>
</div>
<script>
new Vue({
el: "#app",
data: {
selectedOption:null,
options : [
{
name : 'Plasterboard with Skim Finish',
range : false,
selected : false,
selectedValue : 0
},
{
name : 'Plasterboard on Dabs',
range : { min : 0, max : 100},
selected : false,
selectedValue : 0
},
{
name : 'Plaster Finish',
range : { min : 60, max : 100},
selected : false,
selectedValue : 0
},
]
}
})
</script>
I edited your code.I have simply added a new value for each record in the array as id.And a new data named selected with a #click event for the input. So it checks whether the id is equal to the selected item id, if so it will display only the relevant one. You can see the fiddle here https://jsfiddle.net/bugnrvt2/1/
<label :for="'l' + item.name">{{ item.name }}</label>
<input #click="selected = item.id" type="radio" :id="'l' + item.name" name="internalFinish" v-model="item.selected" :value="true">
<div v-if="selected == item.id">
<p>Accordion Open</p>
</div>
</div>
</div>
new Vue({
el: "#app",
data: {
options : [
{
name : 'Plasterboard with Skim Finish',
range : false,
selected : false,
selectedValue : 0,
id: 1
},
{
name : 'Plasterboard on Dabs',
range : { min : 0, max : 100},
selected : false,
selectedValue : 0,
id: 2
},
{
name : 'Plaster Finish',
range : { min : 60, max : 100},
selected : false,
selectedValue : 0,
id: 3
},
],
selected: 0
}
})
I would recommend doing something like this instead of a radio button or atleast try with a checkbox because checkbox can either hold true or false but its not so in case of radio button
new Vue({
el: "#app",
data: {
selectedRow: null,
options : [
{
name : 'Plasterboard with Skim Finish',
range : false,
selected : false,
selectedValue : 0
},
{
name : 'Plasterboard on Dabs',
range : { min : 0, max : 100},
selected : false,
selectedValue : 0
},
{
name : 'Plaster Finish',
range : { min : 60, max : 100},
selected : false,
selectedValue : 0
},
]
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-for="(item, index) in options">
<label :for="'l' + item.name">{{ item.name }}</label>
<!-- <input type="checkbox" :id="'l' + item.name" name="internalFinish" v-model="item.selected" :value="true">-->
<span :id="'l' + item.name" name="internalFinish" class="caret-down" #click="selectedRow = item.name">
<span v-if="selectedRow === item.name">▲</span>
<span v-else>▼</span></span>
<div v-if="selectedRow === item.name">
<p>Accordion Open</p>
</div>
</div>
</div>
Thanks for the suggestions, I ended up reseting each option to selected = false on change.
unselectOthers(index) {
for (var i = this.options.length - 1; i >= 0; i--) {
if (i != index) {
this.options[i].selected = false;
}
}
}
<input type="radio" :id="'l' + item.name" name="internalFinish" v-model="item.selected" :value="item.name" #change.prevent="unselectOthers(index)">
I have a URL: https://local.test/real-price?city_id=2
Vue template
<template>
<ul>
<li
v-for="city in states"
:key="city.id"
:id="city.id"
v-bind:class="{ active: (city.id === city_id) }"
>
{{ city.name }}
</li>
</ul>
</template>
<script>
export default {
data: function() {
return {
states: [],
city_id: 0,
};
},
created() {
this.states = [{id: 1, name: "City A"}, {id: 2, name: "City B"}, {id: 3, name: "City C"}];
},
mounted() {
this.city_id = this.$route.query.city_id ? this.$route.query.city_id : 0
},
}
</script>
Result not auto add class "active" when city_id=2
This is because the city_id variable is a number in your code while it's a string in the url.
Try parsing it in your mounted
this.city_id = this.$route.query.city_id ? parseInt(this.$route.query.city_id) : 0
Another way of solving this is to use ==
v-bind:class="{ active: (city.id == city_id) }"
I have two nested v-for elements that look like:
<div class="category" v-for="(category, categoryIndex) in categories">
<div class="product" v-for"(product, productIndex) in cateogry)">
{{product.name}}
</div>
</div>
I would like to show only the first five products, regardless of the number of categories and the number of products in each category.
How would I get the cumulative index count (in relation to the total number of products that's been displayed from the categoryIndex parent array) inside of the second v-for element?
If I understood, it will be something like this
<div class="category" v-for="(category, categoryIndex) in categories">
{{ category.name }}
<div class="product" v-for="(product, productIndex) in category.products.slice(0, nbProductToShow)">
{{(categoryIndex*nbProductToShow)+(productIndex+1)}}
{{product.name}}
</div>
</div>
Vue
new Vue({
data : {
nbProductToShow : 5,
categories : [{
id : 3445,
name : 'shoes',
products : [
{ id: 23234, name : 'Pink unicorn shoes'},
// ...
]
},{
id : 3447,
name : 'hat',
products : [
{ id: 232, name : 'Pink long hat with unicorn'},
// ...
]
}
]
}
})
You can check if the product is within the first five products rendering by passing its id to a function and showing the product according to its returned value. Here is an example:
<template>
<div>
<div class="category" v-for="(category, categoryIndex) in categories" :key="categoryIndex">
<div class="product" v-for="(product, productIndex) in category.products" :key="productIndex">
<span v-if="incrementCount(product.id)">{{product.name}}</span>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
categories: [
{
products: [
{ id: 1, name: "1" },
{ id: 2, name: "2" },
{ id: 3, name: "3" },
]
},
{
products: [
{ id: 4, name: "4" },
{ id: 5, name: "5" },
{ id: 6, name: "6" },
]
}
]
};
},
methods: {
incrementCount: function(id) {
let count = 0;
for(let i = 0; i < this.categories.length; i++)
for(let j = 0; j < this.categories[i].products.length; j++)
if(count++ < 5 && this.categories[i].products[j].id===id)
return true;
return false;
}
},
};
</script>
Correspondingly, the output would be:
1
2
3
4
5
I am working on simple vuejs example projects.
Basically, I am trying to display set of data in a loop. But I can't print the random name array from the nested object in vue 2.
It prints as a full array like { "name": "check1.1" } I want to print them separately like Name, Chcek1.1 ... etc
new Vue({
el: '#sample',
data: {
private: {
folders : [{
name : 'folder1',
checks : [
{ "name" : 'check1.1' },
{ "name2" : 'check1.2' },
{ "name4" : 'check1.1' },
{ "abcd" : 'check1.2' },
]
},
{
name : 'folder2',
checks : [
{ "name" : 'check1.1' },
{ "name2" : 'check1.2'},
{ "abcs" : 'check1.1' },
{ "abcd" : 'check1.2' },
]
}
]
}
}
})
<script src="https://cdn.jsdelivr.net/vue/latest/vue.js"></script>
<div id="sample">
<h1>current result: </h1>
<div v-if = "private.folders!=null" v-for="folder in private.folders">
{{folder.name}}
<div v-for="check in folder.checks">
{{check}}
</div>
</div>
</div>
<h1>expected result: </h1>
<ul>
<li>folder1
<ul>
<li>name</li>
<li>check1.1</li>
</ul>
---
<ul>
<li>name2</li>
<li>check1.1</li>
</ul>
---
<ul>
<li>name3</li>
<li>check1.1</li>
</ul>
</li>
<li>folder2
<ul>
<li>name</li>
<li>check1.1</li>
</ul>
---
<ul>
<li>name2</li>
<li>check1.1</li>
</ul>
---
<ul>
<li>name3</li>
<li>check1.1</li>
</ul>
</li>
</ul>
The outer loop works just fine but inside checks[] I can't get them as I expected.
Well, this problem has me stumped... Having a bit of trouble getting nested for-loop data to show up:
<div v-if = "private.folders!=null" v-for="folder in private.folders">
{{folder.name}}
<div v-for="check in folder.checks">
{{check.name}}
</div>
</div>
And then the data that I'm trying to use looks like this:
folders [Array]
-object [this is a single 'folder']
--name
--checks [array] [each folder has this array]
---[object] [the actual 'check' object]
----[name]
The outer loop works just fine, and returns the data I expect. However, check.name doesn't return anything, and there are no errors in the console. Is it possible to do nested for-loops like this?
I tested you template, it's works.
new Vue({
el: '#sample',
data: {
private: {
folders : [{
name : 'folder1',
checks : [
{ name : 'check1.1' },
{ name : 'check1.2' }
]
},
{
name : 'folder2',
checks : [
{ name : 'check2.1' },
{ name : 'check2.2' }
]
}
]
}
}
})
<script src="https://cdn.jsdelivr.net/vue/latest/vue.js"></script>
<div id="sample">
<div v-if = "private.folders!=null" v-for="folder in private.folders">
{{folder.name}}
<div v-for="check in folder.checks">
{{check.name}}
</div>
</div>
</div>
This is how you might set it up with HTML table:
new Vue({
el: '#sample',
data: {
private: {
folders : [{
name : 'folder1',
checks : [
{ name : 'check1.1' },
{ name : 'check1.2' }
]
},
{
name : 'folder2',
checks : [
{ name : 'check2.1' },
{ name : 'check2.2' }
]
}
]
}
}
})
<script src="https://cdn.jsdelivr.net/vue/latest/vue.js"></script>
<div id="sample">
<table>
<tr>
<th>Folder</th>
<th>Checks</th>
<th>Checks</th>
</tr>
<tr v-if = "private.folders!=null" v-for="folder in private.folders">
<td><b>{{folder.name}}</b></td>
<td v-for="check in folder.checks">
{{check.name}}
</td>
</tr>
</table>
</div>