Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Vue.js v-model with dynamic list of radio buttons

I am trying to make a reuseable vue radio-button component that will take a variable name, and an object containing labels and values, then render a list of radio buttons using v-for.

I have had success with each half of the problem, but have not managed to combine them:

  1. I can make a set of radio buttons bound to the data model, where the buttons are defined statically in the template, but I don't know how to make the list dynamic. Here is the code for that:

//component
const Radio = {
	template: '#test',
  prop: ['value'],
  data () {
    return {
      selected: this.value
    }
  },
  model: {
	  prop: 'value',
      event: 'change'
  },
  methods: {
    handleClickInput (e) {
      this.$emit('change', this.selected)
    }
  }
}

//app
var app2 = new Vue({
  el: '#app2',
  data: { 	
  		door: '',
			doorOptions: {
				'Yes': 1,
				'No': 0,
			}
	},
  components: { Radio, }
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app2">
  <radio v-model="door"></radio>
  <p>
    door = {{door}}
  </p>
</div>


<template id="test">
  <div>
    <input type="radio" value="0" v-model="selected" @change="handleClickInput">0
    <input type="radio" value="1" v-model="selected" @change="handleClickInput">1
  </div>
</template>
  1. I can make a dynamic list of radio buttons based on an "options" object, but can't find a way to bind them to the data model. Here is the code for that:

// component
Vue.component('radio-set', {
  template: '#radio-set',
  props: {
			'label-name': '',
			'variable': '',
			'options': '',
		},
  methods: {
    clicked: function(variable, key, value) {
    // none of this is right, it doesn't update the vue data model
		window[variable] = value; //assign the new value to the dynamic variable name
		selected = value;
		this.$emit("click-event", variable) //create the click event for model updating by the parent
    }
  },
})

//app
var app = new Vue({
  el: '#vueApp',
  data: {
    door:'initial value',
		doorOptions: {
		  'Yes':1,
		  'No':0,
      'Maybe':5,
      'A new option':25
		},
		
  },
  methods: {
		buttonClick: function(p1){
			console.log(p1+': '+window[p1]); //the variable was assigned inside the child component
		}
  }
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="vueApp">
	<radio-set 
		label-name="Radio button set" 
		variable="door" 
		:options="doorOptions"
		@click-event="buttonClick" 
	>door: {{door}}
	</radio-set>
</div>

<template id="radio-set">
	<div>
		<label>{{labelName}}:</label>
		<button 
      type="button" 
      v-for="(val, key) in options"
      @click="clicked(variable, key, val)" 
      >
      {{ key }}
		</button>
	 </div>
</template>

Could anyone help with a couple of pointers on how I could move forwards?

like image 441
Tom Avatar asked Oct 28 '19 12:10

Tom


People also ask

How do you create a dynamic radio button?

For creating dynamic RadioButton, we need to use android. view. ViewGroup. LayoutParams which configures the width and height of views and implements setOnCheckedChangeListener() method of RadioGroup class.

How do you use radio buttons in Vue?

Radio button is an element that allows the user to select only one option from multiple options. You can check my previous articles on Vue. js from the below mentioned links.

What is V-model lazy in Vue?

.lazy. By default, v-model syncs the input with the data after each input event (with the exception of IME composition as stated above). You can add the lazy modifier to instead sync after change events: <!-- synced after "change" instead of "input" --> <input v-model.


3 Answers

As @PierreSaid mentioned, you can read more about v-model usage on custom componet.

This is an other example to use input[type="radio"] and emit change event back to parent componet.

// component
Vue.component('radio-set', {
  template: '#radio-set',
  props: {
    'label-name': '',
    'value': '',
    'options': '',
  }
})

//app
var app = new Vue({
  el: '#vueApp',
  data() {
    return {
      door: null,
      doorOptions: {
        'Yes': 1,
        'No': 0,
        'Maybe': 5,
        'A new option': 25
      }
    };
  }
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
<div id="vueApp">
  <radio-set label-name="Radio button set" v-model="door" :options="doorOptions"></radio-set>
  door: {{door}}
</div>

<template id="radio-set">
  <div>
    <div>{{labelName}}:</div>
    <label v-for="(val, key) in options" :key="val">
      <input type="radio" 
        :name="labelName" 
        :value="val" 
        :checked="val == value" 
        @change="$emit('input', val)"> 
      {{ key }}
    </label>
  </div>
</template>
like image 157
Ja9ad335h Avatar answered Nov 15 '22 11:11

Ja9ad335h


First of all : For your options it would be easier to have an array.

      doorOptions: [
        { key: "Yes", value: 1 },
        { key: "No", value: 0 },
        { key: "Maybe", value: 5 },
        { key: "A new option", value: 25 }
      ]
    };

That way you can iterate over it.

Also a good way to synchronise the selected value between your custom component and your app would be to use v-model.

A tutorial to implement v-model

That way we can create a reusable component like that :

<template>
  <div>
    <label>{{labelName}}:</label>
    <button
      type="button"
      v-for="(val, idx) in options"
      :key="idx"
      @click="clicked(val)"
    >{{ val.key }}</button>
  </div>
</template>

<script>
export default {
  props: ["value", "options", "labelName"],
  methods: {
    clicked(val) {
      this.$emit("input", val);
    }
  }
};
</script>

And use it like this

<template>
  <div id="app">
    <radio-set v-model="selected" label-name="Radio button set" :options="doorOptions"/>
    Selected : {{selected.key}}
  </div>
</template>

<script>
import RadioSet from "./components/RadioSet";

export default {
  name: "App",
  components: {
    RadioSet
  },
  data() {
    return {
      selected: null,
      doorOptions: [
        { key: "Yes", value: 1 },
        { key: "No", value: 0 },
        { key: "Maybe", value: 5 },
        { key: "A new option", value: 25 }
      ]
    };
  }
};
</script>

Live demo

like image 25
Pierre Said Avatar answered Nov 15 '22 11:11

Pierre Said


Based on one of the other answers, I ended up with this for Vue 3. There are some breaking changes with how v-model works, namely in the props names. (https://v3-migration.vuejs.org/breaking-changes/v-model.html)

Styling is rudimentary, but I wanted something to indicate what the active selection was.

<template>
  <div>
    <label>{{ labelName }}:</label>
    <button
      :class="val.key === modelValue ? 'active' : 'inactive'"
      type="button"
      v-for="(val, idx) in options"
      :key="idx"
      @click="clicked(val)"
    >
      {{ val.key }}
    </button>
  </div>
</template>

<script>
export default {
  props: ["modelValue", "options", "labelName"],
  methods: {
    clicked(val) {
      console.log("emitting click", val.key);
      this.$emit("update:modelValue", val.key);
    },
  }
};
</script>

<style scoped>
button {
  padding: 10px;
  margin: 5px;
}
.active {
  background-color: #42b983;
}
.inactive {
  background-color: lightgray;
}
</style>

It gets used in the same way. I just map a basic array to {key, value} since that's all I need, but it would be easy to do something more explicit.

<template>
    <radio-set
      label-name="On / Off"
      v-model="myValue"
      :options="options.radioOptions"
    ></radio-set>
</template>


<script>
import RadioSet from "./RadioSet.vue";

let options = {
  radioOptions: ["on", "off"].map((x) => {
    return { key: x, val: x };
  }),
};

export default {
  name: "MyComponent",
  components: {
    RadioSet,
  },
  data: () => {
    return {
      options,
      myValue: "unknown",
    };
  },
};
</script>

like image 23
emragins Avatar answered Nov 15 '22 10:11

emragins