简体   繁体   中英

Vue.js custom select component with v-model

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', { template: `<div class="component-container" @click="showOptions = !showOptions"> <div class="component__select"> <span class="component__select--name">Select Fruit</span> <span class="c-arrow-down" v-if="!showOptions"></span> <span class="c-arrow-up" v-if="showOptions"></span> </div> <ul class="component__select-options" v-if="showOptions" > <li class="select--option" v-for="option in options"> <label> <input type="checkbox" :value="option"/> {{option.name}}</label> </li> </ul> </div>`, methods: { selectOption(option) { this.$emit('option', option) } }, data: () => ({ showOptions: false, }), props: ['options'] }); var vm = new Vue({ el: '#app', data: () => ({ options: [ {id: 0, name: 'Apple'}, {id: 1, name: 'Banana'}, {id: 2, name: 'Orange'}, {id: 2, name: 'Strawberry'}, ], selectedFruit: '' }), })
 .component__select { height: 38px; background-color: #F5F7FA; border: 1px solid #dddddd; line-height: 38px; display: grid; max-width: 500px; grid-template-columns: 10fr 1fr; } .component__select--name { font-size: 0.8rem; padding: 0 0 0 25px; cursor: pointer; } .c-arrow-down { justify-self: end; } .component__select-options { max-height: 180px; border: 1px solid #dddddd; border-top: none; overflow: auto; position: absolute; z-index: 1500; max-width: 500px; width: 500px; margin: 0; padding: 0; } .select--option { height: 35px; display: grid; align-content: center; padding: 0 0 0 25px; background-color: #f5f5fa; border-bottom: 1px solid #dddddd; } .select--option:last-child { border-bottom: none; } .select--option:nth-child(2n) { background-color: #ffffff; } .select--option input{ display: none; } .single-option { height: 55px; background-color: #2595ec; font-size: 0.8rem; border: 1px solid red; } .cust-sel { width: 200px; height: 38px; background-color: #f5f5fa; border: 1px solid #dddddd; } .cust-sel:focus { outline-width: 0; }
 <html> <head> <title>An example</title> </head> <body> <div id="app"> <span> This is parent component</span> <p>I want to have data from select here: "{{selectedFruit}}"</p> <child :options="options" v-model="selectedFruit"></child> </div> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> </body> </html>

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?

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 Vue.component('child', { template: `<div class="component-container" @click="showOptions = !showOptions"> <div class="component__select"> <span class="component__select--name">{{value ? value.name : 'Select Fruit'}}</span> <span class="c-arrow-down" v-if="!showOptions"></span> <span class="c-arrow-up" v-if="showOptions"></span> </div> <ul class="component__select-options" v-if="showOptions" > <li class="select--option" v-for="option in options" @click="selectOption(option)"> <label> <input type="checkbox" :value="option"/> {{option.name}}</label> </li> </ul> </div>`, methods: { selectOption(option) { this.$emit('input', option) } }, data: () => ({ showOptions: false }), props: ['options', 'value'] }); var vm = new Vue({ el: '#app', data: () => ({ options: [ {id: 0, name: 'Apple'}, {id: 1, name: 'Banana'}, {id: 2, name: 'Orange'}, {id: 2, name: 'Strawberry'}, ], selectedFruit: '' }), })
 .component__select { height: 38px; background-color: #F5F7FA; border: 1px solid #dddddd; line-height: 38px; display: grid; max-width: 500px; grid-template-columns: 10fr 1fr; } .component__select--name { font-size: 0.8rem; padding: 0 0 0 25px; cursor: pointer; } .c-arrow-down { justify-self: end; } .component__select-options { max-height: 180px; border: 1px solid #dddddd; border-top: none; overflow: auto; position: absolute; z-index: 1500; max-width: 500px; width: 500px; margin: 0; padding: 0; } .select--option { height: 35px; display: grid; align-content: center; padding: 0 0 0 25px; background-color: #f5f5fa; border-bottom: 1px solid #dddddd; } .select--option:last-child { border-bottom: none; } .select--option:nth-child(2n) { background-color: #ffffff; } .select--option input{ display: none; } .single-option { height: 55px; background-color: #2595ec; font-size: 0.8rem; border: 1px solid red; } .cust-sel { width: 200px; height: 38px; background-color: #f5f5fa; border: 1px solid #dddddd; } .cust-sel:focus { outline-width: 0; }
 <script src="https://unpkg.com/vue@2.5.16/dist/vue.js"></script> <div id="app"> <span> This is parent component</span> <p>I want to have data from select here: "{{selectedFruit}}"</p> <child :options="options" v-model="selectedFruit"></child> </div>

When using v-model on custom component all you need is to declare a prop named 'value' and when you need the component to chance it emit an 'input' event.

Something like this:

<template>
  <form @submit.prevent="$emit('onSearch',val)" class="form-perfil">
    <div class="form-group col-md-12">
      <input v-model="val" @input="$emit('input',val)" 
      placeholder="filtrar resultados" class="form-control">
    </div>
  </form>
</template>
<script>
module.exports = {
  name: "CaixaFiltro",
  props: ["value"],
  data: _ => ({ val: "" }),
  created() {
    this.val = this.value
  }
}
</script>

Then you can use (after register the component) it like this:

<caixa-filtro v-model="textoBusca" @onSearch="listar"></caixa-filtro>

You can find more detailed info there :

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM