Sneaky Drupal Pagers

Robert Douglass's picture

See the update at the bottom!

Drupal’s pagers are neat, and when they were first developed, were way ahead of their time. They also have a couple problems. One of them is scalability. When you’ve got 10,000,000 somethings, calculating how many pages there are so that you can skip to the last one is time consuming.

Another limitation is that the pager is designed to page over a database query. The Apache Solr Search module uses Drupal pagers to move through pages of search results that come from Solr. The pseudo code for getting this to work looks like this:

<?php
// What result do we want to start on?
$offset = $page * $number_per_page;

// How many search results are there in total?
$total = $result->get_total();

// Send a very simple database query to the pager system to trick it.
pager_query("SELECT %d", $offset, 0, NULL, $total);

// Magic happens here. A pager appears.
$output .= theme('pager');
?>

That’s great! But I recently had a case where it was impossible to tell how many results there are in the total set. What is really needed is the ability to advance the pager until there aren’t any more pages, but Drupal doesn’t support anything like this by default. Twitter does it, but Drupal… meh. It’s sneaky time!

<?php
// Remember: http://is.gd/3Sf9Z
$total = 0;

// Note that $page is zero based (page zero is the first page).
// Note also that count($results) is just one page's worth of results,
// not the entire possible set (which is impossible to calculate).

// If there are fewer results than what we want to show per page,
// we know we've come to the end of the result set, and don't need
// to show any more pages.
if (count($results) < $number_per_page) {
 
$total = $number_per_page * ($page + 1);
}
// Otherwise, we want to tell the pager to give us yet another
// page to go to.
else {
 
$total = $number_per_page * ($page + 2);
}
// Now the pager will either end where we are, or add one
// more page to the end. This way you can keep advancing one
// more page until there are no more results left.
pager_query("SELECT %d", $number_per_page, 0, NULL, $total);
$output .= theme('pager');
?>

This strategy could be applied to both of the problem cases I mentioned above. If you have a HUGE result set and need a pager, and don’t want to destroy your database, this is a viable technique. It also works if you’re getting your results from a source that can’t tell you how many results there are in total. And it’s sneaky. Enjoy.

Update:

Instead of using a database query to manipulate the page you can manipulate the globals instead:
$GLOBALS[‘pager_page_array’][] = 1; //what page you are on
$GLOBALS[‘pager_total’][] = 3; // total number of pages
$items_per_page = 50;
print theme(‘pager’, NULL, $items_per_page);

Thanks Chx for the tip!

read more

Comments

chrisshattuck's picture
Chris Shattuck

This is almost as sneaky as

Posted on October 2, 2009 - 11:54am by Chris Shattuck.

This is almost as sneaky as hiding a pesky RSS icon with display:none. I assume that using a COUNT() query would be too expensive or inflexible in most cases? Now, would this solution work if the number of items on the last page was equal to the number of results per page?

Cheers!
Chris

Robert Douglass's picture
Robert Douglass
Acquia Staff

Well, I think it's even more

Posted on October 2, 2009 - 12:02pm by Robert Douglass.

Well, I think it's even more sneaky than display:none... but I guess the sneaky quotient can be debated.

In my case there is no COUNT() that can be done because I'm paging over something that isn't a database table, and it is impossible or impractical to know at the time you're generating the pager, how many results are in the total set. It might be 10, it might be 10 thousand.

Yes, unless I've coded wrongly, if the number of items on the last page is equal to the number you want to see per page, it will display the full last page and not allow you to page further.

Robert Douglass
Senior Drupal Advisor, Acquia

Robert Douglass's picture
Robert Douglass
Acquia Staff

Chx has let me know that it

Posted on October 2, 2009 - 2:36pm by Robert Douglass.

Chx has let me know that it is better to just manually manipulate the globals that store the pager values. See this change in Drupal core that essentially replaced the sneak query with global action.

My point was only half about generating pagers. The more important part, to me, was making a pager that doesn't know its ending point, that doesn't know how many results there are in the total set.

Robert Douglass
Senior Drupal Advisor, Acquia

Robert Douglass's picture
Robert Douglass
Acquia Staff

Correction - the linked diff

Posted on October 2, 2009 - 5:25pm by Robert Douglass.

Correction - the linked diff above comes from the paging module: http://drupal.org/project/paging

Robert Douglass
Senior Drupal Advisor, Acquia

Could you PLEASE fix the

Posted on October 2, 2009 - 6:29pm by Karoly Negyesi.

Could you PLEASE fix the article as I asked in email? It's spreading already and it's incredibly bad practice!

Another example is http://cvs.drupal.org/viewvc.py/drupal/contributions/tricks/pager_withou... here.

Also it's bad practice that I can't leave anonymous comments here... that's why I mailed you originally instead of bothering with logging in just to leave a comment.

Related Content

AcquiaBlog