Search code examples
vue.jsnuxt.jshtml-selectv-model

Vue.js custom component with HTML <select> and v-model (W3C compliant)


I'm new to Vue.js (using Nuxt.js) and what I'm trying to achieve is to have a Select component that I can reuse everywhere and is W3C compliant.

With the help of @Jasmonate answers, I managed to create this component, it's working. But the value attribute is still visible in the source code and so isn't W3C compliant. Maybe the problem is coming from somewhere else in the project ?!

Parent component

<custom-select
  :options="options"
  v-model="selectedOption"
></custom-select>
<span>Selected : {{ selectedOption }}</span>

<script>
  data() {
    return {
      selectedOption: "A",
      options: [
        { label: "One", value: "A" },
        { label: "Two", value: "B" },
        { label: "Three", value: "C" }
      ],
    };
  }
</script>

custom-select.vue

<template>
  <select :value="value" @input="clicked">
    <option
      v-for="option in options"
      :key="option.label"
      :value="option.value"
    >
      {{ option.label }}
    </option>
  </select>
</template>

<script>
  export default {
    props: {
      value: {
        required: true
      },
      options: {
        type: Array,
        required: true
      }
    },
    methods: {
      clicked($event) {
        this.$emit("input", $event.target.value);
      }
    }
  };
</script>

I read those documentation pages:

And also looked around the web to find example of v-model in a custom component, but it's always about the input tag. The only example I found about a custom select with v-model isn't actually a select tag, like the Vue Select plugin or this thread on StackOverflow.


Solution

  • v-model is syntax sugar. By default, the value is a prop that has the name value, and it changes (two-way-binding) whenever the event input is emitted.

    Also, v-model is bound on the select element, not option.

    Your code can be modified as such:

    <template>
      <select :value="value" @input="clicked">
        <option
          v-for="option in options"
          :key="option.label"
          :value="option.value"
        >
          {{ option.label }}
        </option>
      </select>
    </template>
    
    <script>
      export default {
        props: {
          value: {
            required: true
          },
          options: {
            type: Array,
            required: true
          }
        },
        methods: {
          clicked($event) {
            this.$emit('input', $event.target.value);
          }
        }
      };
    </script>
    

    Documentation here: https://v2.vuejs.org/v2/guide/components.html#Using-v-model-on-Components

    You can also change the prop name and event name that v-model uses, see: https://v2.vuejs.org/v2/guide/components-custom-events.html#Customizing-Component-v-model