With the following AMP HTML:
<amp-list width="auto" height="200" layout="fixed-height" credentials="include"
[state]="comments" src="https://example.com">
<template type="amp-mustache">
<table>
<tr>
<td>Comment:</td>
<td>{{content}}</td>
</tr>
{{#replies}}
<tr>
<td>Reply:</td>
<td>{{content}}</td>
</tr>
{{/replies}}
</table>
</template>
</amp-list>
<button on="tap:AMP.setState({comments: [{content: 'Hello', replies: [{content: 'Hi'}]}, {content: 'Howdy', replies: []}]})">
Set state
</button>
I expect the rendered tables to be
Comment: | Hello
-- | --
Reply: | Hi
Comment: | Howdy
-- | --
Because the first comment in the comments
state has one item in the replies
list, but the second comment doesn't.
However, the actual rendered tables are
Comment: | Hello
-- | --
Reply: | Hello
Comment: | Howdy
-- | --
Reply: | Howdy
The {{#replies}}
section is evaluated to the wrong value for the first comment (should be Reply: Hi
instead of Reply: Hello
); The {{#replies}}
section is surprisingly evaluated for the second comment even though the second comment has an empty replies
list.
If I change the template to the following, then the presentation becomes okay (although not quite the DOM structure I was hoping for):
<template type="amp-mustache">
<table>
<tr>
<td>Comment:</td>
<td>{{content}}</td>
</tr>
</table>
{{#replies}}
<table>
<tr>
<td>Reply:</td>
<td>{{content}}</td>
</tr>
</table>
{{/replies}}
</template>
Presentationally this looks the same as my expectation (without table/cell borders), but structurally it renders 3 tables instead of 2.
Only tested on Chrome.
Tested with AMP version 1504040004635.
Tried this out on the mustache demo and looks like this is WAI.
Mustache:
<table>
<tr>
<td>Comment:</td>
<td>{{content}}</td>
</tr>
{{#replies}}
<tr>
<td>Reply:</td>
<td>{{content}}</td>
</tr>
{{/replies}}
</table>
JSON:
{
"content": "Howdy",
"replies": []
}
Rendered HTML:
<table>
<tr>
<td>Comment:</td>
<td>Howdy</td>
</tr>
</table>
Is it possible to use a different variable name for the reply text? I.e. something other content
to avoid clashing with comment's content
?
Hmm, it looks like the Mustache engine itself is working as intended. The second row of the table is _correctly_ not rendered because replies
is empty.
However, rendering the same Mustache template with the same JSON in AMP HTML (after clicking the button):
<amp-list width="auto" height="200" layout="fixed-height" credentials="include"
[state]="comments" src="https://example.com">
<template type="amp-mustache">
<table>
<tr>
<td>Comment:</td>
<td>{{content}}</td>
</tr>
{{#replies}}
<tr>
<td>Reply:</td>
<td>{{content}}</td>
</tr>
{{/replies}}
</table>
</template>
</amp-list>
<button on="tap:AMP.setState({comments: [{content: 'Howdy', replies: []}]})">
Set state
</button>
Gives me this:
<table role="listitem">
<tr>
<td>Comment:</td>
<td>Howdy</td>
</tr>
<tr>
<td>Reply:</td>
<td>Howdy</td>
</tr>
</table>
The second row of the table shouldn't be rendered because replies
is empty.
Did using the same variable name in the reply text cause this? I can certainly use a different name, but I was under the impression that the {{#replies}}
section shouldn't even be rendered when replies
in the JSON is empty. Also, shouldn't using the same variable name in different "sections" of the template be allowed? I thought {{content}}
in the {{#replies}}
section should always evaluate to the content
property of each item in the replies
list...
Sorry, you're right. 馃槢 Looks like innerHTML
returns a mixed up version of the template markup:
> $0.innerHTML
"
{{#replies}}
{{/replies}}
<table>
<tbody><tr>
<td>Comment:</td>
<td>{{content}}</td>
</tr><tr>
<td>Reply:</td>
<td>{{content}}</td>
</tr></tbody></table>
"
Not related to AMP code. Investigating.
The issue is that the browser parses the children of <template>
elements into a document fragment at page load, and text nodes are not valid children of <table>
elements.
This results in the {{#replies}}
and {{/replies}}
text nodes being foster parented. A good example of this here: https://www.w3.org/TR/html5/single-page.html#unexpected-markup-in-tables
A workaround is to use elements that have looser semantics, e.g. <div>
and <p>
. I've tried the following which works:
<template type="amp-mustache">
<div>
<div>
<p>Comment:</p>
<p>{{content}}</p>
</div>
{{#replies}}
<div>
<p>Reply:</p>
<p>{{content}}</p>
</div>
{{/replies}}
</div>
</template>
Another method is to support a way to specify a template as an unparsed string, e.g. as a <script type="text/plain">
child.
Thanks for reporting this.
This issue hasn't been updated in awhile. @choumx Do you have any updates?
I managed to work this out (although in a not very elegant way) by commenting out the section placeholders (so the browser's parser won't foster parent them):
<template type="amp-mustache">
<table>
<!-- {{#replies}} -->
<tr>
<td>Reply:</td>
<td>{{content}}</td>
</tr>
<!-- {{/replies}} -->
</table>
</template>
Another workaround: triple mustache now supports rendering table elements.
Closing since there are a few ways to solve this.
@choumx said:
A workaround is to use elements that have looser semantics, e.g.
<div>
and<p>
.
<p>
also generates a role="lisitem"
and additional element. see: https://github.com/ampproject/amphtml/issues/17509
Further, if there are 2+ span structures nested in <p>
, the output generates a new <p>
structure for each span.
That created havoc for elements that are intended to be inline - instead, we get multiple block level output: one for each new <p>
Re: @jaygray0919's comment, see https://github.com/ampproject/amphtml/issues/17509#issuecomment-413701362.
Most helpful comment
I managed to work this out (although in a not very elegant way) by commenting out the section placeholders (so the browser's parser won't foster parent them):