Earlier today I pushed out an update to the TPM PollTracker which, among other small bugfixes, switched all of the pagination over from my previously hacked-together solution to the fantastic will_paginate gem. Will_paginate is stupid simple to use. You just
require it and then it adds a
paginate method that you can use instead of
find. Then you can generate the whole nav link package with one line of code:
The problem is, it’s too easy. As long as you’re fine with the default settings, you can get it up and running in minutes. But if you want to modify even the slightest bit of markup that the
will_paginate method generates for you, you need to patch the gem, specifically the WillPaginate::LinkRenderer class.
I needed a little more flexibility, so I patched the class to offer a reverse option. For chronological data like the front page list of polls, and individual contest pages, I wanted navigation to work in “reverse,” e.g.
← Older | Newer →
where you navigate backwards in time, and the “last” page is the oldest. But for alphabetized data like pollster and candidates lists, I wanted conventional left to right pagination, e.g.
← Previous | Next →
where clicking ‘next’ loads subsequent pages.
Before this update, I had all pagination in “reverse,” and a number of people told me this was confusing. Hopefully this dual system makes more sense.
To accomplish this in will_paginate, I needed to create a
reverse option that could be passed when generating the nav links.
I did that like this (most of this code is from the original gem, I just added the
class WillPaginate::LinkRenderer def to_html links = @options[:page_links] ? windowed_links :  # previous/next buttons if @options[:reverse] === true links.push page_link_or_span(@collection.previous_page, 'disabled prev_page', @options[:previous_label]) links.unshift page_link_or_span(@collection.next_page, 'disabled next_page', @options[:next_label]) else links.unshift page_link_or_span(@collection.previous_page, 'disabled prev_page', @options[:previous_label]) links.push page_link_or_span(@collection.next_page, 'disabled next_page', @options[:next_label]) end html = links.join(@options[:separator]) @options[:container] ? @template.content_tag(:div, html, html_attributes) : html end end
Then in my view, for “reversed” nav packages:
<%= will_paginate @polls, :previous_label => "Newer »", :next_label => "« Older", :reverse => true, :page_links => false %>
To do this, I wrote a jQuery plugin I call ajaxPaginate. The plugin “hijacks” the links generated by will_paginate and uses jQuery’s
...#page=2. And if someone gives you a hashed link, ajaxPaginate will recognize that, and inject the correct page on the fly.
Another feature— ajaxPaginate will find the computed height of the injected div, in order to avoid collapsing the page while it fetches the new page. During this period, it adds an
ajaxLoading class to the div, which I used to show a snazzy spinner.
The code as written is pretty tailored to TPM’s needs (there is a custom callback function after the XHR for creating logged-in elements, for example). At some point, if I can, I may release a clean version of the code for public consumption. Though you’re welcome to give this a whirl as is and let me know how it goes.
To get started, just link ajaxpaginate.js in your page, and then put this init code somewhere below that:
This is still an early sketch, and can certainly be refactored, but hey, it’s my first jQuery plugin.
Late Update: I neglected to mention, you’ll have to patch LinkRenderer’s
html_attributes method also. This is to surface data like
total_pages as a CSS selector so that jQuery can latch onto it when replacing the page. For reference, here’s my complete will_paginate patch.