Vue.js custom select component with v-model
Posted By: Anonymous
I want to create a custom select component in Vue.js. Since I need specific options styling, I need to create ‘select’ made of div’s etc that looks and acts like a real html select.
Currently I have something like this:
Vue.component('child', {_x000D_
template: `<div class="component-container" @click="showOptions = !showOptions">_x000D_
<div class="component__select">_x000D_
<span class="component__select--name">Select Fruit</span>_x000D_
_x000D_
<span class="c-arrow-down" v-if="!showOptions"></span>_x000D_
<span class="c-arrow-up" v-if="showOptions"></span>_x000D_
</div>_x000D_
<ul class="component__select-options" v-if="showOptions" >_x000D_
<li class="select--option" v-for="option in options">_x000D_
<label> <input type="checkbox" :value="option"/> {{option.name}}</label>_x000D_
</li>_x000D_
</ul>_x000D_
</div>`,_x000D_
_x000D_
methods: {_x000D_
selectOption(option) {_x000D_
this.$emit('option', option)_x000D_
}_x000D_
},_x000D_
data: () => ({_x000D_
showOptions: false,_x000D_
}),_x000D_
props: ['options']_x000D_
});_x000D_
_x000D_
var vm = new Vue({_x000D_
el: '#app',_x000D_
data: () => ({_x000D_
options: [_x000D_
{id: 0, name: 'Apple'},_x000D_
{id: 1, name: 'Banana'},_x000D_
{id: 2, name: 'Orange'},_x000D_
{id: 2, name: 'Strawberry'},_x000D_
],_x000D_
selectedFruit: ''_x000D_
}),_x000D_
})
_x000D_
.component__select {_x000D_
height: 38px;_x000D_
background-color: #F5F7FA;_x000D_
border: 1px solid #dddddd;_x000D_
line-height: 38px;_x000D_
display: grid;_x000D_
max-width: 500px;_x000D_
grid-template-columns: 10fr 1fr;_x000D_
}_x000D_
_x000D_
.component__select--name {_x000D_
font-size: 0.8rem;_x000D_
padding: 0 0 0 25px;_x000D_
cursor: pointer;_x000D_
}_x000D_
_x000D_
.c-arrow-down {_x000D_
justify-self: end;_x000D_
}_x000D_
_x000D_
.component__select-options {_x000D_
max-height: 180px;_x000D_
border: 1px solid #dddddd;_x000D_
border-top: none;_x000D_
overflow: auto;_x000D_
position: absolute;_x000D_
z-index: 1500;_x000D_
max-width: 500px;_x000D_
width: 500px;_x000D_
margin: 0;_x000D_
padding: 0;_x000D_
}_x000D_
_x000D_
.select--option {_x000D_
height: 35px;_x000D_
display: grid;_x000D_
align-content: center;_x000D_
padding: 0 0 0 25px;_x000D_
background-color: #f5f5fa;_x000D_
border-bottom: 1px solid #dddddd;_x000D_
}_x000D_
_x000D_
.select--option:last-child {_x000D_
border-bottom: none;_x000D_
}_x000D_
_x000D_
.select--option:nth-child(2n) {_x000D_
background-color: #ffffff;_x000D_
}_x000D_
_x000D_
.select--option input{_x000D_
display: none;_x000D_
}_x000D_
_x000D_
.single-option {_x000D_
height: 55px;_x000D_
background-color: #2595ec;_x000D_
font-size: 0.8rem;_x000D_
border: 1px solid red;_x000D_
}_x000D_
_x000D_
.cust-sel {_x000D_
width: 200px;_x000D_
height: 38px;_x000D_
background-color: #f5f5fa;_x000D_
border: 1px solid #dddddd;_x000D_
}_x000D_
_x000D_
.cust-sel:focus {_x000D_
outline-width: 0;_x000D_
}
_x000D_
<html>_x000D_
<head>_x000D_
<title>An example</title>_x000D_
</head>_x000D_
<body>_x000D_
<div id="app">_x000D_
<span> This is parent component</span>_x000D_
<p>I want to have data from select here: "{{selectedFruit}}"</p>_x000D_
<child :options="options" v-model="selectedFruit"></child>_x000D_
</div>_x000D_
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>_x000D_
</body>_x000D_
</html>
_x000D_
_x000D_
_x000D_
But my problem is now how to return data from child to parent component using v-model on child component.
(I know I could emit data from child component and do:
<custom-select :options="someOptions" @selected="setSelectedOption"/>
but I need it to be reusable and writing more and more methods to retrieve data from every select in parent component is not exactly how it should work I think.)
Also I need to have an entire object returned, not only ID. (that’s why i’ve got :value="option"
)
Any ideas?
Solution
As Vue Guide said:
v-model is essentially syntax sugar for updating data on user input
events, plus special care for some edge cases.
The syntax sugar will be like:
the directive=v-model
will bind value, then listen input
event to make change like v-bind:value="val" v-on:input="val = $event.target.value"
So for your use case, you need to create one prop=value, then emit the selected option with event=input.
Like below demo (bind/emit the whole option object):
Vue.config.productionTip = false_x000D_
Vue.component('child', {_x000D_
template: `<div class="component-container" @click="showOptions = !showOptions">_x000D_
<div class="component__select">_x000D_
<span class="component__select--name">{{value ? value.name : 'Select Fruit'}}</span>_x000D_
_x000D_
<span class="c-arrow-down" v-if="!showOptions"></span>_x000D_
<span class="c-arrow-up" v-if="showOptions"></span>_x000D_
</div>_x000D_
<ul class="component__select-options" v-if="showOptions" >_x000D_
<li class="select--option" v-for="option in options" @click="selectOption(option)">_x000D_
<label> <input type="checkbox" :value="option"/> {{option.name}}</label>_x000D_
</li>_x000D_
</ul>_x000D_
</div>`,_x000D_
_x000D_
methods: {_x000D_
selectOption(option) {_x000D_
this.$emit('input', option)_x000D_
}_x000D_
},_x000D_
data: () => ({_x000D_
showOptions: false_x000D_
}),_x000D_
props: ['options', 'value']_x000D_
});_x000D_
_x000D_
var vm = new Vue({_x000D_
el: '#app',_x000D_
data: () => ({_x000D_
options: [_x000D_
{id: 0, name: 'Apple'},_x000D_
{id: 1, name: 'Banana'},_x000D_
{id: 2, name: 'Orange'},_x000D_
{id: 2, name: 'Strawberry'},_x000D_
],_x000D_
selectedFruit: ''_x000D_
}),_x000D_
})
_x000D_
.component__select {_x000D_
height: 38px;_x000D_
background-color: #F5F7FA;_x000D_
border: 1px solid #dddddd;_x000D_
line-height: 38px;_x000D_
display: grid;_x000D_
max-width: 500px;_x000D_
grid-template-columns: 10fr 1fr;_x000D_
}_x000D_
_x000D_
.component__select--name {_x000D_
font-size: 0.8rem;_x000D_
padding: 0 0 0 25px;_x000D_
cursor: pointer;_x000D_
}_x000D_
_x000D_
.c-arrow-down {_x000D_
justify-self: end;_x000D_
}_x000D_
_x000D_
.component__select-options {_x000D_
max-height: 180px;_x000D_
border: 1px solid #dddddd;_x000D_
border-top: none;_x000D_
overflow: auto;_x000D_
position: absolute;_x000D_
z-index: 1500;_x000D_
max-width: 500px;_x000D_
width: 500px;_x000D_
margin: 0;_x000D_
padding: 0;_x000D_
}_x000D_
_x000D_
.select--option {_x000D_
height: 35px;_x000D_
display: grid;_x000D_
align-content: center;_x000D_
padding: 0 0 0 25px;_x000D_
background-color: #f5f5fa;_x000D_
border-bottom: 1px solid #dddddd;_x000D_
}_x000D_
_x000D_
.select--option:last-child {_x000D_
border-bottom: none;_x000D_
}_x000D_
_x000D_
.select--option:nth-child(2n) {_x000D_
background-color: #ffffff;_x000D_
}_x000D_
_x000D_
.select--option input{_x000D_
display: none;_x000D_
}_x000D_
_x000D_
.single-option {_x000D_
height: 55px;_x000D_
background-color: #2595ec;_x000D_
font-size: 0.8rem;_x000D_
border: 1px solid red;_x000D_
}_x000D_
_x000D_
.cust-sel {_x000D_
width: 200px;_x000D_
height: 38px;_x000D_
background-color: #f5f5fa;_x000D_
border: 1px solid #dddddd;_x000D_
}_x000D_
_x000D_
.cust-sel:focus {_x000D_
outline-width: 0;_x000D_
}
_x000D_
<script src="https://unpkg.com/[email protected]/dist/vue.js"></script>_x000D_
<div id="app">_x000D_
<span> This is parent component</span>_x000D_
<p>I want to have data from select here: "{{selectedFruit}}"</p>_x000D_
<child :options="options" v-model="selectedFruit"></child>_x000D_
</div>
_x000D_
_x000D_
_x000D_
Answered By: Anonymous
Disclaimer: This content is shared under creative common license cc-by-sa 3.0. It is generated from StackExchange Website Network.