So, I've implemented this custom dropdown list in Vue JS (2.x) and pretty much have what I need, except once the list is open, I would like for a click outside the component (anywhere on the parent page or component) to close the list. I tried catching the blur
event of the root div
of my component, but it understandably didn't work, because div
s don't get focus and hence cannot be blurred. So at this moment, the solution seems to be - to be able to listen for a click event outside the component. Is that possible? Can a child listen for events on its parent in Vue? If or even if not, what is the best and/or the easiest way to achieve this behavior?
Here's my code in a CodeSandbox, and I am also reproducing it below for convenience - https://codesandbox.io/s/romantic-night-ot7i8
Dropdown.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>
ok... this is by far not the best solution but it is a working solution. i used all my MacGyver powers and found a way.
please check this CodeSandbox
all i did was to use your listOpen
and added a eventListner. i figured out that your custom dropdown has no build in @blur
because its not a input ofc.
so i added a event for it inside the mounted
hook.
the key is i also added a setTimeout
on 100ms because otherwise you where not able to select any item inside your dropdown, the dropdown would close with blur
faster then you are able to select anything.