Create a searchable dropdown list in Vue

How to build a dynamically filtered dropdown list in Vue.js

Chris Nicholas
8 min readFeb 11, 2020

Before we start, let’s take a look at what we’re building. The aim is to create a Vue.js component to display an input field, that upon input, will display a dropdown list of items, dynamically filtered to match the input value. We’ll be retrieving the list data from a RESTful API.

Animated gif of the dropdown list we’ll be creating
The dropdown list we’ll be making

Prerequisites

This article is suitable for Vue.js beginners and above. It is assumed that you understand simple Javascript and the basic outline of Vue Single-File Components (.vue files).

Aims

  • Retrieve the data from an API
  • Display a list of items when input is provided
  • Filter out items that do not match the input
  • Allow the user the select an item

Retrieving the data

Firstly, we need to retrieve our data. In my example I’ll be using Axios, to retrieve data from the Rest Countries free RESTful API.

Why Axios?

Axios is a simple promise based HTTP client that allows us to easily create XMLHttpRequests/AJAX requests without worrying about browser support.

Installing Axios

Install Axios by running the following terminal command in your project directory:

$ npm install axios

Initial script

We begin by creating a DropdownList.vue file, importing Axios, and creating two variables.

Create DropdownList.vue:

<template></template><script>
import axios from 'axios'
export default {
data () {
return {
itemList: [],
apiLoaded: false,
apiUrl: 'https://restcountries.eu/rest/v2/all?fields=name;flag'
}
}
}
</script>

Using Axios

Now we’ll retrieve and store the API data. Create a method, getList, that fetches the data, passes it to itemList, and updates apiLoaded to true. We run getList when the component is mounted.

mounted () {
this.getList()
},
methods: {
getList () {
axios.get(this.apiUrl).then( response => {
this.itemList = response.data
this.apiLoaded = true
})
}
}

Perfect, now we’ve got the data we can start creating the component.

DropdownList.vue after retrieving the data

Creating the base template

Next we’ll create the basic HTML template for the DropdownList.vue component, as well as creating a second component for each list item.

Create the template

Create a template with a containing element .dropdown, a text input field .dropdown-input, and a container to hold the list .dropdown-list, as follows. You can enter placeholder text as a label for the input field.

<template>
<div class="dropdown">
<input class="dropdown-input" type="text" placeholder="Find country" />
<div class="dropdown-list"></div>
</div>
</template>

Getting the search term

Add a new local property to data, inputValue, and initialise it as an empty string. We’ll be using this to hold the text field input.

data () {
return {
inputValue: '',
...
}
}

Now, to bind the input text to inputValue we’ll be using v-model. The v-model Vue directive links the value of an input to a data () property; if one changes, then so does the other. Add the following to .dropdown-input’s properties:

<input v-model.trim="inputValue" ... />

Whenever .dropdown-input’s value changes, inputValue will be passed its value, and vice versa. Removing whitespace either side of your input is often a good idea, and this is why we’re using the .trim modifier here.

DropdownList.vue with basic template and added styling

Building the logic

Now we’ll write the list and search logic:

  • Create the list of items
  • If the user is searching, we’ll check if each item contains the search term
  • And if the item does not contain the search term, we’ll hide it

Generating the list

We’ll use v-for to generate the list of items, passing the data to each .dropdown-item, in my case a country’s name and flag. Place the following markup inside of the .dropdown-list element:

<div v-for="item in itemList" :key="item.name" class="dropdown-item">
<img :src="item.flag class="dropdown-item-flag"/>
{{ item.name }}
</div>

Here, v-for is looping through itemList, containing our API data, assigning each item to item. We’re assigning a unique value to :key, because this is helpful for Vue to keep track of the different elements created using v-for.

As you can see, our API data is in this format, therefore item.name provides a country name, and item.flag provides the URL of a flag image:

[
{"flag":"https://restcountries.eu/data/alb.svg","name":"Albania"},
{"flag":"https://restcountries.eu/data/dza.svg","name":"Algeria"},
...
]

Filter the list

Next we’ll create a method to check if each item should be visible or not. We’ll compare the current user input with the data of the item, and return true or false depending on whether it partially, or fully, matches.

methods: {
itemVisible (item) {
let currentName = item.name.toLowerCase()
let currentInput = this.inputValue.toLowerCase()
return currentName.includes(currentInput)
},
...
}

Here, includes() returns true if currentInput can be found within currentName, and falseif not. We also converted both the country name and the user input to lower case, so that the user doesn’t need to perfectly match the letter case of the country name when searching.

Pass the method to the items, and hide list

Now we pass the new method to .dropdown-item with v-show like so:

<div v-show="itemVisible(item)" ... >

This will apply display: none to .dropdown-item if itemVisible() returns true for the current item. We should now have a working (unstyled) filter, try it out!

Hide the list unless searching

Adding the following to .dropdown-list will hide the list unless the API is loaded, and the user has started searching:

<div v-show="inputValue && apiLoaded" ... >

inputValue will return false if it contains an empty string '', and true otherwise, essentially checking for any user input. Then we check to see if the data is loaded, and if both are true the element is visible.

Using v-show instead of v-if

Using v-show (which hides the item) is preferable to using v-if(which removes the item from the DOM completely) in this situation, because repeatedly manipulating the DOM can be less performant than toggling display: none.

Styling

We don’t want the dropdown to interfere with the page layout, we’d like it to float on top, so we apply the following styles:

.dropdown{
position: relative;
}
.dropdown-list{
position: absolute;
}

You should now be able to search for items in the list!

I’ve applied a number of other non-essential CSS rules to style my component in the Gist below, I’d recommend copying the <style> section if you’re creating my country name list.

DropdownList.vue after adding the filter and other logic

Selecting and resetting the item

Now we need to write a method to select, and display, the chosen item, and another method to reset it.

Selecting an item

First, create a new data () property, selectedItem, to hold the selected item:

data (){ 
return {
selectedItem: {},
...
}
}

And now, we’ll create a simple method to pass an item to the new property, and then clear the text input (don’t worry, we’ll explain the last line later):

method: {
selectItem (theItem) {
this.selectedItem = theItem
this.inputValue = ''
this.$emit('on-item-selected', theItem)
}
...
}

And we’ll pass this method to .dropdown-item with a @click handler, passing on theitem from the v-for loop. In other words, when the element is clicked, the new method will be run. Add to .dropdown-item:

<div @click="selectItem(item)" ... >

Hiding the input

The input should only be visible when a selected item has not been chosen, and should be replaced by an element displaying the chosen item afterwards.

Add the following to .dropdown-input:

<input v-if="Object.keys(selectedItem).length === 0" ... />

This is the easiest way to check if an object is empty or not. It returns a list of keys held by selectedItem, in this case, if an item is selected, ['name', 'flag'] and then we check the length. If length === 0 then the object has no keys, is empty, and we have not selected an item.

Displaying the selected item

Now we can display the new element. Add the following directly after .dropdown-input:

<div v-else @click="resetItem" class="dropdown-selected">
<img :src="selectedItem.flag" class="dropdown-item-flag" />
{{ selectedItem.name }}
</div>

An element with v-else directly after one with v-if will only display itself if the v-if element does not. So .dropdown-selected will only appear if .dropdown-input (which is directly before and contains a v-if attribute) does not.

Inside of .dropdown-selected we’ve copied the code from inside of .dropdown-item and swapped out for item for selectedItem. We’ll create theresetItem medthod in the next step.

Resetting an item

We’ll use .dropdown-selected as a button to reset the selection. When clicking on it, we want to reset the current item, display the input, and allow another choice to be made. After displaying the input again, we would like to focus back on the input element, so that the user can type without clicking.

This is quite simple to do. In the last step we provided a @click handler to the .dropdown-selected element, now we’ll write that method:

methods: {
resetItem () {
this.selectedItem = {}
this.$nextTick( () => this.$refs.dropdowninput.focus() )
this.$emit('on-item-reset')
},
...
}

Now add a ref attribute to .dropdown-input. This is a simple way to create an identifier for an element, that Vue can manipulate. Avoiding symbols and capitals makes for easier access here!

<input ref="dropdowninput" ... />

Explanation

So what did we just do with this method? The first line of resetItem () resets the selected item to an empty object. The second line, in particular this.$refs.dropdowninput.focus(), simply finds the input element referenced by ref and focusses on it.

$nextTick is used because the method is run before the DOM has updated, and the .dropdown-input has not been drawn yet. $nextTick waits until the next page draw, and then runs the code block contained within it.

You should now have a working, filterable, and selectable, dropdown list!

DropdownList.vue’s final state, after implementing the final methods

Communicating the selection to a parent

The final step is to add this component to your Vue app, and pass on the selection information to the instance’s parent.

Earlier, we added the following to selectItem () and resetItem () respectively:

this.$emit('on-item-selected', theItem) this.$emit('on-item-reset')

Those two statements trigger custom events that we can catch within the <DropdownList> tag in a parent component, to retrieve the selected item, and then reset it. Here’s an example of a .vue file importing our dropdown list component, and catching our events:

A file containing a DropdownList component

Now we can access the selected item data from within dropdownSelection, perfect!

Conclusion

There you have it! A reusable, filterable, dropdown list component in Vue.js.

Animated gif of the dropdown list we’ll be creating

I hope this tutorial has been helpful, thanks for reading!

--

--

Chris Nicholas
Chris Nicholas

Written by Chris Nicholas

It’s a mistake to think you can solve any major problems just with potatoes. Luckily I’m a JS developer. I do some design too. Articles: https://ctnicholas.dev

Responses (1)