[英]VueJS - Click event is not triggered as parent event prevaults
I am building a search input that fetchs data from my API and lists it in a dropdown list.我正在构建一个搜索输入,它从我的 API 中获取数据并将其列在下拉列表中。
Here is the behavior I want my component to have:这是我希望我的组件具有的行为:
My issue has to do with Event Bubbling.我的问题与事件冒泡有关。
I can't click the elements in the dropdown list as the @focusout event from the input is being triggered first and closes the list.我无法单击下拉列表中的元素,因为输入中的 @focusout 事件首先被触发并关闭列表。
import ...
export default {
components: {
...
},
props: {
...
},
data() {
return {
results: [],
activeItem: null,
isFocus: false,
}
},
watch: {
modelValue: _.debounce(function (newSearchText) {
... API Call
}, 350)
},
computed: {
computedLabel() {
return this.required ? this.label + '<span class="text-primary-600 font-bold ml-1">*</span>' : this.label;
},
value: {
get() {
return this.modelValue
},
set(value) {
this.$emit('update:modelValue', value)
}
}
},
methods: {
setActiveItem(item) {
this.activeItem = item;
this.$emit('selectItem', this.activeItem);
},
resetActiveItem() {
this.activeItem = null;
this.isFocus = false;
this.results = [];
this.$emit('selectItem', null);
},
},
emits: [
'selectItem',
'update:modelValue',
],
}
</script>
<template>
<div class="relative">
<label
v-if="label.length"
class="block text-tiny font-bold tracking-wide font-medium text-black/75 mb-1 uppercase"
v-html="computedLabel"
></label>
<div :class="widthCssClass">
<div class="relative" v-if="!activeItem">
<div class="flex items-center text-secondary-800">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-3.5 w-3.5 ml-4 absolute"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
stroke-width="2"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
/>
</svg>
<!-- The input that triggers the API call -->
<input
class="text-black py-2.5 pr-3.5 pl-10 text-black focus:ring-primary-800 focus:border-primary-800 block w-full rounded sm:text-sm border-gray-300"
placeholder="Search for anything..."
type="text"
@input="$emit('update:modelValue', $event.target.value)"
@focusin="isFocus = true"
@focusout="isFocus = false"
>
</div>
<!-- The Dropdown list -->
<Card
class="rounded-t-none shadow-2xl absolute w-full z-10 mt-1 overflow-y-auto max-h-48 px-0 py-0"
v-if="isFocus && results.length"
>
<div class="flow-root">
<ul role="list" class="divide-y divide-gray-200">
<!-- API results are displayed here -->
<li
v-for="(result, index) in results"
:key="index"
@click="setActiveItem(result)" <!-- The event I can't trigger -->
>
<div class="flex items-center space-x-4 cursor-pointer px-4 py-3">
<div class="flex-shrink-0">
<img
class="h-8 w-8 rounded-md ring-2 ring-lighter shadow-lg"
:src="result.image ?? this.$page.props.page.defaultImage.url"
:alt="result.title"
/>
</div>
<div class="min-w-0 flex-1">
<p
class="truncate text-sm font-medium text-black"
:class="{
'text-primary-900 font-bold': result.id === activeItem?.id
}"
>
{{ result.title }}
</p>
<p class="truncate text-sm text-black/75">
{{ result.description }}
</p>
</div>
<div v-if="result.action">
<Link
:href="result.action?.url"
class="inline-flex items-center rounded-full border border-gray-300 bg-white px-2.5 py-0.5 text-sm font-medium leading-5 text-black/75 shadow-sm hover:bg-primary-50"
target="_blank"
>
{{ result.action?.text }}
</Link>
</div>
</div>
</li>
</ul>
</div>
</Card>
</div>
<!-- Display the active element, can be ignored for this example -->
<div v-else>
<article class="bg-primary-50 border-2 border-primary-800 rounded-md">
<div class="flex items-center space-x-4 px-4 py-3">
<div class="flex-shrink-0">
<img
class="h-8 w-8 rounded-md ring-2 ring-lighter shadow-lg"
:src="activeItem.image ?? this.$page.props.page.defaultImage.url"
:alt="activeItem.title"
/>
</div>
<div class="min-w-0 flex-1">
<p class="truncate text-sm font-medium text-black font-bold">
{{ activeItem.title }}
</p>
<p class="truncate text-sm text-black/75 whitespace-pre-wrap">
{{ activeItem.description }}
</p>
</div>
<div class="flex">
<AppButton @click.stop="resetActiveItem();" @focusout.stop>
<svg
class="w-5 h-5 text-primary-800"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M6 18L18 6M6 6l12 12"
></path>
</svg>
</AppButton>
</div>
</div>
</article>
</div>
</div>
</div>
</template>
Here is a look at the input:下面是输入:
With API results (can't click the elements):结果为 API(无法单击元素):
When no data is found:当没有找到数据时:
I tried:我试过:
handleFocusOut(e) {
console.log(e.relatedTarget, e.target, e.currentTarget)
// No matter where I click:
// e.relatedTarget = null
// e.target = <input id="search" class="...
// e.currentTarget = <input id="search" class="...
}
...
<input
id="search"
class="..."
placeholder="Search for anything..."
type="text"
@input="$emit('update:modelValue', $event.target.value)"
@focusin="isFocus = true"
@focusout="handleFocusOut($event)"
>
The solution:解决方案:
relatedTarget will be null if the element you click on is not focusable.
如果您单击的元素不可聚焦,则 relatedTarget 将为 null。 by adding the tabindex attribute it should make the element focusable and allow it to be set as relatedTarget.
通过添加 tabindex 属性,它应该使元素可聚焦并允许将其设置为 relatedTarget。 if you actually happen to be clicking on some container or overlay element make sure the element being clicked on has that tabindex="0" added to it so you can maintain isFocus = true
如果您确实碰巧点击了某个容器或覆盖元素,请确保被点击的元素添加了 tabindex="0" 以便您可以维护 isFocus = true
Thanks to @yoduh for the solution感谢@yoduh 的解决方案
The root issue looks to be how the dropdown list is being removed from the DOM as soon as the input loses focus because of the v-if
on it.根本问题似乎是一旦输入因为
v-if
而失去焦点,下拉列表如何从 DOM 中删除。
<Card
v-if="isFocus && results.length"
>
This is ok to have, but you'll need to work around it by coming up with a solution that keeps isFocus
true whether the focus is on the input or the dropdown.这没关系,但您需要通过提出一个解决方案来解决这个问题,无论焦点是在输入还是下拉列表上,该解决方案都能使
isFocus
保持为真。 I would suggest your input's @focusout
to execute a method that only sets isFocus = false
if the focus event's relatedTarget is not any of the dropdown items (can be determined via classname or other attribute).如果焦点事件的relatedTarget不是任何下拉项(可以通过类名或其他属性确定),我建议您输入的
@focusout
执行仅设置isFocus = false
的方法。 One roadblock to implementing this is that some elements aren't natively focusable, like <li>
items, so they won't be set as the relatedTarget, but you can make them focusable by adding the tabindex attribute.实现这一点的一个障碍是某些元素本身不是可聚焦的,例如
<li>
项,因此它们不会被设置为 relatedTarget,但您可以通过添加tabindex属性使它们可聚焦。 Putting it all together should look something like this:把它们放在一起应该是这个样子:
<input
type="text"
@input="$emit('update:modelValue', $event.target.value)"
@focusin="isFocus = true"
@focusout="loseFocus($event)"
/>
...
<li
v-for="(result, index) in results"
:key="index"
class="listResult"
tabindex="0"
@click="setActiveItem(result)"
>
loseFocus(event) {
if (event.relatedTarget?.className !== 'listResult') {
this.isFocus = false;
}
}
setActiveItem(item) {
this.activeItem = item;
this.isFocus = false;
this.$emit('selectItem', this.activeItem);
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.