Customising the pager (next, previous navigation)

While I really like the basic look and design of Drupal, one thing I never liked was the look of the pager.

The code, In My Not So Humble Opinion, is brilliant, and works extremely well. But the look? Well, it leaves a bit to be desired. And is very hard to theme. But it is doable.

Here is an alternative.

One note: Due to Internet Explorer's incorrect handling of padding, you may have to manually adjust the IE Hacks section for each div a pager appears in that has a different padding value. In most sites, the padding values don't change, so I don't see this as a big problem.

Note: The following code is for phptemplate themes. For themes that aren't phptemplate, you may have to figure out how to change this on your own; however, for themes that have a THEMENAME.theme, you can put this in that file and do a search and replace to change phptemplate_ to THEMENAME_. I'm not sure how to do it with xtemplate themes, offhand.

The following goes in template.php

<?php
// This is the main theme override.
// one downside is that we ignore the $tags() because they don't fit
// the way we want to display. I don't think most theme('pager')s use them.
function phptemplate_pager($tags = array(), $page_size = 10, $element = 0, $attributes = array()) {
  global
$pager_from_array, $pager_total;
 
$output = '';

 
// It's easier to calculate, to me at least, using page numbers
 
if (($page_num = (ceil(($pager_from_array[$element] + 1) / $page_size))) < 1) {
   
$page_num = 1;
  }

 
// and number of pages
 
if (($num_pages = (ceil(($pager_total[$element] + 1) / $page_size))) < 1) {
   
$num_pages = 1;
  }

 
// Display the pager.
 
if ($pager_total[$element] > $page_size) {
   
$output .= '<div class="pager-top">';
   
$output .= _pager_prev($page_size, $element, $attributes, $page_num, $num_pages);
   
$output .= _pager_next($page_size, $element, $attributes, $page_num, $num_pages);
   
$output .= _pager_page_num($page_size, $element, $attributes, $page_num, $num_pages);
   
$output .= '</div>';

    return
$output;
  }
}

// This function creates a proper pager url.
function _pager_url($page_num, $text, $element, $attributes, $page_size) {
  global
$pager_from_array;
 
$from = ($page_num - 1) * $page_size;

 
$from_new = pager_load_array($from, $element, $pager_from_array);
 
  return
'<a href="/'. pager_link($from_new, $element, $attributes) . "\">$text</a>";
}

function
_pager_page_num($page_size, $element, $attributes, $page_num, $num_pages) {
  return
"<div class='pager-middle'>" . _pager_little_prev_button($page_size, $element, $attributes, $page_num, $num_pages)
    .
"<span class='page-num'>Page $page_num</span>"
   
. _pager_little_next_button($page_size, $element, $attributes, $page_num, $num_pages)
    .
"</div>\n";

}

function
_pager_little_prev_button($page_size, $element, $attributes, $page_num, $num_pages)
{
   
// Is there a previous button to even print?
    // page < 1 == page 1.
   
if ($page_num <= 1)
        return
"";

 
$prev = $page_num - 1;
    return
_pager_url($prev, "&lt;&lt;", $element, $attributes, $page_size);
}

function
_pager_little_next_button($page_size, $element, $attributes, $page_num, $num_pages)
{
   
// Is there a previous button to even print?
    // page < 1 == page 1.
   
if ($page_num >= $num_pages)
        return
"";

 
$next = $page_num + 1;
    return
_pager_url($next, "&gt;&gt;", $element, $attributes, $page_size);
}

// function _pager_prevButton($url, $page, $page_size, $defaultPageSize, $count)
function _pager_prev($page_size, $element, $attributes, $page, $num_pages)
{
   
// Is there a previous button to even print?
    // page < 1 == page 1.
   
if ($page <= 1)
        return
"";

   
// First, let's just do the previous page.
   
$prev = $page - 1;
   
$string = _pager_url($prev, $prev, $element, $attributes, $page_size);

   
// Let's do five pages, plus page #1, and a ... if there's a blank spot.
   
for ($i = $page - 2; $i > max(1, ($page - 5)); $i--)
       
$string = _pager_url($i, $i, $element, $attributes, $page_size) . " $string";

    if (
$i != 1 && $i != 0) // if we stopped at something other than 1 there was > 5
       
$string = "... " . $string;

 
// Now see if we need to do powers of 10.
  // This is probably more complicated than it needs to be, and someone
  // clever could rewrite this -- but I got tired of fiddling with it.
   
for ($counter = 1; $counter < 5; $counter++) {
       
$pow = pow(10, $counter);
        if (
$page > 16 * pow(10, $counter - 1))
            for (
$i = floor((($page)/$pow) - 1) * $pow, $j = 0; $i > 1 && $j < 2; $i -= $pow, $j++)
               
$string = _pager_url($i, $i, $element, $attributes, $page_size) . " $string";
    }

 
// And finally, the very first page always shows up, unless we've already hit it.
   
if ($prev != 1)
       
$string = _pager_url(1, 1, $element, $attributes, $page_size) . " $string";

    return
"<div class='pager-prev'>$string</div>";
}

function
_pager_next($page_size, $element, $attributes, $page, $num_pages)
{
   
// Is there a next button to even print?
   
if ($page >= $num_pages)
        return
"";

   
// First, let's just do the previous page.
   
$next = $page + 1;
   
$string = _pager_url($next, $next, $element, $attributes, $page_size);
   
// Let's do five pages, plus page #1, and a ... if there's a blank spot.

   
for ($i = $page + 2; $i < min($num_pages, ($page + 5)); $i++)
       
$string .= " " . _pager_url($i, $i, $element, $attributes, $page_size);
    if (
$i != $num_pages && $i != $num_pages + 1) // if we stopped at something other than 1 there was > 5
       
$string .= " ...";

    for (
$counter = 1; $counter < 5; $counter++) {
       
$pow = pow(10, $counter);
        if ((
$num_pages - $page) > 11 * pow(10, $counter - 1))
            for (
$i = ceil(($page + 5)/$pow) * $pow, $j = 0; $i < $num_pages && $j < 2; $i += $pow, $j++)
               
$string .= " " . _pager_url($i, $i, $element, $attributes, $page_size);
    }
    if (
$next != $num_pages)
       
$string .= " " . _pager_url($num_pages, $num_pages, $element, $attributes, $page_size);
    return
"<div class='pager-next'>$string</div>";

}
?>

And this goes in style.css.

.pager-top {
  margin-top: 1em;
  margin-bottom: 1em;
  position: relative;
}

.pager-middle {
  text-align: center;
  vertical-align: top;
  clear: none;
  position: relative;
  width: 100%;
  font-size: .8em;
  z-index: 0;
  top: 0;
}

.pager-prev {
  text-align: left;
  position: absolute;
  top: 0;
  left: 0;
  font-size: .8em;
  z-index: 2;
}

.pager-next {
  text-align: right;
  position: absolute;
  right: 0;
  float: right;
  font-size: .8em;
  z-index: 1;
}

/* IE hacks */
* html .main-content .pager-next {
  \margin-right: 5px;
  /* You may have to adjust this manually!!! */
}

* html .main-content td .pager-next {
  \margin-right: 0px;
}


.page-num {
  margin-left: .5em;
  margin-right: .5em;
}

Customize the pager: another example

wmostrey - March 30, 2007 - 08:11

A really simple pager, with no first/last link:

One note about this: you might notice that the $page_curr and $page_next have the same value. This is because the link ?page= is always 1 number below the actual page number. So if you're on the first page, you're actually on ?page=0. The second page is ?page=1. So to display the current page number, you need to do the page variable + 1. But to go to the next page, you need to do the same to put that value in the link again.

<?php
function phptemplate_pager($tags = array(), $limit = 10, $element = 0, $parameters = array()) {
  global
$pager_page_array, $pager_total;
 
$page_prev = $pager_page_array[$element] - 1;
 
$page_curr = $pager_page_array[$element] + 1;
 
$page_next = $pager_page_array[$element] + 1;

  if (
$pager_total[$element] > 1) {
   
$output = '<div class="previous-next">';

    if (
$pager_page_array[$element]!=0) $output.= '<a href="?page='.$page_prev.'" class="previous">Previous</a>';

   
$output.= '<div class="previous-next-page">Page '.$page_curr.'/'.$pager_total[$element].'</div>';

    if (
$page_curr!=$pager_total[$element]) $output.= '<a href="?page='.$page_next.'" class="next">Next</a>';

   
$output.= '</div>';
    return
$output;
  }
}
?>

You can theme it yourself, nothing fancy there.

A slightly altered version of wmostrey's example

mikemccaffrey - March 25, 2008 - 21:31

I added the first and last links back into the mix, and made it so that First, Prev, Next, and Last show up as text even if they are not going to be displayed as links.

I created more robust class specifications that will allow you to hide any part you don't want using CSS.

<?php
function phptemplate_pager($tags = array(), $limit = 10, $element = 0, $parameters = array()) {

    global
$pager_page_array, $pager_total;
   
$page_prev = $pager_page_array[$element] - 1;
   
$page_curr = $pager_page_array[$element] + 1;
   
$page_next = $pager_page_array[$element] + 1;
   
$page_last = $pager_total[$element] - 1;


    if (
$pager_total[$element] > 1) {
   
       
$output = '<div class="pager">';

        if (
$pager_page_array[$element]!=0) {
           
           
$output.= '<span class="pager-first pager-first-active"><a href="?page=0">First</a></span>';
           
           
$output.= '<span class="pager-previous pager-previous-active"><a href="?page='.$page_prev.'">Prev</a></span>';
       
        } else {
       
           
$output.= '<span class="pager-first pager-inactive pager-first-inactive">First</span>';
           
           
$output.= '<span class="pager-previous pager-inactive pager-previous-inactive">Prev</span>';

        }

       
$output.= '<span class="pager-pagenumbers">Page '.$page_curr.'/'.$pager_total[$element].'</span>';


        if (
$page_curr!=$pager_total[$element]) {
       
           
$output.= '<span class="pager-next pager-next-active"><a href="?page='.$page_next.'">Next</a></span>';
       
           
$output.= '<span class="pager-last pager-last-active"><a href="?page='.$pager_total[$element].'">Last</a></span>';
   
        } else {
       
           
$output.= '<span class="pager-next pager-inactive pager-next-inactive">Next</span>';

           
$output.= '<span class="pager-last pager-inactive pager-last-inactive">Last</span>';
        }

       
$output.= '</div>';
  
        return
$output;
    }
}
?>

Hope it helps!

Using Page Numbers

mikemccaffrey - March 25, 2008 - 21:40

After I wrote the code above, my client decided that they still wanted the clickable numbers between the Prev and Next links, so I wrote some code to handle that.

Just take this line from my code above:

<?php
$output
.= '<span class="pager-pagenumbers">Page '.$page_curr.'/'.$pager_total[$element].'</span>';
?>

and replace it with this:

<?php
$output
.= '<span class="pager-pagenumbers">';

$num_page_links = 5;

if(
$pager_total[$element] <= $num_page_links) {

   
$pagenumbers_start = 1;
   
   
$pagenumbers_end = $pager_total[$element];

   
} else {
   
    if(
$page_curr <= ceil($num_page_links/2)) {

       
$pagenumbers_start = 1;
   
       
$pagenumbers_end = $num_page_links;
   
   
    } else if (
$page_curr >= $pager_total[$element] - floor($num_page_links / 2)) {

       
$pagenumbers_start = $pager_total[$element] - $num_page_links + 1;
   
       
$pagenumbers_end = $pager_total[$element];
   
   
    } else {
           
       
$pagenumbers_start = $page_curr - floor(($num_page_links - 1) / 2);
       
       
$pagenumbers_end $page_curr + ceil(($num_page_links - 1) / 2);
       
    }

}

if(
$pagenumbers_start > 1) $output .= ' <span class="pager-inactive pager-pagenumbers-inactive">...</span> ';

for(
$i = $pagenumbers_start; $i <= $pagenumbers_end; $i++) {

    if(
$i == $page_curr) $output .= ' <span class="pager-inactive pager-pagenumbers-inactive">'.$page_curr.'</span> ';

    else
$output .= ' <a href="?page='.($i-1).'">'.$i.'</a> ';

}

if(
$pager_total[$element] > $pagenumbers_end) $output .= ' <span class="pager-inactive pager-pagenumbers-inactive">...</span> ';


$output.= '</span>';
?>

Obviously, this is a bit more complicated than just displaying what page you are on, and I admit that this code is pretty messy and repetitive, but it seems to work alright. You can even specify the number of page links you want to show at the top, and any additional pages are hidden with an ellipsis.

One addition..

Fiasst - November 28, 2007 - 18:18

Thanks guys, this is a very nice snippet. However, when using the smaller custom pager on a view with filters, the pager won't use the filters querystrings. I've written a little extra code. (My first template.php code):

<?php
function phptemplate_pager($tags = array(), $limit = 10, $element = 0, $parameters = array()) {
    global
$pager_page_array, $pager_total;
   
$page_prev = $pager_page_array[$element] - 1;
   
$page_curr = $pager_page_array[$element] + 1;
   
$page_next = $pager_page_array[$element] + 1;
   
   
# get querystrings (except q="" and page="")
   
$cgi = $_SERVER['REQUEST_METHOD'] == 'GET' ? $_GET : $_POST;
   
$query = '';
    foreach (
$cgi as $key => $val) {
        if (
$key != 'page' && $key != 'q') {
           
$query .= '&'. $key .'='. $val;
        }
    }
   
$query = substr($query, 1);
   
    if (
$pager_total[$element] > 1) {
       
$output = '<div id="pager-wrap">';
        if (
$pager_page_array[$element]!=0) $output.= '<a href="?page='.$page_prev.'?'.$query.'" class="previous">‹</a>';
       
$output.= '<div class="pagecount">Page '.$page_curr.' / '.$pager_total[$element].'</div>';
   
        if (
$page_curr!=$pager_total[$element]) $output.= '<a href="?page='.$page_next.'?'.$query.'" class="next">›</a>';
       
$output.= '</div>';
        return
$output;
    }
}
?>

Realworks Media Website Design

 
 

Drupal is a registered trademark of Dries Buytaert.