Reputation: 1156
I have some related and neighboring table columns that I would like to group in the same component. But the Vue.js template system has this limitation that only one tag can be directly in <template>
. Often this meaningless wrapper is a <div>
. But what can I use in a table?
I can abuse tags like <span>
for that, but the consequences are not acceptable. The cells in the same column do not have the same size, and the table borders do not collapse. Is there a way to have a wrapper tag that ideally will not show up in the HTML at all, or will at least be as neutral as a <div>
?
Table row:
<template>
<tr v-for="thing in things">
<td>{{thing.name}}</td>
<size-component :size="thing.size"></size-component>
<time-component :time="thing.time"></time-component>
</tr>
</template>
Size columns:
<template>
<wrap>
<td>{{size.x}}</td>
<td>{{size.y}}</td>
<td>{{size.z}}</td>
</wrap>
</template>
Time columns:
<template>
<wrap>
<td>{{time.h}}</td>
<td>{{time.m}}</td>
<td>{{time.s}}</td>
</wrap>
</template>
Edit:
To me this boils down to the problem, that there is no tag to group <td>
s in a <tr>
(like <tr>
s can be grouped in <table>
with multiple <tbody>
tags). Compare Is there a tag for grouping "td" or "th" tags?
Semantically <colgroup>
is intended for this purpose, but that does not help.
For me using vue-fragment turned out to be the right solution:
<template>
<fragment>
<td>{{size.x}}</td>
<td>{{size.y}}</td>
<td>{{size.z}}</td>
<td>{{volume}}</td>
</fragment>
</template>
<script>
import { Fragment } from 'vue-fragment';
export default {
computed: {
volume() { return this.size.x * this.size.y * this.size.z },
},
components: { Fragment },
props: ['size']
}
</script>
Upvotes: 2
Views: 1008
Reputation: 23533
To add computed to @Andreas solution, can reference a parent property.
Demo CodeSandbox
functional component
export default {
functional: true,
props: {
size: {
type: Object,
default: () => ({
x: 1,
y: 2,
z: 3
})
}
},
render: (createElement, context) => [
createElement("td", context.props.size.x),
createElement("td", context.props.size.y),
createElement("td", context.props.size.z),
createElement("td", context.parent.volume(context.props.size))
]
};
parent component
export default {
components: {
SizeComponent
},
data() {
return {
things: [
{
name: 'aName',
size: { x: 1, y: 2, z: 3 }
}
]
};
},
mounted() {
// test it is reactive
setTimeout(()=> { this.things[0].size.x = 2 }, 3000)
},
computed: {
volume() {
return (size) => size.x * size.y * size.z;
}
},
}
Upvotes: 1
Reputation: 1099
You are right, this is a limitation with Vue and has something to do with the diffing algorithm. You can, however, use a functional component to render multiple root elements.
<script>
export default {
functional: true,
props: {
size: {
type: Object,
default: () => ({
x: 1,
y: 2,
z: 3
})
}
},
render: (createElement, context) => [
createElement('td', context.props.size.x),
createElement('td', context.props.size.y),
createElement('td', context.props.size.z)
]
}
</script>
Another option is to use the plugin vue-fragments
Upvotes: 4