Vue listen and handle from Emits from elements inside slots - vue.js

Basically I have Parent, child, child-of-child component while child component have 2 named slots and child-of-child component have 1 slot so template ends up like this:
Structure of the components
<parent>
<child>
<child-of-child></child-of-child>
</child>
</parent>
Parent tempalte:
<template>
<div>
<labeled-menu>
<label slot="label">
Open Menu
</label>
<ul slot="menu">
<li v-for="i in [1,2,3,4,5,6,7]" #click="$emit('test', `${i} li is clicked`)">List {{i}}</li>
</ul>
</labeled-menu>
</div>
</template>
Child Template with 2 named slots while the 'menu' slot is passed as slot of the child-of-child component:
<template>
<div>
<div #click="show">
<slot name="label">
</slot>
</div>
<v-dialog :class="getMenuSize" :is-active="isActive" #hide="hide" :position="position">
<slot name="menu">
</slot>
</v-dialog>
</div>
</template>
child-of-child component
<template>
<div>
<div class="dialog-container">
<div class="dialog-header">
</div>
<div class="dialog-body">
<slot>
//here the ul and lis are basically displayed
</slot>
</div>
</div>
</div>
</template>

I didn't get your problem but if you want a global emitter handler a common pattern is to create a singleton of a vue instance and import it when you need it.
src/bus/index.js
import Vue from 'vue'
const bus = new Vue()
export default bus
src/components/Whatever.vue
import bus from 'src/bus'
// And then in your functions
bus.$on(...)
bus.$emit(...)
bus.$off(...)
If you're looking for a more structured event system, you should take a look at the store pattern with vuex.

Related

Components as slot content Vue3

I wonder how to add components as slot content. Instead of doing it like:
<template v-slot:content>CONTENT HERE</template>
I'd like to pass a component.
PkDynModalCenter:
<div class="...">
<div id="modalIndicator" class="...">
<slot name="content"></slot>
</div>
</div>
Parent:
<PkDynModalCenter v-if="togglePayment" #closeModal="togglePayment = false">
<PaymentTemplate :info="voucherInfo" name="content"></PaymentTemplate>
</PkDynModalCenter>
But it's not displayed. Docs are very vague there...
Have a look at the docs for named slots: https://vuejs.org/guide/components/slots.html
You can only use your parent code with the default slot, so your PkDynModalCenter should look like this:
<div class="...">
<div id="modalIndicator" class="...">
<slot></slot> <!-- no name → default slot -->
</div>
</div>
With your current code for the modal, you can only call it like you did in your first code snippet.

Slot on Vuetify.js custom component

I have a component which renders a standard .
I would like to use slots from my component, I would like to write something like:
<MyComponent>
<header>
Titolo
</header>
<body>
my component body
</body>
</MyComponent>
then final component should be:
<v-dialog>
<h1>
// header slot content
</h1>
// body slot content
</v-dialog>
how can I do this? This only works with <slot> but not with named slot.
To use multiple slots you can use the following syntax:
<MyComponent>
<template v-slot:header>
Titolo
</template>
<template v-slot:body>
<p>my component body</p>
</template>
</MyComponent>
So you can pass some HTML in the template blocks and it will render in the component.
MyComponent.vue has the next content:
<template>
<v-dialog>
<h1>
<slot name="header"></slot>
</h1>
<slot name="body"></slot>
</v-dialog>
</template>
You can define names for your slots in your custom component by using the name attribute available for the <slot> element, e.g. <slot name="header">. If you don't define a name for the slot, its name will just be default. See the Vue.js slots documentation here: https://v2.vuejs.org/v2/guide/components-slots.html
Also, I made a simple usage example that you can check out here: https://codesandbox.io/s/unruffled-mopsa-f47hm?file=/src/App.vue
So in your case, your custom component could look something like this:
<v-dialog>
<slot name="header" />
<slot name="body" />
</v-dialog>
And to use it in the parent component, you could have:
<MyComponent>
<template v-slot:header>
Titolo
</template>
<template v-slot:body>
<p>my component body</p>
</template>
</MyComponent>

Does using slots rather inline declarations make a component more flexible?

I am trying to make a grid component that is not too opinionated. I have 2 different types of components that have different cover images, DVD and Cassette.
I am assuming the best way to do this is by not using v-ifs like I am below:
Parent.vue
<MyUniversalComponent
:items="items"
>
</MyUniversalComponent>
MyUniversalComponent.vue
import DVD from '#/components/DVD.vue';
import Cassette from '#/components/Cassette.vue';
<template>
<div class="grid">
<div v-for="item in items">
<div v-if="item.type === 'dvd'">
<DVD :data="item" />
</div>
<div v-else-if="item.type === 'cassette'">
<Cassette :data="item" />
</div>
</div>
</div>
</template>
Is there a more flexible way to do this using slots? I sort of want it to be a "shell" grid that can be used in different ways so I assume I'd want to take out the logic of having these components living in here. Can I translate this to use slots?
In this case, the built-in <component> would be more appropriate than slots. It takes an is prop that sets the component type, and any bindings are passed through the resolved component:
<script>
import DVD from '#/components/DVD.vue';
import Cassette from '#/components/Cassette.vue';
export default {
components: {
DVD,
Cassette,
}
}
</script>
<template>
<div class="grid">
<div v-for="item in items">
<component :is="item.type" :data="item" />
</div>
</div>
</template>
demo

Slot and referring template in the same component in parent

I am stuck with a problem in vue 2. Basically I have a parent and child component. Basically I want to do something like this.
Parent.vue:
<template>
<div>
<Child>
<template #MyComponent>
<slot name="MyComponent" />
</template>
</Child>
<template #MyComponent>
<MyComponent/>
</template>
</div>
</template>
Child.vue:
<template>
<slot name="myComponent"/>
</template>
Can this be done in vue? I have tried to do this. But it doesn't refer to the MyComponent
Thanks in advance.
This is not, how the structure of slots work. You are calling <template #MyComponent> outside of your <Child> and trying to do something there, that is hard to understand.
I´ll asume you try to pass a component named MyComponent inside of the slot of another component named Child. This is a small example for this case:
// Parent, where you call your Child with myComponent in the slot
<Child>
<template #mySlot>
<my-component></my-component>
</template>
</Child>
// Child
<template>
<div>
<slot name="mySlot"></slot>
</div>
</template>
// myComponent
<template>
<div>
Text from myComponent.vue
</div>
</template>

Vuejs nested slots: how to pass slot to grandchild

I use different vuetify components, for example v-menu. It has a template like this:
<v-menu>
<a slot="activator">menu</a>
<v-list>
<v-list-tile>Menu Entry 1</v-list-tile>
<v-list-tile>Menu Entry 2</v-list-tile>
</v-list>
</v-menu>
Suppose I want to add another wrapper around it. That is my special menu component that has some predefined menu options. And I want it to has an activator slot as well. And the last should be somehow assigned to the original v-menu activator slot. Is it possible?
Example:
// index.vue:
<template>
<my-special-menu>
<button>My special menu trigger</button>
</my-special-menu>
</template>
// MySpecialMenu.vue
<template>
<v-menu>
<slot slot="activator"/> <-- I don't know how to write this line
<v-list>...</v-list>
</v-menu>
</template>
<slot slot="activator"> is an incorrect equation. The goal is to pull the content from the parent (that is <button>..</button> in the example), and use it as slot="activator" in v-menu.
I can write it like this:
<v-menu>
<a slot="activator"><slot/></a>
...
</v-menu>
But this case the result template will be:
<div class="v-menu__activator">
<a>
<button>My special menu trigger</button>
</a>
</div>
That's not exactly what I want. Is it possible to get rid off <a> wrapper here?
Update:
We can use a construction like <template slot="activator"><slot name="activator"/></template> to throw some slot to a grand child. But what if we have multiple slots and we want to proxy them all? That's like inheritAttrs and v-bind="$attrs" for slots. Is it currently possible?
For example, there's <v-autocomplete> component in vuetify that has append, prepend, label, no-data, progress, item, selection etc slots. I write some wrapper component around this, it currently looks like:
<template>
<v-autocomplete ..>
<template slot="append"><slot name="append"/></template>
<template slot="prepend"><slot name="prepend"/></template>
<template slot="label"><slot name="label"/></template>
...
<template slot="item" slot-scope="props"><slot name="item" v-bind="props"/></template>
</v-autocomplete>
</template>
Is it possible to avoid all slots enumeration here?
If you put the slot attribute on a html element, that html element is passed to the child component to fill the slot with that name. If you don't want to pass along a html element, you can use slot on a template tag within your component. A template tag groups elements, but does not render to a html element, which is perfect here. You can use template tags also for other things, such as to group elements in a v-if for example, or to repeat multiple elements with a v-for.
// App.vue
<template>
<div id="app">
<test>
<template slot="activator">
Click <b>me</b>!
</template>
</test>
</div>
</template>
// Test.vue
<template>
<div class="wrapper">
<grand-child>
<template slot="activator">
<slot name="activator"></slot>
</template>
</grand-child>
This is some text
</div>
</template>
// GrandChild.vue
<template>
<div>
<a href="#" #click="toggle = !toggle">
<slot name="activator">Default</slot>
</a>
<div v-if="toggle">This appears and disappears</div>
</div>
</template>
Edit: If you want to do this for arbitrary slots, this is also possible. this.$slots contains the slots and their content, so with something like the following, you can pass the slot content to a slot with the same name:
<grand-child>
<template v-for="(_, slot) in $slots">
<template :slot="slot">
<slot :name="slot"></slot>
</template>
</template>
</grand-child>
For completeness sake, scoped slots can be accessed through $scopedSlots and be propagated like so:
<grand-child>
<template v-for="(_, slot) in $scopedSlots" v-slot:[slot]="props">
<slot :name="slot" v-bind="props" />
</template>
</grand-child>
source and comment
I had EsLint errors because of the depreciated :slot and $scopedSlots attributes.
So I combined both of #Sumurai8 answers like this and it works great:
<template v-for="(_, slot) in $slots" v-slot:[slot]>
<slot :name="slot"></slot>
</template>
If you have both named and unnamed slots with props:
Vue 3
<template v-for="(_, name) in $slots" #[name]="slotData">
<slot :name="name" v-bind="slotData || {}" />
</template>
Typescript version
<template v-for="(_, name) in ($slots as {})" #[name]="slotData">
<slot :name="name" v-bind="slotData || {}" />
</template>