Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Simple pagination algorithm
// Implementation in ES6
function pagination(c, m) {
var current = c,
last = m,
delta = 2,
left = current - delta,
right = current + delta + 1,
range = [],
rangeWithDots = [],
l;
for (let i = 1; i <= last; i++) {
if (i == 1 || i == last || i >= left && i < right) {
range.push(i);
}
}
for (let i of range) {
if (l) {
if (i - l === 2) {
rangeWithDots.push(l + 1);
} else if (i - l !== 1) {
rangeWithDots.push('...');
}
}
rangeWithDots.push(i);
l = i;
}
return rangeWithDots;
}
/*
Test it:
for (let i = 1, l = 20; i <= l; i++)
console.log(`Selected page ${i}:`, pagination(i, l));
Expected output:
Selected page 1: [1, 2, 3, "...", 20]
Selected page 2: [1, 2, 3, 4, "...", 20]
Selected page 3: [1, 2, 3, 4, 5, "...", 20]
Selected page 4: [1, 2, 3, 4, 5, 6, "...", 20]
Selected page 5: [1, 2, 3, 4, 5, 6, 7, "...", 20]
Selected page 6: [1, "...", 4, 5, 6, 7, 8, "...", 20]
Selected page 7: [1, "...", 5, 6, 7, 8, 9, "...", 20]
Selected page 8: [1, "...", 6, 7, 8, 9, 10, "...", 20]
Selected page 9: [1, "...", 7, 8, 9, 10, 11, "...", 20]
Selected page 10: [1, "...", 8, 9, 10, 11, 12, "...", 20]
Selected page 11: [1, "...", 9, 10, 11, 12, 13, "...", 20]
Selected page 12: [1, "...", 10, 11, 12, 13, 14, "...", 20]
Selected page 13: [1, "...", 11, 12, 13, 14, 15, "...", 20]
Selected page 14: [1, "...", 12, 13, 14, 15, 16, "...", 20]
Selected page 15: [1, "...", 13, 14, 15, 16, 17, "...", 20]
Selected page 16: [1, "...", 14, 15, 16, 17, 18, 19, 20]
Selected page 17: [1, "...", 15, 16, 17, 18, 19, 20]
Selected page 18: [1, "...", 16, 17, 18, 19, 20]
Selected page 19: [1, "...", 17, 18, 19, 20]
Selected page 20: [1, "...", 18, 19, 20]
*/
@vcostin

This comment has been minimized.

Copy link

@vcostin vcostin commented Apr 12, 2016

Thanks for sharing! 👍

@philipgiuliani

This comment has been minimized.

Copy link

@philipgiuliani philipgiuliani commented Aug 18, 2016

Wow this is awesome! You have saved me some hours... :) Thanks!

@martinmanzo

This comment has been minimized.

Copy link

@martinmanzo martinmanzo commented Oct 17, 2016

Thanks!

@alexadusei

This comment has been minimized.

Copy link

@alexadusei alexadusei commented Dec 30, 2016

Great gist. Thanks!

@jkokh

This comment has been minimized.

Copy link

@jkokh jkokh commented Jan 22, 2017

Awesome! Thnx!

@x-ror

This comment has been minimized.

Copy link

@x-ror x-ror commented Feb 6, 2017

Thanks dude !

@jeantoledo

This comment has been minimized.

Copy link

@jeantoledo jeantoledo commented Feb 25, 2017

I Have a simple version without dots, first and last page.

function pagination(currentPage, pageCount) {
        let delta = 2,
            left = currentPage - delta,
            right = currentPage + delta + 1,
            result = [];

        result = Array.from({length: pageCount}, (v, k) => k + 1)
            .filter(i => i && i >= left && i < right);

        return result;
}
@sinau123

This comment has been minimized.

Copy link

@sinau123 sinau123 commented Mar 14, 2017

Thanks, it's help me but there is a problem when number of page is too large.
So I have optimized your code at range.push,

function pagination(c, m) {
    var current = c,
        last = m,
        delta = 2,
        left = current - delta,
        right = current + delta + 1,
        range = [],
        rangeWithDots = [],
        l;
  
    range.push(1)  
    for (let i = c - delta; i <= c + delta; i++) {
        if (i >= left && i < right && i < m && i > 1) {
            range.push(i);
        }
    }  
    range.push(m);

    for (let i of range) {
        if (l) {
            if (i - l === 2) {
                rangeWithDots.push(l + 1);
            } else if (i - l !== 1) {
                rangeWithDots.push('...');
            }
        }
        rangeWithDots.push(i);
        l = i;
    }

    return rangeWithDots;
}
@longprao

This comment has been minimized.

Copy link

@longprao longprao commented May 22, 2017

Thanks @sinau123, I think it can be further optimized a little bit by eliminating the left and right variable and its if checks:

function pagination(c, m) {
    var delta = 2,
        range = [],
        rangeWithDots = [],
        l;
  
    range.push(1)  
    for (let i = c - delta; i <= c + delta; i++) {
        if (i < m && i > 1) {
            range.push(i);
        }
    }  
    range.push(m);

    for (let i of range) {
        if (l) {
            if (i - l === 2) {
                rangeWithDots.push(l + 1);
            } else if (i - l !== 1) {
                rangeWithDots.push('...');
            }
        }
        rangeWithDots.push(i);
        l = i;
    }

    return rangeWithDots;
}
@Liiion911

This comment has been minimized.

Copy link

@Liiion911 Liiion911 commented Jun 2, 2017

thx @longprao, but
pagination(1, 1)

In my project i change:
range.push(m);
to
if (m != 1) range.push(m);

@dirceup

This comment has been minimized.

Copy link

@dirceup dirceup commented Jul 14, 2017

Thank you very much.

@anotherstarburst

This comment has been minimized.

Copy link

@anotherstarburst anotherstarburst commented Sep 21, 2017

It breaks with a page count of 1 - here's a version that simply returns an array of [1] if there is only 1 page. Also changed variable names to make life a little easier.

function pagination(currentPage, nrOfPages) {
    var delta = 2,
        range = [],
        rangeWithDots = [],
        l;

    range.push(1);  

    if (nrOfPages <= 1){
 	return range;
    }

    for (let i = currentPage - delta; i <= currentPage + delta; i++) {
        if (i < nrOfPages && i > 1) {
            range.push(i);
        }
    }  
    range.push(nrOfPages);

    for (let i of range) {
        if (l) {
            if (i - l === 2) {
                rangeWithDots.push(l + 1);
            } else if (i - l !== 1) {
                rangeWithDots.push('...');
            }
        }
        rangeWithDots.push(i);
        l = i;
    }

    return rangeWithDots;
}
@UdochukwuNweke

This comment has been minimized.

Copy link

@UdochukwuNweke UdochukwuNweke commented Nov 18, 2017

PHP version

function pagination($c, $m) 
{
    $current = $c;
    $last = $m;
    $delta = 2;
    $left = $current - $delta;
    $right = $current + $delta + 1;
    $range = array();
    $rangeWithDots = array();
    $l = -1;

    for ($i = 1; $i <= $last; $i++) 
    {
        if ($i == 1 || $i == $last || $i >= $left && $i < $right) 
        {
            array_push($range, $i);
        }
    }

    for($i = 0; $i<count($range); $i++) 
    {
        if ($l != -1) 
        {
            if ($range[$i] - $l === 2) 
            {
                array_push($rangeWithDots, $l + 1);
            } 
            else if ($range[$i] - $l !== 1) 
            {
                array_push($rangeWithDots, '...');
            }
        }
        
        array_push($rangeWithDots, $range[$i]);
        $l = $range[$i];
    }

    return $rangeWithDots;
}
@lanteret

This comment has been minimized.

Copy link

@lanteret lanteret commented Dec 17, 2017

Good !!!! you are the BEST for me !!

@TimJKStrickland

This comment has been minimized.

Copy link

@TimJKStrickland TimJKStrickland commented Jan 12, 2018

I've been pulling my hair out trying to implement something that you made so simple to use. Muchas gracias!

@martiuh

This comment has been minimized.

Copy link

@martiuh martiuh commented Jan 15, 2018

Thanks! Great work

@eskeyy

This comment has been minimized.

Copy link

@eskeyy eskeyy commented Jan 20, 2018

Hello. I had a question ? If we have a movie website and on the first page we have 20 movies but when we want to include in the new movie, as a matter of fact that this new movie we introduced to go to the second page and on the first page there are still 20 movies. I hope someone helps me.
if anyone has this, you can distribute it here

@eskeyy

This comment has been minimized.

Copy link

@eskeyy eskeyy commented Jan 20, 2018

I need for website 👍

@danilopolani

This comment has been minimized.

Copy link

@danilopolani danilopolani commented Feb 10, 2018

Version of jeantoledo with dots:

function pagination(currentPage, pageCount) {
        let delta = 2,
            left = currentPage - delta,
            right = currentPage + delta + 1,
            result = [];

        result = Array.from({length: pageCount}, (v, k) => k + 1)
            .filter(i => i && i >= left && i < right);

        if (result.length > 1) {
          // Add first page and dots
          if (result[0] > 1) {
            if (result[0] > 2) {
              result.unshift('...')
            }
            result.unshift(1)
          }

          // Add dots and last page
          if (result[result.length - 1] < pageCount) {
            if (result[result.length - 1] !== pageCount - 1) {
              result.push('...')
            }
            result.push(pageCount)
          }
        }
  
        return result;
}
@BolatovAlau

This comment has been minimized.

Copy link

@BolatovAlau BolatovAlau commented Feb 16, 2018

c# version

        static object Maker(int c, int r)
        {
            int current = c, last = r;
            int delta = 2, l = 0;

            int left = current - delta;
            int right = current + delta + 1;

            List<int> range = new List<int>();
            List<object> rangeWithDots = new List<object>();

            for (int i = 1; i < last; i++)
            {
                if (i == 1 || i == last || i >= left && i < right)
                    range.Add(i);
            }

            foreach (var i in range)
            {
                if (l != 1000)
                {
                    if (i - l == 2)
                        rangeWithDots.Add(l + 1);
                    else if (i - l != 1)
                        rangeWithDots.Add("...");
                }
                rangeWithDots.Add(i);
                l = i;
            }

            return rangeWithDots;
        }
@samsonasik

This comment has been minimized.

Copy link

@samsonasik samsonasik commented Mar 5, 2018

Thank you very much ;)

@ihnat-mikhalkovich

This comment has been minimized.

Copy link

@ihnat-mikhalkovich ihnat-mikhalkovich commented Mar 7, 2018

ty

@ihnat-mikhalkovich

This comment has been minimized.

Copy link

@ihnat-mikhalkovich ihnat-mikhalkovich commented Mar 7, 2018

java version

public List paginating(int currentPage, int pageAmount) {
int current = currentPage,
last = pageAmount,
delta = 2,
left = current - delta,
right = current + delta + 1;
List range = new ArrayList<>();
List rangeWithDots = new ArrayList<>();
int l = 0;

    for (int i = 1; i <= last; i++) {
        if (i == 1 || i == last || i >= left && i < right) {
            range.add("" + i);
        }
    }

    for (String i : range) {
        if (l > 0) {
            if (Integer.parseInt(i) - l == 2) {
                rangeWithDots.add("" + (l + 1));
            } else if (Integer.parseInt(i) - l != 1) {
                rangeWithDots.add("...");
            }
        }
        rangeWithDots.add(i);
        l = Integer.parseInt(i);
    }

    return rangeWithDots;
}
@dafranco

This comment has been minimized.

Copy link

@dafranco dafranco commented Apr 6, 2018

Thank you very much!

@tanguyantoine

This comment has been minimized.

Copy link

@tanguyantoine tanguyantoine commented Apr 26, 2018

Thank you. It helped :-)

@pourush77

This comment has been minimized.

Copy link

@pourush77 pourush77 commented Apr 27, 2018

Thanks kottenator, I always appreciate your work.
Now suppose I have these set of conditions then what changes do I need as I need to show the page numbers in last too ?

b. Currently, my pagination component just displays all the page numbers, but it should actually show some dots when there are more than 8 total pages. Here’s what it should look like:
c. total < 9 pages: p1 selected {1} 2 3 4 5 6 7 8
d. total < 9 pages: p5 selected 1 2 3 4 {5} 6 7 8
e. total < 9 pages: p8 selected 1 2 3 4 5 6 7 {8}
f. total >= 9 pages: p2 selected 1 {2} 3 ... 7 8 9
g. total >= 9 pages: p3 selected 1 2 {3} 4 ... 7 8 9
h. total >= 9 pages: p4 selected 1 ... 3 {4} 5 ... 9
i. total >= 9 pages: p6 selected 1 ... 5 {6} 7 ... 9
j. total >= 9 pages: p7 selected 1 2 3 ... 6 {7} 8 9
k. total >= 9 pages: p8 selected 1 2 3 ... 7 {8} 9

Any help is appreciated.Thanks !

@iamuteti

This comment has been minimized.

Copy link

@iamuteti iamuteti commented May 12, 2018

Awesome gist

@dunika

This comment has been minimized.

Copy link

@dunika dunika commented Jun 1, 2018

I have struggled with this all day so I would like to help any future travellers. This algorithm mimics the Google.com pagination on desktop.

*/
  1 [2] 3 4 5 6 7 8 9 10
  6 7 8 9 10 [11] 12 13 14 15
  91 92  93 94 95 96 97 [98] 99 100
/*

const getPaginationOptions = ({
  listLength = 10,
  currentPage = 0, // page index starts at 0
  totalPages,
}) => {
  const offset = Math.ceil(listLength / 2);

  let start = currentPage - offset;
  let end = currentPage + offset;

  if (totalPages <= listLength) {
    start = 0;
    end = totalPages;
  } else if (currentPage <= offset) {
    start = 0;
    end = listLength;
  } else if ((currentPage + offset) >= totalPages) {
    start = totalPages - listLength;
    end = totalPages;
  }

  // Use lodash's range function to produce an array of numbers in
  // the specified range
  // Map over these numbers to produce an array of objects. Value matches the page index.
  // Label is for display purposes
  return range(start, end)
    .map(value => ({    
      label: value + 1,
      value
    }));
}

@tranlehaiquan

This comment has been minimized.

Copy link

@tranlehaiquan tranlehaiquan commented Jun 7, 2018

Thank you for sharing : )

@codehaiku

This comment has been minimized.

Copy link

@codehaiku codehaiku commented Jun 20, 2018

:) 👍

Thank you!

@aef-

This comment has been minimized.

Copy link

@aef- aef- commented Jun 20, 2018

I wrote a version that iterates over the number of pages to be shown, not total pages: https://github.com/aef-/paginaator/blob/master/src/index.js

@seanmadi

This comment has been minimized.

Copy link

@seanmadi seanmadi commented Jul 31, 2018

Here's a little more simplified version

export const pagination = (currentPage: number, pageCount: number) => {
  const delta = 2

  let range = []
  for (let i = Math.max(2, currentPage - delta); i <= Math.min(pageCount - 1, currentPage + delta); i++) {
    range.push(i)
  }

  if (currentPage - delta > 2) {
    range.unshift("...")
  }
  if (currentPage + delta < pageCount - 1) {
    range.push("...")
  }

  range.unshift(1)
  range.push(pageCount)

  return range
}

And then if you are using Ramda.js

const delta = 2

  let range = R.range(
    R.max(2, currentPage - delta),
    R.min(pageCount - 1, currentPage + delta) + 1
  )

  if (currentPage - delta > 2) {
    range = R.prepend("...", range)
  }
  if (currentPage + delta < pageCount - 1) {
    range = R.append("...", range)
  }

  range = R.prepend(1, range)
  range = R.append(pageCount, range)

  return range
@btastic

This comment has been minimized.

Copy link

@btastic btastic commented Aug 2, 2018

For some reason the provided C# implementation did not work for me

Here is the one I "translated"

private static List<object> Maker(int c, int m)
{
    int current = c;
    int last = m;
    int delta = 4;
    int left = current - delta;
    int right = current + delta + 1;

    List<int> range = new List<int>();
    List<object> rangeWithDots = new List<object>();

    int l = 0;

    for(int i = 1; i<=last; i++)
    {
        if(i == 1 || i == last || i>= left && i < right)
        {
            range.Add(i);
        }
    }

    foreach (var item in range)
    {
        if(l > 0)
        {
            if(item - l == 2)
            {
                rangeWithDots.Add(l + 1);
            } else if (item - l != 1)
            {
                rangeWithDots.Add("...");
            }
        }
        rangeWithDots.Add(item);
        l = item;
    }

    return rangeWithDots;
}
@lord-zeus

This comment has been minimized.

Copy link

@lord-zeus lord-zeus commented Sep 16, 2018

Thanks Keep up the good work

@samuelrego

This comment has been minimized.

Copy link

@samuelrego samuelrego commented Dec 2, 2018

Thanks for sharing. keep up the the good work

@agm1984

This comment has been minimized.

Copy link

@agm1984 agm1984 commented Dec 18, 2018

Here is the corrected version, as the latest ones will all have an issue if there is only one page:

generatePageRange(currentPage, lastPage) {
    const delta = 3;

    const range = [];
    for (let i = Math.max(2, (currentPage - delta)); i <= Math.min((lastPage - 1), (currentPage + delta)); i += 1) {
        range.push(i);
    }

    if ((currentPage - delta) > 2) {
        range.unshift('...');
    }
    if ((currentPage + delta) < (lastPage - 1)) {
        range.push('...');
    }

    range.unshift(1);
    if (lastPage !== 1) range.push(lastPage);

    return range;
},

Without if (lastPage !== 1) range.push(lastPage);, then return range will return [1, 1] if there is only one page. The if condition stops the duplicate 1 in the case where lastPage === 1.

In practice, it probably doesn't matter because usually pagination controls are hidden if there is only one page, but it's a quick fix if you want that flawless algorithm.

@mtvw

This comment has been minimized.

Copy link

@mtvw mtvw commented Jan 4, 2019

Thanks!

@mypetertw

This comment has been minimized.

Copy link

@mypetertw mypetertw commented Mar 12, 2019

amazing...

@sjnonweb

This comment has been minimized.

Copy link

@sjnonweb sjnonweb commented Mar 19, 2019

Thanks man! It's perfect

@iliarostovtsev

This comment has been minimized.

Copy link

@iliarostovtsev iliarostovtsev commented Mar 21, 2019

Perl implementation with generating Bootstrap pagination like output:

sub pagination
{

    my ($page, $pages) = @_;

    my $left  = $page - 2;
    my $right = $page + 3;
    my @range;
    my $last;
    my $pagination;

    my $start = sub {
        my $start;
        my $disabled = ($page == 1 ? " disabled" : undef);

        $start = '<div class="dataTables_paginate paging_simple_numbers spaginates">';
        $start .= '<ul class="pagination">';
        $start .= "<li class='paginate_button previous$disabled'>";
        $start .= '<a href="#"><i class="fa fa-fw fa-angle-left"></i></a>';
        $start .= "</li>";
        return $start;
    };

    my $current = sub {
        my ($i) = @_;
        my $end;
        my $active = ($page eq $i ? " active" : undef);
        $end = "<li class='paginate_button$active'>";
        $end .= "<a class='spaginated' href='list.cgi?page=$i'>$i</a>";
        $end .= "</li>";
        return $end;
    };

    my $range = sub {
        my $range;
        $range = '<li class="paginate_button disabled">';
        $range .= '<a href="#">…</a>';
        $range .= "</li>";

    };

    my $end = sub {
        my $end;
        my $disabled = ($page == $pages ? " disabled" : undef);
        $end = "<li class='paginate_button next$disabled'>";
        $end .= '<a href="#"><i class="fa fa-fw fa-angle-right"></i></a>';
        $end .= "</li>";
        $end .= '</ul>';
        $end .= '</div>';
        return $end;
    };

    for (my $i = 1; $i <= $pages; $i++) {
        if ($i == 1 || $i == $pages || $i >= $left && $i < $right) {
            push(@range, $i);
        }
    }

    foreach my $i (@range) {
        if ($last) {
            if ($i - $last == 2) {
                $pagination .= &$current($last + 1);
            } elsif ($i - $last != 1) {
                $pagination .= &$range();
            }
        }
        $pagination .= &$current($i);
        $last = $i;
    }

    $pagination = &$start() . $pagination . &$end();
    return $pagination;
}

my $page = 10;
my $pages = 100;
my $pagination = pagination($page, $pages);

Output example:
image

@BaNru

This comment has been minimized.

Copy link

@BaNru BaNru commented Mar 25, 2019

https://gist.github.com/kottenator/9d936eb3e4e3c3e02598#gistcomment-2788898

generatePageRange(2,7)
generatePageRange(6,7)

Result

[1, 2, 3, 4, 5, "...", 7]
[1, "...", 3, 4, 5, 6, 7]

Expected Result:

[1, 2, 3, 4, 5, 6, 7] or [1, 2, 3, 4, "...", 7]
[1, 2, 3, 4, 5, 6, 7] or [1, "...", 4, 5, 6, 7]

Same:

generatePageRange(3,8)
generatePageRange(6,8)

generatePageRange(4,9)
generatePageRange(6,9)

generatePageRange(5,10)
generatePageRange(6,10)

If changed delta, the range will change.

=====

Performed correctly

generatePageRange(3,7)
generatePageRange(5,7)

[1, 2, 3, 4, 5, 6, 7]

@BaNru

This comment has been minimized.

Copy link

@BaNru BaNru commented Mar 25, 2019

My modification

function generatePageRange(currentPage, delta, lastPage) {
    var range = [];
    for (let i = Math.max(2, (currentPage - delta)); i <= Math.min((lastPage - 1), (currentPage + delta)); i += 1) {
        range.push(i);
    }

    if ((currentPage - delta) > 2) {
        if (range.length == lastPage - 3) {
            range.unshift(2);
        } else {
            range.unshift('...');
        }
    }

    if ((currentPage + delta) < (lastPage - 1)) {
        if (range.length == lastPage - 3) {
            range.push(lastPage - 1);
        } else {
            range.push('...');
        }
    }

    range.unshift(1);
    if (lastPage !== 1) range.push(lastPage);

    return range;
}

console.log(' ' +
    generatePageRange(2, 3, 7) + '\n',
    generatePageRange(6, 3, 7) + '\n',
    generatePageRange(3, 3, 8) + '\n',
    generatePageRange(6, 3, 8) + '\n',
    generatePageRange(4, 3, 9) + '\n',
    generatePageRange(6, 3, 9) + '\n',
    generatePageRange(5, 3, 10) + '\n',
    generatePageRange(6, 3, 10) + '\n',
    generatePageRange(5, 3, 11) + '\n',
    generatePageRange(6, 3, 11) + '\n', //  1,...,3,4,5,6,7,8,9,10,11 // BAD
    generatePageRange(2, 3, 5) + '\n',
    generatePageRange(1, 3, 1) + '\n',
    generatePageRange(1, 3, 2) + '\n',
    generatePageRange(1, 3, 3) + '\n',
    generatePageRange(1, 3, 5) + '\n',
    generatePageRange(2, 3, 5) + '\n',
    generatePageRange(3, 3, 5) + '\n',
    generatePageRange(1, 3, 5) + '\n',
    generatePageRange(8, 3, 21) + '\n',
    generatePageRange(6, 3, 21) + '\n' // 1,...,3,4,5,6,7,8,9,...,21 // BAD
)
@stangerjm

This comment has been minimized.

Copy link

@stangerjm stangerjm commented Apr 2, 2019

Thank you so much, this was totally what I was looking for! Saved me a ton of time figuring out the logic.

For anyone looking for an ES6+ pseudo-functional version I did some tweaking to come up with a more potentially readable version:

/**
 * Generates an array to be used for pagination
 * @param {number} current - The current page
 * @param {number} last - The last possible page in the paged list
 * @returns {array} List of desired page numbers with ellipsis for unimportant pages
 */
function generatePagination(current, last) {
  const offset = 2;
  const leftOffset = current - offset;
  const rightOffset = current + offset + 1;

  /**
   * Reduces a list into the page numbers desired in the pagination
   * @param {array} accumulator - Growing list of desired page numbers
   * @param {*} _ - Throwaway variable to ignore the current value in iteration
   * @param {*} idx - The index of the current iteration
   * @returns {array} The accumulating list of desired page numbers
   */
  function reduceToDesiredPageNumbers(accumulator, _, idx) {
    const currIdx = idx + 1;

    if (
      // Always include first page
      currIdx === 1
      // Always include last page
      || currIdx === last
      // Include if index is between the above defined offsets
      || (currIdx >= leftOffset && currIdx < rightOffset)) {
      return [
        ...accumulator,
        currIdx,
      ];
    }

    return accumulator;
  }

  /**
   * Transforms a list of desired pages and puts ellipsis in any gaps
   * @param {array} accumulator - The growing list of page numbers with ellipsis included
   * @param {number} currentPage - The current page in iteration
   * @param {number} currIdx - The current index
   * @param {array} src - The source array the function was called on
   */
  function transformToPagesWithEllipsis(accumulator, currentPage, currIdx, src) {
    const prev = src[currIdx - 1];

    // Ignore the first number, as we always want the first page
    // Include an ellipsis if there is a gap of more than one between numbers
    if (prev != null && currentPage - prev !== 1) {
      return [
        ...accumulator,
        '...',
        currentPage,
      ];
    }

    // If page does not meet above requirement, just add it to the list
    return [
      ...accumulator,
      currentPage,
    ];
  }

  const pageNumbers = Array(last)
    .fill()
    .reduce(reduceToDesiredPageNumbers, []);

  const pageNumbersWithEllipsis = pageNumbers.reduce(transformToPagesWithEllipsis, []);

  return pageNumbersWithEllipsis;
}

The output is only different in that it keeps consistent with the two number offset in either direction. See the unit test:

expect(generatePagination(10, 50)).toEqual([1, '...', 8, 9, 10, 11, 12, '...', 50]);
expect(generatePagination(50, 50)).toEqual([1, '...', 48, 49, 50]);
expect(generatePagination(49, 50)).toEqual([1, '...', 47, 48, 49, 50]);
expect(generatePagination(45, 50)).toEqual([1, '...', 43, 44, 45, 46, 47, '...', 50]);
expect(generatePagination(30, 50)).toEqual([1, '...', 28, 29, 30, 31, 32, '...', 50]);
expect(generatePagination(6, 50)).toEqual([1, '...', 4, 5, 6, 7, 8, '...', 50]);
expect(generatePagination(5, 50)).toEqual([1, '...', 3, 4, 5, 6, 7, '...', 50]);
expect(generatePagination(4, 50)).toEqual([1, 2, 3, 4, 5, 6, '...', 50]);
expect(generatePagination(3, 50)).toEqual([1, 2, 3, 4, 5, '...', 50]);
expect(generatePagination(2, 50)).toEqual([1, 2, 3, 4, '...', 50]);
expect(generatePagination(1, 50)).toEqual([1, 2, 3, '...', 50]);
@gjelard

This comment has been minimized.

Copy link

@gjelard gjelard commented Apr 4, 2019

Another way: https://github.com/gjelard/pagination/blob/master/main.js

`function pagination(currentPage, nrOfPages) {

var pagesToShow = 3,
	page = currentPage - pagesToShow > 0 ?  (currentPage - pagesToShow) : 1,
	first = 0,
    pageList = [];

	 for (let i = 0; i < (pagesToShow * 2) && page < nrOfPages; i++) {
		pageList.push(page);
        page++;

	}

  pageList.unshift(1); //add first page
  pageList.push(nrOfPages); //add last page
		

 
return pageList;

}//pagination`

console.log(pagination(6, 20));

//sample output::: [1, "...", 4, 5, 6, 7, 8, "...", 20]

@tarrball

This comment has been minimized.

Copy link

@tarrball tarrball commented Apr 22, 2019

👍

@zygos

This comment has been minimized.

Copy link

@zygos zygos commented Apr 26, 2019

Javascript ES2019 functional version.

function pagination(currentPage, pageCount, delta = 2) {
  const separate = (a, b) => [a, ...({
    0: [],
    1: [b],
    2: [a + 1, b],
  }[b - a] || ['...', b])]

  return Array(delta * 2 + 1)
    .fill()
    .map((_, index) => currentPage - delta + index)
    .filter(page => 0 < page && page <= pageCount)
    .flatMap((page, index, { length }) => {
      if (!index) return separate(1, page)
      if (index === length - 1) return separate(page, pageCount)

      return [page]
    })
}

Same output as gist's. Remove 2: [a + 1, b] line if you prefer a constant distance from current page ([1, "...", 2, 3, 4] over [1, 2, 3, 4, 5]).

@phgsilva

This comment has been minimized.

Copy link

@phgsilva phgsilva commented May 24, 2019

Thanks Man! This work fine for me too!

@silasakk

This comment has been minimized.

Copy link

@silasakk silasakk commented Jun 23, 2019

Awesome!!!

@josueaqp92

This comment has been minimized.

Copy link

@josueaqp92 josueaqp92 commented Jul 12, 2019

ty

@jackmcpickle

This comment has been minimized.

Copy link

@jackmcpickle jackmcpickle commented Jul 17, 2019

Another version using reduce.

generatePageRange(currentPage, lastPage, delta = 2) {
    // creates array with base 1 index
    const range = Array(lastPage)
        .fill()
        .map((_, index) => index + 1);

    return range.reduce((pages, page) => {
        // allow adding of first and last pages
        if (page === 1 || page === lastPage) {
            return [...pages, page];
        }

        // if within delta range add page
        if (page - delta <= currentPage && page + delta >= currentPage) {
            return [...pages, page];
        }

        // otherwise add 'gap if gap was not the last item added.
        if (pages[pages.length - 1] !== '...') {
            return [...pages, '...'];
        }

        return pages;
    }, []);
}
@quibaritaenperdresatrompe

This comment has been minimized.

Copy link

@quibaritaenperdresatrompe quibaritaenperdresatrompe commented Jul 17, 2019

Did someone find a solution to generate range with fixed length ? 🤔

e.g.
generatePageRange(1, 11); // [1, 2, 3, 4, 5, 6, 7, '...', 11]
generatePageRange(6, 11); // [1, '...', 4, 5, 6, 7, 8, '...', 11]
generatePageRange(11, 11); // [1, '...', 5, 6, 7, 8, 9, 10, 11]

cf. unit tests in ./src/PaginationButtonGroup/pagination.test.js

@ajruckman

This comment has been minimized.

Copy link

@ajruckman ajruckman commented Aug 13, 2019

Here is a fixed length C# solution I came up with that always shows buttons for the first and last page. It is kindof messy. -1 is replaced with ... when I render the page buttons.

0 -> [0, 1, 2, 3, 4, -1, 11]
1 -> [0, 1, 2, 3, 4, -1, 11]
2 -> [0, 1, 2, 3, 4, -1, 11]
3 -> [0, 1, 2, 3, 4, -1, 11]
4 -> [0, -1, 3, 4, 5, -1, 11]
5 -> [0, -1, 4, 5, 6, -1, 11]
6 -> [0, -1, 5, 6, 7, -1, 11]
7 -> [0, -1, 6, 7, 8, -1, 11]
8 -> [0, -1, 7, 8, 9, 10, 11]
9 -> [0, -1, 7, 8, 9, 10, 11]
10 -> [0, -1, 7, 8, 9, 10, 11]
11 -> [0, -1, 7, 8, 9, 10, 11]
public IEnumerable<int> Pages()
{
    const int radius   = 3;
    const int diameter = 2 * radius + 1;
    const int offset   = (int) (diameter / 2.0);

    List<int> pages = new List<int>();

    int start, end;

    if (NumPages <= diameter)
    {
        start = 0;
        end   = Math.Max(NumPages - 3, NumPages);

        pages.AddRange(Enumerable.Range(start, end - start).ToList());
    }
    else if (Current <= offset)
    {
        start = 0;
        end   = diameter - 1;

        pages.AddRange(Enumerable.Range(start, end - start - 1).ToList());

        pages.Add(-1);
        pages.Add(NumPages - 1);
    }
    else if (Current + offset >= NumPages)
    {
        start = NumPages - diameter;
        end   = NumPages - 1;

        pages.Add(0);
        pages.Add(-1);

        pages.AddRange(Enumerable.Range(start + 2, end - start - 1).ToList());
    }
    else
    {
        start = Current - radius + 2;
        end   = Current + radius - 2;

        pages.Add(0);
        pages.Add(-1);

        pages.AddRange(Enumerable.Range(start, end - start + 1).ToList());

        if (Current == NumPages - radius - 1)
            pages.Add(NumPages - 2);
        else
            pages.Add(-1);

        pages.Add(NumPages - 1);
    }

    return pages;
}
@artanik

This comment has been minimized.

Copy link

@artanik artanik commented Aug 20, 2019

Range with fixed length:

function getRange(start, end) {
  return Array(end - start + 1).fill().map((v,i) => i + start);
}

function pagination(current, length, delta = 4) {
  const range = {
    start: Math.round(current - delta / 2),
    end: Math.round(current + delta / 2)
  };

  if(range.start - 1 === 1 || range.end + 1 === length) {
    range.start += 1;
    range.end += 1;
  }

  let pages = current > delta ? getRange(
    Math.min(range.start, length - delta),
    Math.min(range.end, length)
  ) : getRange(1, Math.min(length, delta + 1));

  const withDots = (value, pair) => pages.length + 1 !== length? pair : [value];

  if (pages[0] !== 1) {
    pages = withDots(1, [1, '...']).concat(pages);
  }

  if (pages[pages.length - 1] < length) {
    pages = pages.concat(withDots(length, ['...', length]));
  }

  return pages;
}

Examples

for (let current = 1, length = 20; current <= length; current++) {
  console.log(`Selected page ${current}:`, pagination(current, length));
}
Output
Selected page 1: [1, 2, 3, 4, 5, "...", 20]
Selected page 2: [1, 2, 3, 4, 5, "...", 20]
Selected page 3: [1, 2, 3, 4, 5, "...", 20]
Selected page 4: [1, 2, 3, 4, 5, "...", 20]
Selected page 5: [1, "...", 3, 4, 5, 6, 7, "...", 20]
Selected page 6: [1, "...", 4, 5, 6, 7, 8, "...", 20]
Selected page 7: [1, "...", 5, 6, 7, 8, 9, "...", 20]
Selected page 8: [1, "...", 6, 7, 8, 9, 10, "...", 20]
Selected page 9: [1, "...", 7, 8, 9, 10, 11, "...", 20]
Selected page 10: [1, "...", 8, 9, 10, 11, 12, "...", 20]
Selected page 11: [1, "...", 9, 10, 11, 12, 13, "...", 20]
Selected page 12: [1, "...", 10, 11, 12, 13, 14, "...", 20]
Selected page 13: [1, "...", 11, 12, 13, 14, 15, "...", 20]
Selected page 14: [1, "...", 12, 13, 14, 15, 16, "...", 20]
Selected page 15: [1, "...", 13, 14, 15, 16, 17, "...", 20]
Selected page 16: [1, "...", 14, 15, 16, 17, 18, "...", 20]
Selected page 17: [1, "...", 16, 17, 18, 19, 20]
Selected page 18: [1, "...", 16, 17, 18, 19, 20]
Selected page 19: [1, "...", 16, 17, 18, 19, 20]
Selected page 20: [1, "...", 16, 17, 18, 19, 20]
for (let current = 1, length = 15; current <= length; current++) {
  console.log(`Selected page ${current}:`, pagination(current, length, 3));
}
Output
Selected page 1: [1, 2, 3, 4, "...", 15]
Selected page 2: [1, 2, 3, 4, "...", 15]
Selected page 3: [1, 2, 3, 4, "...", 15]
Selected page 4: [1, "...", 3, 4, 5, 6, "...", 15]
Selected page 5: [1, "...", 4, 5, 6, 7, "...", 15]
Selected page 6: [1, "...", 5, 6, 7, 8, "...", 15]
Selected page 7: [1, "...", 6, 7, 8, 9, "...", 15]
Selected page 8: [1, "...", 7, 8, 9, 10, "...", 15]
Selected page 9: [1, "...", 8, 9, 10, 11, "...", 15]
Selected page 10: [1, "...", 9, 10, 11, 12, "...", 15]
Selected page 11: [1, "...", 10, 11, 12, 13, "...", 15]
Selected page 12: [1, "...", 12, 13, 14, 15]
Selected page 13: [1, "...", 12, 13, 14, 15]
Selected page 14: [1, "...", 12, 13, 14, 15]
Selected page 15: [1, "...", 12, 13, 14, 15]
for (let current = 1, length = 5; current <= length; current++) {
  console.log(`Selected page ${current}:`, pagination(current, length, 3));
}
Output
Selected page 1: [1, 2, 3, 4, 5]
Selected page 2: [1, 2, 3, 4, 5]
Selected page 3: [1, 2, 3, 4, 5]
Selected page 4: [1, 2, 3, 4, 5]
Selected page 5: [1, 2, 3, 4, 5]
@rutu15

This comment has been minimized.

Copy link

@rutu15 rutu15 commented Sep 12, 2019

It can be optimized further like this..

var current = currentPage, rangeWithDots = [], l;
for (let i = 1; i <= response.total_pages; i++) {
if (i==1 || i == response.total_pages || i >= current-text/2 && i <= current+text/2) {
l = l ? i - l !== 1 ? rangeWithDots.push(...) : null : l
rangeWithDots.push(<a className={currentPage === i ? 'active' : ''} href='#' key={i} id={i}
onClick={() => this.fetchData(i)}>{i})
l = i;
}
}

@adedayomoshood

This comment has been minimized.

Copy link

@adedayomoshood adedayomoshood commented Sep 13, 2019

Implementing @BaNru's modification in ReactJs.

Pagination.js

import React from 'react';
import PropTypes from 'prop-types';

const paginate = (currentPage, lastPage, clickEvent) => {
  const delta = 1;
  const range = [];

  for (let i = Math.max(2, (currentPage - delta)); i <= Math.min((lastPage - 1), (currentPage + delta)); i += 1) {
    range.push(i);
  }

  if ((currentPage - delta) > 2) {
    range.unshift('...');
  }

  if ((currentPage + delta) < (lastPage - 1)) {
    range.push('...');
  }

  range.unshift(1);
  if (lastPage !== 1) range.push(lastPage);

  return range.map((i, index) => {return (
    !isNaN(i) ?
      <button
        value={i}
        key={index}
        onClick={clickEvent}
        className={currentPage === i ? "active" : ""}
      >{i}</button>
      : <span key={index}>{i}</span>
  )
  });
};

const Pagination = ({ currentPage, lastPage, clickEvent }) =>{
  return(
    <section className="pagination">
      {paginate(currentPage, lastPage, clickEvent)}
    </section>
  )
};

Pagination.defaultProps = {
  currentPage: 0,
  lastPage: 0,
  clickEvent: null,
};

Pagination.propTypes = {
  currentPage: PropTypes.number,
  lastPage: PropTypes.number,
  clickEvent: PropTypes.func,
};

export default Pagination;

Usage

<Pagination currentPage={1} lastPage={10} clickEvent={handlePagination} />
@BaNru

This comment has been minimized.

Copy link

@BaNru BaNru commented Nov 16, 2019

My modification https://gist.github.com/kottenator/9d936eb3e4e3c3e02598#gistcomment-2871200

PHP version of my modification

function pagination($currentPage, $delta, $lastPage) {
	$lastPage = intval($lastPage); // see below UPDATE 2021
	$range = [];
	for ($i = max(2, ($currentPage - $delta)); $i <= min(($lastPage-1), ($currentPage + $delta)); $i += 1) {
		$range[] = $i;
	}

	if (($currentPage - $delta) > 2) {
		if (count($range) == $lastPage - 3) {
			array_unshift($range,2);
		} else {
			array_unshift($range,'...');
		}
	}

	if (($currentPage + $delta) < ($lastPage - 1)) {
		if (count($range) == $lastPage - 3) {
			$range[] = ($lastPage - 1);
		} else {
			$range[] = '...';
		}
	}

	array_unshift($range,1);
	if ($lastPage !== 1) $range[] = $lastPage;

	return $range;
}
pagination(2, 3, 7); // 1,2,3,4,5,6,7
pagination(6, 3, 7); // 1,2,3,4,5,6,7
pagination(3, 3, 8); // 1,2,3,4,5,6,7,8
pagination(6, 3, 8); // 1,2,3,4,5,6,7,8
pagination(4, 3, 9); // 1,2,3,4,5,6,7,8,9
pagination(6, 3, 9); // 1,2,3,4,5,6,7,8,9
pagination(5, 3, 10); // 1,2,3,4,5,6,7,8,9,10
pagination(6, 3, 10); // 1,2,3,4,5,6,7,8,9,10
pagination(5, 3, 11); // 1,2,3,4,5,6,7,8,'...',11
pagination(6, 3, 11); // 1,'...',3,4,5,6,7,8,9,10,11 // BAD
pagination(2, 3, 5); // 1,2,3,4,5
pagination(1, 3, 1); // 1
pagination(1, 3, 2); // 1,2
pagination(1, 3, 3); // 1,2,3
pagination(1, 3, 5); // 1,2,3,4,5
pagination(2, 3, 5); // 1,2,3,4,5
pagination(3, 3, 5); // 1,2,3,4,5
pagination(1, 3, 5); // 1,2,3,4,5
pagination(8, 3, 21); // 1,'...',5,6,7,8,9,10,11,'...',21
pagination(6, 3, 21); // 1,'...',3,4,5,6,7,8,9,'...',21 // BAD

UPDATE 2021

pagination(1, 3, floor(1)); // [1,1] // BAD!!!
gettype( floor(1) ) => (ceil, round) type double, but need a integer

function pagination($currentPage, $delta, $lastPage) {
	$lastPage = intval($lastPage); // HOTFIX
	$range = [];
@jomlamladen

This comment has been minimized.

Copy link

@jomlamladen jomlamladen commented Dec 23, 2019

Thanks!

Python version:

def pagination(current, last, delta=2):
    _range = []
    rangeWithDots = []
    l = None

    for i in range(1, last):
        if i == 1 or i == last or i >= (current - delta) and i < (current + delta + 1):
            _range.append(i)
    
    for i in _range:
        if l is not None:
            
            if i - l == 2:
                rangeWithDots.append(l + 1)
            
            if i - l != 1:
                rangeWithDots.append('...')
                
        rangeWithDots.append(i)
        l = i
    
    return rangeWithDots
@sarifconrati

This comment has been minimized.

Copy link

@sarifconrati sarifconrati commented Jan 29, 2020

Thanks!

An optimized version with capacity handling a large number of pages https://gist.github.com/sarifconrati/9f64c69757a95f2a34f679e9a330f72a .

/**
 * Generate pagination.
 * @param {number} current Current page.
 * @param {number} last Last page.
 * @param {number} width width.
 * @returns {Array} Returns array of pages.
 */

const paginationGenerator = (current, last, width = 2) => {
  const left = current - width;
  const right = current + width + 1;
  const range = [];
  const rangeWithDots = [];
  let l;

  for (let i = 1; i <= last; i += 1) {
    if (i === 1 || i === last || (i >= left && i <= right)) {
      range.push(i);
    } else if (i < left) {
      i = left - 1;
    } else if (i > right) {
      range.push(last);
      break;
    }
  }

  range.forEach(i => {
    if (l) {
      if (i - l === 2) {
        rangeWithDots.push(l + 1);
      } else if (i - l !== 1) {
        rangeWithDots.push('...');
      }
    }
    rangeWithDots.push(i);
    l = i;
  });

  return rangeWithDots;
};
@mothsART

This comment has been minimized.

Copy link

@mothsART mothsART commented Feb 15, 2020

@jomlamladen

not exactly the same implementation.
With 2 corrections, PEP8 and snake_case :

def pagination(current, last, delta=2):
    _range = []
    range_with_dots = []
    l = None

    for i in range(1, last + 1):
        if (
            i == 1 or i == last
            or i >= (current - delta)
            and i < (current + delta + 1)
        ):
            _range.append(i)

    for i in _range:
        if l is not None:
            if i - l == 2:
                range_with_dots.append(l + 1)
            elif i - l != 1:
                range_with_dots.append('...')
        range_with_dots.append(i)
        l = i
    return range_with_dots
@joekelley

This comment has been minimized.

Copy link

@joekelley joekelley commented Mar 15, 2020

Thanks!

@tomblanchard

This comment has been minimized.

Copy link

@tomblanchard tomblanchard commented Mar 18, 2020

Thanks a lot - saved me a ton of time.

@jorrit91

This comment has been minimized.

Copy link

@jorrit91 jorrit91 commented Apr 3, 2020

@artanik

We ended up using a slightly altered version of your code. What I like about this approach is that the amount of UI elements is always the same as initial due to the variable delta, which makes this possible:

image

const getRange = (start: number, end: number) => {
  return Array(end - start + 1)
    .fill()
    .map((v, i) => i + start)
}

const pagination = (currentPage: number, pageCount: number) => {
  let delta: number
  if (pageCount <= 7) {
    // delta === 7: [1 2 3 4 5 6 7]
    delta = 7
  } else {
    // delta === 2: [1 ... 4 5 6 ... 10]
    // delta === 4: [1 2 3 4 5 ... 10]
    delta = currentPage > 4 && currentPage < pageCount - 3 ? 2 : 4
  }

  const range = {
    start: Math.round(currentPage - delta / 2),
    end: Math.round(currentPage + delta / 2)
  }

  if (range.start - 1 === 1 || range.end + 1 === pageCount) {
    range.start += 1
    range.end += 1
  }

  let pages: any =
    currentPage > delta
      ? getRange(Math.min(range.start, pageCount - delta), Math.min(range.end, pageCount))
      : getRange(1, Math.min(pageCount, delta + 1))

  const withDots = (value, pair) => (pages.length + 1 !== pageCount ? pair : [value])

  if (pages[0] !== 1) {
    pages = withDots(1, [1, '...']).concat(pages)
  }

  if (pages[pages.length - 1] < pageCount) {
    pages = pages.concat(withDots(pageCount, ['...', pageCount]))
  }

  return pages
}```
@iiceman40

This comment has been minimized.

Copy link

@iiceman40 iiceman40 commented Apr 15, 2020

@jorrit91 very nice, works like a charm and feels pretty good to always have the same number of UI elements, indeed.

@ktmud

This comment has been minimized.

Copy link

@ktmud ktmud commented Jun 3, 2020

My simple implementation in TypeScript and Bootstrap:

// first, ..., prev, current, next, ..., last
const MINIMAL_PAGE_ITEM_COUNT = 7;

/**
 * Generate numeric page items around current page.
 *   - Always include first and last page
 *   - Add ellipsis if needed
 */
function generatePageItems(total: number, current: number, width: number) {
  if (width < MINIMAL_PAGE_ITEM_COUNT) {
    throw new Error(`Must allow at least ${MINIMAL_PAGE_ITEM_COUNT} page items`);
  }
  if (width % 2 === 0) {
    throw new Error(`Must allow odd number of page items`);
  }
  if (total < width) {
    return [...new Array(total).keys()];
  }
  const left = Math.max(0, Math.min(total - width, current - Math.floor(width / 2)));
  const items: (string | number)[] = new Array(width);
  for (let i = 0; i < width; i += 1) {
    items[i] = i + left;
  }
  // replace non-ending items with placeholders
  if (items[0] > 0) {
    items[0] = 0;
    items[1] = 'prev-more';
  }
  if (items[items.length - 1] < total - 1) {
    items[items.length - 1] = total - 1;
    items[items.length - 2] = 'next-more';
  }
  return items;
}
interface PaginationProps {
  pageCount: number; // number of pages
  currentPage?: number; // index of current page, zero-based
  maxPageItemCount?: number;
  ellipsis?: string; // content for ellipsis item
  gotoPage: (page: number) => void; // `page` is zero-based
}

export default React.forwardRef(function Pagination(
  { pageCount, currentPage = 0, maxPageItemCount = 9, gotoPage }: PaginationProps,
  ref: React.Ref<HTMLDivElement>,
) {
  const pageItems = generatePageItems(pageCount, currentPage, maxPageItemCount);
  return (
    <div ref={ref} className="dt-pagination">
      <ul className="pagination pagination-sm">
        {pageItems.map((item, i) =>
          typeof item === 'number' ? (
            // actual page number
            <li key={item} className={currentPage === item ? 'active' : undefined}>
              <a
                href={`#page-${item}`}
                role="button"
                onClick={e => {
                  e.preventDefault();
                  gotoPage(item);
                }}
              >
                {item + 1}
              </a>
            </li>
          ) : (
            <li key={item} className="dt-pagination-ellipsis">
              <span></span>
            </li>
          ),
        )}
      </ul>
    </div>
  );
});
@edgarjaviertec

This comment has been minimized.

Copy link

@edgarjaviertec edgarjaviertec commented Jun 4, 2020

@ktmud Bro your implementation helped me a lot, thank you very much for sharing it 👍

@marcofbb

This comment has been minimized.

Copy link

@marcofbb marcofbb commented Aug 11, 2020

great !

@yapalenov

This comment has been minimized.

@narthur

This comment has been minimized.

Copy link

@narthur narthur commented Aug 11, 2020

Version that is just as fast regardless of number of pages:

export function pagination(current, total) {
    const center = [current - 2, current - 1, current, current + 1, current + 2],
        filteredCenter = center.filter((p) => p > 1 && p < total),
        includeThreeLeft = current === 5,
        includeThreeRight = current === total - 4,
        includeLeftDots = current > 5,
        includeRightDots = current < total - 4;

    if (includeThreeLeft) filteredCenter.unshift(2)
    if (includeThreeRight) filteredCenter.push(total - 1)

    if (includeLeftDots) filteredCenter.unshift('...');
    if (includeRightDots) filteredCenter.push('...');

    return [1, ...filteredCenter, total]
}

Tests:

describe("pagination algorithm", () => {
    // https://gist.github.com/kottenator/9d936eb3e4e3c3e02598
    const runner = test.each([
        [1,[1, 2, 3, "...", 20]],
        [2,[1, 2, 3, 4, "...", 20]],
        [3,[1, 2, 3, 4, 5, "...", 20]],
        [4,[1, 2, 3, 4, 5, 6, "...", 20]],
        [5,[1, 2, 3, 4, 5, 6, 7, "...", 20]],
        [6,[1, "...", 4, 5, 6, 7, 8, "...", 20]],
        [7,[1, "...", 5, 6, 7, 8, 9, "...", 20]],
        [8,[1, "...", 6, 7, 8, 9, 10, "...", 20]],
        [9,[1, "...", 7, 8, 9, 10, 11, "...", 20]],
        [10,[1, "...", 8, 9, 10, 11, 12, "...", 20]],
        [11,[1, "...", 9, 10, 11, 12, 13, "...", 20]],
        [12,[1, "...", 10, 11, 12, 13, 14, "...", 20]],
        [13,[1, "...", 11, 12, 13, 14, 15, "...", 20]],
        [14,[1, "...", 12, 13, 14, 15, 16, "...", 20]],
        [15,[1, "...", 13, 14, 15, 16, 17, "...", 20]],
        [16,[1, "...", 14, 15, 16, 17, 18, 19, 20]],
        [17,[1, "...", 15, 16, 17, 18, 19, 20]],
        [18,[1, "...", 16, 17, 18, 19, 20]],
        [19,[1, "...", 17, 18, 19, 20]],
        [20,[1, "...", 18, 19, 20]],
    ])

    runner('pagination(%i, 20)', (index, expected) => {
        expect(pagination(index, 20)).toStrictEqual(expected)
    })

    it("maintains performance", () => {
        const t0 = performance.now()
        pagination(1, 99999999999)
        const t1 = performance.now()

        expect(t1 - t0).toBeLessThan(1)
    })
})
@mavyfaby

This comment has been minimized.

Copy link

@mavyfaby mavyfaby commented Aug 15, 2020

Thanks bro!

@hazmihaz

This comment has been minimized.

Copy link

@hazmihaz hazmihaz commented Nov 18, 2020

@narthur I like your implementation. Thanks for sharing!

edit: I found a bug
If total page is 1, it returns [1,1] instead of [1].

So I added simple checking at line 2:
if (total <= 1) return [1]

@eliot-akira

This comment has been minimized.

Copy link

@eliot-akira eliot-akira commented Nov 26, 2020

@jorrit91 Thanks for sharing your implementation, brilliant.

Of all the variations in this thread, yours made the most sense for my use case; I believe it provides the best UX with a constant number of visible page elements.

@RoLYroLLs

This comment has been minimized.

Copy link

@RoLYroLLs RoLYroLLs commented Dec 8, 2020

@jorrit91 Love your version and was exactly what I'm looking for, but I need it for C#. I'll be trying to convert it, unless someone gets in before me.

@RoLYroLLs

This comment has been minimized.

Copy link

@RoLYroLLs RoLYroLLs commented Dec 8, 2020

Here's my C# version @jorrit91. I'm sure some of you can optimize it, so let me know if you do.. Thanks.

https://gist.github.com/RoLYroLLs/c165202f72a256938da15c916b1362b8

or

public IEnumerable<object> Pages(int current, int pageCount) {
    List<object> pages = new List<object>();
    var delta = 7;

    if (pageCount > 7) {
            delta = current > 4 && current < pageCount - 3 ? 2 : 4;
    }

    var startIndex = (int)Math.Round(current - delta / (double)2);
    var endIndex = (int)Math.Round(current + delta / (double)2);

    if (startIndex - 1 == 1 || endIndex + 1 == pageCount) {
            startIndex += 1;
            endIndex += 1;
    }

    var to = Math.Min(pageCount, delta + 1);
    for (int i = 1; i <= to; i++) {
            pages.Add(i);
    }

    if (current > delta) {
            pages.Clear();
            var from = Math.Min(startIndex, pageCount - delta);
            to = Math.Min(endIndex, pageCount);
            for (int i = from; i <= to; i++) {
                    pages.Add(i);
            }
    }

        if (pages[0].ToString() != "1") {
                if (pages.Count() + 1 != pageCount) {
                        pages.Insert(0, "...");
                }
                pages.Insert(0, 1);
        }

        if ((int)pages.Last() < pageCount) {
                if (pages.Count() + 1 != pageCount) {
                        pages.Add("...");
                }
                pages.Add(pageCount);
        }

    return pages;
}
@danielkochdakitec

This comment has been minimized.

Copy link

@danielkochdakitec danielkochdakitec commented Mar 22, 2021

Thank you very much! In case someone needs this in PHP, I took your example and moved it to PHP:

<?php
  $currentPage = 10;
  $length = 20;
  
  $delta = 2;
  $left = $currentPage - $delta;
  $right = $currentPage + $delta + 1;
  $range = [];
  $rangeWithDots = [];
  $l;

  for($i = 1; $i <= $length; $i++) {
    if($i == 1 || $i == $length || $i >= $left && $i < $right) {
      $range[] = $i;
    }
  }

  foreach($range as $i) {
    if($l) {
      if ($i - $l === 2) {
        $rangeWithDots[] = $l + 1;
      } else if ($i - $l !== 1) {
        $rangeWithDots[] = '...';
      }
    }
    
    $rangeWithDots[] = $i;
    $l = $i;
  }
  
  print_r($rangeWithDots);
@chrisk8er

This comment has been minimized.

Copy link

@chrisk8er chrisk8er commented Jun 23, 2021

Thanks dude, you saved my day...

@taythebot

This comment has been minimized.

Copy link

@taythebot taythebot commented Jul 17, 2021

Thank you so much for this. Now I don't have to rip out my hair figuring this out..

@robozb

This comment has been minimized.

Copy link

@robozb robozb commented Aug 25, 2021

Thank you so much!

@YuraKostin

This comment has been minimized.

Copy link

@YuraKostin YuraKostin commented Aug 25, 2021

Hello, everyone

Here is my view of paginator implementation

type PaginatorInput = {
    current: number;
    last: number;
    betweenFirstAndLast?: number;
};

type Paginator = {
    first: number;
    current: number;
    last: number;
    pages: Array<number>;
    leftCluster: number | null;
    rightCluster: number | null;
};

const thresholds = (current: number, side: number): [number, number] => [
    current - side,
    current + side,
];

const range = (from: number, to: number) =>
    Array.from({ length: to + 1 - from }).map((_, i) => i + from);

const middle = (n: number): number => Math.floor(n / 2);

const paginator = (options: PaginatorInput): Paginator | null => {
    const base = {
        first: 1,
        last: options.last,
        current: options.current,
    };

    const { betweenFirstAndLast = 7 } = options;

    if (options.last <= betweenFirstAndLast + 2) {
        return {
            pages: range(2, options.last - 1),
            leftCluster: null,
            rightCluster: null,
            ...base,
        };
    }

    const side = middle(betweenFirstAndLast);
    const [left, right] = thresholds(options.current, side);

    if (left > 1 && right < options.last) {
        return {
            pages: range(left, right),
            leftCluster: middle(1 + left),
            rightCluster: middle(right + options.last),
            ...base,
        };
    }

    if (left < 1) {
        return {
            pages: range(2, right + side),
            leftCluster: null,
            rightCluster: middle(right + options.last),
            ...base,
        };
    }

    if (right > options.last) {
        return {
            pages: range(left - side, options.last - 1),
            leftCluster: middle(right + options.last),
            rightCluster: null,
            ...base,
        };
    }

    return null;
};

And here is the example of render function

const paginatorList = (p: Paginator) => {
    if (!p) {
        return [];
    };

    const {first, current, last, pages, leftCluster, rightCluster} = p;
    const list = [];

    if (current !== first) {
        list.push(first);
    } else {
        list.push('[' + first +']')
    }

    if (leftCluster) {
        list.push('...')
    }

    pages.forEach(page => {
        if (page === current) {
            list.push('[' + page +']');
        } else {
            list.push(page);
        }
    });

    if (rightCluster) {
        list.push('...')
    }

    if (current !== last) {
        list.push(last);
    } else {
        list.push('[' + last +']')
    }

    return list;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment