Skip to content

Instantly share code, notes, and snippets.

@mracette
Created October 26, 2020 22:41
Show Gist options
  • Save mracette/7b54c984d67493f94f8c8c39f170d746 to your computer and use it in GitHub Desktop.
Save mracette/7b54c984d67493f94f8c8c39f170d746 to your computer and use it in GitHub Desktop.
Vanilla JS improved pagination algorithm
/**
* Calculates a fixed-length array of pagination display elements,
* Example return value: [1, "...", 4, 5, 6, "...", 20]
*
* Inspired by https://gist.github.com/kottenator/9d936eb3e4e3c3e02598, but re-worked to ensure
* that the number of display elements stays constant regardless of the current page. Thus, the
* algorithm mimics the behavior of https://material-ui.com/components/pagination/
*
* @param {number} currentPage
* The current page. The rest of the display is oriented around this page.
* @param {number} totalPages
* The total number of pages available.
* @param {number} [displaySize]
* The number of display elements.
*
* @returns An array of length === displaySize, containing content for the pagination element.
*/
const pagination = (currentPage, totalPages, displaySize = 7) => {
// do not display more than the total number of pages
const displaySizeAdj = Math.min(displaySize, totalPages);
// subtract 1 to leave room for the first and last values
const displaySizeHalf = Math.floor(displaySizeAdj / 2) - 1;
// at a minimum, we want a range of [1]
const range = [1];
if (totalPages <= 1) {
return range;
}
/*
If currentPage is near the center of the range, it will be bounded by +/- (displaySizeHalf - 1)
As currentPage approaches totalPages, i needs a downward adjustment to ensure
range.length = displaySize
Example: if totalPages = 20, currentPage = 19,
we want a range:
[1, '...', 16, 17, 18, 19, 20]
rather than:
[1, '...', 17, 18, 19, 20]
*/
let i = currentPage - displaySizeHalf;
i = Math.min(i, totalPages - displaySizeHalf * 2 - 1);
while (range.length < displaySizeAdj && i !== totalPages) {
if (i < totalPages && i > 1) {
range.push(i);
}
i++;
}
// totalPages is always the last entry
range[displaySizeAdj - 1] = totalPages;
// check for starting ellipsis
if (range[1] !== 2) {
range[1] = "...";
}
// check for ending ellipsis
if (range[displaySizeAdj - 2] !== totalPages - 1) {
range[displaySizeAdj - 2] = "...";
}
return range;
};
// illustrates function output
const total = 20;
const testValues = [...Array(total + 1).keys()].slice(1);
for (const x in testValues) {
console.log(
`pagination(${x}, ${total}) -> ` + pagination(x, total).toString()
);
}
/*
pagination(0, 20) -> 1,2,3,4,5,...,20
pagination(1, 20) -> 1,2,3,4,5,...,20
pagination(2, 20) -> 1,2,3,4,5,...,20
pagination(3, 20) -> 1,2,3,4,5,...,20
pagination(4, 20) -> 1,2,3,4,5,...,20
pagination(5, 20) -> 1,...,4,5,6,...,20
pagination(6, 20) -> 1,...,5,6,7,...,20
pagination(7, 20) -> 1,...,6,7,8,...,20
pagination(8, 20) -> 1,...,7,8,9,...,20
pagination(9, 20) -> 1,...,8,9,10,...,20
pagination(10, 20) -> 1,...,9,10,11,...,20
pagination(11, 20) -> 1,...,10,11,12,...,20
pagination(12, 20) -> 1,...,11,12,13,...,20
pagination(13, 20) -> 1,...,12,13,14,...,20
pagination(14, 20) -> 1,...,13,14,15,...,20
pagination(15, 20) -> 1,...,14,15,16,...,20
pagination(16, 20) -> 1,...,15,16,17,...,20
pagination(17, 20) -> 1,...,16,17,18,19,20
pagination(18, 20) -> 1,...,16,17,18,19,20
pagination(19, 20) -> 1,...,16,17,18,19,20
*/
@weroro-sk
Copy link

weroro-sk commented Feb 10, 2025

Hello. :)
I'm trying this and the output is not consistent. The ellipsis must not be part of displaySize. If I set displaySize to 7, the output is 1,...,4,5,6,...,20, but it should be 1,...,3,4,5,6,7,...,20. Your solution is very fast, can you fix it, please?

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