https://github.com/chrisnajman/paginators
A modular, lightweight and accessible pagination system for displaying content from various (and varied) JSON data sources. Supports multiple pages with a customisable number of items per page and dynamic pagination buttons.
https://github.com/chrisnajman/paginators
accessibility aria-attributes chatgpt-free css-flexbox css-grid css-nesting cssnano es6-modules esbuild history-management html-css-javascript html-minifier-terser json live-regions loading-spinner no-js pagination postcss theme-switcher
Last synced: 2 months ago
JSON representation
A modular, lightweight and accessible pagination system for displaying content from various (and varied) JSON data sources. Supports multiple pages with a customisable number of items per page and dynamic pagination buttons.
- Host: GitHub
- URL: https://github.com/chrisnajman/paginators
- Owner: chrisnajman
- License: mit
- Created: 2025-08-23T11:08:49.000Z (10 months ago)
- Default Branch: main
- Last Pushed: 2025-09-14T13:21:19.000Z (9 months ago)
- Last Synced: 2025-09-14T15:10:41.492Z (9 months ago)
- Topics: accessibility, aria-attributes, chatgpt-free, css-flexbox, css-grid, css-nesting, cssnano, es6-modules, esbuild, history-management, html-css-javascript, html-minifier-terser, json, live-regions, loading-spinner, no-js, pagination, postcss, theme-switcher
- Language: HTML
- Homepage: https://chrisnajman.github.io/paginators/
- Size: 148 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# Paginators
Menu
- [Introduction](#introduction)
- [Features](#features)
- [File Structure Overview](#file-structure-overview)
- [HTML](#html)
- [Paginator](#paginator)
- [How to Add a New JSON Type](#how-to-add-a-new-json-type)
- [Link Handling in Templates](#link-handling-in-templates)
- [Email Address Handling in Templates](#email-address-handling-in-templates)
- [Telephone Number Handling in Templates](#telephone-number-handling-in-templates)
- [CSS](#css)
- [Use of ChatGPT (Free version)](#use-of-chatgpt-free-version)
- [Accessibility](#accessibility)
- [Theme Toggling](#theme-toggling)
- [Testing and Compatibility](#testing-and-compatibility)
- [How to Run](#how-to-run)
- [Build & Deployment Setup for `/docs` Folder](#build--deployment-setup-for-docs-folder)
## Introduction
A modular, lightweight and accessible pagination system for displaying content from various (and varied) JSON data sources. Supports multiple pages with a customisable number of items per page and dynamic pagination buttons.
[View on GitPage](https://chrisnajman.github.io/paginators)
[Back to menu](#menu)
---
## Features
- Separate JSON files for different content types (`pages.json`, `posts.json`, `users.json`, etc.).
- HTML `` elements for rendering content dynamically. Note: `` element is **required**.
- Modular JS with `render-content.js`, `render-[type]-content.js`, and `paginator.js`.
- URL syncing and browser back/forward support.
- Optional preprocessing/normalization of JSON fields (`normalise-data.js`).
- Supports nested data, arrays, and custom transformations per content type.
- Pagination button rendering with max visible buttons and dynamic updates.
- Paginator is only rendered if there are multiple pages.
[Back to menu](#menu)
---
## File Structure Overview
Relevant paginator-related files are listed below.
- `index.html`, `posts.html`, `users.html` — HTML pages displaying paginated content via `` element.
- `index.js`: Loads main JavaScript modules. Displays them conditionally, according to relevant HTML `body` id.
- **`json/`**
- `pages.json`: Array of objects, with `"content"` field containing escaped HTML.
- `posts.json`: Object with key of `"posts"` containing an array of objects.
- `users.json`: Array of objects with two-level nesting.
- **`js-modules/`**
- `globals.js`: Global constants (e.g., max visible pagination buttons, HTML `body` ids, etc).
- `loader.js`: Loader animation logic.
- `page-number.js`: (Optional) display page number in `
`
- `details-page-nav.js`: Close `details` if you click outside it.
- **`pagination/`**
- `paginator.js`: Main paginator initialization logic, handles updates, renders content, pagination buttons, and URL sync.
- **`components/`**
- `live-region.js`: Accessibility: announces page changes.
- `render-page-buttons.js`: Generates pagination buttons.
- `render-pages-content.js`: Pages-type renderer that calls `render-content.js`.
- `render-posts-content.js`: Posts-type renderer that calls `render-content.js`.
- `render-users-content.js`: Users-type renderer that calls `render-content.js`.
- `render-content.js`: Generic renderer for JSON arrays to templates.
- `normalise-data.js`: Generic JSON preprocessing / normalization utility.
- **`render-customisations/`**
- `normalise-users-data.js`: Example of per-type custom transformations.
- **`helpers/`**
- `redirect_404.js`: Redirect to `404.html` if page number is out of range.
- `set-loader-timeout.js`: Utility for controlling loader display timing.
- `url-sync.js`: Syncs current page with URL and supports back/forward.
- **`page-item-types/`**: Item type-specific code that fetches JSON and calls `initPaginator`.
- `pages.js`
- `posts.js`
- `users.js`
### Other
- `theme.js`: Handles theme toggling (light/dark mode) and local storage management.
- `about.html`
[Back to menu](#menu)
---
## HTML
> [!IMPORTANT]
> To successfully output JSON (via `render-content.js`), a `` is used to output each instance of 'page', 'post' and 'user' content types.
[Back to menu](#menu)
---
## Paginator
The paginator makes it easy to move through long lists of items without overwhelming the page. You can choose how many page buttons to display at once — for example, showing just a few nearby pages or spreading out more options.
When there are too many pages to fit, the paginator automatically adds ellipsis (…) to show that more pages exist before or after the visible range. This keeps the navigation clean and simple, while still giving quick access to the first page, last page, and the pages closest to where you are.
After a page loads, focus is set on the page `
`
[Back to menu](#menu)
---
## How to Add a New JSON Type
### 1. Add the JSON File
Place your file in the `/json/` folder.
**Example**: `json/products.json`
**Structure**: an array of objects or an object containing an array under a specific key (e.g., "items").
```json
[
{
"id": 1,
"title": "Digital camera",
"price": "£199.90",
"manufacturer": "Sony"
},
...
]
```
### 2. Create a New HTML Page: `products.html`
1. Add an id to the `body` tag:
```javascript
```
2. Add a `` to `products.html`:
```html
.
Price:
Manufacturer:
```
3. Add Paginator and Pagination Containers
Make sure your HTML page has a container and paginator element:
```html
```
## 3. Create a Type-Specific Renderer
Create a new JS module in `js-modules/pagination/components/`, e.g., `render-products-content.js`:
```javascript
import renderContent from "./render-content.js"
export default function renderProductsContent(
data,
template,
containerId,
page,
itemsPerPage
) {
renderContent(data, template, containerId, {
page,
itemsPerPage,
contentKeys: [], // optional keys that contain escaped HTML - see 'pages.json' for an example.
itemsKey: null, // or the array key if JSON is { "items": [...] }
})
}
```
**Optional**: preprocess your JSON via `normalise-data.js` or a custom file in `render-customisations/normalise-products-data.js` for things like formatting, splitting strings, etc.
### 4. Create a Page-Specific Loader Script
**Example**: `js-modules/products/products.js`
```javascript
import { maxVisiblePaginationButtons } from "../globals.js"
import initPaginator from "../pagination/paginator.js"
export default async function loadProducts() {
try {
const res = await fetch("./json/products.json")
if (!res.ok) throw new Error(`Failed to load products.json: ${res.status}`)
const data = await res.json()
const templateId = "article-template-product"
const containerId = "products-page-container"
initPaginator({
data,
templateId,
containerId,
itemsPerPage: 5, // Modify as required
// maxButtons: Modify as required, e.g. 'maxButtons: 7'.
// !If you set the value equal to or greater than the corresponding number of entries in users.json,
// there won't be any ellipsis.
maxButtons: maxVisiblePaginationButtons, // Default = 5
itemsKey: null, // or "items" if JSON has a key
})
} catch (err) {
console.error(err)
}
}
document.addEventListener("DOMContentLoaded", loadProducts)
```
### 5. Update `globals.js`
Add the products `body` id to the `PAGE_TYPES` variable:
```javascript
export const PAGE_TYPES = {
PAGES: "pages-pagination",
POSTS: "posts-pagination",
USERS: "users-pagination",
PRODUCTS: "products-pagination", // Products
}
// Edit if defaultLoaderTimeout is not long enough/too long:
export const loaderTimeouts = {
[PAGE_TYPES.PAGES]: 500, // Loads images so more time required
// These can use defaultLoaderTimeout = 250 for now, but uncomment and change,
// if required
// [PAGE_TYPES.POSTS]: 250,
// [PAGE_TYPES.USERS]: 250,
// [PAGE_TYPES.PRODUCTS]: 250, // Products
}
```
### 6. Update `paginator.js`
- At the top of the file, add:
```javascript
import renderProductsContent from "./components/render-products-content.js"
```
- Then, within `requestAnimationFrame()` add:
```javascript
if (bodyId === PAGE_TYPES.PRODUCTS)
renderProductsContent(data, template, containerId, page, itemsPerPage)
```
### 7. Update `index.js`
- At the top of the file, add:
```javascript
import products from "./js-modules/page-item-types/products.js"
```
- Then add:
```javascript
else if (bodyId === PAGE_TYPES.PRODUCTS) {
products()
```
to the end of the `if` statement.
### 8. Finish
The new type is now fully integrated into the paginator system.
This pattern keeps everything modular and the core `render-content.js` and `paginator.js` remain untouched.
[Back to menu](#menu)
---
## Link Handling in Templates
#### Example
**JSON field**
```json
{
"website": "https://example.com",
"url": "https://another.com"
}
```
**HTML Template**:
Note the difference between the placement of the `data-[key]` in the following:
```html
```
**Rendered HTML**:
```html
```
### Explanation
- `data-[field-name]` → **required**. This is the `data-` attribute that `render-content.js` uses to find the element in the template.
- `data-href="[field-name]"` → **required**. This tells `render-content.js` which JSON key to use to populate the `href` attribute of the link.
- Inner content ([Link text or inner span]) → **required**. Can be e.g. a `` linked to another JSON key, or just plain text. `render-content.js` **does not set this automatically**.
- `target="_blank"` and `rel="noopener noreferrer"` → **optional**, for opening external links safely.
The `render-content.js` script will set the `href` of each `` automatically from the corresponding JSON key.
[Back to menu](#menu)
---
## Email Address Handling in Templates
**JSON field**
```json
"email": "name@name.com",
"email-address": "companyname@ecompanyname.com"
```
**HTML Template**:
```html
```
**Rendered HTML**:
```html
```
### Explanation
- E.g. `data-mailto="email"` → tells `render-content.js` to look up the JSON field e.g. `"email"` and build a `mailto:` link.
- E.g. `data-email` → required to bind the element to the JSON field e.g. `"email"`.
- The inner text of the ` `will automatically display the email address.
[Back to menu](#menu)
---
## Telephone Number Handling in Templates
**JSON field**
```json
"phone": "+44 1234 567890",
"telno": "+44 3333 444444"
```
**HTML Template**:
```html
```
**Rendered HTML**:
```html
```
### Explanation
- E.g. `data-tel-link="tel"` → tells `render-content.js` to look up the JSON field e.g. `"tel"` and build a `tel:` link.
- E.g.`data-tel` → required to bind the element to the JSON field e.g. `"tel"`.
- The inner text of the ` `will automatically display the email address.
[Back to menu](#menu)
---
## CSS
- `style.css`: `@imports` all files in `/css/` folder.
- **`css/`**
- `root.css`
- `base.css`
- `loader.css`
- `navigation.css`
- `redirect.css`
- `details-transition.css`
- `theme-toggle.css`
- **`pagination/`**
- `index.css`: `@imports` all files in `pagination/` folder.
- `shared.css`
- `paginator.css`
- `pages.css`
- `posts.css`
- `users.css`
---
## Use of ChatGPT (Free version)
Usually, I only consult the AI when I run into insurmountable difficulties with the JavaScript. This time, as an experiment, I decided to let ChatGPT handle **all** of the JavaScript from scratch, only prodding it when its output was faulty.
This resulted in a huge thread (about 20,000 lines) generated over several days. I ran into difficulties about halfway through: the size of the thread by that time was taxing the capabilities of the server and the page often became inactive. By the time it reached the 20,000 mark, it was almost totally inert, requiring that the browser be shut down after posting a question, then reopening it a few minutes later to see the response.
However, ChatGPT came through in the end, and after much to-ing and fro-ing, it answered all my questions and fixed all the bugs.
The thread may be fat, but the generated code is pretty lean.
[Back to menu](#menu)
---
## Accessibility
The site includes the following accessibility enhancements:
- Fully keyboard-navigable using tab keys.
- ARIA roles and attributes are implemented throughout (e.g. for navigation and live announcements).
- A visually hidden skip link is provided for screen reader users.
- An ARIA live region (`
### No JS
If JavaScript is disabled, a `` message is displayed. Additionally, the loading animation and theme-toggler cease to function.
[Back to menu](#menu)
---
## Theme Toggling
The application includes a dark mode and light mode toggle:
- The current theme state is stored in **local storage** and applied automatically on page reload.
- Accessible buttons with appropriate ARIA attributes are used to improve usability.
> [!IMPORTANT]
> Remember to change `const LOCAL_STORAGE_PREFIX` in `js-modules/theme.js` to a unique identifier.
[Back to menu](#menu)
---
## Testing and Compatibility
The application has been tested on the following platforms and browsers:
- **Operating System**: Windows 10/11
- **Browsers**:
- Google Chrome
- Mozilla Firefox
- Microsoft Edge
### Device View Testing
The layout and functionality have been verified in both browser and device simulation views to ensure responsiveness and usability.
[Back to menu](#menu)
---
## How to Run
1. Clone or download the repository to your local machine.
2. Open the project folder and start a simple HTTP server (e.g., using `Live Server` in VS Code or Python's `http.server` module).
3. Open the project in a modern browser (e.g., Chrome, Firefox, or Edge).
[Back to menu](#menu)
---
## Build & Deployment Setup for `/docs` Folder
If you want to deploy a minified version of this project to **GitHub Pages**, read on.
### 1. Install Required Packages
Run this once in your project root to install dev dependencies:
```bash
npm install
```
### 2. Run the full build process
In the terminal, run:
```bash
npm run build
```
### 3. Deploy to GitHub Pages
Once you've created a repository and pushed the files,
- go to `https://github.com/[your-name]/[your-project-name]/settings/pages`.
- Under "Build and deployment > Branch" make sure you set the branch to `main` and folder to `/docs`.
- Click "Save".
> [!NOTE]
> For a detailed description of the build process, configuration files and npm packages see my [GitHub Pages Optimised Build](https://github.com/chrisnajman/github-pages-optimised-build).
[Back to menu](#menu)