// 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] | |
*/ |
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} />
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 = [];
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
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;
};
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
Thanks!
Thanks a lot - saved me a ton of time.
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:
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
}```
@jorrit91 very nice, works like a charm and feels pretty good to always have the same number of UI elements, indeed.
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>
);
});
@ktmud Bro your implementation helped me a lot, thank you very much for sharing it
great !
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)
})
})
Thanks bro!
@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]
@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.
@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.
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;
}
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);
Thanks dude, you saved my day...
Thank you so much for this. Now I don't have to rip out my hair figuring this out..
Thank you so much!
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;
};
I really liked the currying approach that @narthur used. It's so straight forward and readable and removes the need for looping! I thought about this and realized even a flexible API would only need to offer offset of say 1 | 2
and so I adapted his to support both. Here's the React hook version (I plan to also implement this in Vue 3, Svelte, and Angular):
export type allowedOffsets = 1 | 2;
export type GAP = '...';
export type PageArrayItem = number | GAP;
export interface PaginationProps {
offset?: allowedOffsets;
onChange?: (page: number, pages: PageArrayItem[]) => void;
}
export const usePagination = ({ offset = 2 }: PaginationProps) => {
const getPaddedArray = (
filtered: PageArrayItem[],
shouldIncludeLeftDots: boolean,
shouldIncludeRightDots: boolean,
totalCount: number,
) => {
if (shouldIncludeLeftDots) {
filtered.unshift('...');
}
if (shouldIncludeRightDots) {
filtered.push('...');
}
if (totalCount <= 1) {
return [1];
}
return [1, ...filtered, totalCount];
};
const generatePagingPaddedByOne = (current: number, totalPageCount: number) => {
const center = [current - 1, current, current + 1];
const filteredCenter: PageArrayItem[] = center.filter((p) => p > 1 && p < totalPageCount);
const includeLeftDots = current > 3;
const includeRightDots = current < totalPageCount - 2;
return getPaddedArray(filteredCenter, includeLeftDots, includeRightDots, totalPageCount);
};
const generatePagingPaddedByTwo = (current: number, totalPageCount: number) => {
const center = [current - 2, current - 1, current, current + 1, current + 2];
const filteredCenter: PageArrayItem[] = center.filter((p) => p > 1 && p < totalPageCount);
const includeThreeLeft = current === 5;
const includeThreeRight = current === totalPageCount - 4;
const includeLeftDots = current > 5;
const includeRightDots = current < totalPageCount - 4;
if (includeThreeLeft) {
filteredCenter.unshift(2);
}
if (includeThreeRight) {
filteredCenter.push(totalPageCount - 1);
}
return getPaddedArray(filteredCenter, includeLeftDots, includeRightDots, totalPageCount);
};
// https://gist.github.com/kottenator/9d936eb3e4e3c3e02598#gistcomment-3413141
const generate = (current: number, totalPageCount: number): PageArrayItem[] => {
if (offset === 1) {
const generatedPages = generatePagingPaddedByOne(current, totalPageCount);
return generatedPages;
}
const generatedPages = generatePagingPaddedByTwo(current, totalPageCount);
return generatedPages;
};
return {
generate,
};
};
Tests are here. This is what it looks like used in my pagination component when the offset
is set to 2 (I've left the focus ring as it supports keyboard navigation via tabbing):
I did notice Ant Design and Zendesk Garden ones used padding (offset, sibling) of just 1 on each side so it's probably worth supporting as well.
I do like the implementations I've seen here that keep the number of page links constant as they don't "jump around" but I think it's a bit of a trade off.
I hope this will help:
export const getPaginationGenerator = (
currentPageNumber: number,
totalPageNumber: number,
offset = 2
): number[] | string[] => {
// By doing this, when we are close to the beginning or end of the pagination, two numbers are generated after/before the current page,
// but when we are far from these points (in the middle of the pagination), we generate only one number after/before the current page.
const offsetNumber =
currentPageNumber <= offset || currentPageNumber > totalPageNumber - offset ? offset : offset - 1;
const numbersList = [];
const numbersListWithDots = [];
// If itemsPerPage is less than what the user selected with the Select component or if there is no page or only one page:
if (totalPageNumber <= 1 || totalPageNumber === undefined) return [1];
// Create list of numbers:
numbersList.push(1);
for (let i = currentPageNumber - offsetNumber; i <= currentPageNumber + offsetNumber; i++) {
if (i < totalPageNumber && i > 1) {
numbersList.push(i);
}
}
numbersList.push(totalPageNumber);
// Add three dots to the list of numbers:
numbersList.reduce((accumulator, currentValue) => {
if (accumulator === 1) {
numbersListWithDots.push(accumulator);
}
if (currentValue - accumulator !== 1) {
numbersListWithDots.push('...');
}
numbersListWithDots.push(currentValue);
return currentValue;
});
return numbersListWithDots;
};
I've slightly altered the version of your codes by adding pagesShown
to have a variable fixed length. pagesShown
is clamped to 5 as it makes no sense to have a [1 ... ]
or [1 ... ... 10]
scenario in pages. With this, we could set whichever fixed length we want.
const getRange = (start: number, end: number) => {
const length = end - start + 1;
return Array.from({length}, (_, i) => start + i);
};
const clamp = (number: number, lower: number, upper: number) => {
return Math.min(Math.max(number, lower), upper);
}
const pagination = (
currentPage: number,
pageCount: number,
pagesShown: number,
MINIMUM_PAGE_SIZE: number = 5,
) => {
let delta: number;
currentPage = clamp(currentPage, 1, pageCount);
pagesShown = clamp(pagesShown, MINIMUM_PAGE_SIZE, pageCount);
const centerPagesShown = pagesShown - 5;
const boundaryPagesShown = pagesShown - 3;
if (pageCount <= pagesShown) {
delta = pagesShown;
} else {
delta =
currentPage < boundaryPagesShown || currentPage > pageCount - boundaryPagesShown
? boundaryPagesShown
: centerPagesShown;
}
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: (string | number)[] =
currentPage > delta
? getRange(Math.min(range.start, pageCount - delta), Math.min(range.end, pageCount))
: getRange(1, Math.min(pageCount, delta + 1));
if (currentPage > pageCount - boundaryPagesShown && pageCount > pagesShown) {
pages = getRange(pageCount - delta, pageCount);
}
const withDots = (value: number, pair: (string | number)[]) =>
pages.length + 1 !== pageCount ? pair : [value];
const lastPage = pages[pages.length - 1];
if (pages[0] !== 1) {
pages = withDots(1, [
1,
'...',
]).concat(pages);
}
if (lastPage && lastPage < pageCount) {
pages = pages.concat(
withDots(pageCount, [
'...',
pageCount,
]),
);
}
return pages;
};
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]
*/
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..
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;
}
}