Create an ImageSelect component on top of vue-multiselect

#Vue

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

Remember you can read this tip online (with copy/pasteable code), and don’t forget to share VueDose with your colleagues, so they also know about these tips as well!

See you next week.

Alex

Do you want to sponsor a VueDose tip? Send an email to info@vuedose.tips and have more info!

Start saving time and get a tip about the Vue ecosystem every week, right in your inbox.