Menu

Creating a Autocomplete component from Vue JS and typescript

I had to create an Autocomplete component for fun, its no way near perfect. There are plenty of other implementations that are already serving the same purpose – but hey I wanted to do it my self – fair enough? 😉

This is using bootstrap 4 styles, I am using a text box and drop-down menu controls to archive this. Below is the code for the component.

<template>
    <div>
        <div class="lookup lookup-right">
            <input type="text" 
                   class="no-radius" 
                   autocomplete="nope" 
                   v-bind:class="cssClass" 
                   v-bind:placeholder="placeholderText" 
                   v-model="value" 
                   @change="onChange" 
                   @keypress="onChange" 
                   @keyup="onChange">
        </div>
        <div class="raw">
            <div class="col-12">
                <div class="dropdown-menu" x-placement="bottom-start" v-bind:class="{ 'show': open}">
                    <a v-for="item in items" :key="item" class="dropdown-item" href="#" @click="onClicked(item)">{{item[displayField]}}</a>
                </div>
            </div> 
        </div>       
    </div>
</template>
<script lang="ts">
import Vue from "vue";
import Component from "vue-class-component";

@Component({
  props: {
    cssClass: String,
    displayField: String,
    valueField: String,
    items: Array,
    placeholderText:String
  }
})
export default class AutocompleteComponent extends Vue {
  open: boolean = false;
  value: String = "";

  private onClicked(selected: any) {
    this.open = false;
    this.value = selected[this.$props["displayField"]];
    this.$emit("click", selected);
  }
  private onChange() {
    this.open = this.$props["items"].length > 0 ? true : false;
    this.$emit("change", this.value);
  }
}
</script>
<style scoped>
.dropdown-menu {
  position: absolute;
  top: 0px;
  left: 0px;
  will-change: top, left;
  width: 100%;
}
.lookup{
    width: 100%;
}
.lookup input{
    width: 100%;
    padding: 5px 12px;
}
</style>

This requires few improvements, like keyboard navigation on the auto lookup, etc.

But good enough to get started.

Below is the usage.

<template>
    <div v-bind:class="cssClass">       
        <autocomplete 
            :cssClass="'form-control'"
            :items="places" 
            :displayField="'Description'" 
            :valueField="'PlaceId'"
            :placeholderText="'Search for place ...'"
            v-model="selection"
            @change="onChange"
            @click="onSelect">
        </autocomplete>      
    </div>
</template>
<script lang="ts">
import Vue from "vue";
import Component from "vue-class-component";
import { googlePlacesService } from "../../services/GooglePlacesService";
import autocomplete from "../shared/Autocomplete.vue";
import { toasterService } from "../../services/ToasterService";
import { IRestResponse, IGooglePlaceItem, IGooglePlaceDetails } from "../../shared/models";
@Component({
  props: {
    cssClass: String   
  },
  components: {
    autocomplete
  }
})
export default class AutocompleteSuburbComponent extends Vue {
  places: IGooglePlaceItem[] = []; 
  selection: string = "";

  private onSelect(placeItem: IGooglePlaceItem) {
    googlePlacesService
      .get(placeItem.PlaceId)
      .then((response: IRestResponse<IGooglePlaceDetails>) => {
        let place: IGooglePlaceDetails = <IGooglePlaceDetails>response.content;       
        this.$emit("click", place);
      })
      .catch(error => {
        this.$emit("click", null);       
      });      
  }

  private onChange(value: any) {
    googlePlacesService
      .getAll(value)
      .then((response: IRestResponse<IGooglePlaceItem[]>) => {
        this.places = <IGooglePlaceItem[]>response.content;
      })
      .catch(error => {
        this.places = [];
      });
  }
}
</script>
<style scoped>
</style>

 

Leave a comment