37

I'm looking for an example algorithm of smart pagination. By smart, what I mean is that I only want to show, for example, 2 adjacent pages to the current page, so instead of ending up with a ridiculously long page list, I truncate it.

Here's a quick example to make it clearer... this is what I have now:

Pages: 1 2 3 4 [5] 6 7 8 9 10 11

This is what I want to end up with:

Pages: ... 3 4 [5] 6 7 ...

(In this example, I'm only showing 2 adjacent pages to the current page)

I'm implementing it in PHP/Mysql, and the "basic" pagination (no trucating) is already coded, I'm just looking for an example to optimize it... It can be an example in any language, as long as it gives me an idea as to how to implement it...

jeannicolas
  • 3,139
  • 4
  • 24
  • 25
  • See my question on [paging links](http://stackoverflow.com/questions/44542/algorithm-pseudo-code-to-create-paging-links) – Geoff Oct 02 '08 at 18:28
  • Questions on SO should show the code you've tried, not be just a general request for solutions. See http://stackoverflow.com/about – Blazemonger Feb 24 '14 at 15:46

8 Answers8

33

Here is some code based on original code from this very old link. It uses markup compatible with Bootstrap's pagination component, and outputs page links like this:

[1] 2 3 4 5 6 ... 100
1 [2] 3 4 5 6 ... 100
...
1 2 ... 14 15 [16] 17 18 ... 100
...
1 2 ... 97 [98] 99 100
<?php

// How many adjacent pages should be shown on each side?
$adjacents = 3;

//how many items to show per page
$limit = 5;

// if no page var is given, default to 1.
$page = (int)$_GET["page"] ?? 1;

//first item to display on this page
$start = ($page - 1) * $limit;

/* Get data. */
$data = $db
    ->query("SELECT * FROM mytable LIMIT $start, $limit")
    ->fetchAll();

$total_pages = count($data);

/* Setup page vars for display. */
$prev = $page - 1;
$next = $page + 1;
$lastpage = ceil($total_pages / $limit);
//last page minus 1
$lpm1 = $lastpage - 1;

$first_pages = "<li class='page-item'><a class='page-link' href='?page=1'>1</a></li>" .
    "<li class='page-item'><a class='page-link' href='?page=2'>2</a>";

$ellipsis = "<li class='page-item disabled'><span class='page-link'>...</span></li>";

$last_pages = "<li class='page-item'><a class='page-link' href='?page=$lpm1'>$lpm1</a></li>" .
    "<li class='page-item'><a class='page-link' href='?page=$lastpage'>$lastpage</a>";

$pagination = "<nav aria-label='page navigation'>";
$pagincation .= "<ul class='pagination'>";

//previous button

$disabled = ($page === 1) ? "disabled" : "";
$pagination.= "<li class='page-item $disabled'><a class='page-link' href='?page=$prev'>« previous</a></li>";

//pages 
//not enough pages to bother breaking it up
if ($lastpage < 7 + ($adjacents * 2)) { 
    for ($i = 1; $i <= $lastpage; $i++) {
        $active = $i === $page ? "active" : "";
        $pagination .= "<li class='page-item $active'><a class='page-link' href='?page=$i'>$i</a></li>";
    }
} elseif($lastpage > 5 + ($adjacents * 2)) {
    //enough pages to hide some
    //close to beginning; only hide later pages
    if($page < 1 + ($adjacents * 2)) {
        for ($i = 1; $i < 4 + ($adjacents * 2); $i++) {
            $active = $i === $page ? "active" : "";
            $pagination .= "<li class='page-item $active'><a class='page-link' href='?page=$i'>$i</a></li>";
        }
        $pagination .= $ellipsis;
        $pagination .= $last_pages;
    } elseif($lastpage - ($adjacents * 2) > $page && $page > ($adjacents * 2)) {
        //in middle; hide some front and some back
        $pagination .= $first_pages;
        $pagination .= $ellipsis
        for ($i = $page - $adjacents; $i <= $page + $adjacents; $i++) {
            $active = $i === $page ? "active" : "";
            $pagination .= "<li class='page-item $active'><a class='page-link' href='?page=$i'>$i</a></li>";
        }
        $pagination .= $ellipsis;
        $pagination .= $last_pages;
    } else {
        //close to end; only hide early pages
        $pagination .= $first_pages;
        $pagination .= $ellipsis;
        $pagination .= "<li class='page-item disabled'><span class='page-link'>...</span></li>";
        for ($i = $lastpage - (2 + ($adjacents * 2)); $i <= $lastpage; $i++) {
            $active = $i === $page ? "active" : "";
            $pagination .= "<li class='page-item $active'><a class='page-link' href='?page=$i'>$i</a></li>";
        }
    }
}

//next button
$disabled = ($page === $last) ? "disabled" : "";
$pagination.= "<li class='page-item $disabled'><a class='page-link' href='?page=$next'>next »</a></li>";

$pagination .= "</ul></nav>";

if($lastpage <= 1) {
    $pagination = "";
}


echo $pagination;

foreach ($data as $row) {
    // display your data
}

echo $pagination;

miken32
  • 42,008
  • 16
  • 111
  • 154
changelog
  • 4,646
  • 4
  • 35
  • 62
18

Kinda late =), but here is my go at it:

function Pagination($data, $limit = null, $current = null, $adjacents = null)
{
    $result = array();

    if (isset($data, $limit) === true)
    {
        $result = range(1, ceil($data / $limit));

        if (isset($current, $adjacents) === true)
        {
            if (($adjacents = floor($adjacents / 2) * 2 + 1) >= 1)
            {
                $result = array_slice($result, max(0, min(count($result) - $adjacents, intval($current) - ceil($adjacents / 2))), $adjacents);
            }
        }
    }

    return $result;
}

Example:

$total = 1024;
$per_page = 10;
$current_page = 2;
$adjacent_links = 4;

print_r(Pagination($total, $per_page, $current_page, $adjacent_links));

Output (@ Codepad):

Array
(
    [0] => 1
    [1] => 2
    [2] => 3
    [3] => 4
    [4] => 5
)

Another example:

$total = 1024;
$per_page = 10;
$current_page = 42;
$adjacent_links = 4;

print_r(Pagination($total, $per_page, $current_page, $adjacent_links));

Output (@ Codepad):

Array
(
    [0] => 40
    [1] => 41
    [2] => 42
    [3] => 43
    [4] => 44
)
Alix Axel
  • 151,645
  • 95
  • 393
  • 500
  • its works all good except $result = range(1, intval($data / $limit)); instead of $result = range(1, ceil($data / $limit)); ceil is creating one extra page – Pramendra Gupta Jun 09 '12 at 09:33
  • @JapanPro: And it should? If you have 99 results and you want to display 10 per page, you'll need `ceil(99 / 10) = 10` pages. – Alix Axel Jun 09 '12 at 23:49
  • 2
    @Alix Axel Nice answer!! but a better implementation would have been creating the array at the end instead of slicing a pre-existing one.... (e.g. what if you have 12k pages) – Aliceiw May 13 '16 at 13:49
  • Nice solution! I created ruby version [here](https://gist.github.com/shqear93/17957824532f057388aa3ce05bab2f58) if anybody need it – Khaled AbuShqear Aug 19 '21 at 19:36
10

I started from the lazaro's post and tried to make a robust and light algorithm with javascript/jquery... No additional and/or bulky pagination libraries needed... Look on fiddle for an live example: http://jsfiddle.net/97JtZ/1/

var totalPages = 50, buttons = 5;
var currentPage = lowerLimit = upperLimit = Math.min(9, totalPages);

//Search boundaries
for (var b = 1; b < buttons && b < totalPages;) {
    if (lowerLimit > 1 ) { lowerLimit--; b++; }
    if (b < buttons && upperLimit < totalPages) { upperLimit++; b++; }
}

//Do output to a html element
for (var i = lowerLimit; i <= upperLimit; i++) {
    if (i == currentPage) $('#pager').append('<li>' + i + '</li> ');
    else $('#pager').append('<a href="#"><li><em>' + i + '</em></li></a> ');
}
Edwin
  • 733
  • 8
  • 20
3
List<int> pages = new List<int>();
int pn = 2; //example of actual pagenumber
int total = 8;

for(int i = pn - 9; i <= pn + 9; i++)
{
  if(i < 1) continue;
  if(i > total) break;
  pages.Add(i);
}

return pages;
Salman A
  • 262,204
  • 82
  • 430
  • 521
lazaro
  • 31
  • 1
2

I made a pagination class and put in on Google Code a while ago. Check it out its pretty simple http://code.google.com/p/spaceshipcollaborative/wiki/PHPagination

$paging = new Pagination();
$paging->set('urlscheme','class.pagination.php?page=%page%');
$paging->set('perpage',10);
$paging->set('page',15);
$paging->set('total',3000);
$paging->set('nexttext','Next Page');
$paging->set('prevtext','Previous Page');
$paging->set('focusedclass','selected');
$paging->set('delimiter','');
$paging->set('numlinks',9);
$paging->display();
Jacob
  • 10,452
  • 5
  • 22
  • 11
0

I would use something simple on the page you are showing the paginator, like:

if (
  $page_number == 1 || $page_number == $last_page ||
  $page_number == $actual_page ||
  $page_number == $actual_page+1 || $page_number == $actual_page+2 ||
  $page_number == $actual_page-1 || $page_number == $actual_page-2
  ) echo $page_number;

You can adapt it to show each 10 or so pages with % operator ...

I think using switch() case would be better in this case, I just don't remember the syntax now

Keep it Simple :)

Fernando Barrocal
  • 12,584
  • 9
  • 44
  • 51
0

If it's possible to generate the pagination on the client, I would suggest my new Pagination plugin: http://www.xarg.org/2011/09/jquery-pagination-revised/

The solution to your question would be:

$("#pagination").paging(1000, { // Your number of elements
        format: '. - nncnn - ', // Format to get Pages: ... 3 4 [5] 6 7 ...
        onSelect: function (page) {
                // add code which gets executed when user selects a page
        },
        onFormat: function (type) {
                switch (type) {
                case 'block': // n and c
                        return '<a>' + this.value + '</a>';
                case 'fill': // -
                        return '...';
                case 'leap': // .
                        return 'Pages:';
                }
        }
});
0

The code of the CodeIgniter pagination-class can be found on GitHub

(what you call) Smart pagination can be achieved by configuration.

$config['num_links'] = 2;

The number of "digit" links you would like before and after the selected page number. For example, the number 2 will place two digits on either side, as in the example links at the very top of this page.

Natrium
  • 30,772
  • 17
  • 59
  • 73