Hotwire Discussion

How to wrap a table row with a turbo frame?

Given these views:

# index.html.eb
<tbody>
  <%= render @exercises %>
</tbody>

and

# _exercise.html.erb
<%= turbo_frame_tag dom_id(exercise) do %>
  <tr>
    <td><%= exercise.name %></td>
    <td><%= exercise.description %></td>
    <td><%= link_to exercise.video_url, exercise.video_url %></td>
    <td><%= link_to 'Show', exercise %></td>
    <td><%= link_to 'Edit', edit_exercise_path(exercise) %></td>
    <td><%= link_to 'Destroy', exercise, method: :delete, data: { confirm: 'Are you sure?' } %></td>
  </tr>
<% end %>

I get some funny markup. If I use “View Source” in Firefox, the markup seems ok (the table row is inside the turbo frame). However, when using the web inspector, I see that the turbo frames actually appear above the table:

How can I fix this?

1 Like

Browsers re-organize the HTML they are given into a DOM that follows their interpretation of the rules of layout. Since Turbo uses a made-up tag that does not adhere to the rules of what tags may be children of a tag, that made-up tag gets sorted out of the hierarchy. You will find that you can put a around the entire , and that will not get re-organized, but just like if you tried to put a

inside the
, anything that cannot (legally) be a child of the will be moved within the DOM. All browsers do this, not just Firefox. All browsers are free to interpret what the correct re-organization scheme is, though, so you may see some put the foreign tags after the table rather than before it.

Is the solution you coded working for your purposes, philosophical purity of markup aside?

Or is the updated content being rendered above your table when Turbo re-builds the page?

Walter

1 Like

Ugh, sorry, this got all stripped out because I replied by e-mail. Here’s what I wrote, without angle brackets and capitalized to give it the same meaning:

Browsers re-organize the HTML they are given into a DOM that follows their interpretation of the rules of layout. Since Turbo uses a made-up TURBO-FRAME tag that does not adhere to the rules of what tags may be children of a TBODY tag, that made-up tag gets sorted out of the hierarchy. You will find that you can put a TURBO-FRAME around the entire TABLE, and that will not get re-organized, but just like if you tried to put a DIV inside the TBODY, anything that cannot (legally) be a child of the TBODY will be moved elsewhere within the DOM. All browsers do this, not just Firefox. All browsers are free to interpret what the correct re-organization scheme is, though, so you may see some put the foreign tags after the table rather than before it.
Is the solution you coded working for your purposes, philosophical purity of markup aside?

Or is the updated content being rendered above your table when Turbo re-builds the page?

Walter

1 Like

Hey @walterdavis, thank you very much for your answer, it does make things clearer for me. The problem for me is that in this case Turbo does not work at all. What I’m trying to do is that when clicking on edit, I get the edit form of the given recorded rendered in place of the row. However, as the edit button is in this case outside of the turbo frame, this does not work. Right now, I’ve fallen back to not using a table, but divs. Once again, thank you for your answer.

You can try setting turbo-frame on content inside individual <td>s

  <tr>
    <td><turbo-frame id="name-<%= exercise.id  %>"><%= exercise.name %></turbo-frame></td>
    <td><turbo-frame id="description-<%= exercise.id  %>"><%= exercise.description %></turbo-frame></td>
  </tr>

Hey @dylan, how would that help me, though? I’d like, that when I click the Edit button, the whole row is replaced with an edit form.

I had the same issue. Indeed table elements are very strict and can only accept a set of element.

The solution I decided to go with and that I suggest is to stop using tables and use div instead, you can then use CSS to style it just like a table with display: table, display: table-row, display: table-cell etc.

If you use a utility CSS framework like tailwind it’s very simple.

4 Likes

I ended up doing this and it solves the problem.

Add an id to tbody:

    # index.html.eb
    <tbody id="exercises">
         <%= render @exercises %>
    </tbody>

Make some changes to # _exercise.html.erb

<%= content_tag :tr, id: dom_id(exercise) do %>
        <td><%= exercise.name %></td>
        <td><%= exercise.description %></td>
        <td><%= link_to exercise.video_url, exercise.video_url %></td>
        <td><%= link_to 'Show', exercise %></td>
        <td><%= link_to 'Edit', edit_exercise_path(exercise) %></td>
        <td><%= link_to 'Destroy', exercise, method: :delete, data: { confirm: 'Are you sure?' } %></td>
<% end %>

Create a new file # views/exercises/create.turbo_stream.erb

<% turbo_stream.append 'exercises', @exercise %>

It should now working.

There is a closed issue on github about this problem. (without solution :frowning:)

1 Like

I’ll second @Intrepidd - stop using HTML Table and start using CSS Table. Just had my own tour of the trenches over this issue - I did a small post on the issue - feel free to comment if I’m totally in the woods! Tailwind, Hotwire, more

Yep it’s a good point. But for now I tied with bootstrap responsive tables.

@vincentlee has the right solution for this when using tables.

@vincentlee certainly has a point - if ‘turbo-charging’ HTML Tables was/is the only issue to deal with but IMHO we also have to finally start making “tables” responsive - perhaps peruse my second installment on the topic

I did some research and played with this a bit last week. It’s not impossible for Turbo to make this work, but I think it would take support from the core team.

The <turbo-frame> custom html tag is an “Autonomous custom element”. It stands alone.

But, there are also “Customized built-in elements.” For example, defining:

customElements.define('turbo-frame-row', TableRowFrameElement, { extends: 'tr' });

and implementing:

HTMLTableRowElement

class TableRowFrameElement extends HTMLTableRowElement

which adds the same logic in the existing FrameElement class which backs our turbo frame tag.

Then in your tables you would use:

<tbody>
  <tr is="turbo-frame-row">...</tr>
</tbody>

and Turbo would need to look for these in addition.

I’m just guessing that they don’t want to add this complexity if you can use CSS display: table styling…

But I’m also not a fan of html documents becoming a steaming pile of divs.
Tables are tables, screen readers know that too.

I had the same problem and played around for hours until even REALIZING the problem why my <td> tags were stripped. Although it makes sense from a rendering perspective, this behaviour is totally unexpected when trying turbo for the first time.