Why it fails
Right, the documentation of renderAsFragment
is a bit confusing; it reads:
Renders a Soy template into a single node or a document fragment. If the rendered HTML string represents a single node, then that node is returned
However, the (simplified) implementation of renderAsFragment
is:
var output = template(opt_templateData);
var html = goog.soy.ensureTemplateOutputHtml_(output);
goog.soy.assertFirstTagValid_(html); // This is your failure
var safeHtml = output.toSafeHtml();
return dom.safeHtmlToNode(safeHtml);
So why do the closure author assert that the first tag is not <tr>
?
That's because, internally, safeHtmlToNode
places safeHtml
in a temporary div
, before deciding if it should return the div
wrappper (general case) or the only child (if the rendered HTML represents only one Node). Once again simplified, the code of safeHtmlToNode
is:
var tempDiv = goog.dom.createElement_(doc, goog.dom.TagName.DIV);
goog.dom.safe.setInnerHtml(tempDiv, html);
if (tempDiv.childNodes.length == 1) {
return tempDiv.removeChild(tempDiv.firstChild);
} else {
var fragment = doc.createDocumentFragment();
while (tempDiv.firstChild) {
fragment.appendChild(tempDiv.firstChild);
}
return fragment;
}
renderAsElement won't work either
And I'm unsure what you are asking for fragments, but unfortunately goog.soy.renderAsElement()
will behave the same because it also uses a temporary div
to render the DOM.
renderElement cannot loop
The error message suggests goog.soy.renderElement
, but that will only work if your table has one row, since it replaces content, and doesn't append children nodes.
Recommended approach
So usually, we do the for loop in the template:
{template .myTable}
<table>
{foreach $person in $data.persons}
<tr><td>Hello {$person.name}</td></tr>
{/foreach}
</table>
{/template}
Of course, we can keep the simple template you have for one row and call
it from the larger template.