Amphtml: amp-mustache: Mustache tags don't work in tables

Created on 7 Sep 2017  路  9Comments  路  Source: ampproject/amphtml

What's the issue?

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.

What browsers are affected?

Only tested on Chrome.

Which AMP version is affected?

Tested with AMP version 1504040004635.

Soon Bug

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):

  <template type="amp-mustache">
    <table>
      <!-- {{#replies}} -->
        <tr>
          <td>Reply:</td>
          <td>{{content}}</td>
        </tr>
      <!-- {{/replies}} -->
    </table>
  </template>

All 9 comments

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.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

choumx picture choumx  路  3Comments

gmajoulet picture gmajoulet  路  3Comments

cvializ picture cvializ  路  3Comments

choumx picture choumx  路  3Comments

jpettitt picture jpettitt  路  3Comments