09 Oct 2009 - Warsaw
The industry standard
If you ever had a chance to meet Mislav Marohnic (or see him on a video), for the first few minutes you were probably thinking how this crazy and hyperactive guy could have written one of the most popular Rails library. But after some time with Mislav it becomes obvious that he’s an insanely smart guy able to create some of the best ruby code I’ve ever seen.
All the pages, linked everywhere
The Problem (AKA “design”)
In one project (that’s not live yet) a designer has shown a pretty original approach to displaying pagination links and client was immediately convinced that’s the way to go.
The idea was: we present first and last pages as standard links, but between them — instead of the standard three-dot — we put a select with all the pages “between”, of course doing a javascript-powered redirect to the chosen page. Like this:
In terms of business requirements, if the page count is equal to or below 10, we show just the links to the pages. If there are more than 10 pages, we put 5 to the left, 5 to the right and all the excess pages are accessed via a select in the middle. The redirect occurs automatically on select. The current page, if it’s amongst the options in select, is displayed in bold.
The Approach
From the design it seems we need a constant amount of page links on both “sides” of the pagination helper. And (this is an important part of the approach) we need to generate the select in place of gap.
Dive In
That’s where mislav-will_paginate really shines. It can use custom renderers — classes (I put them in app/helpers directory) extending WillPaginate::LinkRenderer with a few methods to (re)implement: in this case we’re going to implement prepare (for assigning our custom gap) and visible_page_numbers (so we always have links on both sides to first and last pages and no links around the gap).
Let’s make it flexible enough so the number “5” (amount of page links to the left and right of select) isn’t hardcoded, just use well-known outer_window option.
In our application we had to reimplement also the to_html method to have some additional fancy stuff on the edges, but that’s not relevant.
Now I know it’s not a very preferred way of learning, but let’s see the code that does the trick:
It’s pretty obvious from the code (don’t worry, it took me some time to write it) that mislav-will_paginate rendering uses @gap_marker variable and we need to call will_paginate helper with :gap option. If we don’t give it
it looks like this:
So let’s see if it really displays our custom stuff in place of gap:
result:
It’s time to write the code that’ll create the select for remaining “between” pages!
We’re not there yet
You know what I like most about Rails? That it’s written in Ruby. And in Ruby you can really do some fancy shit that’s readable and usable at the same time.
We’re going to write a “gap select” generator that’s going to create a required amount of relevant select options with paginated paths as values. But we want to make the code as elasic and reusable as possible (DRY, motherfucker! Do you follow it?), so calling the given resource path generator within our gap-generating helper is not an option.
So… how about something like this, sexy enough?
The “stripped_params” are here of course for the sake of including all the other GET parameters (see “links for extra parameters” section in my post about Searchlogic), made by cloning params and removing key/value pairs that could interfere with path generator.
For instance:
(You might want to put it in helper.)
So, we have sexy, DRY and cool code ready to reuse. Let’s write the page_selector_generator helper (finally!) to feed it with these stripped_params and path helper block.
And it looks this way:
So… voila!
(Yeah, I know, the “5” left-and-right offset is hardcoded, so I’ll probably refactor this code soon)
Almost there, i.e. proofing the awesome
The above code is great with one exception: if you supply some extra parameters, the path in select option’s value is not generated properly — the ampersands (&) get escaped and thus the paths from value don’t yield what would be expected of them.
We have to write our own variant of options_for_select:
The above code may be ugly (I never really digged html-generating helpers), but it does the trick: the paths are not entity-escaped and we can finally move onto javascript redirects.
I wanted to end it with “this remains as an exercise for the reader”, but I just got reminded how pissed I always was when encountering such crap: it’s a tech blog article, goddamit. People come here for answers, not for fucking riddles.
Of course we’ll write the javascript the unobtrusive way. Here’s the snippet if you’re using Prototype:
I’ll leave the jQuery version as an exercise for the reader.
Searchlogic + Mislav_Will-Paginate = WIN
Old School
If you’re using Searchlogic 1.6.x, you can easily prepare instance variable in a way digestible for will_paginate helper:
And you’re ready to use will_paginage(@models) in your views!
Searchlogic 2.x
New searchlogic exposes “all” method’s result as named scope (and thus works great in named scope chains), so it’s even easier to make it work with model methods added by mislav-will_paginate:
(thanks to folks, especially gRuby, from polish RubyOnRails forum)
Conclusion
That’s all folks, at least for the Part 2 of Rails Tricks series.
I hope it succeeded in showing that you can do some really fancy stuff with mislav-will_paginate and exploiting the fact we’re writing Ruby.
Of course suggestions and comments are welcome (damn I learned a lot from previous’ part comments!).