DEV Community

Yogesh Galav
Yogesh Galav

Posted on

How to build Autocomplete in Vue with tailwind

In my previous blog I have talked about making validation component and creating a package. Today I will talk about something simple that's autocomplete. I wanted to use Autocomplete in my site which could handle api on itself and set data on itself, also which could allow to create new item. In short it will have following features

Features
1. Autocomplete Input with filterable array.
2. Use Api url or promise as source instead of static array.
3. Use label and responseProperty for array of objects.

Let's build it step by step or feature by feature

1. Autocomplete Input with filterable array.
First building autocomplete component we will start with input box then list of items in ul>li tag and an array as prop.

<template>
    <div class="my-2">
      <input
            type="text" 
            class="text-base font-medium block w-full rounded-md border transition ease-in-out focus:ring-1 border-gray-300 border-solid py-2 px-3 text-gray-700 placeholder-gray-400 focus:border-blue-200 focus:ring-blue-500 focus:outline-none "
            :name="name" 
      >
      <ul 
            class="mt-1 border-2 border-slate-50 overflow-auto  shadow-lg rounded list-none"
           >
        <li
          :class="[ 
'hover:bg-blue-100 hover:text-blue-800',
'w-full list-none text-left py-2 px-3 cursor-pointer']"
        >
          item
        </li>
      </ul>
    </div>
  </template>
<script>
export default {
  props:['name', 'source']
}
</script>
Enter fullscreen mode Exit fullscreen mode

Now the accepted behaviour is when something is typed into input box it should filter items from array and show in results or li tag. Also we don't wanna show list initially but only when something is entered.

<input
...
v-model="search"
@change="onChange"
>
...
<ul
v-if="isOpen"
...
>
  <li 
    v-for="(result, i) in results" :key="i"
...
Enter fullscreen mode Exit fullscreen mode
data(){
  return {
    search:'',
    isOpen:false,
    results:[],
  };
},
methods:{
  onChange() {
    if (!this.source || !this.search) 
      return false;

    this.isOpen = true;
    this.arrayLikeSearch(this.source);
  },
  arrayLikeSearch(items){
    this.results = items.filter((item) => {
          return item.toLowerCase()
        .indexOf(this.search.toLowerCase()) > -1;
            });
  }
}
Enter fullscreen mode Exit fullscreen mode

2. Use Api url or promise as source instead of static array.
Now that we have created the basic structure let' blent it to support source as string(Api url) or promise function, so we don't have to create extra functions everywhere for simple data get and set. Promise will be really helpful when you want to use vuex store action or custom api function.

props: {
  name: {
    type: String,
    required: false,
    default: 'autocomplete',
  },
  source: {
    type: [String, Array, Function],
    required: true,
    default: '',
  },
},
methods:{
onChange() {
        if (!this.source || !this.search) 
          return false;

        this.isOpen = true;
        switch (true) {
          case typeof this.source === 'string':
            return this.request(this.source + this.search);
          case typeof this.source === 'function':
            return this.source(this.search).then(response=>{    
              this.results = this.getResults(response);
            });
          case Array.isArray(this.source):
            return this.arrayLikeSearch(this.source);
          default:
            throw new Error("typeof source is "+(typeof this.source))
        }
      },
request(url) {
        return axios.get(url)
          .then(response => {
            this.results = this.getResults(response);
          });
      },
}
Enter fullscreen mode Exit fullscreen mode

3. Use label and responseProperty for array of objects.
We have implemented array search and display with source given as simple array, Api url and promise. But in real world scenario we have nested objects and array objects instead of array of strings, therefore we have used "this.getResults(response)" instead of just "response" in above example.
Let's add two more props label and responsePropery to our component,
The label will be used for final object property string which will be displayed in list.
The responseProperty will be the key or name of Array which we will loop upon.
Suppose we have api response like this

{
  obj1,
  obj2,
  'ourArray':[
    {
      id:0,
      full_name:''
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Then our label will be 'full_name' and responseProperty will be 'ourArray'. Now let's code for it

<li
  v-for="(result, i) in results" :key="i"
  :class="..."
  @click="setResult(result)"
>
  {{ result[label] }}
</li>
Enter fullscreen mode Exit fullscreen mode
props:{
...
  label: {
    type: String,
    required: false,
    default: 'name',
  },
  responseProperty: {
    type: String,
    required: false,
    default: 'name',
  },
},
methods:{
...

      getResults (response) {
        if (this.responseProperty) {
            let foundObj;
            JSON.stringify(response, (_, nestedValue) => {
              if (nestedValue && nestedValue[this.responseProperty]) 
                foundObj = nestedValue[this.responseProperty];

              return nestedValue;
            });
            return foundObj;
        }
        if(Array.isArray(response)){
            return response;
        }
        return []
      },
      setResult(result) {
        this.search = result[this.label];
        this.isOpen = false;
      },
}
Enter fullscreen mode Exit fullscreen mode

I hope you like this,
Please contribute to my open source repos,
Thank you and Have a nice day

Top comments (0)