Vignesh Swaminathan
Vignesh Swaminathan

Reputation: 329

how to create a custom autocomplete component in vue js?

Currently i am using buefy autocomplete.But there are a few issues with it.

DepartmentDetail.vue

<template slot-scope="props">
  <div class="container is-fluid">
    <b-loading :is-full-page="true" :active.sync="this.isLoading"></b-loading>
<b-field label="Business Unit">
      <b-autocomplete
        :data="dataBusinessUnit"
        placeholder="select a business unit..."
        field="businessUnit"
        :loading="isFetching"
        :value="this.objectData.businessUnit"
        @typing="getAsyncDataBusinessUnit"
        @select="(option) => {updateValue(option.id,'businessUnit')}"
      >
        <template slot-scope="props">
          <div class="container">
            <p>
              <b>ID:</b>
              {{props.option.id}}
            </p>
            <p>
              <b>Description:</b>
              {{props.option.description}}
            </p>
          </div>
        </template>
        <template slot="empty">No results found</template>                
      </b-autocomplete>
    </b-field> 
</div>
</template>  

Function that fetches the results based on user input-

    getAsyncDataBusinessUnit: debounce(function(name) {
     //  console.log('getAsyncDataBusinessUnit you typed'+name);
      if (!name.length) {
        this.dataBusinessUnit = [];
        return;
      }
      this.isFetching = true;
      api
        .getSearchData(this.sessionData.key,`/businessunit/${name}`)
        .then(response => {
          this.dataBusinessUnit = [];
          response.forEach(item => {
            this.dataBusinessUnit.push(item);
          });
        })
        .catch(error => {
          this.dataBusinessUnit = [];
          throw error;
        })
        .finally(() => {
          this.isFetching = false;
        });
    }, 500), 

So instead of using the buefy's b-autocomplete i want to create and use my own autocomplete component. So i went ahead and created my own autocomplete component and called it from the DepartmentDetail vue page like this-

DepartmentDetail.vue

<b-field label="Custom Business Unit ">
    <AutoComplete :method="getAsyncDataBusinessUnit" title='businessUnit'   :autocompleteData="dataBusinessUnit" viewname='DepartmentDetail'>
      </AutoComplete>
</b-field>

AutoComplete.vue

<template>

  <div class="autocomplete">
    <input style="font-size: 12pt; height: 36px; width:1800px; " type="text"    v-model="this.objectData[this.title]" @input="getAsyncDataBusinessUnit"/>
<ul v-show="isFetching" >
      <li v-for="(dataBusinessUnit, i) in dataBusinessUnit" :key="i" @click="setResult(dataBusinessUnit)" >
         <!-- {{ autocompleteData }} -->
          <template v-if="title!='manager'">
          <div class="container">
            <p>
              <b>ID:</b>
              {{dataBusinessUnit.id}}
            </p>
            <p>
              <b>Description:</b>
              {{dataBusinessUnit.description}}
            </p>
          </div>
        </template>
         <template v-else>
        <div class="container">
            <p>
              <b>ID:</b>
              {{dataBusinessUnit.id}}
            </p>
            <p>
              <b>First Name:</b>
              {{dataBusinessUnit.firstName}}
            </p>
            <p>
              <b>Last Name:</b>
              {{dataBusinessUnit.lastName}}
            </p>
          </div>
        </template>
           </li>
    </ul>
  </div>
</template>


<script>
import { viewMixin } from "../viewMixin.js";
import schemaData from '../store/schema';
import debounce from "lodash/debounce";
import api from "../store/api";
const ViewName = "AutoComplete";
var passedview;

export default {
  name: "AutoComplete",
   props: {
    method: { 
        type: Function 
        },
        title: String,
        viewname:String,
        autocompleteData: {
       type: Array,
       required: true
    }
  },
  

   data() {
    return {
  //   results: [],
      dataBusinessUnit: [],
      isFetching: false
     // vignesh: this.objectData[this.title]
    };
  }, 
  computed: {
            viewData() {
                return this.$store.getters.getViewData('DepartmentDetail')
            },
            objectData() {             
                  return this.$store.getters.getApiData(this.viewData.api_id).data
            },
            sessionData() {
                return this.$store.getters.getSessionData()
            },
            isLoading() {
                return this.$store.getters.getApiData(this.viewData.api_id).isLoading
            },
            newRecord() {
                return this.$route.params.id === null;
            },
            getTitle() {
              return this.title
            }            
  }, 
    mounted() {
     
      
  },
    methods: {
       setResult(result) {
      
          this.updateValue(result.id,this.title);
        //  localStorage.setItem(this.title,result.id );        
         this.isFetching = false;
      },  
        updateValue(newValue, fieldName) {
                var val;

                var schema = schemaData[this.viewData.schema];
               

                if(typeof schema!=='undefined' && schema['properties'][fieldName]['type'] == 'date'){
                    val = this.formatDate(newValue);
                } else {
                    val = newValue;
                }
                
                this.$store.dispatch('updateDataObjectField', {
                    key: this.viewData.api_id,
                    field: fieldName,
                    value: val
                });
            },
            
 getAsyncDataBusinessUnit: debounce(function(name) {
             
       console.log('getAsyncDataBusinessUnit you typed'+name.target.value);
      if (!name.target.value.length) {
        this.dataBusinessUnit = [];
        this.isFetching = false;
        return;
      }
   //   this.isFetching = true;
      

      api
        .getSearchData(this.sessionData.key,`/businessunit/${name.target.value}`)
        .then(response => {
          this.dataBusinessUnit = [];
          if (!response.length)
          {
            console.log('inside if')
          this.isFetching = false;
          }

          else{
            console.log('inside else')
            response.forEach(item => {
            this.dataBusinessUnit.push(item);
          });
          this.isFetching = true;
          }
         
          console.log('length of dataBusinessUnit is '+(this.dataBusinessUnit).length)
          console.log('contents of dataBusinessUnit array '+JSON.stringify(this.dataBusinessUnit))
        })
        .catch(error => {
          this.dataBusinessUnit = [];
          throw error;
        })
        .finally(() => {
         // this.isFetching = true;
        });

    }, 500), 
      
    },
    
    components: {
    

    }

};
</script>

The issue iam facing is whenever i start to type anything into the Custom Business Unit input field, then immediately after 1-2 seconds the value gets reset(with the value coming from the store getters ).This however does not happen if i remove the line this.dataBusinessUnit = []; in the getAsyncDataBusinessUnit function . Why is this happening? I even tried using :input instead of v-model for the input field , but i am still facing the same issue.And also second issue is when i click an existing record under DepartmentDetail page, the value that should be set for the custom business unit input field coming from the store getters (this.objectData.businessUnit) is not showing up sometimes? please help

Upvotes: 0

Views: 3736

Answers (1)

muka.gergely
muka.gergely

Reputation: 8329

The basic logic of an autocomplete is not really hard:

Vue.component('Autocomplete', {
  props: ['list'],
  data() {
    return {
      input: null
    }
  },
  template: `<div><input v-model="input" @input="handleInput"><div class="bordered" v-if="input"><ul><li v-for="(item, i) in list" :key="i" @click="setInput(item)">{{ item }}</li></ul></div></div>`,
  methods: {
    handleInput(e) {
      this.$emit('input', e.target.value)
    },
    setInput(value) {
      this.input = value
      this.$emit('input', value)
    }
  }
})

new Vue({
  el: "#app",
  computed: {
    filteredList() {
      if (this.filterInput) {
        return this.list.filter(e => e.toLowerCase().indexOf(this.filterInput.toLowerCase()) !== -1)
      } else {
        return this.list
      }
    }
  },
  data: {
    filterInput: null,
    list: [
      "First",
      "Second",
      "Third",
      "Fourth",
      "Fifth",
      "Sixth",
      "Seventh"
    ]
  },
  methods: {
    handleInput(e) {
      this.filterInput = e
    }
  }
})
.bordered {
  border: 1px solid black;
  display: block;
}

ul li {
  cursor: pointer;
}

ul li:hover {
  background: rgba(0, 0, 0, 0.3)
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
  <autocomplete :list="filteredList" @input="handleInput" />
</div>

I wouldn't handle the data in the presentational component (autocomplete is a presentational component in the structure I created), but in its parent-container. That way the autocomplete just gets a list as a prop and displays it; every action is $emitted to the parent, who does the data-handling.

It's easier to control displayed data this way - even with an async data source.

Upvotes: 1

Related Questions