Skip to main content

Pagination partials

These partials follow the shared rules in Server templates. The tabs remember your framework, and a link can choose it with ?server-framework=laravel.

The partial takes the current page, the page count, a function that returns the URL of a page, and an optional landmark name. It renders nothing for a single page, leaves out the previous and next links at either end, and lists the pages the way paginationRange() from @rcarls/rc-pagination/range does: the first and last page, the pages beside the current one, and an ellipsis for each hidden run of two or more pages. Each framework gets a port of that function, tested against the original.

app/helpers/rc_helper.rb
module RcHelper
# The pages to list, with :ellipsis for each run of hidden pages.
# A port of paginationRange() from @rcarls/rc-pagination/range.
def rc_pagination_range(page, total, siblings: 1, boundaries: 1)
last = [total.to_i, 0].max
return [] if last.zero?

current = page.to_i.clamp(1, last)
shown = [
*(1..boundaries),
*((last - boundaries + 1)..last),
*((current - siblings)..(current + siblings)),
].select { |value| value.between?(1, last) }.uniq.sort

shown.each_with_index.flat_map do |value, index|
gap = index.zero? ? 1 : value - shown[index - 1]

if gap == 1
[value]
elsif gap == 2
[value - 1, value]
else
[:ellipsis, value]
end
end
end

# The direction a sortable header link asks for: the reverse of the current
# direction on the sorted column, else the column's first direction.
def rc_next_sort_direction(key, sort_key, sort_direction, first: "ascending")
return first unless key == sort_key

sort_direction == "ascending" ? "descending" : "ascending"
end
end
app/views/rc/_pagination.html.erb
<%# locals: (page:, total:, href:, label: nil) -%>
<% if total > 1 -%>
<rc-pagination page="<%= page %>" total="<%= total %>">
<nav <%= tag.attributes(aria: { label: label }) %>>
<ul>
<% if page > 1 -%>
<li><a href="<%= href.call(page - 1) %>" data-rc-page="previous" rel="prev">Previous</a></li>
<% end -%>
<% rc_pagination_range(page, total).each do |item| -%>
<% if item == :ellipsis -%>
<li aria-hidden="true">…</li>
<% else -%>
<li><a <%= tag.attributes(href: href.call(item), data: { rc_page: item }, aria: { current: ("page" if item == page) }) %>><%= item %></a></li>
<% end -%>
<% end -%>
<% if page < total -%>
<li><a href="<%= href.call(page + 1) %>" data-rc-page="next" rel="next">Next</a></li>
<% end -%>
</ul>
</nav>
</rc-pagination>
<% end -%>

Render it from a view with a lambda for the page URLs:

<%= render "rc/pagination",
page: @page,
total: @page_count,
href: ->(page) { members_path(page: page) } %>

With Pagy, pass @pagy.page, @pagy.last, and ->(page) { pagy_url_for(@pagy, page) }.

The visible text, "Previous" and "Next", is the partial's. Translate it with your framework's localization helpers. The element translates only the names it adds, such as "Page 3", and leaves visible text alone.