提问人:Gaël Duval 提问时间:2/1/2023 最后编辑:Gaël Duval 更新时间:2/1/2023 访问量:258
VueJS - Click 事件不会作为父事件 prevault 触发
VueJS - Click event is not triggered as parent event prevaults
问:
我正在构建一个搜索输入,该输入从我的 API 获取数据并将其列在下拉列表中。
以下是我希望我的组件具有的行为:
- 如果我开始输入并且我的 API 找到数据,它会打开下拉菜单并列出它。
- 如果我单击列表中的某个元素,它将设置为“activeItem”,并且下拉列表将关闭
- 否则,我可以单击组件(输入和下拉列表)并关闭下拉列表
- 否则,不会出现下拉列表,我的输入就像常规文本输入一样工作
我的问题与事件冒泡有关。
- 我的列表项(来自 API)有一个@click输入,将单击的元素设置为“activeItem”。
- 我的输入同时具有@focusin和@focusout事件,这些事件允许我显示或隐藏下拉列表。
我无法单击下拉列表中的元素,因为首先触发了输入中的@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"
>
{{ 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>
以下是输入:
使用 API 结果(无法单击元素):
未找到数据时:
我试过了:
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)"
>
解决方案:
如果单击的元素不是,则 relatedTarget 将为 null 可聚焦。通过添加 tabindex 属性,它应该使元素 focusable 并允许将其设置为 relatedTarget。如果你真的 碰巧点击一些容器或覆盖元素,确保 被单击的元素添加了 tabindex=“0”,因此您 可以保持 isFocus = true
感谢@yoduh的解决方案
答:
2赞
yoduh
2/1/2023
#1
根本问题似乎是一旦输入因为 on 而失去焦点,下拉列表是如何从 DOM 中删除的。v-if
<Card
v-if="isFocus && results.length"
>
这是可以的,但你需要通过提出一个解决方案来解决它,无论重点是输入还是下拉列表,都保持真实。我建议您的输入执行一个方法,该方法仅在焦点事件的 relatedTarget 不是任何下拉项(可以通过类名或其他属性确定)时设置。实现此目的的一个障碍是,某些元素(如 items)本身不可聚焦,因此它们不会设置为 relatedTarget,但可以通过添加 tabindex 属性使它们可聚焦。把它们放在一起应该看起来像这样:isFocus
@focusout
isFocus = false
<li>
<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);
}
评论
0赞
Gaël Duval
2/1/2023
谢谢你的回答。event.relatedTarget 在我的情况下始终为 null。event.target 和 event.currentTarget 返回输入文本,无论我单击在哪里。
0赞
yoduh
2/1/2023
relatedTarget
如果单击的元素不可聚焦,则为 null。通过添加该属性,它应该使元素可聚焦并允许将其设置为 。如果您碰巧单击了某个容器或覆盖元素,请确保被单击的元素已添加到其中,以便您可以维护tabindex
relatedTarget
tabindex="0"
isFocus = true
0赞
Gaël Duval
2/1/2023
该死的,这个tabindex的东西太疯狂了。我不知道它会阻止我的元素被设置为 relatedTarget。谢谢伙计,它解决了我的问题。
评论
@focusin
@focusout