I have a vue-cli project, that has a component named 'AutoCompleteList.vue' that manually handled for searching experience and this component has some buttons that will be fill out the input.
It listens an array as its item list. so when this array has some items, it will be automatically shown; and when I empty this array, it will be automatically hidden.
I defined an oninput
event method for my input
, that fetch
es data from server, and fill the array. so the autocomplete list, will not be shown while the user doesn't try to enter something into the input.
I also like to hide the autocomplete list when the user blurs the input (onblur
).
but there is a really big problem! when the user chooses one of items (button
s) on the autocomplete list, JS-engine first blurs the input (onblur
runs) and then, tries to run onclick
method in autocomplete list. but its too late, because the autocomplete list has hidden and there is nothing to do. so the input will not fill out...
here is my code:
src/views/LoginView.vue
:
<template>
<InputGroup
label="Your School Name"
inputId="schoolName"
:onInput="schoolNameOnInput"
autoComplete="off"
:onFocus="onFocus"
:onBlur="onBlur"
:vModel="schoolName"
@update:vModel="newValue => schoolName = newValue"
/>
<AutoCompleteList
:items="autoCompleteItems"
:choose="autoCompleteOnChoose"
v-show="autoCompleteItems.length > 0"
:positionY="autoCompletePositionY"
:positionX="autoCompletePositionX"
/>
</template>
<script>
import InputGroup from '../components/InputGroup'
import AutoCompleteList from '../components/AutoCompleteList'
export default {
name: 'LoginView',
components: {
InputGroup,
AutoCompleteList
},
props: [],
data: () => ({
autoCompleteItems: [],
autoCompletePositionY: 0,
autoCompletePositionX: 0,
schoolName: ""
}),
methods: {
async schoolNameOnInput(e) {
const data = await (await fetch(`http://[::1]:8888/schools/${e.target.value}`)).json();
this.autoCompleteItems = data;
},
autoCompleteOnChoose(value, name) {
OO("#schoolName").val(name);
this.schoolName = name;
},
onFocus(e) {
const position = e.target.getBoundingClientRect();
this.autoCompletePositionX = innerWidth - position.right;
this.autoCompletePositionY = position.top + e.target.offsetHeight + 20;
},
onBlur(e) {
// this.autoCompleteItems = [];
// PROBLEM! =================================================================
}
}
}
</script>
src/components/AutoCompleteList.vue
:
<template>
<div class="autocomplete-list" :style="'top: ' + this.positionY + 'px; right: ' + this.positionX + 'px;'">
<ul>
<li v-for="(item, index) in items" :key="index">
<button @click="choose(item.value, item.name)" type="button">{{ item.name }}</button>
</li>
</ul>
</div>
</template>
<script>
export default {
name: 'AutoCompleteList',
props: {
items: Array,
positionX: Number,
positionY: Number,
choose: Function
},
data: () => ({
})
}
</script>
src/components/InputGroup.vue
:
<template>
<div class="input-group mb-3">
<label class="input-group-text" :for="inputId ?? ''">{{ label }}</label>
<input
:type="type ?? 'text'"
:class="['form-control', ltr && 'ltr']"
:id="inputId ?? ''"
@input="$event => { $emit('update:vModel', $event.target.value); onInput($event); }"
:autocomplete="autoComplete ?? 'off'"
@focus="onFocus"
@blur="onBlur"
:value="vModel"
/>
</div>
</template>
<script>
export default {
name: 'input-group',
props: {
label: String,
ltr: Boolean,
type: String,
inputId: String,
groupingId: String,
onInput: Function,
autoComplete: String,
onFocus: Function,
onBlur: Function,
vModel: String
},
emits: [
'update:vModel'
],
data: () => ({
}),
methods: {
}
}
</script>
Notes on LoginView.vue
:
autoCompletePositionX
and autoCompletePositionY
are used to find the best position to show the autocomplete list; will be changed in onFocus
method of the input
(inputGroup
)OO("#schoolName").val(name)
is used to change the value of the input, works like jQuery
(but not exactly)[::1]:8888
is my server that used to fetch the search resultsIf there was any unclear code, ask me in the comment
I need to fix this. any idea?
Thank you, @yoduh
I got the answer.
I knew there should be some differences between when the user focus out the input normally, and when he tries to click on buttons.
the key, was the FocusEvent.relatedTarget
property. It should be defined in onblur
method. here is its full tutorial.
I defined a property named isFocus
and I change it in onBlur
method, only when I sure that the focus is not on the dropdown menu, by checking the relatedTarget