How to create a custom selected list using Multiselect for Vue.js

Damian Dulisz member of the Vue.js team and the author of Vue-Multiselect, says that Vue-Multiselect is “The most complete selecting solution for Vue.js” and I have to agree with him.

Recently I had to create a feature for our product at Fliplet that allow users to select resources to embed into their apps. Similar to how CodePen does it, the user can select from a list of predefined resources and/or type to search for resources that are delivered by cdnjs.com.

Vue-Multiselect example

Having that functionality in mind and also having in mind that our product is built using Vue.js, I went on a search for a select field component built for Vue.js (I didn’t want to spend time building my own) and I found quite a few, I tried a couple, until I tried Vue-Multiselect and I was amazed with the possibilities. You can check the website, but basically is a select field that allows single and multiple select, search, tags, and much more — believe me, it is really good. So today I decided to create a quick tutorial that takes that select field and with a bit of code we will create a custom list of selected resources loaded from cdnjs.com.

What we will be achieving

Getting started

First of all, I will assume you know your way around Vue.js and that you already have your environment set up and running with Vue.js.

You will need the following resources installed in your project:

  • Vue.js
  • Vue-Multiselect
  • Vue-Resource
  • Font Awesome

Installing Vue.js — Link here
NPM is the recommended installation method, but feel free to install it using a different method.

npm install vue --save

Installing Vue-Multiselect — Link here
NPM is the recommended installation method, but feel free to install it using a different method.

npm install vue-multiselect --save

Installing Vue-Resource — Link here
NPM is the recommended installation method, but feel free to install it using a different method.

npm install vue-resource --save

Installing Font Awesome — Link here
Download the files and add them to your project. This is optional, I’m including because I used in my example.

Now that you have all the components necessary for this tutorial let’s get going.

Create the Vue component

First we are going to set up Vue and include the Multiselect component.

Vue

new Vue({
components: {
Multiselect: window.VueMultiselect.default
},
data: {
options: [],
optionsProxy: []
}
}).$mount(‘#app’)

Now the HTML, we will include the markup for the Multiselect select field.

HTML

<div id="app">
<multiselect :options="options" :value="optionsProxy" placeholder="Search"></multiselect>
</div>

Above you can see that the Multiselect field has some props already. You can see the full list of available props in the documentation, but we will be adding more and I’ll explain what the new ones will do and why they are important.

For now let me explain the most important ones that are the following:

<multiselect :options="options" :value="optionsProxy"></multiselect>
  • options — This contains the array of items to display when the users click the select field
  • value — This will contain the selected items in an array

Now that you know the purpose of those two props let’s take a look at the Vue data prop. You can see we have defined two variables as empty arrays:

<multiselect :options="options" :value="optionsProxy"></multiselect>
-----
data: {
options: [],
optionsProxy: []
}
  • options — This must contain the array of items to display when users click the select field
  • optionsProxy — This will be filled in with the items selected by the user

Options shouldn’t be an empty array however in this tutorial we will be filling it with resources from cdnjs.com, so for now we will keep it empty.

If you have been following along you should now have something like this:

Create the custom list used to display the selected resources

One of the most valuable things of Multiselect is that the list of items can be an array of objects, and those objects can contain several keys that you can use to display data after the user selected one.

Let’s have a look at the markup of the list of selected resources.

<ul class="resources-list">
<template v-for="(resource, index) in selectedResources">
<li class="resource-item" :data-index="index">
<div class="resource-info">
<div class="resource-title" :id="index">
<span>{{ resource.name }}</span>
<span class="version">{{ resource.version }}</span>
</div>
<div class="resource-description">
<span>{{ resource.description }}</span>
</div>
<div class="resource-url">
<a :href="resource.latest" target="_blank">{{ resource.latest }}</a>
</div>
</div>
<div class="delete-controls" v-on:click.prevent="removeDependency(index)">
<i class="fa fa-times fa-fw"></i>
</div>
</li>
</template>
</ul>

Add this block of HTML inside the <div id="app"> after the Multiselect markup.

  • resource-name — This prints out the name of the resource
  • resource-description — This prints out the description of the resource
  • resource-version — This prints out the version of the resource
  • resource-latest — This prints out the URL of the resource
  • removeDependency — Run function to remove resource from list

Here’s a bit of CSS as well to make that list look good:

.resources-list {
margin-top: 15px;
padding: 0;
list-style: none;
}
.resources-list li {
display: flex;
align-items: center;
min-height: 50px;
padding: 10px 40px 10px 0;
border-bottom: 1px solid rgba(51, 51, 51, 0.1);
position: relative;
}
.resources-list li:last-child {
border-bottom: none;
}
.resources-list li .resource-title {
font-size: 1em;
color: #333;
}
.resources-list li .version {
opacity: 0.7;
margin-left: 5px;
font-size: 75%;
}
.resources-list li .resource-description,
.resources-list li .resource-url {
font-size: 0.8em;
color: #999;
margin-top: 5px;
}
.resources-list li .resource-url {
margin-top: 0;
}
.resources-list li .resource-description:empty {
display: none;
}
.resources-list li .delete-controls {
position: absolute;
right: 0;
width: 40px;
text-align: center;
color: #999;
cursor: pointer;
}
.resources-list li .delete-controls:hover,
.resources-list li .delete-controls:focus {
color: red;
}

Now that we have all the HTML and CSS in place you should have something like this:

Update Multiselect with important props

We need to add a few more props to Multiselect to achieve what we want, so let give that a go:

<multiselect :options="options" :value="optionsProxy" @input="updateSelected" @search-change="searchQuery" :multiple="true" :searchable="true" :close-on-select="true" placeholder="Search" :custom-label="customLabel" :loading="showLoadingSpinner"></multiselect>
  • @input— Runs a function, this is triggered when the user selects an item from the list
  • @search-change — Runs a function, this is triggered when a user starts a search — This only works when searchable is active
  • searchable — Accepts a Boolean, and makes the select field list searchable
  • multiple — Accepts a Boolean, and allows users to select multiple items from the list
  • close-on-select — Accepts a Boolean, and forces the list to close when the user selects an item — Use this is you want.
  • custom-label — Templated string that will be rendered as an item in the list
  • loading — Accepts a Boolean, and if true shows a loading spinner

Now that you have updated your Multiselect and understand what we have added and what for, let’s update our Vue component.

Update Vue component

Data

Let’s start with the easiest things, let update our variables in the data prop. We will add two more:

data: {
options: [],
optionsProxy: [],
selectedResources: [],
showLoadingSpinner: false
}
  • selectedResources— This will be the array of selected items from the list
  • showLoadingSpinner — This will trigger the loading spinner in the Multiselect

Methods

Now lets add our event handlers. We will add 5 event handlers but we’ll be taking a look at them individually, but here’s the full list.

methods: {
customLabel(option) {
return `${option.name} — ${option.version}`
},
updateSelected(value) {
value.forEach((resource) => {
// Adds selected resources to array
this.selectedResources.push(resource)
})
// Clears selected array
// This prevents the tags from being displayed
this.optionsProxy = []
},
cdnRequest(value) {
this.$http.get(`https://api.cdnjs.com/libraries?search=${value}&fields=version,description`)
.then((response) => {
// get body data
this.options = []
response.body.results.forEach((object) => {
this.options.push(object)
})
this.showLoadingSpinner = false
}, (response) => {
// error callback
})
},
searchQuery(value) {
this.showLoadingSpinner = true
// GET
this.cdnRequest(value)
},
removeDependency(index) {
this.selectedResources.splice(index, 1)
}
}

Let’s get into every single one of the event handlers:

Custom labels

customLabel(option) {
return `${option.name} — ${option.version}`
}

This one will return the custom label to display in the list of the Multiselect, tweak it as you like. This example looks like this:

Add to custom list

updateSelected(value) {
value.forEach((resource) => {
// Adds selected resources to array
this.selectedResources.push(resource)
})
  // Clears selected array
// This prevents the tags from being displayed
this.optionsProxy = []
}

This will be triggered every time the user selects a resource from the list. In this little function we add the selected item in value to the selectedResources array that was defined earlier.

this.optionsProxy = []

We then clear the optionsProxy array so that the selected item doesn’t stay visible in the Multiselect field as a tag. Feel free to comment this line to see how it looks in the UI.

CDN JS request

cdnRequest(value) {
this.$http.get(`https://api.cdnjs.com/libraries?search=${value}&fields=version,description`)
.then((response) => {
// get body data
this.options = []
      response.body.results.forEach((object) => {
this.options.push(object)
})
      this.showLoadingSpinner = false
}, (response) => {
// error callback
})
}

In this function we do a request to cdnjs.com to retrieve a list of resources based on what the user typed.

For that we need to clear the existing list by this.options = [] and then we iterate each result and we add them to the list using a forEach() loop.

response.body.results.forEach((object) => {
this.options.push(object)
})

We also disable the spinner with this.showLoadingSpinner = false

There’s also an error callback that you can use if you want to.

Search list

searchQuery(value) {
this.showLoadingSpinner = true
  // GET
this.cdnRequest(value)
}

This function is triggered when the user searches in the Multiselect.

First we initialise the spinner, in case the search query takes some time, with this.showLoadingSpinner = true . Then we run the cdnRequest function that we had a look above, passing it the value the user typed.

Remove resource from custom list

removeDependency(index) {
this.selectedResources.splice(index, 1)
}

Lastly we create the function to remove resources from the custom list. This is run when the user clicks the cross in the UI that we have set in HTML.

<div class="delete-controls" v-on:click.prevent="removeDependency(index)">
<i class="fa fa-times fa-fw"></i>
</div>

When a user clicks the cross we take the index of the resource in the array and we remove it by this.selectedResources.splice(index, 1) .

Populate list on load

The last thing we need to do is to populate the list with resources when users load the page, for that we will use the created() lifecycle hook to query cdnjs.com with an empty string.

created() {
const value = ''
this.cdnRequest(value)
}

If you pass it an empty string it will return all the resources in cdnjs.com (about 3000+), you can set a different default, you can even not query cdnjs.com and have your own static list. If you do that just remember that you need to add you list to this.options .


If you have been following along you should now have the following:

Conclusion

As you can see, with a bit of coding you can create amazing interfaces using this little Vue component.

I hope you liked this tutorial, I had fun creating it, please leave in the comments how did you find it.

Until next time, keep on solving problems!