Sort tables (almost) instantly with Ruby on Rails and Turbo Frames19 Sep 2021
Why build the same thing with two different tools? Because we have great options to choose from in Rails-land, and understanding each option is a great place to start when considering which tool is right for you and your team.
Like yesterday, our application is going to allow users to view a table of players. They’ll be able to click on each header cell to sort the table in ascending and descending order.
When we’re finished, the application will work like this:
This article assumes that you are comfortable building applications with Ruby on Rails and may be difficult to follow if you have never worked with Rails before. Previous experience with Turbo Frames is not required.
Let’s get started!
If you prefer skipping the setup steps, you can clone down the example repo from Github. The
main branch is pinned to the end of the setup process and ready for you to start building.
This article is being written using Rails 6.1. When Rails 7 releases, the install command listed below will change. Until then we need to manually add the bundling gems to our Gemfile.
First, from your terminal:
Here, in addition to the bundling gems, we added Faker to our project to allow us to quickly add seed data to the database and added a
Team model and
Players resource to our project.
The core of our application will be a list of players, we won’t interact with Teams directly so we skip adding a controller and views for Teams.
Finally, we’ll wrap up setup by installing Hotwire in our application. Technically, we only need Turbo for this project, but having Stimulus setup won’t hurt anything. One more time, from your terminal:
With everything installed, you can start up the server and build your assets with
bin/dev from your terminal.
Build table layout
We’ll begin by updating the players index page to display a nicely styled table of all of the players in the database. Rails’ scaffolding gets us most of the way there, but instead of rendering the players table in
index.html.erb we’ll move the table to a partial.
This partial will come in handy later when we use Turbo Frames to sort the list of players.
app/views/players/index.html.erb like this:
The index is now rendering a partial (
players) that hasn’t been created yet, so we’ll create that partial next.
From your terminal:
And fill the partial in like this:
This partial simply renders a table that contains a header row and then rows for each
player in the
players variable. The classes are all built-in Tailwind classes that are not integral to the function of the application.
Now we’ve got a nice looking table ready to display our players, but no players in the database! Let’s fix that by creating enough seed data that we can see sorting in action.
And then from your terminal, seed the database:
If you haven’t already, boot up the app and build assets with
bin/dev from your terminal, and then head to localhost:3000/players and see the list of randomly generated players, ready to sort.
Now that we’ve got our table populated and displaying, let’s start on the fun stuff by making the table sortable with Turbo Frames.
Add turbo frame sorting
As a reminder, our goal is for users to be able to click on a table header to sort the table by that column. We want sorting to happen without a page turn, and we want to use a Turbo Frame to update the list of players in the UI.
To start, we’ll convert the wrapper div in the
players partial to a
turbo_frame_tag with an id of
players and the same classes that the wrapper div had. In
Don’t forget to replace the closing
</div> with the
<% end %> tag!
Now that the
players turbo frame wraps the content of the table, links inside of this frame will automatically attempt to replace the content of the turbo frame with the content they receive from the server.
We’ll come back to this concept in a minute, but first, let’s build out the remainder of the code we need for the first pass at sorting the table.
Next, inside of the
players partial still, update the table header like this:
Here we’ve replaced the static text labeling each column with a call to the
sort_link helper method, passing in a column and a label for each header cell.
This helper method doesn’t exist yet, so let’s add that next. In
sort_link renders a standard Rails
label to display the link to the user, and
column to append a parameter to the generated URL.
link_to points to
list_players_path, which we haven’t defined yet. Let’s do that next, in routes.rb:
And finally, we need to update our controller to define an action for the new
list route we’ve added.
app/controllers/players_controller.rb, add a new
list method, like this:
Here we’ve defined a new controller action that queries the database for all of the players, includes the teams table in the query, and orders the players using the value of
includes(:team) is necessary both to allow sorting players by their team name and for performance, since the
players partial makes a call to
player.team.name to display the players name on each row.
Once the players are retrieved, the
players partial is rendered and sent back to the browser.
With the controller update in place, refresh the page, click on a column header, and, if all has gone well, you should see that the list of players is updated (almost) instantly with the correct column sorting applied.
When you sort by a column, you should see output in your server logs like this:
Nice work so far, let’s pause here to step back and talk about how this all ties together.
We started by wrapping the players table in a Turbo Frame with an id of
players. Then we added a link to each of the header cells, pointing to
Because the link is wrapped in a turbo frame, when the response is sent back from the server, Turbo scans the response for a turbo frame with the same id (players) and replaces the existing frame content with the new frame content.
In our case, this means that the updated list of players, rendered by
players/list, replaces the content of the players table without touching the rest of the page.
This use of Turbo Frames, to replace the content of a matching frame, is the simplest approach to using Turbo Frames.
In more advanced cases, we can have a link inside of a frame break out of a frame, with
target=_top or use
data-turbo-frame to tell Turbo that a link from outside of a frame should target that frame.
Turbo Frames enable us to make fast, efficient page updates without adding significant complexity to our code.
Now that we’ve got a little more context for how Turbo Frames are enabling sorting in our application, let’s finish up by adding the ability to sort in both ascending and descending order and inserting a visual indicator when sorting is applied.
Add descending sorting
We want users to be able to sort in descending order by clicking on the same column header twice in a row. The first click will sort in ascending order, the next click will sort in descending order.
Here’s a demonstration of the desired user experience for this section of the article:
First, we’ll modify the
sort_link helper that we added in the last section to send a direction as well as a column in the
sort_link now adds a second parameter into the
link_to. The new
direction parameter is set to the value of the
next_direction helper method if we are generating the sort link for a column that is being used for sorting; otherwise,
direction is set to ascending.
next_direction simply inverts the current sort direction so the sort direction changes with each click.
Next we’ll update the
list method in
players_controller.rb to use the new
direction parameter in the
With this change in place, we can now sort our table in either direction. Incredible stuff, nice work making it this far!
One more set of changes left: Giving users feedback in the UI that sorting is active.
Add visual indicator of sort direction
Our final task is to add an indicator next to the column name when that column is being used to sort the table. The indicator will be a small triangle, pointing up when sorting in ascending order and down when sorting in descending order.
We’ll start by updating the table header in the players partial like this:
Here we’ve added a conditional call to
sort_indicator into each header cell.
Since we only want to add the indicator when the column is being used to sort the table, we use
if params[:column] to skip the
sort_indicator call on inactive columns.
We also added
relative to the
<th> elements class lists because the sort indicator will be absolutely positioned inside of the header cell.
Next up, we’ll define
sort_indicator simply returns a span with a class that matches the current sort direction, read from
Finally, we’ll add css so our sort indicator isn’t just an invisible span on the page.
For convenience, we’ll insert the css right into
With the CSS added and our new
sort_indicator helper defined, refresh the page, click on the column headers, and see that the table is sorted and a visual indicator is shown for the current column and sort direction:
Great job following along, we’ve reached the end of the code for today!
Today we learned how to build a sortable table with Rails and Turbo Frames. Our table can be sorted without a page turn, and we built the interface using basic set of tools familiar to any Rails developer.
While our table only supports sorting today, our frame-based approach can be enhanced to support filtering and searching without deviating from the core concept of using Turbo Frames to display and update the table.
You’ll note that I did not compare the approach in this article directly to the approach in yesterday’s article where we built the same UI with StimulusReflex. Rather than attempting a direct, side-by-side comparison, I prefer presenting both options as standalone projects that show how to build simple experiences using each tool so that readers can learn how each tool works, begin experimenting, and see which feels right.
Both StimulusReflex and Turbo (Frames and Streams) can deliver world-class user experiences in production applications while providing an unbeatable developer experience. The right choice for you and your team is almost certain to be the option that your team feels most comfortable with and most productive in.
Whichever routet you choose, with the Hotwire stack, StimulusReflex, and CableReady available, Rails is well-positioned for the future.
If you’re ready to go further with on your Turbo journey, here are a few places to start:
- Review the Turbo documentation
- Dive in to the Turbo Rails source code on Github
- Spend time with Stimulus, the other side of the Hotwire stack
- Learn from the community in the Hotwire discussion forum
- Join the StimulusReflex discord, where folks will happily help you learn more about building reactive web applications with Rails
That’s all for today. As always, thanks for reading!