Home > Back-end >  VueJS - How to make a radio button checked if the id matches a value
VueJS - How to make a radio button checked if the id matches a value

Time:01-26

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:

enter image description here

Vue SFC Playground Link

<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>
  • Related