A simple pager script for Drupal

Tuesday, 24 August 2010 - 4:14 pm - by ram

Today I found myself needing to render pager links in Drupal 6 for a list of items. By pager I mean the links you usually see at the bottom of the page which indicate what page you’re currently on, what pages comes before and after and where you are in the grand context of the total number of pages available (example: http://drupal.org/search/apachesolr_search/e).

Drupal has built-in pager generation methods (theme_pager) but these either require you to have loaded the data to be paged from the local Drupal database or require you to shoe-horn the data you wish to page into Drupal’s pager global variables. I needed something simpler which would work with any data but which would still generate output similar to what Drupal’s pager functions generate.

After a bit of work I had something which did the job:

  1. /**
  2.  * Return themed pager using same markup and CSS classes as the standard Drupal pager.
  3.  *
  4.  * @param  $total_pages total no. of pages.
  5.  * @param  $current_page the current page being viewed (1 <= $current_page <= $total_pages).
  6.  * @param  $num_pages_to_show no. of pages links for, excluding first, next, previous and last links .
  7.  * @param  $base_url the base URL for paging links. Each paging link will be at $base_url?page=<page_num>
  8.  * @return themed paging links; empty string if there is only one page in total.
  9.  */
  10. function garland_simple_pager_links($total_pages, $current_page, $num_pages_to_show, $base_url) {
  11.  
  12.   static $buttons;
  13.   if (empty($buttons)) {
  14.     $buttons = array(
  15.       ‘first’ => array(‘text’ => t(‘&laquo; first’), ‘link_tooltip’ => t(‘Go to first page’)),
  16.       ‘prev’ => array(‘text’ => t(‘&lsaquo; previous’), ‘link_tooltip’ => t(‘Go to previous page’)),
  17.       ‘next’ => array(‘text’ => t(‘next &rsaquo;’), ‘link_tooltip’ => t(‘Go to next page’)),
  18.       ‘last’ => array(‘text’ => t(‘last &raquo;’), ‘link_tooltip’ => t(‘Go to last page’)),
  19.       ‘current’ => array(‘class’ => ‘current’),
  20.       ‘ellipsis’ => array(‘text’ => ‘…’),
  21.     );
  22.   }
  23.  
  24.   // show nothing if only one page
  25.   if (1 >= $total_pages)
  26.     return ;
  27.  
  28.   // remove all query params from the base URL
  29.   $base_url = ltrim($base_url,‘/’);
  30.  
  31.   // the first page in current set of pages
  32.   $pager_first = $current_page - intval($num_pages_to_show / 2);
  33.   // the last page in current set of pages
  34.   $pager_last = $current_page + intval($num_pages_to_show / 2);
  35.  
  36.   // normalize
  37.   if (1 > $pager_first) {
  38.     $pager_last += (1 - $pager_first);
  39.     $pager_first = 1;
  40.   }
  41.   if ($total_pages < $pager_last) {
  42.     $pager_first -= ($pager_last - $total_pages);
  43.     if (1 > $pager_first)
  44.       $pager_first = 1;
  45.     $pager_last = $total_pages;
  46.   }
  47.  
  48.   $items = array();
  49.  
  50.   // show ‘prev’ button
  51.   if (1 < $current_page) {
  52.     // show ‘first’ button
  53.     if (1 < $pager_first) {
  54.       $items[] = array(
  55.         ‘class’ => ‘pager-first’,
  56.         ‘data’ => theme(‘simple-pager-link’, $base_url, 1, $buttons[‘first’]),
  57.       );
  58.     }
  59.     $items[] = array(
  60.       ‘class’ => ‘pager-previous’,
  61.       ‘data’ => theme(‘simple-pager-link’, $base_url, $current_page-1, $buttons[‘prev’]),
  62.     );
  63.     // show ellipsis
  64.     if (1 < $pager_first) {
  65.       $items[] = array(
  66.         ‘class’ => ‘pager-ellipsis’,
  67.         ‘data’ => theme(‘simple-pager-link’, $base_url, ‘…’, $buttons[‘ellipsis’]),
  68.       );
  69.     }
  70.   }
  71.  
  72.   // page links
  73.   for ($i=$pager_first; $i<=$pager_last; ++$i) {
  74.     if ($i == $current_page) {
  75.       $items[] = array(
  76.         ‘class’ => ‘pager-current’,
  77.         ‘data’ => theme(‘simple-pager-link’, $base_url, $i, $buttons[‘current’]),
  78.       );
  79.     } else {
  80.       $items[] = array(
  81.         ‘class’ => ‘pager-item’,
  82.         ‘data’ => theme(‘simple-pager-link’, $base_url, $i, array(‘text’ => $i, ‘link_tooltip’ => t(‘Goto page @d’,array(‘@d’ => $i)))),
  83.       );
  84.     }
  85.   }
  86.  
  87.   // show ‘next’ button
  88.   if ($total_pages > $current_page) {
  89.     // show ellipsis
  90.     if ($total_pages > $pager_last) {
  91.       $items[] = array(
  92.         ‘class’ => ‘pager-ellipsis’,
  93.         ‘data’ => theme(‘simple-pager-link’, $base_url, ‘…’, $buttons[‘ellipsis’]),
  94.       );
  95.     }
  96.     $items[] = array(
  97.       ‘class’ => ‘pager-next’,
  98.       ‘data’ => theme(‘simple-pager-link’, $base_url, $current_page+1, $buttons[‘next’]),
  99.     );
  100.     // show ‘last’ button
  101.     if ($total_pages > $pager_last) {
  102.       $items[] = array(
  103.         ‘class’ => ‘pager-last’,
  104.         ‘data’ => theme(‘simple-pager-link’, $base_url, $total_pages, $buttons[‘last’]),
  105.       );
  106.     }
  107.   }
  108.  
  109.   return theme(‘item_list’, $items, NULL, ‘ul’, array(‘class’ => ‘pager’));
  110. }
  111.  
  112.  
  113. /**
  114.  * Return a themed pager link.
  115.  *
  116.  * @param  $base_url the base URL to add the paging query param to.
  117.  * @param  $page the number of the page to link to.
  118.  * @param  $button_attributes array(‘link_tooltip’ =>, ‘text’ => ‘class’ =>).
  119.  * If ‘link_tooltip’ is ommitted then the text is returned witout a wrapping anchor. If ‘text’ is ommitted then
  120.  * $page is used as the link text.
  121.  *
  122.  * @return themed pager link.
  123.  * @see garland_pager_link()
  124.  */
  125. function garland_simple_pager_link($base_url, $page, $button_attributes) {
  126.  
  127.   $text = isset($button_attributes[‘text’]) ? $button_attributes[‘text’] : $page;
  128.  
  129.   $attributes = array(
  130.       ‘title’ => (isset($button_attributes[‘link_tooltip’]) ? $button_attributes[‘link_tooltip’] : ),
  131.   );
  132.   if (isset($button_attributes[‘class’]))
  133.     $attributes[‘class’] = $button_attributes[‘class’];
  134.  
  135.   if (isset($button_attributes[‘link_tooltip’])) {
  136.     return ‘<a href="’. check_url(url($base_url, array(‘query’ => array(‘page’ => $page)))) .‘"’. drupal_attributes($attributes) .‘>’. $text .‘</a>’;
  137.   } else {
  138.     return $text;
  139.   }
  140. }
  141.  
  142.  
  143. // Amend you theme’s theme() method accordingly, as I’ve done below for Garland:
  144.  
  145. function garland_theme($existing, $type, $theme, $path) {
  146.   return array(
  147.     ‘simple-pager-links’ => array(
  148.       ‘arguments’ => array(NULL, NULL, NULL, NULL),
  149.       ‘function’ => ‘garland_simple_pager_links’,
  150.     ),
  151.     ‘simple-pager-link’ => array(
  152.       ‘arguments’ => array(NULL, NULL, NULL),
  153.       ‘function’ => ‘garland_simple_pager_link’,
  154.     )
  155.   );
  156. }

The only hook you need to know is simple-pager-links. You pass it to the total number of pages in your result set, the current page number (should be between 1 and the total number of pages), and the number of page links to show either side of the current page’s item when rendering the pager links. Note that the pager does not need to know about how many data items you wish to show on each page or even what data you plan to show. The generated page links are of the form http://<domain.com>/<path>?page=<page_number>.

Example usage:

// total no. of pages = 20
// current page = 5
// no. of page links to show = 5
// base_url = http://domain.com/directory
$pager_html = theme('simple-pager-links', 20, 5, 5, 'http://domain.com/directory');

This would produce a pager like the following:

Tags:   , ,
  • Lee

    thanks, saved me a heap, you might want to add the keywords 'without database query' to the article title to help with SEO

  • http://twitter.com/hiddentao HiddenTao

    You're welcome. Thanks for the tip!

  • Profachie

    I am trying to create multiple glossaries in drupal 6. I created a node called glossary and seeking a way to modify pager from its current numeric form “« first« previous…34567…next »last »” to an alphabetic form “A | B | C | D | E| F | G | H | I | J | K | L | M | N | O | P | Q | R | S | T | U | V | W | X | Y | Z | # ”

     

    Views does something similar but is not configurable on multiple paths without setting up multiple views. Can you offer any advice on how to accomplish this please?

  • http://twitter.com/hiddentao HiddenTao

    Sorry mate, that’s a bit too complicated to show in one comment. But you’ll want to write a completely custom pager and show it as a Drupal block. I imagine the Views module will come in handy as a way of listing glossary nodes associated with a particular letter.