Reputation: 882
I want to create a parent component for a bootstrap-vue table with custom data rendering (templates).
Right now, this kinda looks like this:
<b-table
:items="result"
:fields="fields"
:current-page="currentPage"
:per-page="perPage">
<template slot="Index" slot-scope="data">
{{ data.index + 1 }}
</template>
<!-- more templates for various columns here -->
</b-table>
<b-pagination
align="center"
:total-rows="result.length"
v-model="currentPage"
:per-page="perPage"
/>
The reason I want to wrap this in a component is because I use this table layout, including the pagination and all of its attributes (like striped, bordered, etc.) multiple times.
The only thing that changes, are the column-templates.
I know, the Vue way to do that would be to create a slot like <slot name="x"></slot>
and fill it with <template slot="x">...</template>
. For one thing, that would coincide with the bootstrap-vue template
and on the other hand, bootstrap-vue only seems to render the templates correctly, if they are placed right inside b-table
.
Basically, what I want to achieve is a component like this:
<b-table>
<slot name="templates"/>
</b-table>
<b-pagination stuff.../>
And use it in a child component like this:
<TemplateTable>
<template slot="templates">
<template slot="Index" slot-scope="data">
{{ data.index + 1 }}
</template>
<!-- more templates -->
</template>
</TableTemplate>
Has anyone done something like this and figured out a way to solve it?
Upvotes: 1
Views: 13664
Reputation: 1056
You can use a component's render function to pass slots and scopedSlots to another component (like b-table
). But then you don't get to use a template. To let you have a template (with pagination, searching, etc.) you can wrap the rendering component into another component that does have a template. So then you would have a custom-table
component which contains pagination and a table-wrapper
component, and the table-wrapper
component would render a b-table
.
Here's a very specific example..
const constItems = [{
index: 0,
isActive: true,
age: 40,
first_name: 'Dickerson',
last_name: 'Macdonald'
},
{
index: 1,
isActive: false,
age: 21,
first_name: 'Larsen',
last_name: 'Shaw'
},
{
index: 2,
isActive: false,
age: 89,
first_name: 'Geneva',
last_name: 'Wilson'
},
{
index: 3,
isActive: true,
age: 38,
first_name: 'Jami',
last_name: 'Carney'
}
];
const bTableProps = {
items: {
type: [Array, Function],
default: undefined
},
fields: {
type: [Object, Array],
default: undefined
}
};
const constFields = [
'index',
'isActive',
'age',
'first_name',
'last_name'
];
Vue.component('table-wrapper', {
props: Object.assign({}, bTableProps),
render(h) {
return h('b-table', {
props: this.$props,
slots: this.$parent.$slots,
scopedSlots: this.$parent.$scopedSlots,
on: {
'row-clicked': (item, index, event) => alert('clicked ' + index)
}
});
}
});
Vue.component('custom-table', {
template: '<div><h3>hello table</h3><table-wrapper :items="items" :fields="fields"></table-wrapper></div>',
props: Object.assign({}, bTableProps)
});
new Vue({
el: "#app",
data: {
items: constItems,
fields: constFields
}
});
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<link href="https://unpkg.com/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" />
<link href="https://unpkg.com/[email protected]/dist/bootstrap-vue.css" rel="stylesheet" />
<script src="https://unpkg.com/[email protected]/dist/polyfill.min.js"></script>
<script src="https://unpkg.com/[email protected]/dist/bootstrap-vue.js"></script>
<div id="app">
<custom-table :items="items" :fields="fields">
<template slot="index" slot-scope="data">
{{ data.index + 1 }}
</template>
</custom-table>
<custom-table :items="items" :fields="fields">
<template slot="index" slot-scope="data">
{{ data.index + 2 }}
</template>
</custom-table>
<custom-table :items="items" :fields="fields">
<template slot="index" slot-scope="data">
{{ data.index + 3 }}
</template>
</custom-table>
</div>
Upvotes: 2
Reputation: 278
just build your custom component and pass any custom props you need some thing like this:
<template>
<b-table v-bind="$attrs" v-on="$listeners" custom-prop="any">
<slot v-for="(_, name) in $slots" :name="name" :slot="name" />
<template
v-for="(_, name) in $scopedSlots"
:slot="name"
slot-scope="slotData"
><slot :name="name" v-bind="slotData"
/></template>
</b-table>
</template>
<script>
export default {
name: 'AppTable',
}
</script>
<style scoped></style>
this works like a charm!
Upvotes: 3
Reputation: 6237
Thanks to @nardnob, this is what I'm using in my .vue
components. All the b-table
components have already been loaded in my app.js
file. Hence the lack of import statements.
First the TableWrapper.vue
. I've added Object.assign(this.$attrs, this.$props)
to make sure that any undefined props
from CustomTable
will still carry over to b-table
by combining the $attrs
and $props
.
<script>
export default {
components: {},
props: [
'items',
'fields'
],
mixins: [],
data: function () {
return {
//
}
},
render(h) {
return h('b-table', {
props: Object.assign(this.$attrs, this.$props),
slots: this.$parent.$slots,
scopedSlots: this.$parent.$scopedSlots,
on: {
// 'row-clicked': (item, index, event) => alert('clicked ' + index)
}
});
},
computed: {
//
},
created() {
//
},
mounted() {
//
},
methods: {
//
},
watch: {
//
}
}
</script>
Then the CustomTable.vue
:
<template>
<table-wrapper
:items="items"
:fields="fields"
:striped="true"
></table-wrapper>
</template>
<script>
export default {
components: {},
props: [
'fields',
'items',
],
mixins: [],
data: function () {
return {
//
}
},
computed: {
//
},
created() {
//
},
mounted() {
//
},
methods: {
//
},
watch: {
//
}
}
</script>
And finally:
<custom-table
:fields="[
{
key: 'code',
label: 'Code',
},
{
key: 'title',
label: 'Titel',
},
{
key: 'start_date',
label: 'Start',
},
{
key: 'end_date',
label: 'End',
},
{
key: 'log',
label: 'Log'
}
]"
:items="episodes"
>
<template v-slot:cell(log)="data">
<i class="fa-icon fa-search ml-1 is-hoverable" @click="test"></i>
</template>
</custom-table>
Upvotes: 0