3

I'm trying to create a customCombobox component that works like a normal v-combobox with one addition - after user presses tab key, it will select the first option automatically (if there are any).

What I've done so far looks good but v-model on this field doesn't work (it's always null).

<template>
  <v-combobox ref="combobox" :rules="rules"
              @keydown.tab.prevent="selectFirst"
              :value="innerValue" :label="label"
              :items="items"

  >

  </v-combobox>
</template>
<script>
module.exports = {
  props: ['value', 'label', 'items', 'rules'],
  data() {
    return {
      innerValue:null,
    }
  },
  watch:{
    value(_new){
       this.innerValue = _new
      this.$emit('input',[_new])
      this.$emit('change')
    }
  },
  methods: {
    selectFirst() {
      var combobox = this.$refs.combobox
      var filteredItems = combobox.filteredItems
      if (filteredItems.length){
          this.innerValue = filteredItems[0]
      }
    }
  },
  computed: {

  }
}
</script>

Do you know why?

Milano
  • 18,048
  • 37
  • 153
  • 353

1 Answers1

5

You can use a computed setter in the custom component to handle the v-model from the parent and pass it down to its own child v-combobox:

Custom component:

<v-combobox v-model="selected" v-bind="$attrs"></v-combobox>
computed: {
  selected: {
    get() {
      return this.value;
    },
    set(val) {
      this.$emit('input', val);
    }
  }
}

Using v-bind="$attrs" also passes down all of the props from the parent. Here's a demo:

Vue.component('comp', {
  props: ['value'],
    template: `
    <v-combobox v-model="selected" v-bind="$attrs"></v-combobox>
  `,
  computed: {
    selected: {
      get() {
        return this.value;
      },
      set(val) {
        this.$emit('input', val);
      }
    }
  }
})

new Vue({
  el: '#app',
  vuetify: new Vuetify(),
  data() {
    return {
      selected: 'Vue',
      label: 'This is my label',
      rules: ['These are my rules'],
      items: ['Programming', 'Design', 'Vue', 'Vuetify']
    }
  }
})
#app {
  padding: 48px;
}
<div id="app">
  <v-app id="inspire">
    Selected in parent: {{ selected }}
    <comp
         v-model="selected"
         :label="label"
         :items="items"></comp>
  </v-app> 
</div>

<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/@mdi/font@4.x/css/materialdesignicons.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/vuetify@2.x/dist/vuetify.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/vue@2.x/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify@2.x/dist/vuetify.js"></script>
Dan
  • 59,490
  • 13
  • 101
  • 110
  • Any reason why sometimes you have to do `update:value` and sometimes `input`? – Riza Khan Jan 22 '23 at 22:35
  • 1
    @RizaKhan - The `update:value` syntax in Vue 2 was for two-way binding using `v-bind.sync`. (But two-way binding is not a good practice, use regular props/events for clarity.) In Vue 3, `v-model` was improved to allow multiple models, and uses a similar `update:modelValue` syntax. See the Vue 3 [v-model migration doc](https://v3-migration.vuejs.org/breaking-changes/v-model.html#using-v-bind-sync) for more info on the history of `v-model`. It includes a paragraph on `v-bind.sync` and the `update:value` syntax. – Dan Jan 23 '23 at 00:02