how to use v-model in nested custom components in vue.js? - vuejs2

How can I use v-model for two level deep nested components?
e.g. in HTML
<opening-hr-field v-model="day"> </opening-hr-field>
here day is an object e.g. {is24Open: true, startTime: '5:00 PM'}
JS template
<template type="text/x-template" id="opening-hr-field-template">
<div>
<input type="checkbox" v-model="value.is24Open"> 24 hour
<time-select v-model = "value.startTime"></time-select>
</div>
</template>
<template type="text/x-template" id="time-select-template">
<select :value="value"
v-on:input="$emit('input', $event.target.value)">
<option v-for="t in getHours()">
{{ t }}
</option>
</select>
</template>
Here, I have two level deep v-model. How can I propagate the emit from 2nd template to first template and all way up to the parent? Can you please show me an example?

Related

multiple select box using v-for

I have multiple select boxes which are rendered using a v-for loop, what I am trying to do is to store each option value for each select box in an array but when I select an option it only stores the chosen option, not multiple options
<template v-for="(gettailor) in gettailors" :key="gettailor.id">
<div class="col-sm-6" >
<div class="form-floating" >
<p>{{ gettailor.names }}</p>
</div>
</div>
<div class="col-sm-6">
<div class="form-floating">
<select class="form-select shadow-none" id="tailors" v-model="tailors" required>
<template v-for="(item) in gettailor.tailor" :key="item.id">
<option :value="item.id">{{ item.tailor }}</option>
</template>
</select>
<label for="tailors">Choose {{ gettailor.names }}</label>
</div>
</div>
</template>
<script>
import { ref, watch,onMounted } from 'vue';
const tailors = ref([]);
</script>
I think tailors should be an object, then you bind the select value to a value in the object:
<select
class="form-select shadow-none"
id="tailors[]" <-------- generates several selects with same id
v-model="tailors[gettailor.id]" <--------- bind to object value
required
>
Of course you can also use an array, but it does not work well with reactivity if you use loop index as key (if order changes, you don't know where the values came from), and if you use tailor id, you get a sparse array, which is probably hard to work with.

Is it possible to use a prop as a v-model value?

Is it possible to use the value of a prop as the input's v-model?
I normally do the following when creating an input:
<template>
<form>
<input v-model="form.email" type="email"/>
</form>
</template>
<script>
export default {
data() {
return {
form: {
email: '',
}
}
}
}
</script>
But now I'm trying to achieve the following where this.myProp is used within the v-model without being displayed as a string on the input:
<template>
<form>
<input v-model="this.myProp" type="email"/>
</form>
</template>
<script>
export default {
props: ['myProp'] // myProp = form.email for example (to be handled in a parent component)
}
</script>
Yes, but while using it in parent component. In child component you need to extract value and #input instead of using v-model (v-model is shortcut for value="" and #input) Here is an example of input with label, error and hint in Vue 3 composition API.
BaseInput.vue
<template>
<div class="flex flex-col">
<label>{{ label }}</label>
<input v-bind="$attrs" :placeholder="label" :value="modelValue" #input="$emit('update:modelValue', $event.target.value)">
<span v-for="item of errors" class="text-red-400">{{ item.value }}</span>
<span v-if="hint" class="text-sm">{{ hint }}</span>
</div>
</template>
<script setup>
defineProps({ label: String, modelValue: String | Number, errors: Array, hint: String })
defineEmits(['update:modelValue'])
</script>
Using v-bind="$attrs" you target where attributes like type="email" need to be applied in child component. If you don't do it, it will be added to the top level DOM element. In above scenario <div>.
ParentComponent.vue
<BaseInput type="email" v-model="formData.email" :label="Email" :errors="formErrors.email"/>

Vue2 generated select triggers event for every other selects

I have a Vue2 project with Buefy extension. Then I have an array of objects which is rendered in the template with one select component for each item. Everything works, but if I trigger #input event on one of the select elements it triggers input event for all selects in the list. I dont understand what is wrong with that.
<div v-for="(inv, index) in pendingInvitations" :key="index" class="columns is-desktop">
<div class="column is-4">{{inv.email}}</div>
<div class="column is-4">
<b-field class="mb-5">
<b-select v-if="invitationRoles"
:input="changeInvitationRole(index)"
v-model="pendingInvitations[index].role"
:placeholder="role">
<option v-for="(value, key) in invitationRoles"
:key="key"
:value="value">
{{ value }}
</option>
</b-select>
</b-field>
</div>
</div>
...
changeInvitationRole(index){
console.log(index);
},
If I change the role and there are three items in the list the console.log() writes 0, 1, 2 as indexes for all items. Why it happens to me? I expect only current itmes index in the log.
Try replacing input with change

v-model for array of objects as props

I pass v-model as props with array of objects, while clicking on one or more options in my select I want to get the whole object as a value.
//select.vue
<template>
<select
class="filter__select"
multiple="true"
:value="value"
#input="$emit('input', $event.target.value)"
>
<option
class="filter__option"
v-for="item in options"
:value="item"
:key="item.id"
>
{{ item.name }}
</option>
</select>
</template>
<script lang="ts">
.....
#Prop({ required: true, type: Array }) options!: ContinentI[] | CountryI[];
#Prop({ required: true, type: Array }) value!: ContinentI[] | CountryI[];
......
</script>
//filters.vue
.......
<Select :options="continentsList" v-model="multipleContinents"
>Continents</Select>
........
When I click on one of more options I get [object Object] in console and my mapping function gets messed because of "continents.map is not a function" which worked before trying to pass it as props. How can I properly get the whole object in #input?
the option value attribute accepts a number or string not object so you should bind it to the id then when you emit the selected value you should find the correspondant object :
<select
class="filter__select"
multiple="true"
:value="value"
#input="$emit('input', options.find(option=>option.id === $event.target.value))"
>
<option
class="filter__option"
v-for="item in options"
:value="item.id"
:key="item.id"
>
{{ item.name }}
</option>

in Vue, Why key duplication occurs when if and for are on a label?

The following code will report an error:key duplication
<option-group
v-if="dataSource[0] && dataSource[0].options"
v-for="item in dataSource"
:key="item.label"
:label="item.label"
>
...
</option-group>
<Option
v-else
v-for="option in dataSource"
:label="option.label"
:value="option.value"
:key="option.value"
></Option>
but following code will not report the error, when I wrap the code with 'template' label:
<template v-if="dataSource[0] && dataSource[0].options">
<option-group
v-for="item in dataSource"
:key="item.label"
:label="item.label"
>
...
</option-group>
</template>
<template v-else>
<Option
v-for="option in dataSource"
:label="option.label"
:value="option.value"
:key="option.value"
></Option>
</template>
I want to know why?
It's because in your first example both the option-group and Option components are rendered thus same key is in two different elements, one in option-group and one in Option.
Both are rendered because you used v-for and v-if at the same time. It is not recommended because
VueJS prioritizes the v-for directive over the v-if directive. So under the hood, it loops through every element and THEN checks the v-if conditional.
You can read more about it here