Home / Sneaky Drupal Pagers

Sneaky Drupal Pagers

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

Sign up for our blog newsletter!

Thanks!

Comments

Posted on 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

Posted on 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

Posted on 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

Posted on by Robert Douglass.

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

Robert Douglass
Senior Drupal Advisor, Acquia

Posted on by chx (not verified).

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/dru pal/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.

Posted on by sdelbosc (not verified).

It is a little bit tricky to implement this for a view. In the piece of code below I update the request used by views to count the total number of results.

<?php/*** Change pager behaviour.* @param object $view*/function mymodule_views_pre_execute(&$view) {  // do not use views pager as it is too expensive, just provide next link  if ($view->pager['use_pager']) {    $current_page = isset($_REQUEST['page']) ? $_REQUEST['page'] : 0;    $fake_total_rows = $view->pager['items_per_page'] * ($current_page + 2);    $view->build_info['count_query'] = 'SELECT '. $view->base_field .' FROM '. $view->base_table .' LIMIT '. $fake_total_rows;  }}/*** Adjust pager in case there is not enough results* @param object $view the view to update*/function mymodule_views_pre_render(&$view) {  // do not use views pager as it is too expensive, just provide next link  // treat the case of the last page  if ($view->pager['use_pager']) {    if (count($view->result) < $view->pager['items_per_page']) {      global $pager_total;      $pager_total[0] = $view->pager['current_page'] + 1;    }  }}?>
Thanks a lot guys for sharing the ideas above that helped me to find a solution to my problem.

Posted on by Robert Douglass.

Nice extension to the initial idea!

In case anyone is copying this for their own use, they should make sure to keep it compatible with table prefixing:

$view->build_info['count_query'] = 'SELECT '. $view->base_field .' FROM {'. $view->base_table .'} LIMIT '. $fake_total_row

Robert Douglass
Senior Drupal Advisor, Acquia