Hello! ☞ This is an archived version of Al Shaw's personal website. The current site is http://shaw.al.
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: will_paginate @foo
.
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 reverse
option):
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 %>
That worked really well. I was able to clear out all my half-baked pagination hacks and streamline how it worked across the site. The next step was to do it in JavaScript, so the “pages” load quickly inline rather than taking you to a new browser page.
To do this, I wrote a jQuery plugin I call ajaxPaginate. The plugin “hijacks” the links generated by will_paginate and uses jQuery’s load()
to inject subsequent pages into a specified div on the page. It also creates its own navigation based on hashes rather than query strings. The coolest part is, pagination will work perfectly with JavaScript turned off, and the links will look like ...?page=2
. With JavaScript enabled, they will look like ...#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:
<script type="text/javascript">
$(document).ready(function() {
$(this).ajaxPaginate('.your_injected_div',true);
//`true` for normal `false` for reverse behavior
});
</script>
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.