Reputation: 2612
I'm using the development version of VueJS 2.6.12.
In my HTML, I have a VueJS template that looks like this (I've stripped it down for the sake of small examples):
<div>
<table class="table">
<thead>
<tr><th>AAA</th><th>BBB</th></tr>
</thead>
<tbody id="main">
<my-item v-for="item in dataset" :d="item" :key="item.key"/> <!-- (this part works) -->
</tbody>
</table>
</div>
In my JavaScript, I defined a global Vue Component like so (and this works, showing my data):
Vue.component('my-item', {
props: ["d"],
template: `<tr><td>{{this.d.AAA}}</td><td>{{this.d.BBB}}</td></tr>`
});
When I load the page, I see my data correctly rendered, but the table headers are at the bottom. The tr
elements (containing my data) are present, though outside the table
.
Viewing the DOM in the browser inspector shows VueJS built this structure, hoisting the elements outside the tbody
and even the table
itself:
<div>
<tr><td>aaa</td><td>bbb</td></tr> <!-- Why are these two rows outside the table? -->
<tr><td>yyy</td><td>zzz</td></tr>
<table class="table">
<thead>
<tr><th>AAA</th><th>BBB</th></tr>
</thead>
<tbody id="main"></tbody>
</table>
</div>
Why is this happening and how can I fix it? I'm expecting my <tr>...</tr> elements to be injected within the tbody
that's under the thead
header rows.
Even slightly stranger, if I manually add in some rows above and below:
<tbody id="main">
<tr><td>111</td><td>222</td></tr>
<my-item v-for="item in dataset" :d="item" :key="item.key"/>
<tr><td>888</td><td>999</td></tr>
</tbody>
Those do get preserved:
<div>
<tr><td>aaa</td><td>bbb</td></tr> <!-- ...but these still got hoisted out if the table -->
<tr><td>yyy</td><td>zzz</td></tr>
<table class="table">
<thead>
<tr><th>AAA</th><th>BBB</th></tr>
</thead>
<tbody id="main">
<tr><td>111</td><td>222</td></tr>
<tr><td>888</td><td>999</td></tr>
</tbody>
</table>
</div>
Upvotes: 3
Views: 278
Reputation: 2612
Ugh, got bit by the same problem, in a slightly different form.
In a nutshell, there are DOM Parsing Caveats, and elements like table
have restrictions on what elements can appear inside them. (FYI: ul
, ol
, select
have this issue as well).
The solution is to use VueJS's is
attribute:
<div>
<table class="table">
<thead>
<tr><th>AAA</th><th>BBB</th></tr>
</thead>
<tbody id="main">
<!-- Specify the element, but then use is="component-name" -->
<tr is="my-item" v-for="item in dataset" :d="item" :key="item.key"/>
</tbody>
</table>
</div>
This will cause the template parsing to be happy, as table
(and tbody
) will be getting a "tr
" element, but its content will be rendered by the named component.
IMPORTANT: Breaking change from VueJS 2 (shown above) to VueJS 3.
In VueJS 2 the is="..."
has become `v-is="..." in VueJS 3.
Also, VueJS 2 took a string, where VueJS now takes a Javascript expression, meaning you must pass it a string, e.g. <tr v-is="'my-item'">
with quotes.
See https://v3.vuejs.org/guide/component-basics.html#dom-template-parsing-caveats and https://v3-migration.vuejs.org/breaking-changes/custom-elements-interop.html#v-is-for-in-dom-template-parsing-workarounds for details.
Upvotes: 3