cferdinandi / reef

A lightweight library for creating reactive, state-based components and UI.

Home Page:https://reefjs.com

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

tbody element not supported as a component root

ianroberts opened this issue · comments

I have a use case where I want to use reef.component to populate the rows of a table. The table columns are not dynamic, so I tried putting this directly in the HTML:

<table id="my-table">
  <thead>
    <tr>
      <th>Col 1</th>
      <th>Col 2</th>
  </thead>
  <tbody></tbody>
</table>

and then binding a reef.component to the tbody:

const rows = reef.signal([]);

function template() {
  return rows.map(r => `
    <tr>
      <td>${row.col1}</td>
      <td>${row.col2}</td>
    </tr>
  `).join('');
}

reef.component('#my-table tbody', template);

However this does not work - the tr and td elements are not included, instead the content of all the td elements is concatenated together and placed directly in the tbody element (which the browser interprets as placing all of the content inside the first cell of the first row of the table).

Test case: https://codepen.io/ianroberts/pen/zYeYLyM

The underlying reason appears to be the use of DOMParser, which expects a whole HTML document - if you give it a fragment it will do its best, synthesizing the html, head and body elements and putting the elements of the fragment inside the body (or head for things like style), but this only works if the top-level elements of the fragment are ones that would normally be allowed directly inside the body or head element. However tr is only valid inside a table or tbody, and td is only valid inside a tr.

Possible fix

Before passing the templated string to DOMParser.parseFromString, wrap it in a <template> element:

function stringToHTML (str) {

    // Create document
    let parser = new DOMParser();
    let doc = parser.parseFromString(`<html><body><template>${str}</template></body></html>`, 'text/html');

    if(doc.body) {
        // extract the document fragment from the parsed <template>
        return doc.body.children[0].content;
    } else {
        return document.createElement('body');
    }
}

A <template> can contain any HTML elements, including things like <style> that normally live in the head, and elements like tr that are normally only usable in specific contexts.

The current workaround would be to generate the entire table using Reef, rather than just the tbody dynamic section, but my overall HTML page is generated by a server-side template language (Grails GSP) and I would like to be able to use GSP tags in the non-Reef parts of the page.

Same behaviour confirmed in all of Chrome, Opera, Firefox and Safari (on Mac OS 13).

Well documented issue, and thanks for the PR as well!