Multiple dropdowns with no duplicate value - vue.js

I'm trying to replicate a Vue component, which is a list of items, each item contains a dropdown and a remove button. There will be an "Add" button that add new item to the list, which is depicted in the snippet below.
The requirement is that when ever user select an option, that option will not be available (or remove) for any other item. In other words, selected option values should not be duplicated. Which is quite similar to the idea in this question (jQuery Prevent duplicate choice in multiple dropdowns)
When user re-select or remove an item, the selected option attached to it should be added again to "available" list. The option list is therefore "reactive" and dynamic.
For example, for the first item, if I select "Option 1". "Option 1" should not be in option list when "Add new item" is clicked. And if first item is removed, "Option 1" will be available for select again, etc,. . .
This is what I got so far, the idea is that option will store all option data, selectedValueArray will responsible for storing selected option value for each item, and selectableOptions array will equal to options set minus selectedValueArray. By interacting with item (changing option, remove), selectedValueArray and selectableOptions array will be changed accordingly.
I can do this with JavaScript. However I'm new to Vue and not sure how to do it in Vue effectively. The problem of the snippet I created is that because of available options comes from selectableOptions array, so when an item is removed from selectableOptions, it will also affect selected option. (e.g: If "Option 1" is removed from this array, the dropdown in the first item will be blank because "Option 1" has already been removed from selectable list). Any help is appreciated.
var app = new Vue({
el: "#app",
data: {
options: [],
items: [],
selectableOptions: [],
selectedValueArray: [],
},
mounted() {
this.options = [
{
name: "Option 1",
value: 1,
},
{
name: "Option 2",
value: 2,
},
{
name: "Option 3",
value: 3,
},
{
name: "Option 4",
value: 4,
},
{
name: "Option 5",
value: 5,
},
{
name: "Option 6",
value: 6,
}
];
this.selectableOptions = this.options;
},
methods: {
addItem: function () {
this.items.push({
'value': 0
});
},
removeItem: function (index) {
this.$delete(this.items, index);
},
changeOption: function () {
this.selectedValueArray = [];
for (let i = 0; i < this.items.length; i++) {
let selectedValue = this.items[i].value;
this.selectedValueArray.push(selectedValue);
}
this.selectableOptions = this.options.filter(
option => {
return this.selectedValueArray.indexOf(option.value) == -1;
})
},
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-for="(item, index) in items">
<select
v-model="item.value">
<option v-for="(option) in selectableOptions" :value="option.value">{{option.name}}</option>
</select>
<button #click="removeItem(index)">Remove this item</button>
</div>
<button #click="addItem">Add new item</button>
</div>

If you want to simply disable an option whose value is present in the items array of objects (which you are using for the v-model directive binding, so it reflects a "live" set of user-selected choices), then it is a matter of using a method to return a disabled state:
<option v-for="(option) in options" :value="option.value" v-bind:disabled="isDisabled(option)">{{option.name}}</option>
Then, you can define a isDisabled(option) method which returns a boolean to indicate if a given option's value is already present in your array:
isDisabled: function(option) {
return this.items.map(item => item.value).includes(option.value);
}
See proof-of-example below:
var app = new Vue({
el: "#app",
data: {
options: [],
items: [],
selectedValueArray: [],
},
mounted() {
this.options = [{
name: "Option 1",
value: 1,
},
{
name: "Option 2",
value: 2,
},
{
name: "Option 3",
value: 3,
},
{
name: "Option 4",
value: 4,
},
{
name: "Option 5",
value: 5,
},
{
name: "Option 6",
value: 6,
}
];
},
methods: {
addItem: function() {
this.items.push({
'value': 0
});
},
removeItem: function(index) {
this.$delete(this.items, index);
},
isDisabled: function(option) {
return this.items.map(item => item.value).includes(option.value);
}
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-for="(item, index) in items">
<select v-model="item.value">
<option v-for="(option) in options" :value="option.value" v-bind:disabled="isDisabled(option)">{{option.name}}</option>
</select>
<button #click="removeItem(index)">Remove this item</button>
</div>
<button #click="addItem">Add new item</button>
</div>

you have to use a computed property, that filter the selectableOptions
something like this
{
computed: {
computedSelectable() {
const chosenValues = this.selectedValueArray.map((i) => i.value);
return this.selectableOptions.filter((item) =>
!chosenValues.includes(item.value)
);
},
}
}

Improved answer, <select> element with selected disabled option will not be submitted. Use v-show instead
var app = new Vue({
el: "#app",
data: {
options: [],
items: [],
selectedValueArray: [],
},
mounted() {
this.options = [{
name: "Option 1",
value: 1,
},
{
name: "Option 2",
value: 2,
},
{
name: "Option 3",
value: 3,
},
{
name: "Option 4",
value: 4,
},
{
name: "Option 5",
value: 5,
},
{
name: "Option 6",
value: 6,
}
];
},
methods: {
addItem: function() {
this.items.push({
'value': 0
});
},
removeItem: function(index) {
this.$delete(this.items, index);
},
isShown: function(option) {
return !(this.items.map(item => item.value).includes(option.value));
},
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-for="(item, index) in items">
<select v-model="item.value">
<option v-for="(option) in options" :value="option.value" v-show="isShown(option)">{{option.name}}</option>
</select>
<button #click="removeItem(index)">Remove this item</button>
</div>
<button #click="addItem" v-show="items.length<options.length">Add new item</button>
</div>

Related

Hey! I want to implement some anchor links in vue with no library used

Here is my code below. I know there is some specific library like vue-scrollto but I want to resolve this task with no library.
I want to add isViewed true only in that certain link. But the problem is that when I clicked on links each object of that link has isViewed true instead of only one.I want it changes dynamically.
I would appreciate any help.
<template>
<div class="anchors">
<ul class="list">
<li class="item" v-for="(link, index) in anchorLinks" :key="link.id">
<a #click="goToSection(link, link.sectionId, link.id, index)" class="anchor__item-link"></a>
</li>
</ul>
</div>
</template>
export default {
name: "AnchorLinks",
data(){
return{
anchorLinks: [
{
id: 1,
sectionId: "work",
},
{
id: 2,
sectionId: "service",
},
{
id: 3,
sectionId: "partner",
},
{
id: 4,
sectionId: "partner",
},
{
id: 5,
sectionId: "partner",
},
{
id: 6,
sectionId: "partner",
},
{
id: 6,
sectionId: "partner",
}
]
}
},
methods: {
goToSection(link, sectionId, id, index){
const element = document.getElementById(sectionId);
if (element) {
link.isViewed = true;
window.scrollTo({
top: element.offsetTop,
behavior: 'smooth'
});
console.log(id)
console.log(index);
console.log(this.anchorLinks)
}
}
}
}
You should ensure that each anchorLink initially has an isViewed property,
The documentation states:
Vue cannot detect property addition or deletion
<template>
<div class="anchors">
<ul class="list">
<li class="item" v-for="(link, index) in anchorLinks" :key="link.id">
<a
#click="goToSection(link, link.sectionId, link.id, index)"
class="anchor__item-link"
></a>
</li>
</ul>
</div>
</template>
export default {
name: 'AnchorLinks',
data() {
return {
anchorLinks: [
{
id: 1,
sectionId: 'work',
isViewed: false,
},
{
id: 2,
sectionId: 'service',
isViewed: false,
},
{
id: 3,
sectionId: 'partner',
isViewed: false,
},
{
id: 4,
sectionId: 'partner',
isViewed: false,
},
{
id: 5,
sectionId: 'partner',
isViewed: false,
},
{
id: 6,
sectionId: 'partner',
isViewed: false,
},
{
id: 6,
sectionId: 'partner',
isViewed: false,
},
],
};
},
methods: {
goToSection(link, sectionId, id, index) {
const element = document.getElementById(sectionId);
if (element) {
link.isViewed = true;
window.scrollTo({
top: element.offsetTop,
behavior: 'smooth',
});
console.log(id);
console.log(index);
console.log(this.anchorLinks);
}
},
},
};
Without reinventing the wheel here, you basically need to remove the previous link.isViewed property each time you click a link.
One straightforward way to do this is to store another variable containing the active link, that you can update each time you click a new link. See simple example below (ignoring excess / cruft).
As an aside, I would consider not updating the isViewed variable at all and just storing the active link (similar to below) for reference wherever you need it.
<ul>
<li v-for="link in anchorLinks">
<a #click="goToSection(link)"></a>
</li>
</ul>
export default {
name: "AnchorLinks",
data() {
return {
activeLink: null,
// you're using an array here, lean on the index
anchorLinks: [
{ id: "work" },
{ id: "service" },
{ id: "partner" },
],
};
},
methods: {
goToSection(link) {
// remove the previous `isViewed` property
if (this.activeLink) {
delete this.activeLink.isViewed;
}
// update the active link
this.activeLink = link;
const el = document.getElementById(link.id);
if (el) {
// now this will be the only link with `isViewed = true`
link.isViewed = true;
window.scrollTo({
top: el.offsetTop,
behavior: 'smooth'
});
}
}
}
}
From what I can tell and from what you have given, I think you need to set the ids of each anchor link.
<a
:id="link.sectionId"
#click="goToSection(link, link.sectionId, link.id, index)"
class="anchor__item-link"
></a>

Default props value are not selected in vue3 options api

I created a select2 wrapper in vue3 with options API everything working fine but the problem is that when getting values from calling API it's not selected the default value in the select2 option. but when I created a static array of objects it does. I don't know why it's working when it comes from the API
Parent Component
Here you can I passed the static options array in options props and my selected value is 2 and it's selected in my Select2 component, but when passed formattedCompanies it's not which is the same format as the static options array then why is not selected any reason here..?
<template>
<Form #submitted="store()" :processing="submitting">
<div class="row">
<div class="col-lg-6">
<div class="form-group">
<label>Company Name</label>
<Select2
:options="options"
v-model="selected"
placeholder="Select Company"
/>
<ValidationError :errors="errors" error-key="name" />
</div>
</div>
</div>
</Form>
</template>
<script>
import Form from "#/components/Common/Form";
import Select2 from "#/components/Common/Select2";
export default {
components: {
Select2,
Form
},
data() {
return {
selected : 2,
companies : [],
options: [ // static array
{ id: 1, text: 'hello' },
{ id: 2, text: 'hello2' },
{ id: 3, text: 'hello3' },
{ id: 4, text: 'hello4' },
{ id: 5, text: 'hello5' },
],
}
},
mounted() {
this.getAllMedicineCompanies()
},
computed:{
formattedCompanies() {
let arr = [];
this.companies.forEach(item => {
arr.push({id: item.id, text: item.name})
});
return arr;
}
},
methods: {
getAllMedicineCompanies(){
axios.get('/api/get-data?provider=companies')
.then(({ data }) => {
this.companies = data
})
},
}
}
</script>
Select2 Component
Here is what my select2 component look like, did I do anything wrong here, please anybody help me
<template>
<select class="form-control">
<slot/>
</select>
</template>
<script>
export default {
name: "Select2",
props: {
options: {
type: [Array, Object],
required: true
},
modelValue: [String, Number],
placeholder: {
type: String,
default: "Search"
},
allowClear: {
type: Boolean,
default: true
},
},
mounted() {
const vm = this;
$(this.$el)
.select2({ // init select2
data: this.options,
placeholder: this.placeholder,
allowClear: this.allowClear
})
.val(this.modelValue)
.trigger("change")
.on("change", function () { // emit event on change.
vm.$emit("update:modelValue", this.value);
});
},
watch: {
modelValue(value) { // update value
$(this.$el)
.val(value)
.trigger("change");
},
options(options) { // update options
$(this.$el)
.empty()
.select2({data: options});
},
},
destroyed() {
$(this.$el)
.off()
.select2("destroy");
}
}
</script>
Probably when this Select2 mounted there is no companies. It is empty array after that it will make API call and it it populates options field and clear all options.
Make:
companies : null,
Change it to
<Select2
v-if="formattedCompanies"
:options="formattedCompanies"
v-model="selected"
placeholder="Select Company"
/>
It should be like this:
<template>
<Form #submitted="store()" :processing="submitting">
<div class="row">
<div class="col-lg-6">
<div class="form-group">
<label>Company Name</label>
<Select2
v-if="formattedCompanies"
:options="formattedCompanies"
v-model="selected"
placeholder="Select Company"
/>
<ValidationError :errors="errors" error-key="name" />
</div>
</div>
</div>
</Form>
</template>
<script>
import Form from "#/components/Common/Form";
import Select2 from "#/components/Common/Select2";
export default {
components: {
Select2,
Form
},
data() {
return {
selected : 2,
companies : null,
options: [ // static array
{ id: 1, text: 'hello' },
{ id: 2, text: 'hello2' },
{ id: 3, text: 'hello3' },
{ id: 4, text: 'hello4' },
{ id: 5, text: 'hello5' },
],
}
},
mounted() {
this.getAllMedicineCompanies()
},
computed:{
formattedCompanies() {
let arr = [];
this.companies.forEach(item => {
arr.push({id: item.id, text: item.name})
});
return arr;
}
},
methods: {
getAllMedicineCompanies(){
axios.get('/api/get-data?provider=companies')
.then(({ data }) => {
this.companies = data
})
},
}
}
</script>
The problem was that my parent component and Select2 component mounted at the same time that's why my computed value is not initialized so the selected value is not selected in the option,
problem solved by setTimeOut function in mounted like this
Select2 Component
<script>
mounted() {
const vm = this;
setTimeout(() => {
$(this.$el)
.select2({ // init select2
data: this.options,
placeholder: this.placeholder,
allowClear: this.allowClear
})
.val(this.modelValue)
.trigger("change")
.on("change", function () { // emit event on change.
vm.$emit("update:modelValue", this.value);
});
}, 500)
},
</script>

vue 3 - binding problem with select, if option selected is not showing in the new values, select is in blank,but it doesn't change to ""

I have two components, an Input and a Select, the select show options depend on what you write in the Input, but if the option selected isn't showing in the new options, select the default option or the first option "select option"
When I select an option and then change the options in the Select and the option selected is not in the new options, the Select shows in blank, but when I change the option selected to "", the Select doesn't change to it(Select option that has value ""), I don't know why but if I change to other option the Select changes to it...
Example:
select option 2 > write 3 characters in the input
as you can see the varaible option_selected that is binding with the select change to "", but the select dosn't change to "Select option"
Link on the documentation for this example:
Component custom events
const input_text = {
name: "input-min-length",
props: {
text: String,
is_min_length: Boolean
},
emits: ["update:text", "update:is_min_length"],
computed: {
is_min(){
return this.text.length >= 3;
}
},
watch: {
is_min(new_value){
this.$emit("update:is_min_length", this.is_min);
}
},
template: `<div><input type="text" :value="text" #input="$emit('update:text', $event.target.value)" /></div>`
};
const select_options = {
name: "select-options",
props: {
option_selected: String,
is_min_length: Boolean
},
emits: ["update:option_selected"],
data() {
return {
options: [
{text: "Select option", value: ""},
{text: "option 1", value: "1", is_min_length: true},
{text: "option 2", value: "2"},
{text: "option 3", value: "3", is_min_length: true},
]
};
},
computed: {
options_filtered(){
if(this.is_min_length == false) return this.options;
return this.options.filter((option) => option.is_min_length == true || option.value == "")
}
},
watch: {
is_min_length(){
const is_presented = this.options_filtered.some((option) => option.value == this.option_selected)
if (is_presented == false){
setTimeout(() => {
this.$emit("update:option_selected", "");
}, 0); // I try to use setTimeout, to see if it changes at all
}
}
},
template: `
<select :value="option_selected" #change="$emit('update:option_selected', $event.target.value)">
<option v-for="option in options_filtered" :value="option.value" :key="option.value">
{{ option.text }}
</option>
</select>
`
}
const app = {
components:{
"input-min-length": input_text,
"select-options": select_options
},
data() {
return {
text: "",
is_min_length: false,
option_selected: ""
}
},
template: `
<div>
<input-min-length v-model:text="text" v-model:is_min_length="is_min_length" />
<select-options v-model:option_selected="option_selected" :is_min_length="is_min_length" /><br>
<button #click="option_selected='1'">change to opt 1</button><br>
<button #click="option_selected=''">change to opt ""</button><br>
<div>
<strong>DATA:</strong><br>
<strong>text:</strong> "{{text}}"<br>
<strong>is_min_length:</strong> {{is_min_length}}<br>
<strong>option_selected:</strong> "{{option_selected}}"
</div>
</div>`
}
Vue.createApp(app)
.mount('#app')
<script src="https://unpkg.com/vue#next"></script>
<div id="app"></div>
Wow, this was a tricky one to drill down into.
What is "value"
So <select> element has no HTML value attribute, but there is a el.value property provided by the DOM API on HTMLSelectElement that gives you the selected option's value. Vue provides a binding to this value property (to be used via v-model). And that's why we can simply use :value in the <select> element.
Problem with your code:
When 'option 2' is selected, and then removed from the DOM via optionsFiltered, it sets the <select> element into an invalid state. In invalid state the element's el.value returns '' (an empty string) (Note: this is DOM API, not Vue). Now your watcher on is_min_length is triggered and emits an update:option_selected event with value '' (an empty string). As you know, Vue is reactive. Since el.value is already '', I imagine Vue does not see any need to update the DOM, and hence never calls el.value = ''. (Setting el.value to empty string even though its already an empty string does give desired behaviour, DOM API seems quite robust, its Vue that's not calling it).
Solution(s):
The easiest way would be to set the default "Select option" value to something other that '' so that it doesn't clash with the invalid state's ''. For e.g. you can set it to 0. Or maybe a string 'none'.
const input_text = {
name: "input-min-length",
props: {
text: String,
is_min_length: Boolean
},
emits: ["update:text", "update:is_min_length"],
computed: {
is_min(){
return this.text.length >= 3;
}
},
watch: {
is_min(new_value){
this.$emit("update:is_min_length", this.is_min);
}
},
template: `<div><input type="text" :value="text" #input="$emit('update:text', $event.target.value)" /></div>`
};
const select_options = {
name: "select-options",
props: {
option_selected: String,
is_min_length: Boolean
},
emits: ["update:option_selected"],
data() {
return {
options: [
{text: "Select option", value: "0"},
{text: "option 1", value: "1", is_min_length: true},
{text: "option 2", value: "2"},
{text: "option 3", value: "3", is_min_length: true},
]
};
},
computed: {
options_filtered(){
if(this.is_min_length == false) return this.options;
return this.options.filter((option) => option.is_min_length == true || option.value == "0")
}
},
watch: {
is_min_length(){
const is_presented = this.options_filtered.some((option) => option.value == this.option_selected)
if (is_presented == false){
setTimeout(() => {
this.$emit("update:option_selected", '0');
}, 0); // I try to use setTimeout, to see if it changes at all
}
}
},
template: `
<select :value="option_selected" #change="$emit('update:option_selected', $event.target.value)">
<option v-for="option in options_filtered" :value="option.value" :key="option.value">
{{ option.text }}
</option>
</select>
`
}
const app = {
components:{
"input-min-length": input_text,
"select-options": select_options
},
data() {
return {
text: "",
is_min_length: false,
option_selected: "0"
}
},
template: `
<div>
<input-min-length v-model:text="text" v-model:is_min_length="is_min_length" />
<select-options v-model:option_selected="option_selected" :is_min_length="is_min_length" /><br>
<button #click="option_selected='1'">change to opt 1</button><br>
<button #click="option_selected='0'">change to opt ""</button><br>
<div>
<strong>DATA:</strong><br>
<strong>text:</strong> "{{text}}"<br>
<strong>is_min_length:</strong> {{is_min_length}}<br>
<strong>option_selected:</strong> "{{option_selected}}"
</div>
</div>`
}
Vue.createApp(app)
.mount('#app')
<script src="https://unpkg.com/vue#next"></script>
<div id="app"></div>
Another solution is to call the el.value = '' yourself alongside emitting the event, but I don't recommend it as it makes your code harder to understand for fellow Vue developers (you should adhere good coding practices even if you're the only one working on the project):
const input_text = {
name: "input-min-length",
props: {
text: String,
is_min_length: Boolean
},
emits: ["update:text", "update:is_min_length"],
computed: {
is_min(){
return this.text.length >= 3;
}
},
watch: {
is_min(new_value){
this.$emit("update:is_min_length", this.is_min);
}
},
template: `<div><input type="text" :value="text" #input="$emit('update:text', $event.target.value)" /></div>`
};
const select_options = {
name: "select-options",
props: {
option_selected: String,
is_min_length: Boolean
},
emits: ["update:option_selected"],
data() {
return {
options: [
{text: "Select option", value: ""},
{text: "option 1", value: "1", is_min_length: true},
{text: "option 2", value: "2"},
{text: "option 3", value: "3", is_min_length: true},
]
};
},
computed: {
options_filtered(){
if(this.is_min_length == false) return this.options;
return this.options.filter((option) => option.is_min_length == true || option.value == "")
}
},
watch: {
is_min_length(){
const is_presented = this.options_filtered.some((option) => option.value == this.option_selected)
if (is_presented == false){
setTimeout(() => {
this.$emit("update:option_selected", "");
document.getElementById("myselect").value = "";
}, 0); // I try to use setTimeout, to see if it changes at all
}
}
},
template: `
<select id="myselect" :value="option_selected" #change="$emit('update:option_selected', $event.target.value)">
<option v-for="option in options_filtered" :value="option.value" :key="option.value">
{{ option.text }}
</option>
</select>
`
}
const app = {
components:{
"input-min-length": input_text,
"select-options": select_options
},
data() {
return {
text: "",
is_min_length: false,
option_selected: ""
}
},
template: `
<div>
<input-min-length v-model:text="text" v-model:is_min_length="is_min_length" />
<select-options v-model:option_selected="option_selected" :is_min_length="is_min_length" /><br>
<button #click="option_selected='1'">change to opt 1</button><br>
<button #click="option_selected=''">change to opt ""</button><br>
<div>
<strong>DATA:</strong><br>
<strong>text:</strong> "{{text}}"<br>
<strong>is_min_length:</strong> {{is_min_length}}<br>
<strong>option_selected:</strong> "{{option_selected}}"
</div>
</div>`
}
Vue.createApp(app)
.mount('#app')
<script src="https://unpkg.com/vue#next"></script>
<div id="app"></div>

Vuetify, set value to v-select ignoring items

I'm trying to set a given string to v-select but no idea of how to do this (it is only displayed when I use a a valid :items value), I dropped using v-model because I have an object to store the data, and I'm also constantly adding/deleting items so users cannot choose a given option twice.
Here's the code:
<v-select v-for="(item, index) in securityQuestions" :key="index"
:menu-props="{ offsetY: true }"
outlined
dense
placeholder="Choose a question"
:items="optionsComputed"
:value="item.question" // * <---------------------- this is my goal
#input="addQuestion(item, index, $event)"
></v-select>
data() {
return {
options: [
"Question 1",
"Question 2",
"Question 3",
"Question 4",
"Question 5"
],
securityQuestions: [
{ question: "", value: "" },
{ question: "", value: "" },
{ question: "", value: "" },
{ question: "", value: "" },
{ question: "", value: "" },
{ question: "", value: "" }
]
}
},
methods: {
addQuestion(item, index, event) {
if (item.question !== "") {
this.options.push(item.question);
}
this.options.splice(this.options.indexOf(event), 1);
this.securityQuestions[index].question = event;
}
}
Any idea of how to achieve this?
Just use item-value and item-text props of v-select.
This code is working but have a problem, you can't have 2 questions with same answer in value.
<template>
<v-app v-if="securityQuestions && options && answers">
<v-select
v-for="(item, index) in securityQuestions"
:key="index"
:menu-props="{ offsetY: true }"
outlined
dense
:disabled="answers[index] !== undefined"
:placeholder="answers[index] || options[index]"
:items="questions"
item-text="question"
item-value="value"
#input="addQuestion(item, index, $event)"
></v-select>
Your selection: {{ answers }}
</v-app>
</template>
<script>
export default {
data() {
return {
options: [
"Question 1 - Blablabla?",
"Question 2 - What more?",
"Question 3 - You did it?",
"Question 4 - Of couse?",
"Question 5 - Anything more?",
"Question 6 - Goal!"
],
securityQuestions: [
{ question: "Option 1", value: "O1", used: false },
{ question: "Option 2", value: "O2", used: false },
{ question: "Option 3", value: "O3", used: false },
{ question: "Option 4", value: "O4", used: false },
{ question: "Option 5", value: "O5", used: false },
{ question: "Option 6", value: "O6", used: false }
],
answers: [],
optionSelected: ""
};
},
methods: {
addQuestion(item, index, value) {
this.answers[index] = value;
this.securityQuestions[
this.securityQuestions.findIndex(
el => el.value === value && el.used === false
)
].used = true;
}
},
computed: {
questions() {
return this.securityQuestions.filter(obj => {
return obj.used === false;
});
}
}
};
</script>
Here you are codesandox:
https://codesandbox.io/s/vue-with-vuetify-eagles-413zf

Two-Way Binding in Component Scope

Suppose I'm trying to make a simple questionnaire, where the user answers a list of questions.
new Vue(
{
el: "#app",
data:
{
questions:
[
{
id: 1,
name: "What is your favorite color?",
selectedId: 2,
choices:
[
{ id: 1, name: "red" },
{ id: 2, name: "green" },
{ id: 3, name: "blue" },
]
},
...
]
}
});
How do I go about making a question component with two-way binding. That is, if the user swaps their favorite color from green to red, by clicking on the respective input, the selectedId will automatically update. I'm not very clear on how v-model works within a component. Does it only have access to the components data? Also, I don't understand the difference between props/data.
There are lots of ways you can approach this, here's my attempt:
let id = 0;
Vue.component('question', {
template: '#question',
props: ['question'],
data() {
return {
radioGroup: `question-${id++}`,
};
},
methods: {
onChange(choice) {
this.question.selectedId = choice.id;
},
isChoiceSelected(choice) {
return this.question.selectedId === choice.id;
},
},
});
new Vue({
el: '#app',
data: {
questions: [
{
title: 'What is your favorite color?',
selectedId: null,
choices: [
{ id: 1, text: 'Red' },
{ id: 2, text: 'Green' },
{ id: 3, text: 'Blue' },
],
},
{
title: 'What is your favorite food?',
selectedId: null,
choices: [
{ id: 1, text: 'Beans' },
{ id: 2, text: 'Pizza' },
{ id: 3, text: 'Something else' },
],
},
],
},
});
.question {
margin: 20px 0;
}
<script src="https://rawgit.com/yyx990803/vue/master/dist/vue.js"></script>
<div id="app">
<question v-for="question of questions" :question="question"></question>
</div>
<template id="question">
<div class="question">
<div>{{ question.title }}</div>
<div v-for="choice of question.choices">
<input type="radio" :name="radioGroup" :checked="isChoiceSelected(choice)" #change="onChange(choice)"> {{ choice.text }}
</div>
<div>selectedId: {{ question.selectedId }}</div>
</div>
</template>