Turbo & Progressive Enhancement
Before jumping into composing Turbo Streams or wrapping elements in Turbo Frames, consider what the experience might be without any JavaScript or Hotwire. While a no-JS flow may not be commonly experienced, thinking in this way can lead to cleaner, more robust solutions.
Let’s say we have a button that toggles a post’s publish
column, and we want to update the screen in-place. Our response could just return the user back to the page they were on, with the button updated:
class Post::PublishesController < ApplicationController
def create
post = Post.find(params[:post_id])
post.publish!
redirect_back_or_to post, status: :see_other
end
def destroy
post = Post.find(params[:post_id])
post.unpublish!
redirect_back_or_to post, status: :see_other
end
end
This would work without any JavaScript, but if the user had scrolled the page, their scroll position would be reset.
Enhance!
The simplest enhancement is to maintain the user’s scroll position by adding <meta name="turbo-refresh-scroll" content="preserve">
to the <head>
. We don’t need to change any controller code, everything is handled by Turbo. What’s more, if the UI augments over time and includes more elements that require dynamic updates (e.g. a published post count), we don’t need to account for them in our response since the entire content is refreshed. (Compare with Turbo Stream actions that may require modification with each UI change.)
Next, if we need to preserve more of the UI state, we could morph the refresh by adding <meta name="turbo-refresh-method" content="morph">
to the <head>
.
A final benefit of this code is that it’s usable in different contexts. redirect_back_or_to
is really handy for these situations. We can create a publish toggle button on another page, point it to this controller, and it will work in the same way—again without having to add or change any controller code, or fiddle about with Turbo Stream actions.
Concepts demonstrated:
- Open-closed principle: our UI is open for extension without having to modify existing controller code
- Sandi Metz’s TRUE principles from Practical Object-Oriented Design: our controller action is Useful in different contexts