Mustache Madness: How to Embed Tables Inside AMP-List

Problem: Mustache tags don’t work in tables

This means that if you’re trying to structure the content of a table using AMP-List, you won’t be able to use mustache’s if/else or iterate over an array.

This means that if you’re trying to structure the content of a table using AMP-List, you won’t be able to use mustache’s if/else or iterate over an array.

Why does this happen?

Tables are fickle creatures that have odd error handling. When a page is loaded, text nodes that aren’t valid children of <table> elements are foster parented, leading the potentially empty mustache expression to be added to the page, regardless of a check for its existence.

What is foster parenting?

Foster parenting is when invalid content is included in a table and, subsequently,  inserted before the table element during page load. This occurs when the browser is processing the html document and deciding where everything should go. Because it’s possible for <table> elements to be modified by a script in the DOM – even after the element has been inserted by the parser – a series of sub-steps occur to move nodes that aren’t valid children of <table> elements, outside of the table.

W3.org provides a good example using the following html:

<table><b><tr><td>aaa</td></tr>bbb</table>ccc

In this example, the <b> element and “bbb” text node aren’t allowed directly inside the table, resulting in the browser placing them outside the table (foster parenting).

The resulting html after being processed by the browser is:

<b></b>
<b>bbb</b>
<table>
<tbody>
<tr>
<td>aaa</td>
</tr>
</tbody>
</table>
<b>ccc</b>

In the case of our Mustache example, our tags being moved outside our tables, with the html remaining inside.

As a result:

<templatetype="amp-mustache">
<table>
{{#prices}}
<tr>
<td>{{priceType}}:</td>
<td>$ {{price}}</td>
</tr>
{{/prices}}
</table>
</template>

Becomes something like:

<templatetype="amp-mustache">
{{#prices}}
{{/prices}}
<table>
<tr>
<td>{{priceType}}:</td>
<td>$ {{price}}</td>
</tr>
</table>
</template>

What does this problem look like?

Let’s say that you’re trying to create a table of prices and you want to loop through an array of prices. You would have JSON and a template that looks something like this:

Example:

JSON:
{
"items": [{
"prices": [
{
"priceType":"Cash",
"price": "1.99",
},
{
"priceType":"Card",
"price": "2.99",
},
{
"priceType":"BitCoin",
"price": "5.99",
}
]
}]
}
AMP List Template:
<template type="amp-mustache">
<table>
{{#prices}}
<tr>
<td>{{priceType}}:</td>
<td>$ {{price}}</td>
</tr>
{{/prices}}
</table>
</template>

Outcome: The result of this example would be an empty template, due to {{#prices}} not being evaluated. Because of foster parenting, the {{#prices}} tags are moved outside the table and the prices array is not looped through.

This looks terrible! How do I fix this?

In our example, we got back nothing. This was because {{#prices}} was moved out of the table (due to foster parenting). In order to fix this, we can set the “items” attribute in the amp-list to locate the array to be rendered within the response. For example, in order to loop through prices we would set: items=”prices”.

Using other tags:

A simple way to fix this problem is to use tags with looser semantics, like <div>. From there, it’s simple to give these elements classes and then style them with CSS to make them look and act like a table. This can be done many different ways, such as converting your tables to <div>s with classes like “table” and “row” or by using a flex-grid to achieve a responsive tabular result.

Working with empty HTML:

Another simple fix to this problem is to add a “noDisplay{{content}}” class to potentially empty elements to hide them if they don’t exist. By adding the {{content}} to our “noDisplay” class, the “noDisplay” class will only be applied to elements that don’t have content and, as a result, won’t be rendered in our resulting table. Using this method, we create the equivalent of a true/false class that will show only elements where the variable/content exists.

CSS :
<style>
.noDisplay : { display: none; }
</style>
JSON :
{
"items": [{
{
"priceType:"",
"price": "1.99",
},
{
"priceType":"Card",
"price": "2.99",
},
{
"priceType":"BitCoin",
"price": "5.99",
}
}]
}
AMP List Template :
<template type="amp-mustache">
<table>
<tr class=”noDisplay{{priceType}}”>
<td>{{priceType}}:</td>
<td>$ {{price}}</td>
</tr>
</table>
</template>
Resulting HTML:
<table>
<tr class=”noDisplay”>
<td></td>
<td>1.99</td>
</tr>
<tr class=”noDisplayCard”>
<td>Card</td>
<td>2.99</td>
</tr>
<tr class=”noDisplayBitCoin”>
<td>BitCoin</td>
<td>5.99</td>
</tr>
</table>

Rendered HTML:

Card2.99
BitCoin5.99

An odd problem with simple solutions:

Even though conditional mustache tags don’t work in tables within amp-templates, there are easy CSS and HTML element workarounds for these issues. The only drawbacks of these solutions is the extra HTML and CSS added to your DOM.

For more AMP tutorials and code examples, check out: wompmobile.com/category/amp-code-examples/