If you look at the number of TopItem
s coming back from the API, you’ll see it’s much more than 30. How do you display all these items? Pagination!
ActiveRecord has an offset
method that tells the database where to start in the records it returns. If you don’t specify an offset, the database starts counting at the first row. The offset tells it to skip a certain number, and then start returning rows. The TopsController
is now going to keep track of the current page. It defaults to zero, and then looks for the page parameter. The offset is the page times ITEMS_PER_PAGE
. It helps to have arbitrary numbers like this stored as constants, first for readability, and second you only need to change the number in one place.
The show
method in apps/controllers/tops_controller.rb
first tries to calculate the page parameter. If it’s not present, it’s set to 0. The total_pages
is a SQL count request. And the loaded stories now have an offset, depending on the page. Here is to controller:
ITEMS_PER_PAGE ||= 30
FIRST_PAGE ||= 0
class TopsController < ApplicationController
def show
@page = params[:page] ? params[:page].to_i : FIRST_PAGE
@total_pages = TopItem.count / ITEMS_PER_PAGE
@stories = TopItem.order(:location)
.limit(ITEMS_PER_PAGE)
.offset(@page * ITEMS_PER_PAGE)
.includes(:item)
end
def create
LoadTopItemsJob.perform_now
end
end
The pagination is wrapped in a partial, app/views/layouts/_page_navigation.html.erb
. It’s extracted into a partial because it will be used on the other navigation pages. It will display a link to go back a page, < prev
, the current page, and a link to go to the next page, more >
. The previous page link will only be visible if we’re not showing the first page, and the next page link will only be visible if we’re not showing the last page. The previous page is calculated by subtracting one from the current page, and the next page is calculated by adding one to the current page. The template is structured to expect a page
, total_pages
, and a path
Here is the template:
<div class="flex">
<div class="flex-initial w-24 flex justify-left">
<% unless page <= 0 %>
<%= link_to "< prev", "#{path}?page=#{page - 1}", class: "border border-red-600 hover:bg-red-500 hover:text-white rounded-full py-1 px-3 m-1" %>
<% end %>
</div>
<div class="flex-grow flex justify-center">
<span class="border border-red-600 bg-red-500 text-white rounded-full py-1 px-3 m-1 object-center">
<%= page + 1 %> / <%= @total_pages + 1 %>
</span>
</div>
<div class="flex-initial w-24">
<% unless page + 1 > total_pages %>
<%= link_to "more >", "#{path}?page=#{page + 1}", class: "border border-red-600 hover:bg-red-500 hover:text-white rounded-full py-1 px-3 m-1 float-right" %>
<% end %>
</div>
</div>
Add it to app/views/tops/show.html.erb
right between the header and the stories grid:
<%= render partial: 'layouts/page_navigation', locals: { page: @page, total_pages: @total_pages, path: top_path } %>
HHNPWA vs. HNPWA
This is no different from the original HNPWA. It’s a pretty standard way to paginate your pages. I think refactoring it into a template helps by keeping its look consistent throughout the app.
Other HOTWire Tutorials
- HOTWire HNPWA #1: Setting up for Top Stories
- Turbo Streaming Top Items – HOTWire HNPWA #2
- Paginating Top Items – HOTWire HNPWA #3
- Russian Doll Caching – Building HOTWire HNPWA #4
- Lazy Loading Lots of Comments HOTWire Tutorial #5
5 comments on “Paginating Top Items – HOTWire HNPWA #3”
[…] Paginating Top Items – HOTWire HNPWA #3 […]
[…] HOTWire HNPWA Tutorial #3: Paginating Top Items […]
[…] HOTWire HNPWA Tutorial #3: Paginating Top Items […]
[…] Paginating Top Items – HOTWire HNPWA #3 […]
[…] Paginating Top Items – HOTWire HNPWA #3 […]