1// javascript/chives-pagination.js
4// ChivesPagination manages the pagination of a collection of items.
8// const pagination = new ChivesPagination(
9// { perPage: 200 }, new URLSearchParams(window.location.search)
12// const itemsToShow = pagination.apply(items);
14// const paginationState = pagination.getState(items.length);
16// if (paginationState.totalPages > 1) {
17// const paginationElem = document.createElement("chives-pagination-nav");
18// paginationElem.state = paginationState;
19// paginationElem.searchParams = urlParams;
20// document.querySelector("#pagination").appendChild(paginationElem);
23class ChivesPagination {
27 // All my sites use "page" as the URL parameter for the current page.
30 constructor(config, searchParams) {
31 this.#perPage = config.perPage || 50;
32 this.#searchParams = searchParams;
35 // Returns the current page number (1-indexed).
37 const page = parseInt(this.#searchParams.get(this.#paramKey), 10);
38 return (!isNaN(page) && page > 0) ? page : 1;
41 // Returns the state of the pagination.
42 getState(totalItems) {
43 const currentPage = this.getCurrentPage();
44 const totalPages = Math.max(1, Math.ceil(totalItems / this.#perPage));
49 // TODO: Allow callers to pass a perPage query parameter in
50 // the URL to customise page sizes.
51 perPage: this.#perPage,
52 paramKey: this.#paramKey,
56 // Extract the slice of items that should be shown on the current page.
58 const { currentPage, perPage } = this.getState(items.length);
60 // Page numbers are 1-indexed, so page 1 corresponds to
61 // the indices 0…(perPage - 1).
62 const start = (currentPage - 1) * perPage;
63 const end = currentPage * perPage;
65 return items.slice(start, end);
69// ChivesPaginationNav provides a <chives-pagination-nav> component that
70// renders navigation controls for pagination.
72// It uses the state provided by ChivesPagination.
73class ChivesPaginationNav extends HTMLElement {
83 set searchParams(params) {
85 this.#searchParams = params;
90 if (!this.#state || !this.#searchParams) {
94 const { currentPage, totalPages } = this.#state;
96 if (totalPages === 1) {
100 const wrapper = document.createElement("nav");
101 wrapper.classList.add("chives_pagination");
103 if (currentPage > 1) {
104 const prevLink = document.createElement("a");
105 prevLink.innerHTML = "← previous";
106 // If you're paginated too far, clamp you to the max page
107 prevLink.href = this.#updatePage(Math.min(currentPage - 1, totalPages));
108 wrapper.appendChild(prevLink);
111 const indicator = document.createElement("span");
112 indicator.classList.add("page_indicator");
113 indicator.innerText = `Page ${currentPage} of ${totalPages}`;
114 wrapper.appendChild(indicator);
116 if (currentPage < totalPages) {
117 const nextLink = document.createElement("a");
118 nextLink.innerHTML = "next →";
119 nextLink.href = this.#updatePage(currentPage + 1);
120 wrapper.appendChild(nextLink);
123 this.replaceChildren(wrapper);
126 // updatePage returns a copy of the URL search parameters with the
127 // new value for `page`.
129 let params = new URLSearchParams(this.#searchParams);
130 params.delete(this.#state.paramKey);
131 params.append(this.#state.paramKey, page);
132 return "?" + params.toString();
136// Register global styles for the <chives-pagination-nav> element.
137if (typeof document !== "undefined" &&
138 !document.getElementById("chives-pagination-styles")) {
139 const style = document.createElement("style");
140 style.id = "chives-pagination-styles";
141 style.textContent = `
143 a + .page_indicator::before,
144 .page_indicator:has(+ a)::after {
149 document.head.appendChild(style);
152// Register the custom HTML tag <chives-pagination-nav>
153if (!customElements.get("chives-pagination-nav")) {
154 customElements.define("chives-pagination-nav", ChivesPaginationNav);
157// renderPaginationNav renders the pagination navigation as
158// a child of `querySelector`.
159function renderPaginationNav({ querySelector, pagination, searchParams, totalItems }) {
160 state = pagination.getState(totalItems);
162 if (state.totalPages > 1) {
163 const elem = document.createElement("chives-pagination-nav");
165 elem.searchParams = searchParams;
166 document.querySelector(querySelector).appendChild(elem);