Paginating Top Items – HOTWire HNPWA #3

If you look at the number of TopItems 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

2 thoughts on “Paginating Top Items – HOTWire HNPWA #3

Leave a Reply