How to load-more paginate with morphing?

I have an index page containing a form for creating new things and a list of things that have been created today. Also, below the form, I show details of the most recently created thing.

When the form is submitted, it creates the new thing and redirects back to the index page, where Turbo morphs it. This all works perfectly. There are no turbo frames or stream actions, just morphing.

I now want to paginate the list of things, such that it shows the 20 most recently created along with a “load earlier” button/link. Clicking “load earlier” should retrieve the next 20 things and append them to the list. (If that’s not possible, as a workaround it could retrieve the latest 40 things and let Turbo morph them into the list.)

Ideally when the user creates a new thing, it will be morphed into the top of the list while the list retains all the things it’s got so far (i.e. from the initial page load combined with any load-earlier pagination).

However I can’t figure out how to do it.

Retrieving the next 20 things and appending them to a list in the UI sounds like it needs either a turbo frame or a turbo stream.

If I use a stream response, it prevents morphing from working. If instead I use a frame, it doesn’t seem to do anything.

There’s a section in the handbook on morphing Turbo frames that sounds promising – it mentions pagination – but I don’t understand it:

You can use turbo frames to define regions in your screen that will get reloaded using morphing when a page refresh happens.

With this mechanism, you can load additional content that didn’t arrive in the initial page load (e.g., pagination). When a page refresh happens, Turbo won’t remove the frame contents; instead, it will reload the turbo frame and render its contents with morphing.

Any help would be much appreciated!

– Andrew Stewart

1 Like

I’ve found a way to satisfy 95% of my requirements.

  • To append an additional page of things requires a turbo stream action.
  • I cannot do this in index.turbo_stream.erb because that would prevent the index page from morphing when the user creates a new thing.
  • Therefore I must embed a turbo stream action inside a turbo frame.

The things-list part of my index page originally looked like:

<ul id="things">
  <%= render @things %>

Now it looks like:

<ul id="things">
  <%= turbo_frame_tag :things_frame %>
    <%= turbo_stream_action_tag "append", target: "things", template: render(@things) %>
    <%= turbo_stream_action_tag "update", target: "pager", template: render("pager", cursor: @cursor) %>
  <% end %>
<div id="pager"></div>

And the pager partial looks a bit like:

<% if @cursor %>
  <%= link_to "Earlier things",
    request.parameters.merge(cursor: @cursor),
    data: {turbo_frame: :things_frame} %>
<% end %>

So instead of rendering the @things directly, the turbo frame appends the @things onto the <ul> (and updates the earlier-things link).

This allows morphing to continue to work when the user creates a new thing.

The only drawback is if the user appends some earlier things and then creates a thing, the morph reduces the list of things to the first page only. I can live with that.

1 Like

I worked out how to retain the existing list of things on the page, even after further pages (in a pagination sense) have been added to it: by making each list item data-turbo-permanent so that it is not removed when the page morphs.

This has the side-effect of changing the order of the list items, because the “append” stream action first removes elements in common. Normally, if you want to control the order of children in a container, you would just re-render the container. But in this case the container could have many subsequently-loaded pages of things and I don’t want to re-render all of them each time – it would defeat the point of pagination.

So I added a custom stream action to sort the list once the incoming things have been appended.

DHH shared a gist showing how to implement pagination. I think this is a good approach.

Morphing is used to handle partial changes, while load-more pagination is to append content, so the benefits of morphing are very small.