因此,我已经在Vue JS(2.x(中实现了这个自定义下拉列表,并且几乎拥有了我所需要的东西,只是一旦列表打开,我希望在组件外部(父页面或组件上的任何位置(单击以关闭列表。我尝试捕获组件的根div
的blur
事件,但可以理解的是,它不起作用,因为div
无法聚焦,因此无法模糊。因此,目前的解决方案似乎是——能够侦听组件外部的单击事件。这可能吗?孩子可以在Vue中侦听其父母的事件吗?如果没有,实现这种行为的最佳和/或最简单的方法是什么?
这是我在CodeSandbox中的代码,为了方便起见,我还在下面复制它https://codesandbox.io/s/romantic-night-ot7i8
下拉.vue
<template>
<div class="main-container" @blur="listOpen = false">
<button class="sel-btn" @click="listOpen = !listOpen">
{{ getText() }}
</button>
<br />
<div class="list-items" v-show="listOpen">
<button
class="item"
v-for="(l, i) in list"
:key="i"
@click="btnClicked(i)"
>
{{ l }}
</button>
</div>
</div>
</template>
<script>
export default {
props: {
defaultText: {
type: String,
required: false,
},
list: {
type: Array,
required: true,
},
},
data() {
return {
selectedIndex: -1,
listOpen: false,
};
},
methods: {
getText() {
if (this.selectedIndex !== -1) return this.list[this.selectedIndex];
if (this.defaultText) return this.defaultText;
return "Please Select";
},
btnClicked(index) {
this.selectedIndex = index;
this.listOpen = false;
this.$emit("input", this.list[index]);
},
},
};
</script>
<style scoped>
.main-container {
position: relative;
display: inline-block;
min-width: 125px;
max-width: 125px;
text-align: left;
border: 0;
background: #0000;
}
.sel-btn {
display: inline-block;
width: 100%;
text-align: left;
border: 0;
background: linear-gradient(0deg, #efefef, #fff);
padding: 8px;
border: 1px solid #1111;
box-shadow: 1px 1px 1px #1115;
cursor: pointer;
overflow: hidden;
}
.sel-btn:hover {
background: linear-gradient(0deg, #cef, #fff);
box-shadow: 1px 1px 1px #6390bd;
color: #58a;
}
.sel-btn::after {
content: "2bc6";
position: absolute;
right: 4%;
}
.list-items {
position: absolute;
width: 100%;
background: #fff;
box-shadow: 1px 1px 3px #3333;
}
.item {
display: block;
width: 100%;
text-align: left;
background: #fff;
border: 0;
cursor: pointer;
padding: 8px;
border-bottom: 1px solid #2221;
font-size: 0.8rem;
}
.item:hover {
background: #4b84c5;
color: #fff;
}
</style>
App.vue
<template>
<div id="app">
<br />
<div style="margin-top: 30px; margin-bottom: 10px">
<Dropdown
:list="regions"
v-model="selectedRegion"
style="margin-right: 10px"
defaultText="--Select Region--"
></Dropdown>
<Dropdown
:list="cities"
v-model="selectedCity"
defaultText="--Select City--"
></Dropdown>
</div>
<div style="padding: 30px; background: #27e2">
(This poem excerpt is here to act as a filler)
<h2>The Daffodils</h2>
<h4><i>William Wordsworth</i></h4>
I wondered lonely<br />
As a cloud<br />
That floats on high<br />
O'er vales and hills<br />
When all at once<br />
I saw a crowd<br />
A host<br />
Of golden daffodils.<br />
</div>
<div style="padding: 30px">
<b>Region Selected:</b> {{ selectedRegion }}<br />
<b>City Selected:</b> {{ selectedCity }}
</div>
</div>
</template>
<script>
import Dropdown from "./components/Dropdown";
export default {
name: "App",
components: {
Dropdown,
},
data() {
return {
set: false,
regions: ["California", "Nova Scotia", "Kerala", "Bavaria", "Queensland"],
cities: [
"Munich",
"San Diego",
"Paris",
"Prague",
"Copenhagen",
"Kolkata",
"Dhaka",
"Colombo",
"Little City",
],
selectedRegion: "",
selectedCity: "",
};
},
};
</script>
<style>
* {
font-family: "Segoe UI", Helvetica, Arial, sans-serif;
}
h1,
h2,
h3,
h4,
b {
font-weight: 400;
color: #27c;
}
</style>
好的。。。这不是迄今为止最好的解决方案,但它是一个有效的解决方案。我用尽了我所有的麦基弗能力,找到了一条路。
请检查这个代码沙盒
我所做的只是使用你的listOpen
并添加了一个eventListner。我发现您的自定义下拉列表在@blur
中没有内置,因为它不是c的输入。所以我在mounted
钩子中为它添加了一个事件。
关键是我还在100ms上添加了一个setTimeout
,因为否则你无法选择下拉列表中的任何项目,用blur
关闭下拉列表的速度会比你选择任何项目的速度更快。
最好的方法是使用Vue自定义指令将焦点功能挂接到组件上,并在单击组件内部或外部时将事件传播到组件外部。
在下面的示例中,我们创建了v-ctrlfocus指令,该指令将挂接到组件的根,并为整个窗口创建一个点击事件侦听器。
这样,当点击事件被触发时,我们可以比较它是发生在组件内部还是外部
如果发生在内部,我们将触发focusin事件,您可以使用@focusin在组件外部侦听。相反,如果单击发生在组件外部,则会引发focusout事件。
为了更多地更新Vue的新玩具,我使用了打字和<脚本设置>语法:
<script setup lang = "ts">
import { defineEmits } from 'vue';
const emit = defineEmits(['focusin', 'focusout']);
let element: HTMLElement;
const vCtrlfocus = {
mounted: (el: HTMLElement) => {
element = el;
window.addEventListener('click', listenerFunction);
},
unmounted: () => {
window.removeEventListener('click', listenerFunction);
},
};
const listenerFunction = (ev: Event) => {
let targetel = < Element > ev.target;
if (targetel === element || element.contains(targetel)) {
emit('focusin');
} else {
emit('focusout');
}
};
</script>
<template>
<div v-ctrlfocus>
...your component code goes here...
</div>
</template>
这种方法的功劳归于这篇Stackoverflow文章和这篇关于这个问题的不同方法的CSS技巧文章。
正如Vue文档所提到的,你也可以在应用程序级别全局注册这个自定义指令,并在你的代码中使用它。