Skip to content

Instantly share code, notes, and snippets.

@a-am
Forked from kottenator/simple-pagination.js
Created July 16, 2019 04:52
Show Gist options
  • Save a-am/117eb6dd77285d290eaf1b4ec8038ca1 to your computer and use it in GitHub Desktop.
Save a-am/117eb6dd77285d290eaf1b4ec8038ca1 to your computer and use it in GitHub Desktop.
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]
*/
@a-am
Copy link
Author

a-am commented Jul 16, 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]).

@a-am
Copy link
Author

a-am commented Jul 16, 2019

/**
 * 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]);

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