Skip to content

fix(template): correct table-mode detection after closed table elements - #318

Merged
smalluban merged 1 commit into
hybridsjs:mainfrom
dobexx:fix/template-table-expressions
Sep 25, 2026
Merged

smalluban merged 1 commit into
hybridsjs:mainfrom
dobexx:fix/template-table-expressions

Conversation

@dobexx

@dobexx dobexx commented Sep 24, 2026

Copy link
Copy Markdown
Contributor

Fixes #317.

createContents() gives an expression a comment placeholder when it is direct content of a table element, so that the HTML parser does not move it in front of the table. The detection had three gaps (details in #317): since ed333a5 (#212) the reset after a placeholder tests the whole signature, so any table element closed earlier ends table mode after the first expression; a placeholder after </tr>, </td>, … is not detected at all; and the mode is carried over any text, so an attribute expression after a cell expression gets <!--…--> in its value. As markers are assigned in placeholder order, a placeholder moved in front of the table also drops every later expression of the template, without an error.

This change decides table mode for every placeholder: after an opening table tag (regex unchanged), after a closing cell, row or section tag (not </table>) in the static text before the placeholder, or when only whitespace or HTML comments separate it from the previous table-mode placeholder. The reset is removed. Common templates like <tbody><tr>…</tr>${rows}</tbody> or <tbody>${rows}${emptyState}</tbody> under a <thead> now work, and so does the template from #244. Reverting the reset to test parts[index] again would fix the first gap only, and would make the third apply regardless of earlier closed elements. Compile time is on par with main.

Regression tests: four cases in the table block of test/spec/html.js (two expressions in <tbody> after a <thead>, the same separated by a comment, an expression after </td>, an attribute expression after a cell expression). All four fail on main and pass with the change. Full suite in Chrome Headless and WebKit: 608 of 612 passing without the change, 612 of 612 with it. Lint and prettier clean.

Side effect: in a cell with text between expressions, such as <td>${a} items ${b}</td> (already unsupported per the docs), the second expression now always gets a text placeholder. Rendering is unchanged. With localization, the runtime key of that text is now always items ${0} instead of items or items ${0} depending on the rest of the template; hybrids extract gives ${0} items ${1} in both cases.

An expression that is direct content of a table element needs a comment
placeholder, otherwise the HTML parser moves it in front of the table.
createContents() detected this only right after an opening table tag,
and it reset the mode after the first placeholder whenever the signature
contained any closing table tag, also one of an element closed earlier.

So the second of two expressions in <tbody> after a <thead>, or an
expression after </tr> or </td>, was moved out of the table, and the
following expressions of the template were not rendered. Carrying the
mode over other text could also put a comment placeholder into an
attribute value.

Table mode is now decided for each expression: after an opening table
tag, after a closing cell, row or section tag in the static text before
the expression, or when only whitespace or HTML comments separate it
from the previous table-mode expression.
@coveralls

Copy link
Copy Markdown

Coverage Status

coverage: 99.875%. remained the same — dobexx:fix/template-table-expressions into hybridsjs:main

@smalluban

Copy link
Copy Markdown
Contributor

I very rarely (if ever) use table elements in my projects, so I am open to table fixes if they are correct. Does your change affect the limitation section of the template engine? (https://hybrids.js.org/#/component-model/templates?id=table-family-elements)
What I remember is that it was a browser limitation, so templates were compiled incorrectly if there was more than one expression placeholder in the table elements family.

@dobexx

dobexx commented Sep 25, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for taking a look! No, the documented limitation stays exactly as it is.

The limitation is about additional text in table family elements with expressions: the browser moves it out of the table while parsing. Several expressions are already allowed there (the docs list <tr>${cellOne} ${cellTwo}</tr> as working), and hybrids handles them with comment placeholders in table mode.

This PR only changes when table mode applies. On main, an expression that follows a closed table element doesn't get a comment placeholder, so templates that follow the documented rule still break. I ran the two docs examples and three rule-conforming templates on main and with this PR (Chrome Headless):

Template main this PR
docs "works fine": <tr>${a} ${b}</tr> ✅ ✅ (same)
docs "breaks": <tr>${a} ${b} some text</tr> text moved before the table text moved before the table (same)
<tbody><tr><td>static</td></tr>${rows}</tbody> row rendered before the table ✅
<tr><td>a</td>${cell}</tr> cell rendered before the table ✅
<thead>…</thead><tbody>${rowA}${rowB}</tbody><p>${text}</p> second row and all later expressions missing ✅

So the limitation section stays valid as written; the PR makes its "works fine" case hold in more templates (the shapes from #244). If you'd like, I can add a sentence there saying that several expressions per element are fine as long as there is no other text.

@smalluban smalluban left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@smalluban
smalluban merged commit 9428a97 into hybridsjs:main Sep 25, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

Expressions in table elements end up outside the table after a closed table element, and later expressions stop rendering

3 participants