Reputation: 13
I'm in the process of implementing a combined select dropdown and searchbox component for a UI library I'm writing, using Vue.js to drive interactions, but I'm running into a problem with how Vue.js seems to handle communication between parent and child components.
Basically, what I'm aiming to achieve is to allow users of the library to define search-select elements in HTML using something like this:
<search-select>
<search-option value="foo">Foo</search-option>
<search-option value="bar">Bar</search-option>
<search-option value="baz">Baz</search-option>
<search-option value="foobar">Foobar</search-option>
</search-select>
My template for the search-select
component looks like this:
<template>
<div class="search-select">
<div class="placeholder" ref="placeholder"></div>
<ul class="dropdown">
<input
type="text"
placeholder="Type to filter..."
v-on:input="updateFilter"
>
<slot></slot>
</ul>
</div>
</template>
The idea is that the search-select
component will add a text input box above the specified inputs, which the user can type in to filter the options that can be selected. The finished component would look something like this.
However, from what I can tell in the documentation, Vue.js doesn't provide any direct way for me to access the properties of the child components from within the parent, nor any way to listen for click
events or similar from children within a parent's <slot>
.
This means that I don't have a way to filter visibility of the children based on the user's input, or to update the value of the search-select
component when a user clicks on one of the children.
The Vue.js documentation mentions ways to pass events from child components to parents, but none seem applicable to elements defined within slots - it appears that parents can only listen for events from components explicitly defined within them.
How would one implement the type of two-way communication between parent and child components required for this use case, without violating any Vue.js best practices related to sharing information between components?
Upvotes: 1
Views: 3067
Reputation: 10872
You can use event bus to solve this problem. Just keep every option listening to input event, then letting it decide whether hide itself or not depending on the argument passed.
See fiddle or demo below.
const bus = new Vue();
Vue.component('search-option', {
template: `
<option v-if="show" :value="this.$attrs.value"><slot></slot></option>
`,
created() {
bus.$on('filter', (input) => {
this.show = this.$attrs.value.includes(input);
});
},
beforeDestory() {
bus.$off('filter');
},
data() {
return {
show: true,
};
},
});
Vue.component('search-select', {
template: `
<div class="search-select">
<div class="placeholder" ref="placeholder"></div>
<ul class="dropdown">
<input
type="text"
placeholder="Type to filter..."
v-on:input="updateFilter"
v-model="myinput"
>
<slot></slot>
</ul>
</div>
`,
methods: {
updateFilter() {
console.log('update filter');
bus.$emit('filter', this.myinput);
},
},
data() {
return {
myinput: undefined,
};
},
});
Vue.component('parent', {
template: `
<div class="parent">
<search-select>
<search-option value="foo">Foo</search-option>
<search-option value="bar">Bar</search-option>
<search-option value="baz">Baz</search-option>
<search-option value="foobar">Foobar</search-option>
</search-select>
</div>
`,
});
new Vue({
el: '#app',
})
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<div id="app">
<parent></parent>
</div>
Upvotes: 3