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 commented Apr 12, 2016

Thanks for sharing! 👍

@philipgiuliani

This comment has been minimized.

Copy link

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 commented Oct 17, 2016

Thanks!

@alexadusei

This comment has been minimized.

Copy link

alexadusei commented Dec 30, 2016

Great gist. Thanks!

@jkokh

This comment has been minimized.

Copy link

jkokh commented Jan 22, 2017

Awesome! Thnx!

@X-tymchenko

This comment has been minimized.

Copy link

X-tymchenko commented Feb 6, 2017

Thanks dude !

@jeantoledo

This comment has been minimized.

Copy link

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 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 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 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 commented Jul 14, 2017

Thank you very much.

@anotherstarburst

This comment has been minimized.

Copy link

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 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 commented Dec 17, 2017

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

@TimJKStrickland

This comment has been minimized.

Copy link

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 commented Jan 15, 2018

Thanks! Great work

@eskeyy

This comment has been minimized.

Copy link

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 commented Jan 20, 2018

I need for website 👍

@danilopolani

This comment has been minimized.

Copy link

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 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 commented Mar 5, 2018

Thank you very much ;)

@ihnat-mikhalkovich

This comment has been minimized.

Copy link

ihnat-mikhalkovich commented Mar 7, 2018

ty

@ihnat-mikhalkovich

This comment has been minimized.

Copy link

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 commented Apr 6, 2018

Thank you very much!

@tanguyantoine

This comment has been minimized.

Copy link

tanguyantoine commented Apr 26, 2018

Thank you. It helped :-)

@pourush77

This comment has been minimized.

Copy link

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 commented May 12, 2018

Awesome gist

@dunika

This comment has been minimized.

Copy link

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 commented Jun 7, 2018

Thank you for sharing : )

@codehaiku

This comment has been minimized.

Copy link

codehaiku commented Jun 20, 2018

:) 👍

Thank you!

@aef-

This comment has been minimized.

Copy link

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 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 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 commented Sep 16, 2018

Thanks Keep up the good work

@samuelrego

This comment has been minimized.

Copy link

samuelrego commented Dec 2, 2018

Thanks for sharing. keep up the the good work

@agm1984

This comment has been minimized.

Copy link

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 commented Jan 4, 2019

Thanks!

@mypetertw

This comment has been minimized.

Copy link

mypetertw commented Mar 12, 2019

amazing...

@sjnonweb

This comment has been minimized.

Copy link

sjnonweb commented Mar 19, 2019

Thanks man! It's perfect

@rostovtsev

This comment has been minimized.

Copy link

rostovtsev 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 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 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 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 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 commented Apr 22, 2019

👍

@zygos

This comment has been minimized.

Copy link

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 commented May 24, 2019

Thanks Man! This work fine for me too!

@silasakk

This comment has been minimized.

Copy link

silasakk commented Jun 23, 2019

Awesome!!!

@josueaqp92

This comment has been minimized.

Copy link

josueaqp92 commented Jul 12, 2019

ty

@jackmcpickle

This comment has been minimized.

Copy link

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 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 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 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 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 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 commented Nov 16, 2019

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

PHP version of my modification

function pagination($currentPage, $delta, $lastPage) {
	$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
@jomlamladen

This comment has been minimized.

Copy link

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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.