How to render VueJS Slot in a another component - javascript

VueJS slot could be placed anywhere inside a parent component.
However, I have a use case where I need to show a slot in another (foreign) component outside the parent component.
For example, I have component Student:
<template>
<slot name="detail" />
<slot name="status" /> <!-- I don't want to render this slot inside this component, but somewhere else -->
</template>
I want to display the student's status in a header section outside the student component as below:
<div class="Header">
<!-- student status slot should go here-->
<student-status-slot></student-status-slot>
</div>
<div class="InputForm">
<student>
<template #detail>This the detail information about student</template>
<template #status>Grade A</template>
</student>
</div>
You may suggest to create a new "student-status-slot" component. However, the idea is to have all student related information, including "status" declared under the component, but render some of the slot somewhere else.
I have tried another approach creating a separate component and pass the status data via prop. But That is not what I wanted to achieve. I don't want to pass data, but re-use the student status component, which can be dynamic.

In a parent component you can access child's slots by adding ref attribute to child, and then render the VNode obtained. See the demo here https://codesandbox.io/s/happy-einstein-9f5rke?file=/src/components/StudentPage.vue
But actually I wouldn't recommend this way such it's not conventional and slots were not designed for this. I think you should rather consider using portals (Vue 3 portals or https://www.npmjs.com/package/portal-vue if you use Vue 2) to achieve your goal

Related

Attach a sibling component using ref of a component

I am using a library in my page, I wish to update the input component of this library when it is rendered. More specifically I wish to add an icon to the input box of this library.
The library doesn't provide any slots to perform the same but what we it does give us a ref for the input element. I wish to use this to update the html by attaching an icon as a sibling of the ref.
I want to know if this is possible and a base guide as to how I achieve it
I have tried to use document to perform the same but I feel vue should be able to do it and the state will be consistent post that.
App.Vue
// App.vue (Parent)
<template>
<Component ref="componentRef" />
</template>
//Component.vue (Child)
<template>
<div id="iconMountPoint">
</div>
<input ref="inputEleRef">
</template>

Iterating slot content for iterated slots in child component in Vue.js

I've got a child component (that I cannot edit) where each row in an object is rendered inside a div with a specific slot, and I'd need to pass data from the parent for each of those elements. I'm trying to iterate through every element of the object in the parent component, generate a slot and pass the desired code to the child, but unfortunately I can't manage to and I can't find any material to support me.
The child component:
<div class="slotchildren" v-for="(child, childindex) in row.elementChildren" :key="childindex">
<span>element nr. {{child.id}}</span>
<slot :name="`element-child-${row[idlabel]}-${childindex}`" :row="child">
...
</slot>
</div>
The parent component (not working):
<template v-for="row in rows"> -->
<template v-slot:`element-row-${row.id}`="{row}">
//--> [vue/valid-v-slot] 'v-slot' directive doesn't support any modifier
//--> [vue/valid-v-slot] 'v-slot' directive must be owned by a custom element, but 'template' is not.
<span>{{row.name}}</span>
</template>
</template>
Is something like this feasible and how? If it's not, what could be a viable workaround, consideind that I can't edit the child component?
Thanks in advance.
I solved it with the following synthax:
<template v-for="row in rows" v-slot:[`element-row-${row.id}`]>
..
</template>

How to pass ref from parent to child component in template

I am using this component https://element.eleme.io/#/en-US/component/popover
Issue related to triggering element (it is used to calculate where is popover located)
For the triggering element, you can write it in two different ways: use the slot="reference" named slot, or use the v-popover directive and set it to Popover's ref.
Everything ok with default examples. But I am using transparent wrapper for el-popover component like so.
<script id="my-popover" type="x-template">
<el-popover
ref="mypopover"
transition="el-zoom-in-top"
v-bind="$attrs"
v-on="$listeners"
>
<!-- Pass on all named slots -->
<slot
v-for="slot in Object.keys($slots)"
:slot="slot"
:name="slot"
/>
<span> My popover </span>
</el-popover>
</script>
It works ok with slot="reference" named slot.
<my-popover
placement="bottom"
title="Title"
width="200"
trigger="manual"
v-model="visible"
>
<el-button
slot="reference"
#click="visible = !visible"
>
Click me
</el-button>
</my-popover>
But due to complex layout I need to use v-popover directive. Got no luck with wrapped component.
<my-popover
ref="popover"
placement="right"
title="Title2"
width="200"
trigger="manual"
v-model="visible2"
>
</my-popover>
<el-button
v-popover:popover
#click="visible2 = !visible2"
>
Click me too
</el-button>
So I need somehow to pass in v-popover reference to ref="mypopover" from wrapped component. I.e. pass ref to child directly in template.
I've tried v-popover:popover.$refs.mypopover but that doesn't work.
Related codepen https://codepen.io/anon/pen/rgRNZr
Click on button Click me too should show popup connected to that button.
The problem is that the ref you want is the one that is on the el-popover but you are using the ref that is set on the my-popover component instead of the one you want.
This is kind of a wierd thing since that component wants a ref but it will be annoying to get one from the component inside your component.
I would put the button and popup with a slot in the same component.

Include component from parent app in component contained in node_modules directory

I am working on Vue app that incorporates Vue Bootstrap Calendar, and I would like to be able to override the content of the day cell (handled by the Day.vue component) to add my own custom content inside. My thought was initially to modify the Day component to include <slot></slot> tags and pass in the custom content that way.
The problem has to do with accessing the Day component. To include the calendar in your app, you include the Calendar.vue component, which includes Week.vue, which in turn includes Day.vue. As I understand slots, I have to have the child component (Day.vue in this case) included in the component where I'm passing the data, which means it would need to be included in my own component.
If this is not possible, my other thought is to perhaps modify the library by adding another configuration prop (something like dayCustomContent) to the Calendar.vue that indicates that the Day cell content is custom content, pass that in to Calendar.vue, and then down to Day.vue, and then in the Day.vue template, have a v-if conditional based on this prop that either displays the custom content or the default cell content, something like:
<template>
<div class="day-cell" v-if="dayCustomContent">
...my custom content here...
</div>
<div class="day-cell" v-else>
...default events from my.events goes here...
</div>
</template>
I would probably then need to define a custom component to render whatever custom content I want to display, and somehow include that component within Day.vue.
So to sum up, my questions are these:
1) Is there a way to do what I need with slots?
2) For my second option, am I going down the right path? I'm open to suggestions.
UPDATE: I was able to get this done by adding a boolean customDayContent prop in Calendar.vue like so and passing it down to Week.vue and then to Day.vue:
<template>
...
<div class="dates" ref="dates">
<Week
v-for="(week, index) in Weeks"
:firstDay="firstDay"
:key="week + index"
:week="week"
:canAddEvent="canAddEvent"
:canDeleteEvent="canDeleteEvent"
:customDayContent="customDayContent"
:displayWeekNumber="displayWeekNumber"
#eventAdded="eventAdded"
#eventDeleted="eventDeleted"
></Week>
</div>
...
</template>
<script>
export default {
...
props: {
...
customDayContent: {
type: Boolean,
default: false
}
},
}
</script>
and then in Day.vue, do like I had suggested with v-if:
<template>
<div class="day-cell" v-if="customDayContent">
<custom-day></custom-day>
</div>
<div
class="day-cell"
:class="{'today' : day.isToday, 'current-month' : day.isCurrentMonth, 'weekend': day.isWeekEnd, 'selected-day':isDaySelected}"
#click="showDayOptions"
v-else
>
... existing code goes here...
</div>
</template>
The last part is referencing the CustomDay.vue component referenced in my v-if block. I want the user to be able to define the content of their own custom CustomDay.vue template in their own parent app. However, I'm having trouble trying to figure out how to do that. Following the pattern of including components already in this component, I added this in the components section of Day.vue:
CustomDay: require("../../../../src/Components/CustomDay.vue").default
? require("../../../../src/Components/CustomDay.vue").default
: require("../../../../src/Components/CustomDay.vue")
However, no matter what I try along these lines, I get an error that the relative module was not found. On top of that, I need to add it to the componentsarray only if customDayContent is true. What is the best way to do that? In a watcher or computer property, perhaps? Or another way?

Vuejs - Passing parent component props to child element with the template

The problem I'm trying to solve is I want the child component to react when a v-expansion panel is expanded/contracted.
Normally this would be trivial, however, I'm attempting to pass a value from a prop within a vuetify component to component via a scoped slot. Because I'm rendering the child components within a loop, I can't just use Data to bind the prop.
<v-expansion-panel expand>
<v-expansion-panel-content
v-for="item in this.items"
:key="item.key">
<div slot="header">
content
</div>
<slot :items="item.children"></slot>
</v-expansion-panel-content>
</v-expansion-panel>
The v-expansion-panel-content has a prop called value. I need to bind that prop to the slot. Ideally, I'd like to achieve something like this:
<slot :items="item.children" :panelValue="value"></slot>
Any ideas would be greatly appreciated.
I've solved this, but in a less than ideal way.
<v-expansion-panel expand>
<v-expansion-panel-content
v-for="item in this.items"
:key="item.key"
v-model="item.isOpen">
<div slot="header">
content
</div>
<slot :items="item.children" :isVisible="item.isOpen"></slot>
</v-expansion-panel-content>

Categories

Resources