I am new to vue 3 component and renderer function, just want to use a simple table component with column meta data with slot supports, like below-attached code snippets, I I don't know how to implement the TheColumn part, the slot #header & #cell cannot work in this version. in method 'columns()', the column item doesn't keep header() & cell() objects too. I googled but didn't find answer yet. anybody help explain in more details? thanks.
App.Vue:
<template>
<TheTable :data="data">
<Column label="Name" prop="name" />
<Column label="City">
<template #header> {{ translate ? '城市' : 'City' }} </template>
<template #cell="{item}">
{{ translate ? cities[item.city] || item.city: item.city}}
</template>
</Column>
<Column label="Email">
<template #cell="{ item }"> {{ item.email }} </template>
</Column>
</TheTable>
</template>
<script>
import TheTable from './TheTable.vue';
import TheColumn from './TheColumn.vue';
export default {
name: 'App',
components: {
TheTable,
Column: TheColumn,
},
data() {
return {
translate: true,
cities: {
NY: '纽约',
LA: '洛杉基',
},
data: [
{
city: 'NY',
name: 'Jacky',
email: '[email protected]'
},
{
city: 'LA',
name: 'Tom',
email: '[email protected]'
}
],
}
}
}
</script>
TheTable.vue:
<template>
<div>
<table>
<thead>
<tr>
<th v-for="(header, column) in columns" :key="column">
<slot name="header">{{ header.label }}</slot>
</th>
</tr>
</thead>
<tbody>
<tr v-for="(row, index) in data" :key="index">
<td v-for="(header, column) in columns" :key="column">
<slot name="cell" :item="row"> {{ row[header.prop] }} </slot>
</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
export default {
name: 'TheTable',
props: {
data: {
type: Array,
required: true
},
},
computed: {
columns() {
let list = this.$slots && this.$slots.default && this.$slots.default()
.filter(e => e.type.name === 'TheColumn')
.map(slot => {
let a = {
label: slot.props.label,
prop: slot.props.prop,
slotHeader: slot.props.header,
slotCell: slot.props.cell
};
return a;
}) || [];
console.log('list ' + list);
return list;
},
},
}
</script>
TheColumn.vue:
<script>
const TheColumn = (props, context) => {
return [];
}
export default TheColumn;
</script>
Basically you should create an array of header and cell components from the default slot filled with TheColumn
to render in TheTable
. The only problem is to detect TheColumn
in production, I used an isColumn
property. Also I
cell
to default
slot for more compact column definitionsApp.vue (the template changed):
<template>
<TheTable :data="data">
<Column :label prop="name" />
<Column>
<template #header> {{ translate ? '城市' : 'City' }} </template>
<template #="{ item }"> {{ translate ? cities[item.city] || item.city: item.city}}</template>
</Column>
<Column v-if="showEmail" label="Email" #="{ item }">
{{ item.email }}
</Column>
</TheTable>
<div style="display:flex; gap: 8px; margin-top:16px">
<button @click="label = label === 'Name' ? 'Name changed' : 'Name'">Toggle name label</button>
<button @click="showEmail = !showEmail">Toggle email column</button>
<button @click="translate = !translate">Toggle translate</button>
</div>
</template>
TheTable.vue
<script setup>
import {h, useSlots, createTextVNode, computed} from 'vue';
const $slots = useSlots();
defineProps({
data: Array
});
const columns = computed(() => {
const header = [], cells = [];
let idx = 0;
$slots.default().map(vnode => {
if(! ('isColumn' in (vnode.type.props ?? {}))) return; // detect the Column component in production
header[idx] = vnode.props?.label ?
() => createTextVNode(vnode.props.label) : () => vnode.children.header?.();
cells[idx] = vnode.props?.prop ?
({item}) => createTextVNode(item[vnode.props.prop]) : props => [h(vnode.children.default, props)];
idx++;
});
return {header, cells};
});
</script>
<template>
<div>
<table>
<thead>
<tr>
<th v-for="(child, column) in columns.header" :key="column">
<component :is="child"/>
</th>
</tr>
</thead>
<tbody>
<tr v-for="(row, index) in data" :key="index">
<td v-for="(cell, column) in columns.cells" :key="column">
<component :is="cell" :item="row"></component>
</td>
</tr>
</tbody>
</table>
</div>
</template>
TheColumn.vue:
<script setup>
defineProps({
isColumn: Boolean
})
</script>
<template></template>