0
votes

I'm new to Vue. I have an autocomplete component that used AXIOS to get the data. When I type in at least 3 characters, it does display the results in a list item, however I can't seem to figure out how to actually select the option and populate the text field.

Here is the code

  <template>
 <div>
  <input type="text" placeholder="Source client" v-model="query" v-on:keyup="autoComplete" @keydown.esc="clearText" class="form-control">
  <div class="panel-footer" v-if="results.length">
   <ul class="list-group">
    <li class="list-group-item" v-for="result in results">
   <span> {{ result.name + "-" + result.oid }} </span>
    </li>
   </ul>
  </div>
 </div>
</template>
<script>
import axios from 'axios'
 export default{
  data(){
   return {
    selected: '',
    query: '',
    results: []
   }
  },
  methods: {

  
    clearText(){
        this.query = ''
    },
    autoComplete(){
    this.results = [];
    if(this.query.length > 2){
     axios.get('/getclientdata',{params: {query: this.query}}).then(response => {
      this.results = response.data;
     });
    }
   }
  }
 }
</script>

Any help would be appreciated!

1
Simply replace UL / LI tags with SELECT / OPTION tags and attach the select to the same v-modelIVO GELOV

1 Answers

1
votes

Writing an autocomplete is a nice challenge.

You want this component to act like an input. That means that if you'd use your autocomplete component, you will probably want to use it in combination with v-model.

<my-autocomplete v-model="selectedModel"></my-autocomplete>

To let your component work in harmony with v-model you should do the following:

  • Your component should accept a prop named value, this may be a string, but this may also be an object. In the example above the value of selectedModel will be available inside your autocomplete component under '{{value}}' (or this.value).
  • To update the value supplied you $emit an input event with the selected value as second parameter. (this.$emit('input', clickedItem))

Inside your autocomplete you have query, which holds the search term. This is a local variable and it should be. Don't link it to value directly because you only want to change the value when a user selects a valid result.

So to make the example work you could do the following:

<template component="my-autocomplete">
 <div>
  <input type="text" placeholder="Source client" v-model="query" v-on:keyup="autoComplete" @keydown.esc="clearText" class="form-control">
  <div class="panel-footer" v-if="results.length">
   <ul class="list-group">
    <li class="list-group-item" v-for="result in results" @click="selectValue(result)">
   <span> {{ result.name + "-" + result.oid }} </span>
    </li>
   </ul>
  </div>
 </div>
</template>
<script>
 //import axios from 'axios'
 export default{
  props: ['value'],
  data(){
   return {
    selected: '',
    query: '',
    results: []
   }
  },
  methods: {
    clearText(){
        this.query = ''
    },
    autoComplete(){
         this.results = [];
         if(this.query.length > 2){
             this.results = [{ name: 'item 1', oid: '1' }, {name: 'item 2', oid: '2'}];
            //axios.get('/getclientdata',{params: {query: this.query}}).then(response => {
             //this.results = response.data;
            //});
         }
    },
     selectValue(selectedValue) {
         this.$emit('input', selectedValue);
     }
  }
 }
</script>

I commented out the axios part for demonstration purposes.

To verify the workings:

<template>
    <div>Autocomplete demo:
        <my-autocomplete v-model="selectedRow">
        </my-autocomplete>
        Selected value: <pre>{{selectedRow}}</pre>
    </div>
</template>
    
     <script>
        export default {
            data() {
                return {
                    selectedRow: null
                }
            }
        }
    </script>       

Now the autocomplete does what it should, it allows you to search and pick a valid result from the list.

One problem remains though. How do you display the selected value. Some options are:

  • Copy the value to query, this works seemlessly if all your options are strings
  • Copy the value of result.name to query, this would make the solution work
  • Accept a scoped slot from the parent component which is responsible for displaying the selected item.

I will demonstrate option 2:

<template component="my-autocomplete">
 <div>
  <input type="text" placeholder="Source client" v-model="query" v-on:keyup="autoComplete" @keydown.esc="clearText" class="form-control">
  <div class="panel-footer" v-if="results.length">
   <ul class="list-group">
    <li class="list-group-item" v-for="result in results" @click="selectValue(result)">
   <span> {{ result.name + "-" + result.oid }} </span>
    </li>
   </ul>
  </div>
 </div>
</template>
<script>
 //import axios from 'axios'
 export default{
  props: ['value'],
  data(){
   return {
    selected: '',
    query: '',
    results: []
   }
  },
  mounted() {
      if (this.value && this.value.name) { 
         this.query = this.value.name;
      }
  },
     // Because we respond to changes to value
     // we do not have to do any more work in selectValue()
     watch: { 
         value(value) {
             if (this.value && this.value.name) { 
                this.query = this.value.name;
            } else {
                this.query = '';
            }
         }
     },
  methods: {
    clearText(){
        this.query = ''
    },
    autoComplete(){
         this.results = [];
         if(this.query.length > 2){
             this.results = [{ name: 'item 1', oid: '1' }, {name: 'item 2', oid: '2'}];
            //axios.get('/getclientdata',{params: {query: this.query}}).then(response => {
             //this.results = response.data;
            //});
         }
    },
     selectValue(selectedValue) {
         this.$emit('input', selectedValue);
     }
  }
 }
</script>

A working example can be found here: https://jsfiddle.net/jangnoe/4xeuzvhs/1/