Getting started
October 28th, 2011 - note that will_paginate has moved on again since this blog was written so make sure you read the comments (particularly this one) to save yourself getting angry when things don't work as they should.
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"
The standard view helper
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- Determines whether or not the container
<div>is included in the generated markup (defaults to true). :class- Allows the CSS class of the container
<div>to be specified (defaults to pagination). :id- This new option allows an ID attribute to be specified for the container
<div>. If set to true then an ID will be auto-generated (similar to the wayform_fordoes) or set to a string to use your own ID. :previous_label:next_label- Customises the previous and next page link labels.
:page_links- Determines whether or not the individual page links are included in the generated markup (defaults to true). If false then only previous and next page links are generated.
:inner_window:outer_window- Customises the number of links displayed at either side of the current page (
:inner_window) and at the start and end of the page links (:outer_window). Defaults to 4 and 1 respectively. :separator- Specifies the text inserted between the individual HTML elements generated by the helper (defaults to a single space).
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 %>
Doing it your way
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:
LinkRendererBase
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.
LinkRenderer
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 methodsmethod_oneandmethod_twobeing 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
And now for an example
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
Using the custom renderer
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.
A Rails 3 autoloading gotcha
If you try and use your custom link renderer at this point you’re likely to get an uninitialized constant ActionView::CompiledTemplates::PaginationListLinkRenderer exception. 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.
How do I...?
So that covers the basics of creating your own link renderer. If you take a look at the comments 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.
Remove page numbers and just have first, previous, next and last page links
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. But in order to add first and last links as well five things need to be done:
- The
container_attributesmethod must be overridden to prevent custom options being output as HTML attributes for the pagination container. - An overridden
paginationmethod that returns an array of symbols and does not call thewindowed_page_numbersmethod. - A
first_pagemethod that handles the generation of HTML for the first page link. - A
last_pagemethod that handles the generation of HTML for the last page link. - Setting new label options for the first and last page links.
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.
Change the order of links
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.
Only show previous, current and next page links
This is also a trivial task with will_paginate, here’s the code:
class SinglePagePaginationLinkRenderer < WillPaginate::ViewHelpers::LinkRenderer
protected
def pagination
[ :previous_page, current_page, :next_page ]
end
end
It’s important to note that the reference to current_page in the array is not a symbol like :previous_page and :next_page but is actually a call to the current_page method provided by the LinkRendererBase class (which delegates to the underlying collection to get the current page number).
This isn’t over yet!
And finally a disclaimer: at the time of writing Rails 3 is only at first release candidate stage and in Rails-land this means things can, and likely will, still change substantially before final release. The will_paginate gem will also continue to be fine tuned as time goes by, so be warned if you’re reading this in 2012 and find that something’s not working, its probably because the APIs have moved on again - and it’ll be time for me to write another new version of this article!


39 comments
Comment on Revisited: roll your own pagination links with will_paginate and Rails 3 by Cyrille
August 23rd, 2010 @ 11:12 – permalink
Comment on Revisited: roll your own pagination links with will_paginate and Rails 3 by Philippe
August 25th, 2010 @ 16:29 – permalink
Comment on Revisited: roll your own pagination links with will_paginate and Rails 3 by Philippe
August 25th, 2010 @ 17:15 – permalink
Comment on Revisited: roll your own pagination links with will_paginate and Rails 3 by Rob Anderton
August 25th, 2010 @ 18:35 – permalink
Comment on Revisited: roll your own pagination links with will_paginate and Rails 3 by Gonzalo
September 20th, 2010 @ 13:23 – permalink
Comment on Revisited: roll your own pagination links with will_paginate and Rails 3 by Brett
October 9th, 2010 @ 17:36 – permalink
Comment on Revisited: roll your own pagination links with will_paginate and Rails 3 by Jeff
October 9th, 2010 @ 17:45 – permalink
Comment on Revisited: roll your own pagination links with will_paginate and Rails 3 by Jeff
October 9th, 2010 @ 17:55 – permalink
Comment on Revisited: roll your own pagination links with will_paginate and Rails 3 by Rob Anderton
October 11th, 2010 @ 22:02 – permalink
Comment on Revisited: roll your own pagination links with will_paginate and Rails 3 by J
November 25th, 2010 @ 21:09 – permalink
Comment on Revisited: roll your own pagination links with will_paginate and Rails 3 by Ayrton
November 27th, 2010 @ 13:35 – permalink
Comment on Revisited: roll your own pagination links with will_paginate and Rails 3 by Rob Anderton
December 1st, 2010 @ 16:29 – permalink
Comment on Revisited: roll your own pagination links with will_paginate and Rails 3 by Dan Benjamin
December 3rd, 2010 @ 14:09 – permalink
Comment on Revisited: roll your own pagination links with will_paginate and Rails 3 by Rob Anderton
December 7th, 2010 @ 22:43 – permalink
Comment on Revisited: roll your own pagination links with will_paginate and Rails 3 by Rob Aldred
December 9th, 2010 @ 18:23 – permalink
Comment on Revisited: roll your own pagination links with will_paginate and Rails 3 by John
February 21st, 2011 @ 23:52 – permalink
Comment on Revisited: roll your own pagination links with will_paginate and Rails 3 by Rob Anderton
February 22nd, 2011 @ 21:14 – permalink
Comment on Revisited: roll your own pagination links with will_paginate and Rails 3 by Demetris Constantinou
March 10th, 2011 @ 21:37 – permalink
Comment on Revisited: roll your own pagination links with will_paginate and Rails 3 by ray
March 11th, 2011 @ 00:29 – permalink
Comment on Revisited: roll your own pagination links with will_paginate and Rails 3 by Rob Anderton
March 14th, 2011 @ 17:33 – permalink
Comment on Revisited: roll your own pagination links with will_paginate and Rails 3 by Rob P
April 6th, 2011 @ 16:59 – permalink
Comment on Revisited: roll your own pagination links with will_paginate and Rails 3 by Rob Anderton
April 7th, 2011 @ 14:48 – permalink
Comment on Revisited: roll your own pagination links with will_paginate and Rails 3 by p()thesis
April 7th, 2011 @ 18:06 – permalink
Comment on Revisited: roll your own pagination links with will_paginate and Rails 3 by Rob Anderton
April 8th, 2011 @ 10:19 – permalink
Comment on Revisited: roll your own pagination links with will_paginate and Rails 3 by AnGQ
May 6th, 2011 @ 07:53 – permalink
Comment on Revisited: roll your own pagination links with will_paginate and Rails 3 by Rob Anderton
May 16th, 2011 @ 18:30 – permalink
Comment on Revisited: roll your own pagination links with will_paginate and Rails 3 by AnGQ
May 22nd, 2011 @ 09:01 – permalink
Comment on Revisited: roll your own pagination links with will_paginate and Rails 3 by yoshyosh
May 25th, 2011 @ 13:50 – permalink
Comment on Revisited: roll your own pagination links with will_paginate and Rails 3 by Gourav Tiwari
May 25th, 2011 @ 22:02 – permalink
Comment on Revisited: roll your own pagination links with will_paginate and Rails 3 by Rob Anderton
May 26th, 2011 @ 11:30 – permalink
Comment on Revisited: roll your own pagination links with will_paginate and Rails 3 by Zequez
August 10th, 2011 @ 01:39 – permalink
Comment on Revisited: roll your own pagination links with will_paginate and Rails 3 by Mislav
August 17th, 2011 @ 13:58 – permalink
Comment on Revisited: roll your own pagination links with will_paginate and Rails 3 by Rob Anderton
August 19th, 2011 @ 09:17 – permalink
Comment on Revisited: roll your own pagination links with will_paginate and Rails 3 by Sean Rucker
September 15th, 2011 @ 00:08 – permalink
Comment on Revisited: roll your own pagination links with will_paginate and Rails 3 by Mark
September 26th, 2011 @ 00:12 – permalink
Comment on Revisited: roll your own pagination links with will_paginate and Rails 3 by Rob Anderton
October 28th, 2011 @ 11:15 – permalink
Comment on Revisited: roll your own pagination links with will_paginate and Rails 3 by Kedar
December 20th, 2011 @ 13:18 – permalink
Comment on Revisited: roll your own pagination links with will_paginate and Rails 3 by datnt
May 2nd, 2012 @ 11:10 – permalink
Comment on Revisited: roll your own pagination links with will_paginate and Rails 3 by Vaselinessa
May 18th, 2012 @ 00:19 – permalink
Leave a reply
You can use Markdown in your comment as well as plain HTML. You can use
<filter:jscode lang="ruby">and</filter:jscode>tags to surround code blocks (supported languages are css, html, javascript and ruby). Your email address will not be published.If your comment doesn’t appear immediately after posting it could have been marked as spam. Don’t worry: we regularly check for and approve incorrectly filtered comments so you shouldn’t have to wait too long for it to be shown.