Vue v-for loop binding attribute - vue.js

I'm trying to figure out how to do this in vue, I'm stuck trying to bind the value of the "selected" in the options.
In the simplified code below, I get exactly what I want, but only for the first product. It binds correctly with the first computed property:
<template>
<div>
<div v-for="index in 2" :key="index">
<select>
<option :selected="product1 === ''">Empty</option>
<option
v-for="(product, index) of products"
:key="index"
:selected="product1 === product.name"
>{{product.name}}</option>
</select>
</div>
</div>
</template>
<script>
// chosen products come from vuex store
computed: {
product1() {
return store.state.product1;
},
product2() {
return store.state.product2;
},
}
</script>
But then how can I change this to be automatic in the v-for loop, probably using the index?
What I need is actually a loop that will render this:
<template>
<div>
<select>
<option :selected="product1 === ''">Empty</option>
<option
v-for="(product, index) of products"
:key="index"
:selected="product1 === product.name"
>{{product.name}}
</option>
</select>
<select>
<option :selected="product2 === ''">Empty</option>
<option
v-for="(product, index) of products"
:key="index"
:selected="product2 === product.name"
>{{product.name}}
</option>
</select>
</div>
</template>
I've tried using something like:
:selected="`product${index}` === product.name"
but that gives a string, not the computed property value...

you can pass parameter to your computed property:
<template>
<div>
<div v-for="index in 2" :key="index">
<select>
<option :selected="getProduct(index) === ''">Empty</option>
<option
v-for="(product, index) of products"
:key="index"
:selected="getProduct(index) === product.name"
>{{product.name}}</option>
<script>
// chosen products come from vuex store
computed: {
getProduct() {
return index=>
store.state['product'+index];
},
}
</script>

You can put the selected products in an array and then access it by index:
computed: {
selectedProducts () {
return [store.state.product1, store.state.product2];
}
}
And then you can do :selected="selectedProducts[index] === product.name".

Related

Creating input tag as v-model's length

<div v-if="quesType === 'Çoktan Seçmeli'" class="row p-3 bg-dark text-light">
<div class="col-4">
<select v-model="coktanSecmeli" class="form-select" name="" id="">
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5</option>
<option value="6">6</option>
</select>
</div>
<div v-for="item in coktanSecmeli">
<input type="text"/>
</div>
</div>
export default defineComponent({
name: "SoruEkle",
data() {
const quesType = "";
const coktanSecmeli = 0;
return {
quesType,
coktanSecmeli,
};
},
components: {
ErrorMessage,
Field,
Form,
},
props: {
widgetClasses: String,
},
methods: {},
});
i tried but i cant fix this. how can i get v-model's length and use this length to create html tag as this model's length. i also tried with array and v-html but it didn't worked.
You have to create a range for coktanSecmeli. Let's define computed property for this:
range() {
return [...Array(this.coktanSecmeli).keys()];
}
or with a standard syntax:
range() {
return Array.from(Array(this.coktanSecmeli).keys());
}
Then you should use this range for v-for:
<div v-for="key in range">
<input type="text" :key="key"/>
</div>

How to get object relation inside <option> loop in Vue?

Let's go to the point of the question. I have a selection component, it looks like this:
<template>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label for="category_id">
Product Category
</label>
<select
name="category_id"
id="category_id"
:class="form.errors.has('category_id') ? 'form-control is-invalid' : 'form-control'"
v-model="form.sharedState.category_id">
<option value="" disabled hidden>Select Category</option>
<option
v-for="category in categories"
:key="category.id"
v-text="category.name"
:value="category.id"
#click="$emit('category-selected', category.sub_categories)">
</option>
</select>
<small
class="form-text text-danger"
v-if="form.errors.has('category_id')"
v-text="form.errors.get('category_id')"></small>
</div>
</div>
<div class="col-md-6">
<div
class="form-group"
v-if="revealSubCategory"
#category-selected="show">
<label for="category_id">
Sub Category
</label>
<select
name="sub_category_id"
id="sub_category_id"
:class="form.errors.has('sub_category_id') ? 'form-control is-invalid' : 'form-control'"
v-model="form.sharedState.sub_category_id">
<option value="" disabled hidden>Select Sub Category</option>
<option
v-for="subcategory in subcategories"
:key="subcategory.id"
v-text="subcategory.name"
:value="subcategory.id">
</option>
</select>
<small
class="form-text text-danger"
v-if="form.errors.has('category_id')"
v-text="form.errors.get('category_id')"></small>
</div>
</div>
</div>
</template>
<script>
import BaseCard from './BaseCard.vue';
export default {
components: {
BaseCard
},
data() {
return {
categories: [],
revealSubCategory: false,
subcategories: [],
form: new Form({
sharedState: product.data
})
}
},
mounted() {
this.getCategories();
},
methods: {
getCategories() {
axios.get('categories')
.then(({data}) => this.categories = data);
},
show(subcategories) {
this.revealSubCategory = true;
this.subcategories = subcategories
}
}
}
</script>
And a select sub category input (it is there on the second column) which is will be displayed once the user has selected one of the categories options. Each category option has relation to sub categories taken from the API.
How can I get the sub categories and display the input? I already tried #change on the select tag but I can't pass the sub category object because it is outside the loop. And #click event seems to be not working in an option tag.
You can watch the v-model of the first select and change subcategory.
watch:{
"form.sharedState.category_id": function (val) {
// update subcategories here
}

VueJS: How do you provide a context variable to #change handler?

I have a select widget inside a loop:
<div v-for="(item, index) in items" :key="index">
<select #change="handleChange">
<option value="A">A</option>
<option value="B">B</option>
<option value="C">C</option>
</select>
</div>
When handleChange is triggered, I want to get following pieces of information:
index of the parent loop
value of the selected option
Above code only gives me #2. How do I get #1?
There are several ways that you can do this:
Solution 1: Bind index to an arbitrary HTML5 data- attribute
This approach involves using :data-index="index" on your select element, and then using the e.target.dataset.index to retrieve the bound index of the element
new Vue({
el: '#app',
data: {
items: ['lorem', 'ipsum', 'dolor', 'sit', 'amet']
},
methods: {
handleChange(e) {
console.log(e.target.value, e.target.dataset.index);
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.min.js"></script>
<div id="app">
<div v-for="(item, index) in items" :key="index">
<select #change="handleChange" :data-index="index">
<option value="A">A</option>
<option value="B">B</option>
<option value="C">C</option>
</select>
</div>
</div>
Of course, you can store the index as any other arbitrarily-named data- attribute, e.g. <select #change="handleChange" :data-abc="index"> and then simply accessing it in your JS using e.target.dataset.abc.
Solution 2: Pass index as an argument to the #click callback
This method involves passing $event and index as parameters to your handleChange callback. The $event variable in VueJS allows you to pass the original JS event to the handler:
new Vue({
el: '#app',
data: {
items: ['lorem', 'ipsum', 'dolor', 'sit', 'amet']
},
methods: {
handleChange(e, idx) {
console.log(e.target.value, idx);
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.min.js"></script>
<div id="app">
<div v-for="(item, index) in items" :key="index">
<select #change="handleChange($event, index)">
<option value="A">A</option>
<option value="B">B</option>
<option value="C">C</option>
</select>
</div>
</div>
for parent loop index,
<select #change="handleChange(index)">
for value of selected option, use v-model for two way binding

Set same value for sibling component's select

Using v-for, I am looping through a component. The component is for each client. In this component, I have same form for each client and when a select value is selected for the first component (client 1), I want to select this value for every client.
Do I need to pass the data to the root and create a single source of truth variable?
I tried setting up a basic version:
<div id="app">
<my-comp v-for="x in 2" v-bind:val="x"></my-comp>
</div>
Vue.component('my-comp', {
props: ['val'],
template: `
<div>
<div>
<label>Status</label>
<select :data-client="val" #change="statusChanged">
<option selected="" disabled="" value="0"></option>
<option value="xxx">Xxx</option>
<option value="yyy">Yyy</option>
<option value="zzz">Zzz</option>
</select>
</div>
</div>
`,
methods: {
statusChanged(e) {
var client = e.target.getAttribute('data-client')
if (client == 1) {
alert('set same value for client 2')
}
}
}
})
new Vue({
el: '#app',
})
Here is a fiddle: https://jsfiddle.net/w53164t2/
I considered a little bit after my original answer and have come up with something I think is a little bit more real world than the example fiddle provided in the original question; specifically it is easy to make all the selects reflect the same value if they are all using the same source value, however I expect in a real world scenario each component would be independently bound to a single client. Each client would want their individual value to change, with the one caveat that if a "master" client changed, then all non-master clients should change to the master client's value.
To that end, this might be a case where I think a component specific bus is appropriate. The master would emit an event when it's value changed and the the other clients would set their value with respect to the master.
console.clear()
const MyCompBus = new Vue()
Vue.component('my-comp', {
props: ['val', 'master'],
computed:{
selected:{
get(){return this.val},
set(v){
this.$emit('update:val', v)
if (this.master)
MyCompBus.$emit("master-updated", v)
}
}
},
methods:{
onMasterUpdated(newMasterValue){
if (this.master) return
this.selected = newMasterValue
}
},
created(){
MyCompBus.$on('master-updated', this.onMasterUpdated)
},
beforeDestroy(){
MyCompBus.$off('master-updated', this.onMasterUpdated)
},
template: `
<div>
<div>
<label>Status</label>
<select v-model="selected">
<option selected="" disabled="" value="0"></option>
<option value="xxx">Xxx</option>
<option value="yyy">Yyy</option>
<option value="zzz">Zzz</option>
</select>
</div>
</div>
`,
})
new Vue({
el: '#app',
data:{
masterValue: null,
clients:[
{id: 1, selectedValue: null, master: true},
{id: 2, selectedValue: null},
{id: 3, selectedValue: null},
{id: 4, selectedValue: null},
]
}
})
<script src="https://unpkg.com/vue"></script>
<div id="app">
<my-comp v-for="client in clients"
:val.sync="client.selectedValue"
:master="client.master"
:key="client.id">
</my-comp>
{{clients}}
</div>
Original Answer
Bind them all to the same value using v-model.
Vue.component('my-comp', {
props: ['value'],
computed:{
selected:{
get(){return this.value},
set(v){this.$emit('input', v)}
}
},
template: `
<div>
<div>
<label>Status</label>
<select v-model="selected">
<option selected="" disabled="" value="0"></option>
<option value="xxx">Xxx</option>
<option value="yyy">Yyy</option>
<option value="zzz">Zzz</option>
</select>
</div>
</div>
`,
})
And in the template:
<my-comp v-for="x in 2" v-model="selectedValue" :key="x"></my-comp>
Here is the updated fiddle.
If you want to stick with val as the property you can use .sync instead.
Vue.component('my-comp', {
props: ['val'],
computed:{
selected:{
get(){return this.val},
set(v){this.$emit('update:val', v)}
}
},
template: `
<div>
<div>
<label>Status</label>
<select v-model="selected">
<option selected="" disabled="" value="0"></option>
<option value="xxx">Xxx</option>
<option value="yyy">Yyy</option>
<option value="zzz">Zzz</option>
</select>
</div>
</div>
`,
})
And in the template:
<my-comp v-for="x in 2" :val.sync="selectedValue" :key="x"></my-comp>
Example fiddle.
If you want just one of them designated as a "master" select, then add a property that does so.
Vue.component('my-comp', {
props: ['val', 'master'],
computed:{
selected:{
get(){return this.val},
set(v){if (this.master) this.$emit('update:val', v)}
}
},
template: `
<div>
<div>
<label>Status</label>
<select v-model="selected">
<option selected="" disabled="" value="0"></option>
<option value="xxx">Xxx</option>
<option value="yyy">Yyy</option>
<option value="zzz">Zzz</option>
</select>
</div>
</div>
`,
})
And in the template:
<my-comp v-for="x in 5" :val.sync="selectedValue" :master="1 == x" :key="x"></my-comp>
Example fiddle.

Why required not working in vue.js 2?

You can see my case below
My vue component is like this :
<template>
<select class="form-control" :name="elementName" v-model="selected" :required="module === 'addProduct'" >
<option>Choose</option>
<option v-for="option in options" v-bind:value="option.id" >{{ option.name }}</option>
</select>
</template>
<script>
...
export default {
...
props: ['elementName', 'module'],
data() {
return {
selected: 'Choose'
};
},
...
};
</script>
The result is like this :
I don't select anything. I click button submit, the required not working. It not display the required
I try like this :
<option value="">Choose</option>
It works. But, when accessed first time, option choose not show
How can I solve this problem?
See their example: https://v2.vuejs.org/v2/guide/forms.html#Select
It doesn't display anything because you have: selected: 'Choose' but you have no option with value="Choose". (the value is the empty string, "Choose" is just the inner text of the option element).
Try this:
<template>
<select class="form-control" :name="elementName" v-model="selected" :required="module === 'addProduct'" >
<option disabled value="">Choose</option>
<option v-for="option in options" v-bind:value="option.id" >{{ option.name }}</option>
</select>
</template>
<script>
...
export default {
...
props: ['elementName', 'module'],
data() {
return {
selected: ''
};
},
...
};
</script>