I'm trying to create a wrapper for the bootstrap-vue Table component. This component uses slots to define cell templates, like that:
<b-table :items="itemsProvider" v-bind="options">
<template v-slot:cell(id)="data">
///...here goes the template for the cell's of itens key "id"
</template>
</b-table>
So, the wrapper i'm creating is like this:
<div>
<b-table :items="itemsProvider" v-bind="options" >
<slot></slot>
</b-table>
<b-pagination
v-model="currentPage"
:total-rows="rows"
:per-page="perPage"
/>
</div>
And i want to call this component like this:
<TableAjax :options="options">
<template v-slot:cell(id)="data">
///...here goes the template for the cell's of itens key "id"
</template>
</TableAjax>
But, since the slots needed on the b-table component are named, i'm having a hard time passing it from the wrapper.
How can i do that?
Passing slots to a child component can be done like this:
<template>
<div>
<b-table :items="itemsProvider" v-bind="options" >
<template v-slot:cell(id)="data">
<slot name="cell(id)" v-bind="data"></slot>
</template>
</b-table>
<b-pagination
v-model="currentPage"
:total-rows="rows"
:per-page="perPage"
/>
</div>
</template>
But since you may not know the slot names ahead of time, you would need to do something similar to the following:
<template>
<div>
<b-table :items="itemsProvider" v-bind="options" >
<template v-for="slotName in Object.keys($scopedSlots)" v-slot:[slotName]="slotScope">
<slot :name="slotName" v-bind="slotScope"></slot>
</template>
</b-table>
<b-pagination
v-model="currentPage"
:total-rows="rows"
:per-page="perPage"
/>
</div>
</template>
Related
So I've created a simple wrapper component with template like:
<wrapper>
<b-table v-bind="$attrs" v-on="$listeners"></b-table>
</wrapper>
using $attrs and $listeners to pass down props and events.
Works fine, but how can the wrapper proxy the <b-table> named slots to the child?
Vue 3
Same as the Vue 2.6 example below except:
$listeners has been merged into $attrs so v-on="$listeners" is no longer necessary. See the migration guide.
$scopedSlots is now just $slots. See migration guide.
Vue 2.6 (v-slot syntax)
All ordinary slots will be added to scoped slots, so you only need to do this:
<wrapper>
<b-table v-bind="$attrs" v-on="$listeners">
<template v-for="(_, slot) of $scopedSlots" v-slot:[slot]="scope"><slot :name="slot" v-bind="scope"/></template>
</b-table>
</wrapper>
Vue 2.5
See Paul's answer.
Original answer
You need to specify the slots like this:
<wrapper>
<b-table v-bind="$attrs" v-on="$listeners">
<!-- Pass on the default slot -->
<slot/>
<!-- Pass on any named slots -->
<slot name="foo" slot="foo"/>
<slot name="bar" slot="bar"/>
<!-- Pass on any scoped slots -->
<template slot="baz" slot-scope="scope"><slot name="baz" v-bind="scope"/></template>
</b-table>
</wrapper>
Render function
render(h) {
const children = Object.keys(this.$slots).map(slot => h('template', { slot }, this.$slots[slot]))
return h('wrapper', [
h('b-table', {
attrs: this.$attrs,
on: this.$listeners,
scopedSlots: this.$scopedSlots,
}, children)
])
}
You probably also want to set inheritAttrs to false on the component.
I have been automating the passing of any (and all) slots using v-for, as shown below. The nice thing with this method is that you don't need to know which slots have to be passed on, including the default slot. Any slots passed to the wrapper will be passed on.
<wrapper>
<b-table v-bind="$attrs" v-on="$listeners">
<!-- Pass on all named slots -->
<slot v-for="slot in Object.keys($slots)" :name="slot" :slot="slot"/>
<!-- Pass on all scoped slots -->
<template v-for="slot in Object.keys($scopedSlots)" :slot="slot" slot-scope="scope"><slot :name="slot" v-bind="scope"/></template>
</b-table>
</wrapper>
Here is updated syntax for vue >2.6 with scoped slots and regular slots, thanks Nikita-Polyakov, link to discussion
<!-- pass through scoped slots -->
<template v-for="(_, scopedSlotName) in $scopedSlots" v-slot:[scopedSlotName]="slotData">
<slot :name="scopedSlotName" v-bind="slotData" />
</template>
<!-- pass through normal slots -->
<template v-for="(_, slotName) in $slots" v-slot:[slotName]>
<slot :name="slotName" />
</template>
<!-- after iterating over slots and scopedSlots, you can customize them like this -->
<template v-slot:overrideExample>
<slot name="overrideExample" />
<span>This text content goes to overrideExample slot</span>
</template>
This solution for Vue 3.2 version and above
<template v-for="(_, slot) in $slots" v-slot:[slot]="scope">
<slot :name="slot" v-bind="scope || {}" />
</template>
I can not loop through the $slots object in Vue 3 to pass all slots from parent to child, the $slots object seems empty in the child component.
How can I loop through the $slots object to pass all parent slots to the child component?
I get this error when I run the code:
TypeError: Cannot read properties of null (reading 'key')
Here is a sandbox about my problem and you can uncomment line 5 to see the complete result:
https://codesandbox.io/s/blazing-bush-g7c9h?file=/src/pages/Index.vue
GitHub sample:
https://github.com/firibz/vue3slots
parent:
<system-input filled v-model="text" label="input">
<template v-slot:before>
<q-icon name="mail" />
</template>
</system-input>
child:
<div class="row justify-center">
<q-input v-model="componentValue" v-bind="$attrs" style="width: 250px">
<template v-for="(_, slot) of $slots" v-slot:[slot]="scope">
<slot :name="slot" v-bind="scope"/>
</template>
</q-input>
<q-separator class="full-width" color="secondary" />
<div class="bg-negative full-width q-pa-lg">slots: {{ $slots }}</div>
<div class="bg-warning full-width q-pa-lg">slots: {{ $slots.before }}</div>
</div>
You have to use Object.keys($slots) in order to use slots on v-for.
<q-input v-model="componentValue" v-bind="$attrs" style="width: 250px">
<template v-for="(slot, index) of Object.keys($slots)" :key="index" v-slot:[slot]>
<slot :name="slot"></slot>
</template>
</q-input>
I have a component that defines a set of slots dynamically from props like this:
<template v-for="(field, index) in fields">
<div
v-if="!field.hide"
:key="index"
class="my-class"
>
<slot :name="getSlotName(field.name, 'label')" />
</div>
</template>
And I have a component WrapperComponent that uses template to fill those slots:
<slotted-component :fields="[{name: 'f1'}]">
<template #f1>
Some content here
</template>
</slotted-component>
And I want to be able to override the content of <template #f1> from the parent component of the WrapperComponent like
<WrapperComponent>
<template #f1>
Some other content
</template>
</WrapperComponent>
I tried to use a PassThroughTemplate component with this template:
<template>
<template v-slot:[name]>
<slot
:name="name"
v-bind="$attrs"
/>
</template>
</template>
but it gives me a compilation error if not compiling at runtime (only eslint yells in this case). I want to use render function, but this code doesn't render anything:
render (h, ctx) {
return h(
'template',
{slot: ctx.props.name},
h('slot', {name: ctx.props.name})
)
}
and i don't know how to use it properly. So, is there a way to do that or another way to achieve template replacement?
I have a delete button on each row and I need to get log_id from items to pass to function deleteLog. That function always alerts log_id is undefined.
How can I pass log_id to the function deleteLog without undefined?
<template>
<b-table striped hover :items="items" :fields="fields">
<template v-slot:cell(Delete)>
<b-button variant="danger" v-on:click="deleteLog(log_id)">Delete</b-button>
</template>
</b-table>
</template>
<script>
export default {
data() {
return {
fields: ['Year', 'Month', 'Round', 'Name', 'Delete', 'log_id'],
items: []
}
}
}
</script>
You can access the row data and its log_id through the slot data:
<b-table striped hover :items="items" :fields="fields">
<template v-slot:cell(Delete)="data"> <!-- `data` -->
<b-button variant="danger" v-on:click="deleteLog(data.item.log_id)">Delete</b-button>
</template>
</b-table>
Here's another syntax, destructuring the slot data:
<b-table striped hover :items="items" :fields="fields">
<template v-slot:cell(Delete)="{ item }"> <!-- `item` -->
<b-button variant="danger" v-on:click="deleteLog(item.log_id)">Delete</b-button>
</template>
</b-table>
Hello I am trying expand default v-data-table component from Vuetify to avoid code redundancy. I have few components with v-data-table. It works on the same principles but table headers can be different. I am using v-slots but how I can expand dynamic slots from Vuetify?
When I have to add custom html to one of column (eg. action) in table I am using:
<v-data-table
:headers="questionTableHeaders"
:items="questions.fetchedData"
class="elevation-1"
hide-default-footer
:page.sync="questions.pagination.page"
:items-per-page="questions.pagination.itemsPerPage"
:loading="loadingData"
loading-text="Loading..."
no-data-text="No data.">
<template v-slot:item.action="{ item }">
<v-icon
small
class="mr-2"
title="title"
#click="clickOnTheItem(item)">
fas fa-plus
</v-icon>
</template>
</v-data-table>
But sometime tables don't have column action. How I can prepare dynamic slots in my component dataTable expanded default v-data-table component to make sure that each of the headers passing to my component have a slot that I can use in parent component?
If I have headers:
[
{text: "text1", value: "name"},
{text: "text2", value: "hash"}
]
I can have access to slots item.name and item.hash?
<dataTable :url="examApiUrl" :headers="examTableHeaders">
<template v-slot:item.name="{ item }"></template>
<template v-slot:item.hash="{ item }"></template>
</dataTable>
But as I wrote before, headers can be different. How I can prepare slots in my dataTable component?
Try it
<v-data-table
:headers="questionTableHeaders"
:items="questions.fetchedData"
class="elevation-1"
hide-default-footer
:page.sync="questions.pagination.page"
:items-per-page="questions.pagination.itemsPerPage"
:loading="loadingData"
loading-text="Loading..."
no-data-text="No data.">
<template v-slot:item.action="{ item }">
<v-icon
small
class="mr-2"
title="title"
#click="clickOnTheItem(item)">
fas fa-plus
</v-icon>
</template>
<template v-for="(slot, name) in $scopedSlots" v-slot:[name]="item">
<slot :name="name" v-bind="item"></slot>
</template>
</v-data-table>