Create an ImageSelect component on top of vue-multiselect

Two tips before you learnt the concept of Adaptive Components and how you can build the base of it by proxifying props and events using v-bind and v-on.

Now it's time to show it in action. By any chance, do you know about vue-multiselect? It's an amazing select component built by Damian Dulisz. vue-multiselect can be used in many different ways given how flexible and customisable it is. That's really how a fine third-party component should be in order to be reusable.

Based on this example from its documentation, let's build an ImageSelect component. To do that, the example redefines some scoped slots that vue-multiselect exposes:

<multiselect v-model="value" :options="options">
  <template slot="singleLabel" slot-scope="{ option }">
    <img class="option__image" :src="option.img" alt="Sth" />
    <span class="option__desc">
      <span class="option__title">{{ option.title }}</span>
    </span>
  </template>

  <template slot="option" slot-scope="{ option }">
    <img class="option__image" :src="option.img" alt="Sth" />
    <span class="option__desc">
      <span class="option__title">{{ option.title }}</span>
      <span class="option__small">{{ option.desc }}</span>
    </span>
  </template>
</multiselect>

I'm not getting into scoped slots, just assume that code works in case you don't know about them. The thing here is that I want to build an ImageSelect component on top of that code.

From the last tip, you probably know already that you need to use v-bind="$props" and v-on="$listeners" in order to make that proxifying of props and events happen.

You also need to redeclare the props from the original vue-multiselect component, and you can take them from the MultiselectMixin of its source code:

<template>
  <multiselect v-bind="$props" v-on="$listeners">
    <template slot="singleLabel" slot-scope="{ option }">
      <img class="option__image" :src="option.img" alt="No Man’s Sky" />
      <span class="option__desc">
        <span class="option__title">{{ option.title }}</span>
      </span>
    </template>

    <template slot="option" slot-scope="{ option }">
      <img class="option__image" :src="option.img" alt="No Man’s Sky" />
      <span class="option__desc">
        <span class="option__title">{{ option.title }}</span>
        <span class="option__small">{{ option.desc }}</span>
      </span>
    </template>
  </multiselect>
</template>

<script>
  import Multiselect from "vue-multiselect";
  import MultiselectMixin from "vue-multiselect/src/multiselectMixin";

  export default {
    components: {
      Multiselect
    },
    props: MultiselectMixin.props
  };
</script>

Here's how you can use this ImageSelect component, passing by the minimal properties to make it work:

<template>
  <ImageSelect
    v-model="imageValue"
    :options="imageOptions"
    label="title"
    track-by="title"
    :show-labels="false"
  />
</template>

<script>
  import ImageSelect from "./ImageSelect";

  export default {
    components: {
      ImageSelect
    },
    data: () => ({
      imageValue: null,
      imageOptions: [
        { title: "Random img", img: "https://picsum.photos/300/150" },
        { title: "Cool image", img: "https://picsum.photos/300/151" }
      ]
    })
  };
</script>

If you run this code you will notice that there is something not working properly. In particular the show-labels prop. The thing is that it's not a prop, but an attribute! And they're accessible through the $attrs component instance option.

Basically we need to proxify not only the props, but also the attributes to make it work.

To do that, I'm going to use a computed property to merge both $props and $attrs into the same object:

<template>
  <multiselect v-bind="allBindings" v-on="$listeners">
    <!-- ... -->
  </multiselect>
</template>

<script>
  import Multiselect from "vue-multiselect";
  import MultiselectMixin from "vue-multiselect/src/multiselectMixin";

  export default {
    components: {
      Multiselect
    },
    props: MultiselectMixin.props,
    computed: {
      allBindings() {
        // Need to proxify both props and attrs, for example for showLabels
        return { ...this.$props, ...this.$attrs };
      }
    }
  };
</script>

You can try yourself and run the code on this Codesandbox example I've prepared for you. You'll see it has some additional Adaptive Components, such as SingleSelect and MultiSelect.

Pss: they have some CSS tricks we'll cover on the next tips

Don't miss out anything about Vue, your favourite framework.

Subscribe to receive all the articles we publish in a concise format, perfect for busy devs.

Related Articles

The new Provide and Inject in Vue 3

Getting stuck into the prop drilling? Learn how provide/inject can make your components more flexible and independent in this short tutorial.

Anthony Konstantinidis

Anthony Konstantinidis

Jul 18, 2022

Data Provider component in Vue.js

Use scoped slots to create a data provider in Vue.js

Alex Jover Morales

Alex Jover Morales

Sep 24, 2019

Using Scoped Slots in Vue.js

Quick example on how to use scoped slots for component reusability in vuejs

Alex Jover Morales

Alex Jover Morales

Sep 15, 2019

Sponsors

VueDose is proudly supported by its sponsors. If you enjoy it, consider supporting it to ensure the project maintainability.

Silver
Learning Partner