v-model for array of objects as props - vue.js

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>

Related

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"/>

vue.js problem with getting value of selected option and passing it to another child component

I'm new with vue.js, and trying to find the best way to get a value from <option> of <select> element, and store it to data() property, so it can be passed to another component.
That <select> section looks like this:
<select ref="select">
<!-- key "item" == js object in array, fetched from API -->
<option
v-for="(item, index) in arr"
v-bind:key="index"
:value="item">{{item.val}}</option>
</select>
So, what's the best way to get item.val and put it into data() ?
Thanks in advance.
new Vue({
template:'
<select v-model="selectedOption" #change="handleChange">
<option value="" disabled>--Select--</option>
<option v-for="item in data" :value="item.val">{{item.text}}</option>
</select>
',
data:{
selectedOption:''
},
methods:{
handleChange:function(event){
console.log(this.selectedOption); //you will find the value here
}
}
})
The vue template:
<select v-model="selectVal" ref="select">
<option
v-for="(item, index) in arr"
v-bind:key="index"
:value="item">{{item.val}}</option>
</select>
The js code:
data: function(){
return {
selectVal: 0,
};
},

V-IF and V-for Loop conditional with select options seems to be never entering the v-else statement

So I was wondering why this v-if and v-else statement did not work and why I to tackle it a different way.
The code is as follows
<select v-else v-model="experiment.workflow" required>
<option selected :value="null">Required: Select a Workflow {{ isChain ? 'Chain' : '' }}</option>
<option
v-if="isWorkflowChain"
v-for="workflow of data.workflows"
:key="workflow.uuid"
:value="workflow"
{{ workflow.head.name }}>
</option>
<option
v-else
v-for="workflow of data.workflowChains"
:key="workflow.uuid"
:value="workflow"
</option>
So I was given this piece of code and it looks like when loading in the data in the v-if statement was fine and the values showed in the drop down menu. When I set the value to
export default {
props: {
isWorkflowChain: {
type: Boolean
value: false
}
}
}
What should have occurred was that it should have skipped the v-if element and head into the v-else (Which I believe it does) and populate the data but the v-for statement doesn't populate the data. From first glance does anyone have any thoughts as to why?
This is a valid question, since it's not obvious that a mix of v-for with v-else is currently not supported.
note the closed feature request at
https://github.com/vuejs/vue/issues/4174
Reason
The problem is that v-for has higher priority and therefor is handled first, and v-if is handled second. That means, not only is the v-if executed on every item, but more importantly in the context of this question, it cannot access the v-else outside of the v-for.
Read more here: https://v2.vuejs.org/v2/style-guide/#Avoid-v-if-with-v-for-essential
Solution
The correct way would be isolate the two blocks, and put the v-if on a higher level, non-rendering <template> component.
<option>
<template v-if="isWorkflowChain">
<option v-for/>
</template>
<template v-else>
<option v-for/>
</template>
</option>
The quicker way to do it is to use an opposite conditional (!isWorkflowChain) with a v-for like this though.
<select v-else v-model="experiment.workflow" required>
<option selected :value="null">Required: Select a Workflow {{ isChain ? 'Chain' : '' }}</option>
<option
v-if="isWorkflowChain"
v-for="workflow of data.workflows"
:key="workflow.uuid"
:value="workflow"
>{{ workflow.head.name }}</option>
<option
v-if="!isWorkflowChain"
v-for="workflow of data.workflowChains"
:key="workflow.uuid"
:value="workflow"
>{{ workflow.head.name }}</option>
</select>
and you can make isWorkflowChain a computed. If you have multiple statements and want to capture the else, it gets a bit trickier, but you can put that logic into a computed.
Vue 3 - Breaking Change
In Vue 3 v-if will have higher precedence than v-for:
https://v3-migration.vuejs.org/breaking-changes/v-if-v-for.html#overview
Computed Properties
However, it is recommended to avoid using both on the same element, and instead of dealing with this at the template level, one method for accomplishing this is to create a computed property that filters out a list for the visible elements:
computed: {
isWorkflowChain: () => {
// do filtering
}
}
Template
<option v-for="workflow in isWorkflowChain">
{{ workflow.head.name }}
</option>
Docs: https://v3-migration.vuejs.org/breaking-changes/v-if-v-for.html#introduction
you need to close <option>, forget > of v-if option :
your code :
<option
v-if="wf = 'workflow'"
v-for="workflow of data.workflows"
:key="workflow.uuid"
:value="workflow" // here where `>` is missing
{{ workflow.head.name }}
</option>
what it's should be :
<option
v-if="wf = 'workflow'"
v-for="workflow of data.workflows"
:key="workflow.uuid"
:value="workflow">
{{ workflow.head.name }}
</option>

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

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?

How can I get value in select vue.js? vue.js 2

My case is like this
I have a component like this :
<template>
<div class="panel panel-default panel-filter">
...
<div id="collapse-location" class="collapse in">
<!-- province -->
<div style="margin-bottom: 10px">
<location-bs-select element-name="province_id" level="provinceList" type="1" module="searchByLocation"/>
</div>
<!-- city -->
<location-bs-select element-name="city_id" level="cityList" type="2" module="searchByLocation"/>
</div>
<!-- button search -->
<div class="panel-item">
<br>
<a href="javascript:;" class="btn btn-block btn-success" v-on:click="searchData">
Search
</a>
</div>
...
</div>
</template>
<script>
export default{
...
data() {
return{
...
province_id:'',
}
},
...
methods: {
...
searchData: function() {
console.log(this.province_id)
console.log(document.getElementsByName("province_id")[0].value)
console.log('testtt')
}
}
}
</script>
The component have child component, that is location-bs-select. The component used to display provincy and city
The component child like this :
<template>
<select class="form-control" v-model="selected" :name="elementName" #change="changeLocation">
<template v-for="option in options">
<template>
<option v-bind:value="option.id" >{{ option.name }}</option>
</template>
</template>
</select>
</template>
<script>
export default{
props: ['elementName', 'level','type','module'],
...
};
</script>
If I do inspect element, the result like this :
When click button search, I want to get the value of province and city
I try javascript like this :
console.log(document.getElementsByName("province_id")[0].value)
it works
But I want to use vue step. I try like this :
console.log(this.province_id)
It does not work
How can I solve it?
I hope I got you right. You want to propagate the value of the select back to the parent. The Child component COULD be like this.
removed template nesting
added change event listener and emit method
added data
And all together:
<template>
<select #change="emitChange" class="form-control" v-model="selected" :name="elementName">
<option v-for="option in options" v-bind:value="option.id" >{{ option.name }}</option>
</select>
</template>
<script>
export default{
props: ['elementName', 'level','type','module'],
data: function() {
return { selected: null }
},
methods: {
emitChange: function() {
this.$emit('changeval', this.selected);
}
}
};
</script>
Now your parent needs to listen to this emit. Here just the relevant parts of your parent
...
<location-bs-select element-name="city_id"
level="cityList"
type="2"
#changeval="changeval"
module="searchByLocation"/>
...
methods: {
changeval: function(sValue) {
this.province_id = sValue;
console.log(this.province_id);
}
}
Quickly summed up
the select value is bound to the selected prop of your data
the select has an attached change event which will emit changes
the parent will listen to this emit and will update it's relevant data prop