How do I preselect a vue-multiselect option when options is an array of objects?

HartleySan picture HartleySan · Feb 20, 2019 · Viewed 12.5k times · Source

I want to pre-select a particular value in a select drop-down generated by vue-multiselect.
I can get this to work fine if I have a simple array of strings like the following:

['Test 1', 'Test 2', 'Test 3']

However, when I use an array of objects, I can't get this to work. For example, if I have the following:

<v-multiselect :options="[{id: 1, name: 'Test 1'}, {id: 2, name: 'Test 2'}, {id: 3, name: 'Test 3'}]"
               label="name"
               track-by="id"
               v-model="test">
</v-multiselect>

No matter what I set the test data property that v-model is connected to, it won't preselect the value. I've tried 1, 2, 3, '1', '2' and '3' for test when track-by is id and 'Test 1', etc. when track-by is name but nothing seems to work.

What am I doing wrong here? I looked at the docs at https://vue-multiselect.js.org/#sub-single-select-object, but they don't seem to provide an example when you want to preset a value for an array of objects for the options. Googling has also not returned what I'm looking for.

On a related topic, once I get this working, what would I have to change to select multiple values for when I set the component to multiple? Thank you.

Answer

tony19 picture tony19 · Feb 21, 2019

track-by usage

The docs indicate that track-by is "Used to compare objects. Only use if options are objects."

That is, it specifies the object key to use when comparing the object values in options. The docs should actually state that track-by is required when the options are objects because <vue-multiselect> uses track-by to determine which options in the dropdown are selected and to properly remove a selected option from a multiselect.

Without track-by, you'd see two buggy behaviors for object-options: (1) the user would be able to re-select already selected options, and (2) attempting to remove selected options would instead cause all options to be re-inserted.

Setting initial values

<vue-multiselect> doesn't support automatically translating a value array, but you could easily do that from the parent component.

  1. Create a local data property to specify track-by and initial multiselect values (e.g., named trackBy and initialValues, respectively):

    export default {
      data() {
        return {
          //...
          trackBy: 'id',
          initialValues: [2, 5],
        }
      }
    }
    
  2. Bind <vue-multiselect>.track-by to this.trackBy and <vue-multiselect>.v-model to this.value:

    <vue-multiselect :track-by="trackBy" v-model="value">
    
  3. Create a watcher on this.initialValues that maps those values into an object array based on this.trackBy, setting this.value to the result:

    export default {
      watch: {
        initialValues: {
          immediate: true,
          handler(values) {
            this.value = this.options.filter(x => values.includes(x[this.trackBy]));
          }
        }
      }
    }
    

Vue.component('v-multiselect', window.VueMultiselect.default);

new Vue({
  el: '#app',
  data () {
    return {
      trackBy: 'id',
      initialValues: [5,2],
      value: null,
      options: [
        { id: 1, name: 'Vue.js', language: 'JavaScript' },
        { id: 2, name: 'Rails', language: 'Ruby' },
        { id: 3, name: 'Sinatra', language: 'Ruby' },
        { id: 4, name: 'Laravel', language: 'PHP' },
        { id: 5, name: 'Phoenix', language: 'Elixir' }
      ]
    }
  },
  watch: {
    initialValues: {
      immediate: true,
      handler(values) {
        this.value = this.options.filter(x => values.includes(x[this.trackBy]));
      }
    }
  }
})
<script src="https://unpkg.com/[email protected]/dist/vue.min.js"></script>
<script src="https://unpkg.com/[email protected]"></script>
<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/vue-multiselect.min.css">

<div id="app">
  <v-multiselect :track-by="trackBy"
                 :options="options"
                 v-model="value"
                 label="name"
                 multiple>
  </v-multiselect>
  <pre>{{ value }}</pre>
</div>