3

I am trying to set the checked value of a Radio Button list in Vue.JS 2. I've reviewed existing articles here and tried them and also several different manual approaches and I just cannot get this to work at all.

I am NOT using v-model here as I'm working on a custom radio button list control which is consumed by a forms builder. This is further complicated by the fact that I am building, on top, a nested radio button list to handle nullable booleans. Putting that complexity aside, my radio component looks like this....

<template>
  <div class="form__radio-list">
    <span class="form__radio-list__intro">{{ introText }}</span>
    <ul class="form__radio-list__options">
      <li v-for="item in options"
          :key="item.key ? item.key : item"
          class="form__radio-list__options__item">
        <input type="radio"
               :id="item.key ? item.key.toKebabCase() : item.toKebabCase()"
               :name="def"
               :value="item.value != undefined ? item.value : item"
               :disabled="disabled"
               :checked="isChecked(item)"
               @input="onInput"
               @change="$emit('change', $event.target.checked)">
        <label :for="item.key ? item.key : item">{{ item.text ? item.text : item }}</label>
      </li>
    </ul>
  </div>
</template>

<script>

export default {

    props: {
        def: {
            type: String,
            required: true
        },
        introText: {
            type: String,
            default: ''
        },
        options: {
            type: Array,
            required: true
        },
        initialValue: {
            type: [String, Number, Boolean],
            default: null
        },
        disabled: {
            type: Boolean,
            default: false
        }
    },

    data() {
        return {
            value: this.initialValue
        }
    },

    methods: {

        _parseValue() {
            return this.value ? parseInt(this.value) : null
        },

        isChecked(item) {
            const checked = item.value === this.value || item === this.value || item.value == this._parseValue()
            this.$logger.logObject({ item, parentValue: this.value, checked }, 'Checking value for an item')
            return checked
        },

        onInput($event) {
            this.value = $event.target.checked
            this.$emit('input', $event.target.checked)
        }
    }
}

</script>

From the logging I can see that the value of 'checked' SHOULD be set correctly (but it isn't).

I also tried splitting the input tag into a 'v-if' statement so I'd have one with a checked parameter set and one without (although this felt horrible) and that worked from an HTML point of view (checked="checked" appeared where I would expect it to) but, on the browser neither of the 2 options were checked.

I am consuming the component through a boolean component renederer that looks like this...

<template>
  <hh-radio class="form__radio-list--yes-no"
            :def="def"
            :intro-text="introText"
            :options="options"
            :initial-value="initialValue"
            :disabled="disabled"
            @change="$emit('change', $event)"
            @input="$emit('input', $event)" />
</template>

<script>

export default {

    props: {
        def: {
            type: String,
            required: true
        },
        introText: {
            type: String,
            default: ''
        },
        initialValue: {
            type: [String, Boolean],
            default: null
        },
        disabled: {
            type: Boolean,
            default: false
        }
    },

    computed: {

        options() {
            return [{
                key: 'yes',
                text: 'Yes',
                value: true
            }, {
                key: 'no',
                text: 'No',
                value: false
            }]
        }
    }
}

</script>

This is then consumed ultimately on a form like this...

  <hh-yes-no class="editable-segment-field__bool"
             :def="pvc-enabled"
             :initial-value="pvc.value"
             @input="onInput" />

The value pass throughs seem to work fine - The key issue that I have is that it will NOT specify the currently selected item from any existing data.

I have tried suggestions here - Vue.JS radio input without v-model and here - Vue.JS checkbox without v-model without much success.

Using the example given me below I've tried to strip this back as far as I can, adding in pieces of the dynamic elements from my components as I go to identify the problem root.

I now have a simpler component which looks like this...

<template>
  <div :class="`form__radio-list ${(this.def ? `form__radio-list--${this.def} js-${this.def}` : '' )}`">
    <span class="form__radio-list__intro">{{ introText }}</span>
    <ul class="form__radio-list__options">
      <li class="form__radio-list__options__item">
        <input id="awesome"
               type="radio"
               name="isawesome"
               :checked="radio === 'Awesome'"
               value="Awesome"
               @change="radio = $event.target.value">
        <label for="awesome">Awesome</label>
      </li>
      <li class="form__radio-list__options__item">
        <input id="super"
               type="radio"
               name="isawesome"
               :checked="radio === 'Super Awesome'"
               value="Super Awesome"
               @change="radio = $event.target.value">
        <label for="super">Super</label>
      </li>
    </ul>
    <span>Selected: {{ value }} | {{ radio }}</span>
  </div>
</template>

<script>

export default {

    props: {
        def: {
            type: String,
            required: true
        },
        introText: {
            type: String,
            default: ''
        },
        initialValue: {
            type: [String, Boolean],
            default: null
        },
        disabled: {
            type: Boolean,
            default: false
        }
    },

    // delete this
    data() {
        return {
            value: this.initialValue,
            radio: 'Awesome'
        }
    },

    computed: {

        options() {
            return [{
                key: 'yes',
                text: 'Yes',
                value: true
            }, {
                key: 'no',
                text: 'No',
                value: false
            }]
        }
    }
}

</script>

I managed to get this to fail as soon as I added name="isawesome" to the radio button items. It seems that when you introduce 'name' something goes awry. Surely I need 'name' to prevent multiple radio button lists interacting with each other or is this something that Vue handles which I've been unaware of.

Keith Jackson
  • 3,078
  • 4
  • 38
  • 66
  • it's html: when you have radios with a (same) `name` attr, you define an implicit radio-group wherein max 1 can be checked at a given time [mdn-doc/input/radio](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/radio) – birdspider Aug 12 '19 at 10:25
  • Yes @birdspider, which is why I want name on my generic radio list component, so that I can have multiple radio button lists on each page. The issue is that NO items will check if name is specified. – Keith Jackson Aug 12 '19 at 10:34

2 Answers2

2

Here is an working example:

new Vue({
  el: "#app",
  data: {
   radio: 'Awesome'
  }
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>

<div id="app">
  <span>Vue is:</span> <br>

  <label>Awesome
  <input 
    type="radio"
    :checked="radio === 'Awesome'" 
    value="Awesome"
    @change="radio = $event.target.value"
  >
  </label>
  
   <label>Super Awesome
  <input 
    type="radio" 
    :checked="radio === 'Super Awesome'"
    value="Super Awesome"
    @change="radio = $event.target.value"
  >
  </label>
  
  <hr>
  
  <span>Selected: {{radio}}</span>
</div>
Roland
  • 24,554
  • 4
  • 99
  • 97
  • I've used this to get to the bottom of the issue by manipulating your example and merging in elements of mine until I can get it to fail again - See above. – Keith Jackson Aug 12 '19 at 10:17
  • I didn't get you. My example works fine. If you can't adjust the changes, create a codepen, jsFiddle or codesandbox example and let me try to make it work. – Roland Aug 12 '19 at 11:05
  • I can't repro this on Codepen as it has to be written in a different way and it seems that when you do that it starts working! @birdspider knocked up a great mockup on the other answer and that works. I've lifted the code almost verbatim into a template file and it doesn't work anymore. – Keith Jackson Aug 12 '19 at 13:35
  • I set this up on Codepen https://codepen.io/tiefling/pen/eqoGgQ which also works, so having it as a template is not the issue. Am wondering if the ES6 Babel translation is breaking somewhere. – Keith Jackson Aug 12 '19 at 14:15
1

This appears to be a bug / oddity with Vue.js. Using roli roli's example I was able to take both his example and my requirement and keep tweaking until they met in the middle so I could find out the problem via process of elimination.

Here is a copy of the component with both elements together. As @birdspider commented above, I would not expect this to work in HTML. In Vue, however, it DOES....

<template>
  <div :class="`form__radio-list ${(this.def ? `form__radio-list--${this.def} js-${this.def}` : '' )}`">
    <span class="form__radio-list__intro">{{ introText }}</span>
    <ul class="form__radio-list__options">
      <li class="form__radio-list__options__item">
        <input id="yes"
               type="radio"
               :checked="value === true"
               :value="true"
               @change="value = $event.target.value">
        <label for="yes">Yes</label>
      </li>
      <li class="form__radio-list__options__item">
        <input id="no"
               type="radio"
               :checked="value === false"
               :value="false"
               @change="value = $event.target.value">
        <label for="no">No</label>
      </li>
      <li class="form__radio-list__options__item">
        <input id="awesome"
               type="radio"
               :checked="radio === 'Awesome'"
               value="Awesome"
               @change="radio = $event.target.value">
        <label for="awesome">Awesome</label>
      </li>
      <li class="form__radio-list__options__item">
        <input id="super"
               type="radio"
               :checked="radio === 'Super Awesome'"
               value="Super Awesome"
               @change="radio = $event.target.value">
        <label for="super">Super</label>
      </li>
    </ul>
    <span>Selected: {{ value }} | {{ radio }}</span>
  </div>
</template>

<script>

export default {

    props: {
        def: {
            type: String,
            required: true
        },
        introText: {
            type: String,
            default: ''
        },
        initialValue: {
            type: [String, Boolean],
            default: null
        },
        disabled: {
            type: Boolean,
            default: false
        }
    },

    data() {
        return {
            value: this.initialValue,
            radio: 'Awesome'
        }
    }
}

</script>

This will work completely fine and renders as 2 separate radio button lists - one for yes / no and one for awesome / super awesome. If you try and add a 'name' tag to the radio inputs then the checked state is no longer set in the radio button group at all.

---- UPDATE ----

This seems like a bug where an attempt to add 'name' should simply be ignored by Vue, but it isn't. However, this isn't the case if you create an ES5 style component in codepen (I am unable to repro this in codepen for this reason.)

Using ES6 style files, this component will not set any checked values (credit to @birdspider for half of this simplified example)...

<template>
  <div>
    <ul class="form__radio-list__options">
      <li class="form__radio-list__options__item">
        <input id="awesome" type="radio" name="isawesome"
               :checked="radio === 'Awesome'"
               value="Awesome"
               @change="onChange($event.target.value)">
        <label for="awesome">Awesome</label>
      </li>
      <li class="form__radio-list__options__item">
        <input id="super" type="radio" name="isawesome"
               :checked="radio === 'Super Awesome'"
               value="Super Awesome"
               @change="onChange($event.target.value)">
        <label for="super">Super</label>
      </li>
    </ul>
    <ul class="form__radio-list__options">
      <li class="form__radio-list__options__item">
        <input id="awesome" type="radio" name="other"
               :checked="radio2 === 'other'"
               value="other"
               @change="onChange2($event.target.value)">
        <label for="awesome">other</label>
      </li>
      <li class="form__radio-list__options__item">
        <input id="super" type="radio" name="other"
               :checked="radio2 === 'another'"
               value="another"
               @change="onChange2($event.target.value)">
        <label for="super">another</label>
      </li>
    </ul>
  </div>
</template>

<script>

export default {

    data() {
        return {
            radio: 'Awesome',
            radio2: 'another'
        }
    },

    methods: {

        onChange(e) {
            this.radio = e
        },
        onChange2(e) {
            this.radio2 = e
        }
    }
}

</script>

(if anyone can tell me how to get it running in Codepen or JS Fiddle like this then that would be great!)

If I remove the name attributes then it works.

If you put essentially the same thing in Codepen as a single Vue instance rather than a component file then that works too.

Keith Jackson
  • 3,078
  • 4
  • 38
  • 66
  • 1
    idk, but having a `name` does not do anything odd in this [mockup](https://codepen.io/birdspider/pen/WVYqgj) since every value change is handled by `vue@` events and :checked binding – birdspider Aug 12 '19 at 10:52
  • If you add 'name' attributes to the radio buttons then the checked value will not be set on initial load in my code above. Now I'm seriously confused! Maybe it's specific to my Vue version. – Keith Jackson Aug 12 '19 at 11:50
  • 1
    When you set value="true" it should be `:value="true"` – Roland Aug 12 '19 at 12:34
  • 1
    If you add a name attribute to the radio buttons everything is working – Roland Aug 12 '19 at 12:42