I've got this soy template
{template .myRowTemplate}
<tr><td>Hello</td></tr>
{/template}
and I want to do something like
var myTable = goog.dom.createElement("table");
goog.dom.appendChild(myTable, goog.soy.renderAsFragment(mytemplates.myRowTemplate));
goog.dom.appendChild(myTable, goog.soy.renderAsFragment(mytemplates.myRowTemplate));
But that causes
Uncaught goog.asserts.AssertionError
Assertion failed: This template starts with a <tr>,
which cannot be a child of a <div>, as required by soy internals.
Consider using goog.soy.renderElement instead.
Template output: <tr><td>Hello</td></tr>
What's the best way to do this?
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;
}
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.
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.
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.