Skip to content
Fix Code Error

Autocomplete – select option with mouse click – vuejs

April 15, 2021 by Code Error
Posted By: Anonymous

Some time ago I created an autocomplete component in vue for a project in which I am involved.

But today I detected a small bug.

When I select the option I want with the click of the mouse, the option does not get transmitted, as you can see in the console.log () that is in the example. If I click on another option again, what will appear in console.log () is the option previously selected.

If I put a setTimeout( () => {}, 200) it already detects and emit the option, but I think it is not the best solution for this case.

Any suggestion?

example

_x000D_

_x000D_

const Autocomplete = {_x000D_
  name: "autocomplete",_x000D_
  template: "#autocomplete",_x000D_
  props: {_x000D_
    items: {_x000D_
      type: Array,_x000D_
      required: false,_x000D_
      default: () => Array(150).fill().map((_, i) => `Fruit ${i+1}`)_x000D_
    },_x000D_
    isAsync: {_x000D_
      type: Boolean,_x000D_
      required: false,_x000D_
      default: false_x000D_
    }_x000D_
  },_x000D_
_x000D_
  data() {_x000D_
    return {_x000D_
      isOpen: false,_x000D_
      results: [],_x000D_
      search: "",_x000D_
      isLoading: false,_x000D_
      arrowCounter: 0_x000D_
    };_x000D_
  },_x000D_
_x000D_
  methods: {_x000D_
    onChange() {_x000D_
      console.log( this.search)_x000D_
      // Let's warn the parent that a change was made_x000D_
      this.$emit("input", this.search);_x000D_
    },_x000D_
    setResult(result, i) {_x000D_
      this.arrowCounter = i;_x000D_
      this.search = result;_x000D_
      this.isOpen = false;_x000D_
    },_x000D_
    showAll() {_x000D_
      this.isOpen = !this.isOpen;_x000D_
			(this.isOpen) ? this.results = this.items : this.results = [];_x000D_
    },_x000D_
  },_x000D_
  computed: {_x000D_
      filterResults() {_x000D_
      // first uncapitalize all the things_x000D_
      this.results = this.items.filter(item => {_x000D_
        return item.toLowerCase().indexOf(this.search.toLowerCase()) > -1;_x000D_
      });_x000D_
      _x000D_
     _x000D_
      return this.results;_x000D_
    },_x000D_
  },_x000D_
  watch: {_x000D_
    items: function(val, oldValue) {_x000D_
      // actually compare them_x000D_
      if (val.length !== oldValue.length) {_x000D_
        this.results = val;_x000D_
        this.isLoading = false;_x000D_
      }_x000D_
    }_x000D_
  },_x000D_
  mounted() {_x000D_
    document.addEventListener("click", this.handleClickOutside);_x000D_
  },_x000D_
  destroyed() {_x000D_
    document.removeEventListener("click", this.handleClickOutside);_x000D_
  }_x000D_
};_x000D_
_x000D_
new Vue({_x000D_
  el: "#app",_x000D_
  name: "app",_x000D_
  components: {_x000D_
    autocomplete: Autocomplete_x000D_
  }_x000D_
});

_x000D_

#app {_x000D_
  font-family: "Avenir", Helvetica, Arial, sans-serif;_x000D_
  -webkit-font-smoothing: antialiased;_x000D_
  -moz-osx-font-smoothing: grayscale;_x000D_
  color: #2c3e50;_x000D_
}_x000D_
_x000D_
.autocomplete {_x000D_
  position: relative;_x000D_
  width: 130px;_x000D_
}_x000D_
_x000D_
.autocomplete-results {_x000D_
  padding: 0;_x000D_
  margin: 0;_x000D_
  border: 1px solid #eeeeee;_x000D_
  height: 120px;_x000D_
  overflow: auto;_x000D_
  width: 100%;_x000D_
}_x000D_
_x000D_
.autocomplete-result {_x000D_
  list-style: none;_x000D_
  text-align: left;_x000D_
  padding: 4px 2px;_x000D_
  cursor: pointer;_x000D_
}_x000D_
_x000D_
.autocomplete-result.is-active,_x000D_
.autocomplete-result:hover {_x000D_
  background-color: #4aae9b;_x000D_
  color: white;_x000D_
}

_x000D_

<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.js"></script>_x000D_
<div id="app">_x000D_
  <autocomplete />_x000D_
_x000D_
</div>_x000D_
_x000D_
<script type="text/x-template" id="autocomplete">_x000D_
  <div class="autocomplete">_x000D_
    <input type="text" @blur="onChange" v-model="search"  @click="showAll" />_x000D_
    <ul id="autocomplete-results" v-show="isOpen" ref="scrollContainer" class="autocomplete-results">_x000D_
      <li class="loading" v-if="isLoading">_x000D_
        Loading results..._x000D_
      </li>_x000D_
      <li ref="options" v-else v-for="(result, i) in filterResults" :key="i" @click="setResult(result, i)" class="autocomplete-result" :class="{ 'is-active': i === arrowCounter }">_x000D_
        {{ result }}_x000D_
      </li>_x000D_
    </ul>_x000D_
_x000D_
  </div>_x000D_
</script>

_x000D_

_x000D_

_x000D_

Solution

You were using onblur event, but its fired when you click outside and before the onclick’s item listener, so the value wasn’t updated.

Use onchange event to capture data if user types anything in the input and call onChange() method inside setResult().

_x000D_

_x000D_

const Autocomplete = {_x000D_
  name: "autocomplete",_x000D_
  template: "#autocomplete",_x000D_
  props: {_x000D_
    items: {_x000D_
      type: Array,_x000D_
      required: false,_x000D_
      default: () => Array(150).fill().map((_, i) => `Fruit ${i+1}`)_x000D_
    },_x000D_
    isAsync: {_x000D_
      type: Boolean,_x000D_
      required: false,_x000D_
      default: false_x000D_
    }_x000D_
  },_x000D_
_x000D_
  data() {_x000D_
    return {_x000D_
      isOpen: false,_x000D_
      results: [],_x000D_
      search: "",_x000D_
      isLoading: false,_x000D_
      arrowCounter: 0_x000D_
    };_x000D_
  },_x000D_
_x000D_
  methods: {_x000D_
    onChange() {_x000D_
      console.log( this.search)_x000D_
      // Let's warn the parent that a change was made_x000D_
      this.$emit("input", this.search);_x000D_
    },_x000D_
    setResult(result, i) {_x000D_
      this.arrowCounter = i;_x000D_
      this.search = result;_x000D_
      this.isOpen = false;_x000D_
      // Fire onChange, because it won't do it on blur_x000D_
      this.onChange();_x000D_
    },_x000D_
    showAll() {_x000D_
      this.isOpen = !this.isOpen;_x000D_
			(this.isOpen) ? this.results = this.items : this.results = [];_x000D_
    },_x000D_
  },_x000D_
  computed: {_x000D_
      filterResults() {_x000D_
      // first uncapitalize all the things_x000D_
      this.results = this.items.filter(item => {_x000D_
        return item.toLowerCase().indexOf(this.search.toLowerCase()) > -1;_x000D_
      });_x000D_
      _x000D_
     _x000D_
      return this.results;_x000D_
    },_x000D_
  },_x000D_
  watch: {_x000D_
    items: function(val, oldValue) {_x000D_
      // actually compare them_x000D_
      if (val.length !== oldValue.length) {_x000D_
        this.results = val;_x000D_
        this.isLoading = false;_x000D_
      }_x000D_
    }_x000D_
  },_x000D_
  mounted() {_x000D_
    document.addEventListener("click", this.handleClickOutside);_x000D_
  },_x000D_
  destroyed() {_x000D_
    document.removeEventListener("click", this.handleClickOutside);_x000D_
  }_x000D_
};_x000D_
_x000D_
new Vue({_x000D_
  el: "#app",_x000D_
  name: "app",_x000D_
  components: {_x000D_
    autocomplete: Autocomplete_x000D_
  }_x000D_
});

_x000D_

#app {_x000D_
  font-family: "Avenir", Helvetica, Arial, sans-serif;_x000D_
  -webkit-font-smoothing: antialiased;_x000D_
  -moz-osx-font-smoothing: grayscale;_x000D_
  color: #2c3e50;_x000D_
}_x000D_
_x000D_
.autocomplete {_x000D_
  position: relative;_x000D_
  width: 130px;_x000D_
}_x000D_
_x000D_
.autocomplete-results {_x000D_
  padding: 0;_x000D_
  margin: 0;_x000D_
  border: 1px solid #eeeeee;_x000D_
  height: 120px;_x000D_
  overflow: auto;_x000D_
  width: 100%;_x000D_
}_x000D_
_x000D_
.autocomplete-result {_x000D_
  list-style: none;_x000D_
  text-align: left;_x000D_
  padding: 4px 2px;_x000D_
  cursor: pointer;_x000D_
}_x000D_
_x000D_
.autocomplete-result.is-active,_x000D_
.autocomplete-result:hover {_x000D_
  background-color: #4aae9b;_x000D_
  color: white;_x000D_
}

_x000D_

<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.js"></script>_x000D_
<div id="app">_x000D_
  <autocomplete />_x000D_
_x000D_
</div>_x000D_
_x000D_
<script type="text/x-template" id="autocomplete">_x000D_
  <div class="autocomplete">_x000D_
    <input type="text" @change="onChange" v-model="search"  @click="showAll" />_x000D_
    <ul id="autocomplete-results" v-show="isOpen" ref="scrollContainer" class="autocomplete-results">_x000D_
      <li class="loading" v-if="isLoading">_x000D_
        Loading results..._x000D_
      </li>_x000D_
      <li ref="options" v-else v-for="(result, i) in filterResults" :key="i" @click="setResult(result, i)" class="autocomplete-result" :class="{ 'is-active': i === arrowCounter }">_x000D_
        {{ result }}_x000D_
      </li>_x000D_
    </ul>_x000D_
_x000D_
  </div>_x000D_
</script>

_x000D_

_x000D_

_x000D_

Answered By: Anonymous

Related Articles

  • Unexpected end of JSON input while parsing
  • Polymer 1.x: Observers
  • error LNK2005: ✘✘✘ already defined in…
  • Scroll with arrow keys on dropdown - vuejs
  • How do I remove single children in a tree?
  • How do you list volumes in docker containers?
  • [Vue warn]: Error in render: "TypeError: Converting circular…
  • Implementing autocomplete
  • Style jQuery autocomplete in a Bootstrap input field
  • Vue&TypeScript: how to avoid error TS2345 when import…

Disclaimer: This content is shared under creative common license cc-by-sa 3.0. It is generated from StackExchange Website Network.

Post navigation

Previous Post:

Using VueJS component multiple times on the same page (and it works only the first time…)

Next Post:

How to import global css in Vue with PostCSS?

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

  • Get code errors & solutions at akashmittal.com
© 2022 Fix Code Error