Can I pass props between two slots inside one Vue component? - vue.js

I have a hard time grasping slots to be honest. Here's my issue:
I've got one component called Projects.vue where I'm using Buefy to create tables and I'm wrapping the tables inside a template tag as such:
<b-table :data="projects" hoverable>
<template v-slot="project">
<b-table-column field="id" label="ID" width="40" numeric>{{ project.row.id }}</b-table-column>
<b-table-column field="name" label="Name">{{ project.row.name }}</b-table-column>
<b-table-column field="owner" label="Owner">{{ project.row.owner }}. </b-table-column>
<b-table-column label="Action">
<b-button
outlined
class="is-small is-primary"
#click="(row) => showUsersInProject(project.row)">Project details
</b-button>
</b-table-column>
</template>
</b-table>
This simply renders a table with details about a project such as name of project, owner, id and a button "Project Details". Upon clicking the button Project Details, I render another table to show all the users inside that project as such:
<b-table :data="usersInProject" hoverable>
<template v-slot="user">
<b-table-column field="id" label="Id" width="40" numeric>{{ user.row.id }}</b-table-column>
<b-table-column field="firstname" label="First Name">{{ user.row.firstname }}</b-table-column>
<b-table-column field="lastname" label="Last Name">{{ user.row.lastname }}</b-table-column>
<b-table-column field="email" label="Email">{{ user.row.email }}</b-table-column>
<b-table-column label="Action">
<b-button
:disabled="deletedUsers.includes(user.row.id)"
outlined class="is-small is-danger"
#click="deleteUserFromProject(user.row)">Delete user
</b-button>
</b-table-column>
</template>
</b-table>
My question is - how would I pass the projects props from the project template to the user template? I need to get the project.row.owner from the first table to be displayed in the second table.

You can expose data via scoped slots, inside your Projects.vue you need to create a slot like so:
<slot :projects="projects"></slot>.
when doing this you later can expose the projects like so:
<Projects>
<template v-slot="{ projects }">
any code you put here has access to projects data property (including user template)
</template>
</Projects>

Related

Vue JS what does # do inside a <template> does in v-data-table?

I am new to vue js, i was given an project example to study, i want to ask what <template #['item.amtv_start_sales_date'] = "{item}'> actually do inside . Any help is appreciated thanks
<v-data-table :headers="headers" :items="data" :search="search">
<template #[`item.amtv_start_sales_date`]="{ item }">
<div
:class="
checkDateActiveColorSales(
item.amtv_start_sales_date,
item.amtv_end_sales_date
)
"
#click="click(item.amtv_start_sales_date)"
style="font-weight: 500"
>
{{ item.amtv_start_sales_date }}
</div>
</v-data-table>
According to official docs
v-slot has a dedicated shorthand #, so <template v-slot:header> can be shortened to just <template #header>. Think of it as "render this template fragment in the child component's 'header' slot"
So <template #[`item.amtv_start_sales_date`]="{ item }"> is originally written as <template v-slot[`item.amtv_start_sales_date`]="{ item }">

How to use v-for to create slot content for multiple slots

I have a table in vuetify where I want to create a template for 14 of the columns. Instead of making 14 different templates like this:
<v-data-table disable-pagination
:headers="headers"
:items="users"
:search="search"
:hide-default-footer="true"
>
<template v-slot:[`item.date_0`]="{ item }">
<ScheduleIcon :item="item.date_0" />
</template>
<template v-slot:[`item.date_1`]="{ item }">
<ScheduleIcon :item="item.date_1" />
</template>
<template v-slot:[`item.date_2`]="{ item }">
<ScheduleIcon :item="item.date_2" />
</template>
</v-data-table>
I want to make a v-for loop with an index from 0-13 and at the same time use that index-value in the slot and props variables. Something like this is pseudo-code:
<template v-slot:[`item.date_INDEX`]="{ item }" v-for="index in 13" :key="index">
<ScheduleIcon :item="item.date_INDEX" />
</template>
How would I do this? The v-for give me the following error:
<template> cannot be keyed. Place the key on real elements instead.
Your "pseudo code" is almost right...
This should work:
<template v-slot:[`item.date_${index-1}`]="{ item }" v-for="index in 14">
<ScheduleIcon :item="item[`date_${index-1}`]" :key="index" />
</template>
key is not allowed or needed on slot content (<template>) even if it is in v-for. Remove it or place it on ScheduleIcon component...

How do I get to open only one detail in b-table?

When the button is clicked, the row details are opened. When another row detail is opened, I want to ensure that the open details are closed. One line detail must remain open
sample screenshot
As you can see in the example, there is more than one line, while the detail in the first line is open, I want the detail in the second line closed.
<b-table
show-empty
small
stacked="md"
:items="items"
:fields="fields"
:current-page="currentPage"
:per-page="perPage"
:filter="filter"
:filter-included-fields="filterOn"
:sort-by.sync="sortBy"
:sort-desc.sync="sortDesc"
:sort-direction="sortDirection"
#filtered="onFiltered"
>
<template #cell(name)="row">
{{ row.value.first }} {{ row.value.last }}
</template>
<template #cell(actions)="row">
<b-button size="sm" #click="info(row.item, row.index, $event.target)" class="mr-1">
Info modal
</b-button>
<b-button size="sm" #click="row.toggleDetails">
{{ row.detailsShowing ? 'Hide' : 'Show' }} Details
</b-button>
</template>
<template #row-details="row">
<b-card>
<ul>
<li v-for="(value, key) in row.item" :key="key">{{ key }}: {{ value }}</li>
</ul>
</b-card>
</template>
</b-table>
I don't have the custom component code such as b-table, but what you would like to do is the following
https://vuetifyjs.com/en/components/data-tables/#expandable-rows
Basically a separate array tracking which rows are currently open.
Assuming b-table extends v-data-table, pass through the :expanded property and add the tracking array.
On details open, set the array to [], which should close everything.
And immediately afterwards, set the index of the row you want to open to 1.
See the above Vuetify link for an example of how they do it, make sure to toggle the "Single Expand" option.

By changing a slot to scoped slot, it no longer appears on this.$slots and this.$scopedslots

I have a parent component and a child component (which has a vuetify table).
I am trying to make certain columns display chips from the parent component. I've run into a weird problem which I'm not sure what is the matter.
Parent Page: (Parent page is more complicated than this which is why I have taken out the table into another component so I can reuse it in combination with other stuff elsewhere)
<simple-table
v-if="!loading"
:tableData="tableData"
:loading="loading"
:headers="headers"
#search=""
:selected.sync="selected"
itemKey="groupName"
>
<template v-slot:userDatasetAccess="">
<v-chip color="red" dark>test</v-chip>
</template>
</simple-table>
When the code is like above, I can see test in a chip appearing in the UserDatasetAccessColumn
Child Page
<v-data-table
:headers="headers"
:items="tableData"
:search="search"
:loading="loading"
loading-text="Loading... Please Wait"
show-select
:item-key="itemKey"
v-model="selected"
#input="$emit('update:selected',selected)"
>
<template v-slot:[getSlot(slot)]="{ item }" v-for="(_, slot) in $slots">
<slot :item="item" :name="slot"></slot>
</template>
</v-data-table>
methods: {
getSlot(slot) {
return `item.${slot}`
}
},
However, when I change the parent to:
<template v-slot:userDatasetAccess="item">
<v-chip color="red" dark>{{item.userDatasetAccess}}</v-chip>
</template>
It no longer works.
I've console logged this.$slots and this.$scopedslots and they become empty.
So my question is: Why is it that when I do v-slot:userDatasetAccess="" vs v-slot:userDatasetAccess="item", it no longer appears as part of $slots and $scopedslots.
Is there a better way to access the columns via slots from the parent?

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>