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 way form_for does) 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 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

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_attributes method must be overridden to prevent custom options being output as HTML attributes for the pagination container.
  • An overridden pagination method that returns an array of symbols and does not call the windowed_page_numbers method.
  • A first_page method that handles the generation of HTML for the first page link.
  • A last_page method 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 => "&#8676; First", :last_label => "Last &#8677;") %>

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!