We build Web & Mobile Applications.
UPDATE! will_paginate has moved on again since this blog was written so make sure you read all the way to the the end to save yourself getting angry when things don’t work as they should.
With a final release of Rails 3 edging closer every day it seems like a good time to revisit some of my old articles from the last few years and bring them up to date.
Back in the summer of 2008 I wrote about custom link renderers using will_paginate and, as it is still one of the most popular posts on the blog, it’s the one I’ve decided to refresh first. Don’t worry if you haven’t read the original article as I’ll be covering the same things here. So without further ado, let’s get stuck in!
Of course before going any further you’ll need to have the Rails 3 release candidate installed. If you haven’t already done this I recommend using rvm to save polluting your existing Rails development environment and as you’d expect Ryan Bates has another of his wonderful RailsCasts to help you set it all up.
You’ll also need the Rails 3 version of will_paginate and as Rails 3 uses bundler to manage gem dependencies you won’t have to install it manually like you used to. Simply add the following to the Gemfile
in your application’s root directory and then run the bundle install
command to install the gem (along with any others required by your application):
gem "will_paginate", "~> 3.0.pre2"
As with previous versions the standard view helper is creatively named will_paginate
which makes it easy to add pagination links to your views. Here’s an example of its use:
# In the controller
@users = User.paginate(:page => params[:page])
# In the view
<%= will_paginate(@users) %>
This will produce the following HTML markup (I’ve broken it into multiple lines to make it more readable):
<div class="pagination">
<span class="previous_page disabled">← Previous</span>
<em>1</em>
<a rel="next" href="/users?page=2">2</a>
<a href="/users?page=3">3</a>
<a href="/users?page=4">4</a>
<a class="next_page" rel="next" href="/users?page=2">Next →</a>
</div>
As you can see the helper uses the fairly common markup of a <div>
containing links to page numbers along with previous and next page links. The main difference to previous versions is the use of <em>
elements for the current page instead of a <span>
, so you’ll need to update your CSS if you’re intending re-using an existing stylesheet.
The helper accepts a hash of options for basic customisation:
:container
<div>
is included in the generated markup (defaults to true).:class
<div>
to be specified (defaults to `pagination`).:id
<div>
. If set to true then an ID will be auto-generated (similar to the way form_for
does) or set to a string to use your own ID.:previous_label
:next_label
:page_links
:inner_window
:outer_window
:inner_window
) and at the start and end of the page links (:outer_window
). Defaults to 4 and 1 respectively.:separator
You can also get away without passing a collection to the helper if it follows the naming convention of the controller. So in the above example the @users
collection is named after the UsersController
, so the helper can be used like this:
<%= will_paginate %>
The standard view helper is fine, but often you’ll need to do your own thing. That’s when you need to use a custom link renderer, and this is where will_paginate has changed the most for Rails 3.
The WillPaginate::LinkRenderer
class has been moved into the ViewHelpers
module and split into two separate classes:
The main method of interest in the LinkRendererBase
class is windowed_page_numbers
: it does the work of figuring out which page numbers should be shown using the :inner_window
and :outer_window
settings and is a replacement for the windowed_links
and visible_page_numbers
methods in previous versions of will_paginate. Unlike the methods it replaces, windowed_page_numbers
doesn’t actually return any HTML, it returns a simple array of page numbers.
The other method to look at is pagination
which calls windowed_page_numbers
(if the :page_links
option has been set) and then adds :previous_page
and :next_page
symbols to the start and end of the array. It is then up to the renderer to convert this array into HTML for display.
In most cases it is unlikely that you’ll be directly subclassing the LinkRendererBase
class: although I’m sure there’s a good reason for a separate base class, it looks to me like an unnecessary abstraction.
The LinkRenderer
class is a subclass of LinkRendererBase
and as this is where the HTML generation takes place it is what you’ll subclass when creating your custom renderer. It provides the to_html
method that is called by the will_paginate
view helper and is responsible for converting the array of page links returned by the pagination
method (from the base class) into HTML markup.
To work with will_paginate, all your renderer has to do is implement two methods: prepare
and to_html
, so we’ll take a look at them first.
The prepare
method is passed the collection being paginated, a hash of options (from the will_paginate
helper), and a reference to the template being rendered. By subclassing the LinkRenderer
class the preparation of the renderer is taken care of for you, however if you need to do any additional setup in your custom renderer this is the place to put it, keeping in mind this method is called every time the will_paginate
view helper is called.
The to_html
method maps the page number array returned by the pagination
method (inherited from the LinkRendererBase
class) to HTML using the page_number
method for the page number links and the previous_page
and next_page
methods (which in turn call the previous_or_next_page
method) for the previous and next page links.
As you will see in the examples below the easiest way to create a custom link renderer is by overriding the pagination
method to change the the contents of the array it returns. By default the LinkRenderer
class handles values in the array like this:
Integer values are converted into links to that page number, for example [1, 2, 3]
would result in links to pages 1, 2 and 3 being generated.
Symbols represent the names of methods in the renderer class that should return HTML. For each symbol in the array the renderer will call the corresponding method (using send
) and the HTML that the method returns will be added to the pagination output. For example [:method_one, :method_two]
will result in the methods method_one
and method_two
being called. If a method name that matches the symbol doesn’t exist then an exception will be raised.
Also note that there is no restriction on the order of values in the array, so you are free to mix integers and symbols however you see fit.
The actual HTML generation takes place in the link
and tag
methods which are responsible for generating <a>
elements for active and <em>
elements for inactive links.
These methods replace the page_link_or_span
, page_link
and page_span
methods from previous versions of will_paginate. Another change from previous versions is that the initialize
method is no longer used to set the gap marker (the HTML that is used to fill in the gaps in page numbers), instead you can override the protected gap
method in your custom renderer to change the markup:
def gap
tag(:li, "…", :class => "gap")
end
In addition to the methods described above there are a number of additional methods in the LinkRenderer
class that can be overridden to generate the markup you require. To see them in action let’s copy from the original article on will_paginate and say we want to generate markup like this for our pagination:
<ul class="pagination">
<li class="previous_page"><a href="/users?page=1">← Previous</a></li>
<li><a href="/users?page=1">1</a></li>
<li class="current">2</li>
<li><a href="/users?page=3">3</a></li>
<li class="next_page"><a href="/users?page=3">Next →</a></li>
</ul>
Our custom link renderer will look like this (put the code in the lib
folder of your application):
class PaginationListLinkRenderer < WillPaginate::ViewHelpers::LinkRenderer
protected
def page_number(page)
unless page == current_page
tag(:li, link(page, page, :rel => rel_value(page)))
else
tag(:li, page, :class => "current")
end
end
def previous_or_next_page(page, text, classname)
if page
tag(:li, link(text, page), :class => classname)
else
tag(:li, text, :class => classname + ' disabled')
end
end
def html_container(html)
tag(:ul, html, container_attributes)
end
end
The first thing you’ll probably notice is that there was no need to override the to_html
method. In previous versions if you wanted to change the element used to contain the pagination links from a <div>
to something else you had no choice, but the new html_container
method means this is no longer the case. The page_number
and previous_or_next_page
methods also need to be overridden to wrap the page links in <li>
tags. Compare this with the code for previous versions of will_paginate and you can see we have a lot less work to do now:
class PaginationListLinkRenderer < WillPaginate::LinkRenderer
def to_html
links = @options[:page_links] ? windowed_links : []
links.unshift(page_link_or_span(@collection.previous_page, 'previous', @options[:previous_label]))
links.push(page_link_or_span(@collection.next_page, 'next', @options[:next_label]))
html = links.join(@options[:separator])
@options[:container] ? @template.content_tag(:ul, html, html_attributes) : html
end
protected
def windowed_links
visible_page_numbers.map { |n| page_link_or_span(n, (n == current_page ? 'current' : nil)) }
end
def page_link_or_span(page, span_class, text = nil)
text ||= page.to_s
if page && page != current_page
page_link(page, text, :class => span_class)
else
page_span(page, text, :class => span_class)
end
end
def page_link(page, text, attributes = {})
@template.content_tag(:li, @template.link_to(text, url_for(page)), attributes)
end
def page_span(page, text, attributes = {})
@template.content_tag(:li, text, attributes)
end
end
As with previous versions of will_paginate, having created a custom renderer you need to tell the view helper to use it. There are two ways to do this, the first is to pass the :renderer
option like this:
<%= will_paginate(@users, :renderer => PaginationListLinkRenderer) %>
The :renderer
option accepts a class name string, a class or an instance of a link renderer. If you want your custom link renderer to be the default for all pagination you don’t have to add a :renderer
option to all of your will_paginate
calls, instead you can specify the default in a Rails initializer like this:
WillPaginate::ViewHelpers.pagination_options[:renderer] = 'PaginationListLinkRenderer'
Your view can then contain <%= will_paginate(@users) %>
and your link renderer will be used automatically.
If you try and use your custom link renderer at this point you’re likely to get the following exception:
uninitialized constant ActionView::CompiledTemplates::PaginationListLinkRenderer
This is because, unlike Rails 2.x, the current release candidate of Rails 3 doesn’t autoload code in the lib
directory. There’s a lengthy and at times heated discussion of this over on the Rails bug tracker which has not yet been formally resolved. In the meantime you can restore the old Rails 2.x behaviour by changing the autoload paths in config/application.rb
:
config.autoload_paths += %W(#{config.root}/lib)
Restart your server and you should be good to go.
So that covers the basics of creating your own link renderer. If you take a look at the FAQ on my original article you’ll see some people have asked how to achieve different kinds of pagination markup. To finish I’ll update my replies for use with Rails 3.
How can page number links be turned off completely?
Turning off page numbers is a simple case of passing :page_links => false
to the will_paginate
view helper which means all you get is previous and next page links.
How can first and last page links be added?
To add first and last page links five things need to be done:
container_attributes
method to prevent custom options being output as HTML attributes for the pagination container.pagination
method to return an array of symbols and to not call the windowed_page_numbers
method.first_page
method that handles the generation of HTML for the first page link.last_page
method that handles the generation of HTML for the last page link.Here’s the code:
class MinimalPaginationLinkRenderer < WillPaginate::ViewHelpers::LinkRenderer
def container_attributes
super.except(:first_label, :last_label)
end
protected
def pagination
[:first_page, :previous_page, :next_page, :last_page]
end
def first_page
previous_or_next_page(current_page == 1 ? nil : 1, @options[:first_label], "first_page")
end
def last_page
previous_or_next_page(current_page == total_pages ? nil : total_pages, @options[:last_label], "last_page")
end
end
As you can see the renderer re-uses the previous_or_next_page
method to render the first and last page links as it handles the generation of the correct markup for when the current page is the first or last page. To use this in your view use:
<%= will_paginate(:renderer => MinimalPaginationLinkRenderer, :first_label => "⇤ First", :last_label => "Last ⇥") %>
Alternatively you can put these options in an initializer using the WillPaginate::ViewHelpers.pagination_options
hash.
How can the ‘previous’ link be put after the page numbers?
To change the order of links, for example to show the page numbers followed by the previous and next page links, is even more straightforward with the Rails 3 version of will_paginate. All you need to do is override the pagination
method to change the order of elements in the array it returns, for example:
class ReorderedPaginationLinkRenderer < WillPaginate::ViewHelpers::LinkRenderer
protected
def pagination
items = @options[:page_links] ? windowed_page_numbers : []
items.push :previous_page, :next_page
end
end
The only change here from the method in LinkRendererBase
is that the :previous_page
symbol is being pushed onto the end of the array instead of being added to the beginning. Similarly if you wanted the previous and next links to appear before the page numbers, just change the second line of the method to items.unshift :previous_page, :next_page
. As always, either pass the :renderer
option to the view helper or set the global option in an initializer to use this renderer.
How can I render Gmail style pagination?
class GmailPaginationLinkRenderer < WillPaginate::ViewHelpers::LinkRenderer
def container_attributes
super.except(:first_label, :last_label, :summary_label)
end
protected
def pagination
items = []
items << :first_page if current_page > 2
items << :previous_page if current_page > 1
items << :summary
items << :next_page if current_page < total_pages
items << :last_page if current_page < total_pages - 1
items
end
def first_page
previous_or_next_page(1, @options[:first_label], "first_page")
end
def last_page
previous_or_next_page(total_pages, @options[:last_label], "last_page")
end
def summary
tag(:span, @options[:summary_label] % [ @collection.offset + 1, @collection.offset + @collection.length, @collection.total_entries ], :class => "summary")
end
end
To use it you’ll need to pass the extra options to the will_paginate
helper:
<%= will_paginate(
:renderer => GmailPaginationLinkRenderer,
:first_label => "« Newest",
:previous_label => "‹ Newer",
:next_label => "Older ›",
:last_label => "Oldest »",
:summary_label => "%d - %d of %d"
) %>
There’s a few things going on here:
container_attributes
method is overridden so that the custom attributes :first_label
, :last_label
and :summary
don’t get output as HTML attributes in the pagination container.pagination
method builds an array of pagination links, conditionally adding symbols depending on the current page. This also shows how to hide the previous/next links when on the first or last page.pagination
array are converted into method calls. The previous_page
and next_page
methods are provided by the WillPaginate::ViewHelpers::LinkRenderer
base class, the first_page
, last_page
and summary
methods are defined to handle the custom links.And finally here’s an example of the HTML output by the renderer:
<div class="pagination">
<a class="previous_page" rel="prev start" href="/users?page=1">‹ Newer</a>
<span class="summary">31 - 60 of 100</span>
<a class="next_page" rel="next" href="/users?page=3">Older ›</a>
<a class="last_page" href="/users?page=4">Oldest »</a>
</div>
At the time I blogged about this, over a year ago now, the Rails 3 version of will_paginate was still in pre-release form and under development. As such the API has changed, and some of what I wrote no longer applies (I really will get around to yet another will_paginate blog one day, but sadly not today).
The class your custom renderers should inherit from is now WillPaginate::ActionView::LinkRenderer
. This new class has been added to separate out Rails specific functionality from the parent WillPaginate::ViewHelpers::LinkRenderer
class, so that other framework specific renderers (e.g. for Sinatra) can also be defined. So as an example, when defining a custom renderer class, your code should now look like this:
class PaginationListLinkRenderer < WillPaginate::ActionView::LinkRenderer
The :renderer
option is now deprecated and cannot be use to set a default renderer: the reasons for this are explained in this pull request on GitHub. This means that in order to use a custom renderer it is now necessary to pass the :renderer
option to every will_paginate
call in your views, or of course you could write some kind of helper wrapper that does it for you.
It’s probably also worth noting the various label options are also now deprecated in favour of using the Rails i18n framework.