Create your own Autocomplete using Vue.js

Note: This article is using Vue.js 1.0. If you are planning to use Vue.js 2, go to this article

From the days of jQuery, I have been pulling in different plugins everytime I wanted to create an Autocomplete input. Later Bootstrap came with an Typeahead component and then they removed it from Bootstrap 3 and I was forced to use typeahead.js when I was developing Encollege

When I moved to Vue.js, I realized how much it gave me the power of creating my own UI Component with a lot less effort. It made me decide to think a lot before pulling in a dependency just for an UI Component. Whenever possible I write my own component using Vue.js quickly and it gives a me a lot of freedom and control. Developing a simple component in Vue.js hardly takes the time which you spend reading the documentation of an external dependency.

So here we will see how to create a quick Autocomplete component with Vue.js.

Let's utilize the Vue CLI tools to quickly jump start developing Vue.js components. I generally use this to prototype components quickly before adding them to the projects.

If you haven't installed Vue CLI tools run npm install -g vue-cli and then run vue init browserify-simple autocomplete

We are going to use the Bootstrap for styling and so make sure you link the CSS file of Bootstrap to the index.html. Your index.html should look like this.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <link rel="stylesheet"
       href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css"
    >
    <title>autocomplete</title>
  </head>
  <body>
    <!--To be coded -->
    <script src="dist/build.js"></script>
  </body>
</html>

Now go to the src folder and create a file name Autocomplete.vue

First lets decide how our component should look from the usage point of view.

<autocomplete :suggestions="cities" :selection.sync="value"></autocomplete>

We need two properties here. suggestions is an array you pass to the component which will be used for showing autocomplete suggestions and selection is the final value which was given in the input by the user. .sync has to be added to two-way bound elements to make sure it syncs whenever there is change in parent & child components.

Time to create the script section of the Vue file.

<script>
import Vue from 'vue';

export default {

    props: {
        suggestions: {
            type: Array,
            required: true
        },

        selection: {
            type: String,
            required: true,
            twoWay: true
        }
    }

    /**
     * More to come here
     */
}

</script>

The selection property should be two-way binding because it have to update a value in the parent component which is bound to the selection property.

Lets define the template for the component in Autocomplete.vue file.

<div style="position:relative" v-bind:class="{'open':openSuggestion}">
    <input class="form-control" type="text" v-model="selection"/>
    <ul class="dropdown-menu" style="width:100%">
        <li v-for="suggestion in matches">
            <a href="#">{{ suggestion }}</a>
        </li>
    </ul>
</div>

Now we have the template and component object. It gives us the basic structure of the component. Half of our work is already done. What we still need to do is to add filtering logic and wiring of events like, When suggestions should appear, What should happen if I select a suggestion etc.

In the template we have mentioned something named matches in v-for. matches should be an array of suggestions matching the current input. This is a good fit for Computed Property. Computed properties are more like watch function in Angular.js. It will compute the matching suggestions based on the input whenever the input changes automatically.

And we need one more computed property named openSuggestion which will be used to add the open class to make the suggestions dropdown visible and invisible according to the context.

So lets add the Computed Property to the component (to the vue file on the More to come part)

data: {
    return() {
        open: false,
        current: 0
    }
},

computed: {

    //Filtering the suggestion based on the input
    matches() {
        return this.suggestions.filter((str) => {
            return str.indexOf(this.selection) >= 0;
        });
    },

    //The flag
    openSuggestion() {
        return this.selection !== "" &&
               this.matches.length != 0 &&
               this.open === true;
    }
}

Now you can test what we have done so far using npm run dev (assuming you would have done npm install). We have already achieved displaying the suggestions. What is remaining yet is to handle the events to select the suggestion.

We need to handle these five events

  • Pressing Down arrow
  • Pressing Up arrow
  • Clicking a suggestion
  • Pressing Enter on a suggestion
  • Changing the input

In the process we also need to update the class to highlight active suggestion.

Lets update the template to add these five events.

<div style="position:relative" v-bind:class="{'open':openSuggestion}">
    <input class="form-control" type="text" v-model="selection"
        @keydown.enter = 'enter'
        @keydown.down = 'down'
        @keydown.up = 'up'
        @input = 'change'
    />
    <ul class="dropdown-menu" style="width:100%">
        <li v-for="suggestion in matches"
            v-bind:class="{'active': isActive($index)}"
            @click="suggestionClick($index)"
        >
            <a href="#">{{ suggestion }}</a>
        </li>
    </ul>
</div>

Now we need to add the methods section to add the handlers for these events.

methods: {
    //When enter pressed on the input
    enter() {
        this.selection = this.matches[this.current];
        this.open = false;
    },

    //When up pressed while suggestions are open
    up() {
        if(this.current > 0)
            this.current--;
    },

    //When up pressed while suggestions are open
    down() {
        if(this.current < this.matches.length - 1)
            this.current++;
    },

    //For highlighting element
    isActive(index) {
        return index === this.current;
    },

    //When the user changes input
    change() {
        if (this.open == false) {
            this.open = true;
            this.current = 0;
        }
    },

    //When one of the suggestion is clicked
    suggestionClick(index) {
        this.selection = this.matches[index];
        this.open = false;
    },
}

Now our Autocomplete component is completely ready!

So lets write the code to use it. In the main.js we are going to use the Autocomplete component.

import Vue from 'vue';
import Autocomplete from './Autocomplete.vue';

new Vue({
    el: 'body',

    components: { Autocomplete },

    data: {
        cities : [
            'Bangalore','Chennai','Cochin',
            'Delhi','Kolkata','Mumbai'
        ],

        value: ''
    }

});

And in the index.html add the following to the body

<div class="container" style="padding-top:50px;">
    <autocomplete :suggestions="cities" :selection.sync="value"></autocomplete>
</div>

So finally you should see something like this.

Autocomplete using Vue.js

Source is available at Github.

Fareez Ahamed

SAP Developer, Full Stack Web Developer, Laravel & Vue.js fanatic