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]
*/
@zacfukuda
Copy link

zacfukuda commented Jul 1, 2022

My personal implementation returns an object with current, prev, next:

function paginate({current, max}) {
  if (!current || !max) return null

  let prev = current === 1 ? null : current - 1,
      next = current === max ? null : current + 1,
      items = [1]
  
  if (current === 1 && max === 1) return {current, prev, next, items}
  if (current > 4) items.push('…')

  let r = 2, r1 = current - r, r2 = current + r

  for (let i = r1 > 2 ? r1 : 2; i <= Math.min(max, r2); i++) items.push(i)

  if (r2 + 1 < max) items.push('…')
  if (r2 < max) items.push(max)

  return {current, prev, next, items}
}
/* Test */
for (let max = 1; max < 10; max+=2) {
  console.log(`max: ${max}`)
  for (let current = 1; current <= max; current++) {
    let pagination = paginate({current, max})
    console.log(`  c:${current}`, pagination.items)
  }
}

/*
Output:
max: 1
  c:1 [1]
max: 3
  c:1 [1, 2, 3]
  c:2 [1, 2, 3]
  c:3 [1, 2, 3]
max: 5
  c:1 [1, 2, 3, '…', 5]
  c:2 [1, 2, 3, 4, 5]
  c:3 [1, 2, 3, 4, 5]
  c:4 [1, 2, 3, 4, 5]
  c:5 [1, '…', 3, 4, 5]
max: 7
  c:1 [1, 2, 3, '…', 7]
  c:2 [1, 2, 3, 4, '…', 7]
  c:3 [1, 2, 3, 4, 5, '…', 7]
  c:4 [1, 2, 3, 4, 5, 6, 7]
  c:5 [1, '…', 3, 4, 5, 6, 7]
  c:6 [1, '…', 4, 5, 6, 7]
  c:7 [1, '…', 5, 6, 7]
max: 9
  c:1 [1, 2, 3, '…', 9]
  c:2 [1, 2, 3, 4, '…', 9]
  c:3 [1, 2, 3, 4, 5, '…', 9]
  c:4 [1, 2, 3, 4, 5, 6, '…', 9]
  c:5 [1, '…', 3, 4, 5, 6, 7, '…', 9]
  c:6 [1, '…', 4, 5, 6, 7, 8, 9]
  c:7 [1, '…', 5, 6, 7, 8, 9]
  c:8 [1, '…', 6, 7, 8, 9]
  c:9 [1, '…', 7, 8, 9]
*/

@orso081980
Copy link

orso081980 commented Jul 7, 2022

Is there a way to add the prev and next arrow (< >) to the original script at the very beginning and at the very end? All the solutions that I found so far is going to another direction..

@rogerfar
Copy link

rogerfar commented Sep 19, 2022

Simple solution if you want the elements to always remain equal and not having your UI shift around:

  const getRange = (start: number, end: number) => {
    const length = end - start + 1;
    return Array.from({ length }, (_, i) => start + i);
  };

  const pagination = (currentPage: number, pageCount: number, delta: number) => {
    const pages: number[] = [];

    if (currentPage <= delta) {
      pages.push(...getRange(1, Math.min(pageCount, delta * 2 + 1)));
    } else if (currentPage > pageCount - delta) {
      pages.push(...getRange(Math.max(1, pageCount - delta * 2), pageCount));
    } else {
      pages.push(...getRange(Math.max(1, currentPage - delta), Math.min(pageCount, currentPage + delta)));
    }

    return pages;
  };
max: 1
   c:1 [1]
max: 3
   c:1 (3) [1, 2, 3]
   c:2 (3) [1, 2, 3]
   c:3 (3) [1, 2, 3]
max: 5
   c:1 (5) [1, 2, 3, 4, 5]
   c:2 (5) [1, 2, 3, 4, 5]
   c:3 (5) [1, 2, 3, 4, 5]
   c:4 (5) [1, 2, 3, 4, 5]
   c:5 (5) [1, 2, 3, 4, 5]
max: 7
   c:1 (7) [1, 2, 3, 4, 5, 6, 7]
   c:2 (7) [1, 2, 3, 4, 5, 6, 7]
   c:3 (7) [1, 2, 3, 4, 5, 6, 7]
   c:4 (7) [1, 2, 3, 4, 5, 6, 7]
   c:5 (7) [1, 2, 3, 4, 5, 6, 7]
   c:6 (7) [1, 2, 3, 4, 5, 6, 7]
   c:7 (7) [1, 2, 3, 4, 5, 6, 7]
max: 9
   c:1 (7) [1, 2, 3, 4, 5, 6, 7]
   c:2 (7) [1, 2, 3, 4, 5, 6, 7]
   c:3 (7) [1, 2, 3, 4, 5, 6, 7]
   c:4 (7) [1, 2, 3, 4, 5, 6, 7]
   c:5 (7) [2, 3, 4, 5, 6, 7, 8]
   c:6 (7) [3, 4, 5, 6, 7, 8, 9]
   c:7 (7) [3, 4, 5, 6, 7, 8, 9]
   c:8 (7) [3, 4, 5, 6, 7, 8, 9]
   c:9 (7) [3, 4, 5, 6, 7, 8, 9]

@Daudongit
Copy link

Daudongit commented Sep 25, 2022

@narthur version in Rust
https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=660c01b68363ea506b1c20fc8bdcda37

use std::collections::VecDeque;

fn pagination(current_page:i64, number_of_pages:i64)->VecDeque<String>{
    const GAP: &str = "...";
    let center = vec![
        current_page - 2, current_page - 1, current_page, current_page + 1, current_page + 2
    ];
    let mut center_deque:VecDeque<String> = center.iter()
        .filter(|&p| *p > 1i64 && *p < number_of_pages).map(i64::to_string).collect();
    let include_three_left = current_page == 5;
    let include_three_right = current_page == number_of_pages - 4;
    let include_left_dots = current_page > 5;
    let include_right_dots = current_page < number_of_pages - 4;

    if include_three_left {center_deque.push_front("2".into());}
    if include_three_right {center_deque.push_back((number_of_pages - 1i64).to_string());}
    if include_left_dots {center_deque.push_front(GAP.into());}
    if include_right_dots {center_deque.push_back(GAP.into());}
    center_deque.push_front("1".into());
    if number_of_pages > 1i64 {
        center_deque.push_back(number_of_pages.to_string());
    }
    center_deque
}

fn main(){
    let (_current_page, per_page, total) = (1u64, 20, 100);
    let mut number_of_pages = total / per_page;
    if (total % per_page) != 0u64  {
        number_of_pages += 1
    }
    _=number_of_pages; // currently not in use
    // println!("{:?}", pagination(1, number_of_pages));
    // println!("{:?}", pagination(1, 1));
    // println!("{:?}", pagination(15, 51));
    // println!("{:?}", pagination(90, 100));
    assert_eq!(pagination(1, 1), ["1"]);
    assert_eq!(pagination(15, 51), ["1", "...", "13", "14", "15", "16", "17", "...", "51"]);
    assert_eq!(pagination(90, 100), ["1", "...", "88", "89", "90", "91", "92", "...", "100"]);
}

@rrmesquita
Copy link

rrmesquita commented Sep 30, 2022

@narthur's approach but supporting custom delta:

function pagination(current: number, total: number, delta = 2, gap = '...') {
  if (total <= 1) return [1]

  const center = [current] as (number | typeof gap)[]

  // no longer O(1) but still very fast
  for (let i = 1; i <= delta; i++) {
    center.unshift(current - i)
    center.push(current + i)
  }

  const filteredCenter = center.filter((page) => page > 1 && page < total)

  const includeLeftGap = current > 3 + delta
  const includeLeftPages = current === 3 + delta
  const includeRightGap = current < total - (2 + delta)
  const includeRightPages = current === total - (2 + delta)

  if (includeLeftPages) filteredCenter.unshift(2)
  if (includeRightPages) filteredCenter.push(total - 1)
  if (includeLeftGap) filteredCenter.unshift(gap)
  if (includeRightGap) filteredCenter.push(gap)

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

/*

tests:

for (let i = 1, l = 20; i <= l; i++)
  console.log(`Page ${i}:`, pagination(i, l, 4)); // delta 4

expected output:

page 1: [1, 2, 3, 4, 5, '...', 20]
page 2: [1, 2, 3, 4, 5, 6, '...', 20]
page 3: [1, 2, 3, 4, 5, 6, 7, '...', 20]
page 4: [1, 2, 3, 4, 5, 6, 7, 8, '...', 20]
page 5: [1, 2, 3, 4, 5, 6, 7, 8, 9, '...', 20]
page 6: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, '...', 20]
page 7: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, '...', 20]
page 8: [1, '...', 4, 5, 6, 7, 8, 9, 10, 11, 12, '...', 20]
page 9: [1, '...', 5, 6, 7, 8, 9, 10, 11, 12, 13, '...', 20]
page 10: [1, '...', 6, 7, 8, 9, 10, 11, 12, 13, 14, '...', 20]
page 11: [1, '...', 7, 8, 9, 10, 11, 12, 13, 14, 15, '...', 20]
page 12: [1, '...', 8, 9, 10, 11, 12, 13, 14, 15, 16, '...', 20]
page 13: [1, '...', 9, 10, 11, 12, 13, 14, 15, 16, 17, '...', 20]
page 14: [1, '...', 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
page 15: [1, '...', 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
page 16: [1, '...', 12, 13, 14, 15, 16, 17, 18, 19, 20]
page 17: [1, '...', 13, 14, 15, 16, 17, 18, 19, 20]
page 18: [1, '...', 14, 15, 16, 17, 18, 19, 20]
page 19: [1, '...', 15, 16, 17, 18, 19, 20]
page 20: [1, '...', 16, 17, 18, 19, 20]

*/

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