Autocomplete – select option with mouse click – vuejs
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
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().
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
Disclaimer: This content is shared under creative common license cc-by-sa 3.0. It is generated from StackExchange Website Network.