'Mystery tbody tag being inserted
I have a custom component I'm working on and I borrowed code from one project verbatim from another. Here's the trouble area:
<style>
#history tr:nth-child(even) {background-color: #f2f2f2;}
</style>
<div class="ce-card" style="height:280px;overflow:auto;" id="myscroller">
<table id="history" style="width:100%">
</table>
</div>
Then later the table is appended to with this:
this.shadowRoot.querySelector('#history').innerHTML += `<tr><td>Term ${this._term}: ${text}</td></tr>`;
All of it works. However, I'm not getting my alternating highlighting because in project #2 the row is being wrapped with <tbody></tbody>
tags. I have no use of tbody anywhere, but it crops up in just the new page I'm toying with. How do I suppress the insertion of <tbody>
tags when setting innerHTML to have a <tr>
?
I know someone will ask what happens when use =
instead of +=
. It's got the same problem.
Summary: xxx.innerHTML = `<tr><td>text</td></tr>`
produces different results in different projects. One of the two following:
<table><tr><td>text</td></tr></table>
<table><tbody><tr><td>text</td></tr></tbody></table>
Edit:
According to what I'm reading in the comments, if I append multiple rows by appending HTML I should get the following: https://i.imgur.com/7PDaMxW.png
But there doesn't seem to be any way to not get it to bracket it with ... except what appears to happen on another page (and apparently by accident).
This table was created identically: https://i.imgur.com/Wc9i18e.png Maybe the better question to ask is "how did I manage to do it the first time around? What did I set in CSS or something that's suppressing the insertion of a tbody?"
Solution 1:[1]
The browser infers the existence of a <tbody>
. You cannot remove it.
A thing to keep in mind is that people have been writing broken HTML since the dawn of the web. Browser makers have chosen to handle that problem by making browsers do more than meet page authors halfway.
Try this experiment.
- Create a bare HTML page like this:
<!DOCTYPE html>
<html>
<body>
<table>
<tr>
<td>one</td>
</tr>
</table>
</body>
</html>
- Open that page in your browser, and use Dev Tools to inspect it. You'll see a
<tbody>
.
Q: Why do new <tbody>
elements keep getting added?
As stated earlier, the browser infers the existence of a <tbody>
. But why?
Because the DOM does not permit <tr>
to be the direct descendant of <table>
.
A little to my surprise, the DOM spec does permit <tr>
to be a direct descendant of <table>
. Here's the exact text (emphasis mine):
Content model:
In this order: optionally a
caption
element, followed by zero or morecolgroup
elements, followed optionally by athead
element, followed by either zero or moretbody
elements or one or moretr
elements, followed optionally by atfoot
element, optionally intermixed with one or more script-supporting elements.
My read is that the spec permits this:
<!-- Snippet 1: row wrapped in tbody -->
<table>
<tbody>
<tr> <td> cell </td> </tr>
</tbody>
</table>
Or this:
<!-- Snippet 2: row as direct child of table -->
<table>
<tr> <td> cell </td> </tr>
</table>
But not this:
<!-- Snippet 3: mixed-case, table with both tbody and row children -->
<table>
<tbody>
<tr> <td> cell </td> </tr>
</tbody>
<tr> <td> cell </td> </tr>
</table>
Since it's clear that modern browsers infer the existence of a <tbody>
, Snippet #2 gets automatically transformed into Snippet #1. And that means any additional <tr>
will be automatically wrapped in <tbody>
in order to avoid Snippet #3.
I got the same results as you when I tried this in a bare HTML page with a table.
Solution 2:[2]
It is normal behaviour for most browsers. It is inserted browser during processing your DOM. For more details refer to official MDN. It is impossible to suppress <tbody>
, maybe you can use it another way.
What about using in CSS #history > tbody > tr:nth-child(even)
first collect all tr
in one string IN JAVASCRIPT and then add it using innerHTML
to #history
Solution 3:[3]
Updated answer:
As the intention here is to continuously add to the "history" table, the following should be more appropriate:
- Start with a suitable markup (including a
<tbody>
element) - Use the DOM element method
.createElement()
to freshly create a new<tr>
-element, - then fill in all the necessary
.innerHTML
into this<tr>
before eventually .append()
-ing it to the<tbody>
-target.
const tbd=document.querySelector("#history tbody");
"abc".split("").forEach((v,i)=>appendEl(tbd,"tr",
`<td>Term ${i+1}:</td><td>${v}</td>`));
function appendEl(trgt,type,html){
const el=document.createElement(type);
el.innerHTML=html;
trgt.append(el);
}
// And here is another "late" addition to the table:
appendEl(tbd,"tr",
`<td>last term:</td><td>"the end"</td>`);
<table id="history"><tbody></tbody></table>
appendEl()
is a little utility function that helps me do the above steps for each <tr>
. jQuery has a built-in method append()
that would be ideal for this job. Unfortunately there is no direct Vanilla JS equivalent method, but appendEl()
kind of works in the same direction.
Doing it this way we avoid re-parsing the already existing table content. This should make the whole process faster and more stable (any previously attached event listeners or changed input values will be unaffected by it).
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 | |
Solution 3 |