1
votes

I've created a custom radio button component in Vue. The component is set up to take these props: label (string), value (string), checked (boolean), and a vModelValue which serves as the v-model value that will be used on the component. I set the internal reference to the v-model value to be prop: vModelValue and event: 'change'.

I have a computed value called local_vModelValue which gets the vModelValue prop sent down in the v-model on the component and sets it to the v-model internally.

This works correctly as is, except for one problem. Accessibility isn't working correctly. When I use voice over controls, and I have two distinct radio groups made up of three buttons each, it will identify the selected button as 1 of 6 even though it should be 1 of 3. It sees all 6 buttons on the page and acts as if there is one group.

To fix this, I want to put a name attribute in my component in the underlying logic, and I set up a computed property to check if there is a vModelValue. If there is, it sets the name to that vModelValue (or it SHOULD do so). I don't want to have to send a name down as a prop at this point. I want it to just use the vModelValue as the name. (Later I will check if there is a name attribute prop on the component and then it will use that as the name but for now I'm just trying to get it work with the vModelValue as the name.)

The problem is it just won't set the name to that vModelValue coming in.

Here is the component:

CustomRadioButtons.vue

<template>
<div>
    <label 
       tabindex="0"
    >
            <input
                type="radio"
                :checked="checked"
                v-model="local_vModelValue"
                :value="value"
                :name="getNameValue"
                @change="onChange"
            >
            <span>
                {{ label }}
            </span>
     </label>
</div>

</template>

<script>
export default {
    name: 'CustomRadioButtons',
    model: {
        prop: 'vModelValue', 
        event: 'change'
        },
    methods: {
        onChange(event) {
            this.$emit('change', event.target.value, this.label, this.name, this.vModelValue)
        },
    },
    props: {
        vModelValue: {
        type: String, 
        default: ''
        },
        label: String,
        value: String,
        name: {
            type: String, 
            default: ''
            },
        checked: {
            type: Boolean, 
            default: false
            } 
    },
    computed: {
        local_vModelValue: {
            get(){
                return this.vModelValue;
            },
            set(value) {
                this.$emit('change', value)
            }
        },
        getNameValue() {
            return this.vModelValue.length > 0 ? this.vModelValue : this.name

        }
    },
    watch: {
        vModelValue:{
            immediate: true,
            handler(){
                console.log(this.vModelValue, this.checked, this.name)
            }

        }
    },
}
</script>

App.vue

<template>
  <div id="app">
  <h3>Custom Radio Buttons 1</h3>
  <div v-for="(button, i) in buttons" :key="'buttons'+i">
  <CustomRadioButtons :label="button.label" :value="button.value" :checked="true" v-model="cat"></CustomRadioButtons>
  </div>
    <h3>Custom Radio Buttons 2</h3>
  <div v-for="(button, i) in otherButtons" :key="'otherbuttons'+i">
  <CustomRadioButtons :label="button.label" :value="button.value" :checked="true" v-model="dog"></CustomRadioButtons>
  </div>
  </div>
</template>

<script>
import CustomRadioButtons from "@/components/CustomRadioButtons"

export default {
  name: 'App',
  components: {
    CustomRadioButtons
  },
  data(){
    return {
      cat: 'cat', 
      dog: 'dog',
      buttons: [{label: 'label 1', value: 'value 1', name: 'name1'}, {label: 'label 2', value: 'value 2', name: 'name1'}, {label: 'label 3', value: 'value 3', name: 'name1'}],
      otherButtons: [{label: 'test 1', value: 'value 1', name: 'name2'}, {label: 'test 2', value: 'value 2', name: 'name2'}, {label: 'test 3', value: 'value 3', name: 'name2'}],
    }  
  },
  props: ['value'],
}
</script>

Using the computed value of getNameValue causes the whole thing to work very strangely and I never see the name get updated to the vModelValue.

1

1 Answers

0
votes

I have an answer now to the name attribute issue. The name attribute WAS getting updated with the vModelValue. However, since I was trying to log this.name in the console to see that value change, I couldn't actually see that anything was being changed. That's because this.name refers to the 'name' prop I set up in my component. But I wasn't passing a name prop down, so the name prop continued to default to an empty string.

If I take the name prop definition out, the name shows up as undefined in the console. If I move 'name' to the data object and make it a data property, it still shows up as an empty string in the console. I need to figure out why that is the case. But at least the name attribute in the DOM updates as it should.

One issue that comes up is that the buttons don't work right if the two radio groups have identical values for any of the corresponding buttons. Since I'm setting the name to the vModelValue, and the vModelValue is the value of which ever button is currently selected, if the two separate radio groups have a matching value for their selected buttons, the name becomes identical and then the groups are seen as one group. This is a problem!