Turbo stream and list of elements with message when empty

Hi.
I have a Rails view which displays a list of elements inside a table, and if no elements is present, will display a message in a div instead of the table:

<% if @elements.blank? %>
	<div>No elements</div>
<% else %>
	<table>
		<tbody id="elements">
			<%= render @elements %>
		</tbody>
	</table
<% end %>

Now I want the list to be updated when elements are changed server side, so I added the necessary code in the view, controller and model to use turbo stream.
This works well when elements are added, modified or destroyed, as long as the list is never empty.

What would I have to do for this scenario to also work when the list starts empty (the table doesn’t yet exist), or when removing the only element of the list (the table has to be replaced by the div)?

Is it something that should be handled client-side by javascript (with stimulus for instance)?

Thank you.

Replying to myself, since I found a solution which works for my case and may help others.
There is a similar question but the answers there are too specific to the particular case on rely and css or javascript “tricks”, while I prefer solving this on the server side.

While my question was specific to the particular case of different markup for empty and non-empty lists, I thought it represented a whole class of cases: how to update a whole list of elements, when one of them changes, instead of updating individual elements.

For instance, I have a case where each elements has attributes (a link to a diff) related to one particular element of the list (the “latest”), and those attributes are calculated in the markup, not persisted in the DB (it doesn’t make sense to update all DB rows each time a row becomes the “latest”).
So when we have a new “latest”, each element of the list is updated.

Another case could be a list limited in number of elements (first X elements). When an element is added, another one needs to be removed. That’s a view concern, not a model one, I think.

The solution I found looks like this:

I used broadcast_render_later_to in my Element model, broadcasting to the Container model the elements belong to, and using a specified turbo stream partial: containers/_refresh_elements_list.turbo_stream.erb.

So something like:

class Element
  belongs_to :container

  after_create_commit :update_elements_list
  after_update_commit :update_elements_list

  def update_elements_list
    broadcast_render_later_to container, partial: "containers/refresh_elements_list", locals: { container: container }
  end
end

The containers/_refresh_elements_list.turbo_stream.erb. partial looks like this:

<%= turbo_stream.replace "elements" do %>
  <%= render partial: "elements/list",
             formats: :html, 
             locals: { container: container } %>
<% end %>

The containers/show.html.erb view, like this:

[...]
<%= turbo_stream_from @container %>

<%= render partial: 'elements/list, locals: { container: @container } %>
[...]

And finally, the elements/_list.html.erb view:

<div id="elements">
  <% elements = container.elements %>
  <% if elements.empty? %>
    <div>
      <div>No elements yet</div>
    </div>
  <% else %>
    <table>
      <thead>
        <tr>....</tr>
      </thead>
      <tbody>
        <%= render @elements %>
      </tbody>
    </table>
  <% end %>
</div>

I didn’t try this exact code, it’s a simplification of the code in my application, so there may be typos, but the idea is there.

I feel a bit like abusing turbo stream so I would welcome alternatives, maybe more idiomatic (if those already exists).

1 Like