I have objects (products) in an array. Each product has its own rating and the rating is a number that comes from the database. I display the rounded average rating of each product on the product itself.
{{ Math.round(Object.values(product.rating)[0]) }}
I would like to display the average value in the stars (radio buttons) that people use to rate a product with. If I click to rate a product then the number of stars that match the current rating should be checked. On an individual product I can just use v-model
but what do I do if it's a bunch of products in a list and each product has a different rating?
The values that I have for each radio button are the ID and the value attribute. How do I match the radio button's id or value with the current rating of the product?
There is no this
so I tried doing this but it obviously does not work:
<div >
<input
type="radio"
value="5"
id="5"
:checked="
this.value ==
Math.round(Object.values(product.rating)[0])
"
@change="rateproduct"
/><label for="5">☆</label>
<input
type="radio"
value="4"
id="4"
@change="rateproduct"
:checked="
this.value ==
Math.round(Object.values(product.rating)[0])
"
/><label for="4">☆</label>
<input
type="radio"
value="3"
id="3"
@change="rateproduct"
:checked="
this.value ==
Math.round(Object.values(product.rating)[0])
"
/><label for="3">☆</label>
<input
type="radio"
value="2"
id="2"
@change="rateproduct"
:checked="
this.value ==
Math.round(Object.values(product.rating)[0])
"
/><label for="2">☆</label>
<input
type="radio"
value="1"
id="1"
@change="rateproduct"
:checked="
this.value ==
Math.round(Object.values(product.rating)[0])
"
/><label for="1">☆</label>
</div>
I tried doing this but it also does not work:
<input
type="radio"
value="5"
id="5"
:checked="
Math.round(Object.values(post.rating)[0])
? 'checked'
: ''
"
@change="ratePost"
/><label for="5">☆</label>
</input>
Here is what I want to accomplish but it's not working and only the last post rating is selected
<template>
<div >
<div v-for="post in posts" :key="post.id">
<div >Title: {{post.title}}</div>
<div >Rating: {{post.rating}}</div>
<div >
<div >
<div v-for="index in stars" :key="index">
<input
type="radio"
name="stars"
:value="index"
v-model="post.rating"
@change="ratePost"
/><label>☆</label>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import {ref} from 'vue'
const stars = 5;
const posts = ref([
{ id:1, title: "post1", rating: 2 },
{ id:2, title: "post2", rating: 3 },
{ id:3, title: "post3", rating: 2 },
{ id:4, title: "post4", rating: 5 },
{ id:5, title: "post5", rating: 2 },
{ id:6, title: "post6", rating: 1 },
{ id:7, title: "post7", rating: 2 },
{ id:8, title: "post8", rating: 3 },
{ id:9, title: "post9", rating: 4 },
{ id:10, title: "post10", rating: 1 },
]);
</script>
<style scoped>
.post{
padding:10px;
}
.rating{
display:flex;
margin-bottom: 10px;
}
.title{
margin-bottom:10px;
}
.currentrating{
margin-bottom:10px
}
</style>
CodePudding user response:
One possible solution:
<template>
<fieldset>
<legend>Average Star Rating</legend>
<div >
<div v-for="index in MAX_VALUE" :key="index">
<input
type="radio"
name="stars"
:value="index"
v-model="starValue"
@click.prevent=""
/>
<label>{{ index }} {{ starText(index) }}</label>
</div>
</div>
<div>
Average Rating: {{ roundedAverageValue }}
</div>
</fieldset>
<fieldset>
<legend>Individual Item Ratings</legend>
<div v-for="(slider, index) in sliderValues" :key="index" >
<label for="slider.name" >{{ slider.name }}</label>
<input
type="range"
name="starSlider"
:min="MIN_VALUE"
:max="MAX_VALUE"
v-model="slider.value"
>
Value: {{ slider.value }}
</div>
<div>
Average: {{ averageValue }}
</div>
</fieldset>
</template>
<script setup>
import { ref, watch, computed } from 'vue';
const MIN_VALUE = 1;
const MAX_VALUE = 5;
const starValue = ref(1);
const sliderValues = ref([
{
value: MIN_VALUE,
name: 'Item 1',
},
{
value: MIN_VALUE,
name: 'Item 2',
},
{
value: MIN_VALUE,
name: 'Item 3',
},
{
value: MIN_VALUE,
name: 'Item 4',
},
]);
const starText = (index) => {
return index > 1 ? 'stars' : 'star';
}
const averageValue = computed(() => {
let sum = 0;
sliderValues.value.forEach((sv) => {
sum = parseInt(sv.value);
})
return sum / sliderValues.value.length;
});
const roundedAverageValue = computed(() => {
return Math.round(averageValue.value);
});
watch(roundedAverageValue, (newValue) => {
starValue.value = newValue;
})
</script>
<style scoped>
fieldset {
margin: 20px 70px;
text-align: center;
}
.radioGroup {
display: inline-block;
text-align: center;
margin: 10px;
}
.radioGroup label {
display: block;
}
.sliderLabel {
margin-right: 8px;
}
</style>
Explanation:
This creates the radio inputs using v-for going from 1 to 5: v-for="index in 5"
. The value of each radio is the int index of the for loop. The model for all the radios is the same, starValue
I then use an array of objects to create my sliders, and using computed properties to get an average and a watcher on the computed property to change the radio's model value (since it is bad practice to have side-effects in a computed property).
Note that the code that you have recently posted has a bug in this part of your template code:
<div v-for="post in posts" :key="post.id">
<div >Title: {{post.title}}</div>
<div >Rating: {{post.rating}}</div>
<div >
<div >
<div v-for="index in stars" :key="index">
<input
type="radio"
name="stars"
:value="index"
v-model="post.rating"
@change="ratePost"
/><label>☆</label>
</div>
</div>
</div>
</div>
This line: name="stars"
is the same for each iteration of the outer v-for loop, meaning every single radio input you create is the and same radio group. And since a radio group can show only one selection at a time, only one star is shown to be selected.
The solution is trivial: give each collection of stars a unique name property for the radio input. You would need to bind the name property and can use the post.id for uniqueness, So change this:
name="stars"
to this:
:name="stars post.id"
and your posted code should work.
For example:
<div v-for="post in posts" :key="post.id">
<div >Title: {{post.title}}</div>
<div >Rating: {{post.rating}}</div>
<div >
<div >
<div v-for="index in stars" :key="index">
<input
type="radio"
:name="stars post.id"
:value="index"
v-model="post.rating"
@change="ratePost"
/><label>☆</label>
</div>
</div>
</div>
</div>