Skip to content

Instantly share code, notes, and snippets.

@caendesilva
Created April 27, 2025 15:27
Show Gist options
  • Save caendesilva/774885d50424b424edd9cd7b7200ce5b to your computer and use it in GitHub Desktop.
Save caendesilva/774885d50424b424edd9cd7b7200ce5b to your computer and use it in GitHub Desktop.
/**
* HydePHP Posts Paginator
* A client-side pagination solution for HydePHP blog post listings
*
* Simply add this script to your posts page and configure the options below.
*/
(function() {
// Configuration options
const config = {
postsPerPage: 5, // Number of posts to show per page
containerSelector: '.max-w-3xl.mx-auto', // Container holding all posts
postSelector: 'article', // Selector for individual post elements
showPageNumbers: true, // Whether to show numbered page buttons
maxPageButtons: 5, // Max number of page number buttons to display
scrollToTop: true, // Whether to scroll to top on page change
scrollOffset: 100, // Offset from top when scrolling (in pixels)
texts: { // Customizable text labels
prev: 'Previous',
next: 'Next',
page: 'Page'
},
classes: { // Customizable CSS classes
paginationContainer: 'flex items-center justify-center space-x-2 mt-8 mb-12',
pageButton: 'px-3 py-1 rounded-md transition-colors duration-200',
activePageButton: 'bg-indigo-500 text-white',
inactivePageButton: 'bg-gray-200 text-gray-700 hover:bg-gray-300 dark:bg-gray-700 dark:text-gray-200 dark:hover:bg-gray-600',
prevNextButton: 'px-4 py-1 rounded-md bg-gray-200 hover:bg-gray-300 text-gray-700 dark:bg-gray-700 dark:text-gray-200 dark:hover:bg-gray-600 transition-colors duration-200',
disabledButton: 'opacity-50 cursor-not-allowed'
}
};
// Wait for the DOM to be fully loaded
document.addEventListener('DOMContentLoaded', function() {
// Find the container element
const container = document.querySelector(config.containerSelector);
if (!container) return;
// Find all posts
const posts = Array.from(container.querySelectorAll(config.postSelector));
if (!posts.length) return;
// Calculate the total number of pages
const totalPages = Math.ceil(posts.length / config.postsPerPage);
if (totalPages <= 1) return; // No need for pagination if there's only one page
// Create pagination controls container
const paginationContainer = document.createElement('div');
paginationContainer.className = config.classes.paginationContainer;
paginationContainer.setAttribute('role', 'navigation');
paginationContainer.setAttribute('aria-label', 'Pagination');
// Create state for current page
let currentPage = 1;
// Function to update the visible posts
function updateVisiblePosts() {
// Calculate indexes
const startIndex = (currentPage - 1) * config.postsPerPage;
const endIndex = Math.min(startIndex + config.postsPerPage, posts.length);
// Hide all posts
posts.forEach(post => post.style.display = 'none');
// Show only the posts for the current page
for (let i = startIndex; i < endIndex; i++) {
posts[i].style.display = 'block';
}
// Update pagination controls
updatePaginationControls();
// Scroll to top if configured
if (config.scrollToTop) {
window.scrollTo({
top: container.offsetTop - config.scrollOffset,
behavior: 'smooth'
});
}
}
// Function to update pagination controls
function updatePaginationControls() {
// Clear previous controls
paginationContainer.innerHTML = '';
// Add previous button
const prevButton = document.createElement('button');
prevButton.textContent = config.texts.prev;
prevButton.className = config.classes.prevNextButton;
prevButton.setAttribute('aria-label', 'Go to previous page');
if (currentPage === 1) {
prevButton.disabled = true;
prevButton.className += ' ' + config.classes.disabledButton;
} else {
prevButton.addEventListener('click', () => {
currentPage--;
updateVisiblePosts();
});
}
paginationContainer.appendChild(prevButton);
// Add page number buttons if configured
if (config.showPageNumbers) {
// Determine which page numbers to show
let startPage = Math.max(1, currentPage - Math.floor(config.maxPageButtons / 2));
let endPage = Math.min(totalPages, startPage + config.maxPageButtons - 1);
// Adjust if we're near the end
if (endPage - startPage + 1 < config.maxPageButtons) {
startPage = Math.max(1, endPage - config.maxPageButtons + 1);
}
// Add ellipsis at start if needed
if (startPage > 1) {
const ellipsis = document.createElement('span');
ellipsis.textContent = '...';
ellipsis.className = 'px-2';
paginationContainer.appendChild(ellipsis);
}
// Add page buttons
for (let i = startPage; i <= endPage; i++) {
const pageButton = document.createElement('button');
pageButton.textContent = i;
pageButton.className = config.classes.pageButton + ' ' +
(i === currentPage ? config.classes.activePageButton : config.classes.inactivePageButton);
pageButton.setAttribute('aria-label', `${config.texts.page} ${i}`);
pageButton.setAttribute('aria-current', i === currentPage ? 'page' : 'false');
if (i !== currentPage) {
pageButton.addEventListener('click', () => {
currentPage = i;
updateVisiblePosts();
});
}
paginationContainer.appendChild(pageButton);
}
// Add ellipsis at end if needed
if (endPage < totalPages) {
const ellipsis = document.createElement('span');
ellipsis.textContent = '...';
ellipsis.className = 'px-2';
paginationContainer.appendChild(ellipsis);
}
}
// Add next button
const nextButton = document.createElement('button');
nextButton.textContent = config.texts.next;
nextButton.className = config.classes.prevNextButton;
nextButton.setAttribute('aria-label', 'Go to next page');
if (currentPage === totalPages) {
nextButton.disabled = true;
nextButton.className += ' ' + config.classes.disabledButton;
} else {
nextButton.addEventListener('click', () => {
currentPage++;
updateVisiblePosts();
});
}
paginationContainer.appendChild(nextButton);
}
// Initialize pagination
container.appendChild(paginationContainer);
updateVisiblePosts();
// Add a post counter
const counter = document.createElement('div');
counter.className = 'text-sm text-center text-gray-600 dark:text-gray-400 mt-2';
counter.textContent = `Showing page ${currentPage} of ${totalPages} (${posts.length} total posts)`;
paginationContainer.after(counter);
// Update counter when page changes
const updateCounter = () => {
counter.textContent = `Showing page ${currentPage} of ${totalPages} (${posts.length} total posts)`;
};
// Add event listeners to update counter
paginationContainer.addEventListener('click', updateCounter);
});
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment