April 22, 2014

StackOverflow Like Pagination

Some while ago, I built a pagination system in PHP to display a long list of pagination links. The pagination system showed at most 5 links at a time and additional links were represented by ellipsis (...). However, I was not able to deal with certain edge cases, so I drew some inspiration from the pagination system used on StackOverflow to tackle them. This is what the end result looked like:

Pagination using ellipsis

  • The first and last page links are always visible
  • The previous and next page links (numeric ones) are always visible
  • At most n page links excluding first and last page links are visible; rest are represented by ellipsis
  • The pagination for first and last n - 1 pages is handled a bit differently

Complete PHP source code below.

Pagination Function

<?php
/**
 * Displays pagination links based on given parameters
 *
 * @param int $currentPage - current page
 * @param int $itemCount - number of items to paginate, used to calculate total number of pages
 * @param int $itemsPerPage - number of items per page, used to calculate total number of pages
 * @param int $adjacentCount - half the number of page links displayed adjacent to the current page
 * @param (string|callable) $pageLinkTemplate - pagination URL string containing %d placeholder or a callable function that accepts page number and returns page URL
 * @param boolean $showPrevNext - whether to show previous and next page links
 * @return void
 */
function pagination($currentPage, $itemCount, $itemsPerPage, $adjacentCount, $pageLinkTemplate, $showPrevNext = true) {
    $firstPage = 1;
    $lastPage  = ceil($itemCount / $itemsPerPage);
    if ($lastPage == 1) {
        return;
    }
    if ($currentPage <= $adjacentCount + $adjacentCount) {
        $firstAdjacentPage = $firstPage;
        $lastAdjacentPage  = min($firstPage + $adjacentCount + $adjacentCount, $lastPage);
    } elseif ($currentPage > $lastPage - $adjacentCount - $adjacentCount) {
        $lastAdjacentPage  = $lastPage;
        $firstAdjacentPage = $lastPage - $adjacentCount - $adjacentCount;
    } else {
        $firstAdjacentPage = $currentPage - $adjacentCount;
        $lastAdjacentPage  = $currentPage + $adjacentCount;
    }
    echo '<div>';
    if ($showPrevNext) {
        if ($currentPage == $firstPage) {
            echo '<span>&lt;</span>';
        } else {
            echo '<a href="' . (is_callable($pageLinkTemplate) ? $pageLinkTemplate($currentPage - 1) : sprintf($pageLinkTemplate, $currentPage - 1)) . '">&lt;</a>';
        }
    }
    if ($firstAdjacentPage > $firstPage) {
        echo '<a href="' . (is_callable($pageLinkTemplate) ? $pageLinkTemplate($firstPage) : sprintf($pageLinkTemplate, $firstPage)) . '">' . $firstPage . '</a>';
        if ($firstAdjacentPage > $firstPage + 1) {
            echo '<span>...</span>';
        }
    }
    for ($i = $firstAdjacentPage; $i <= $lastAdjacentPage; $i++) {
        if ($currentPage == $i) {
            echo '<b>' . $i . '</b>';
        } else {
            echo '<a href="' . (is_callable($pageLinkTemplate) ? $pageLinkTemplate($i) : sprintf($pageLinkTemplate, $i)) . '">' . $i . '</a>';
        }
    }
    if ($lastAdjacentPage < $lastPage) {
        if ($lastAdjacentPage < $lastPage - 1) {
            echo '<span>...</span>';
        }
        echo '<a href="' . (is_callable($pageLinkTemplate) ? $pageLinkTemplate($lastPage) : sprintf($pageLinkTemplate, $lastPage)) . '">' . $lastPage . '</a>';
    }
    if ($showPrevNext) {
        if ($currentPage == $lastPage) {
            echo '<span>&gt;</span>';
        } else {
            echo '<a href="' . (is_callable($pageLinkTemplate) ? $pageLinkTemplate($currentPage + 1) : sprintf($pageLinkTemplate, $currentPage + 1)) . '">&gt;</a>';
        }
    }
    echo '</div>';
}
?>

Example 1

pagination(1, 123, 10, 2, "users.php?page=%d");

Example 2: Using canonical URL for first page

pagination(1, 123, 10, 2, function($page) {
    // use a callback function to handle special cases e.g.
    // return canonical URL for first page and page URL for rest
    return $page == 1 ? "/users/" : "/users/$page/";
});