Dynamic Blog with JavaScript
by Kuligaposten 2022-02-16
Building a fully dynamic blog without a backend
Building a Dynamic Blog with JavaScript: Pagination, Search, and Recent Posts
Building a fully dynamic blog without a backend can seem challenging, but with JavaScript, you can efficiently handle pagination, search, category filtering, and recent posts—all while keeping it fast and user-friendly.
In this guide, we'll create a blog system that:
- Dynamically displays blog posts
- Supports pagination using Bootstrap 5
- Includes search and category filters
- Shows recent posts (excluding the current post)
- Updates the URL (
?page=2&search=term) for better UX
Let's dive in!
1. Preparing the Blog Data
Instead of manually adding blog posts to the page, we'll store them in a JavaScript file (blogData.js) that looks like this:
window.data = [
{
title: "Useful links for devs",
excerpt: "A collection of great resources for developers.",
image: "assets/img/fundamentals.svg",
pageLink: "article/useful-links-for-devs.html",
date: "2024-12-12",
categories: ["Development", "Resources"],
},
{
title: "Spring in Kyoto",
excerpt: "Exploring Kyoto during the beautiful spring season.",
image: "assets/img/articles/kyoto.webp",
pageLink: "article/spring-in-kyoto.html",
date: "2024-09-28",
categories: ["Travel", "Japan"],
},
];
This allows us to dynamically generate the blog posts instead of hardcoding them in HTML.
2. Implementing Pagination, Search & Filtering
We’ll create a BlogPagination class to manage search, pagination, and category filtering.
JavaScript: BlogPagination.js
class BlogPagination {
constructor(
containerSelector,
paginationSelector,
searchSelector,
filterSelector,
perPage = 6,
) {
this.container = document.querySelector(containerSelector);
this.paginationContainer = document.querySelector(paginationSelector);
this.searchInput = document.querySelector(searchSelector);
this.filterSelect = document.querySelector(filterSelector);
this.perPage = perPage;
this.currentPage = 1;
this.searchQuery = "";
this.selectedCategory = "";
this.data = window.data;
this.filteredData = [...this.data];
this.init();
}
init() {
this.handleURLParams();
this.renderPage(this.currentPage);
this.setupPagination();
if (this.searchInput) {
this.searchInput.addEventListener("input", () => this.filterResults());
}
if (this.filterSelect) {
this.populateCategories();
this.filterSelect.addEventListener("change", () => this.filterResults());
}
}
// Populate categories from data
populateCategories() {
const categories = [
...new Set(this.data.flatMap((post) => post.categories)),
];
this.filterSelect.innerHTML =
`<option value="">All Categories</option>` +
categories
.map((category) => `<option value="${category}">${category}</option>`)
.join("");
}
renderPage(page = 1) {
this.container.innerHTML = "";
this.currentPage = page;
const start = (page - 1) * this.perPage;
const end = start + this.perPage;
const currentPosts = this.filteredData.slice(start, end);
currentPosts.forEach((post) => {
const postElement = document.createElement("div");
postElement.classList.add("col-md-4", "mb-4");
postElement.innerHTML = `
<div class="card h-100">
<a class="link-light d-block text-decoration-none" href="${post.pageLink}">
<img src="${post.image}" class="card-img-top" alt="${post.title}">
<div class="card-body">
<h5 class="card-title">${post.title}</h5>
<p class="card-text">${post.excerpt}</p>
<small class="text-muted d-block mt-2">${post.date}</small>
</div>
</a>
</div>
`;
this.container.appendChild(postElement);
});
this.setupPagination();
}
// Filter results based on search and category
filterResults(isUserAction = true) {
this.searchQuery = this.searchInput.value.trim().toLowerCase();
this.selectedCategory = this.filterSelect.value;
this.filteredData = this.data.filter(
(post) =>
post.title.toLowerCase().includes(this.searchQuery) &&
(!this.selectedCategory ||
post.categories.includes(this.selectedCategory)),
);
if (isUserAction) {
this.currentPage = 1;
}
this.renderPage(this.currentPage);
this.setupPagination();
this.updateURL();
}
setupPagination() {
const totalPages = Math.ceil(this.filteredData.length / this.perPage);
this.paginationContainer.innerHTML = "";
if (totalPages <= 1) return;
const ul = document.createElement("ul");
ul.classList.add("pagination", "justify-content-center");
for (let i = 1; i <= totalPages; i++) {
const li = document.createElement("li");
li.classList.add("page-item");
if (i === this.currentPage) li.classList.add("active");
li.innerHTML = `<a class="page-link" href="?page=${i}">${i}</a>`;
li.addEventListener("click", (event) => {
event.preventDefault();
this.changePage(i);
});
ul.appendChild(li);
}
this.paginationContainer.appendChild(ul);
}
changePage(page) {
this.currentPage = page;
this.renderPage(page);
this.updateURL();
}
updateURL() {
const params = new URLSearchParams();
if (this.searchQuery) params.set("search", this.searchQuery);
if (this.selectedCategory) params.set("category", this.selectedCategory);
if (this.currentPage > 1) params.set("page", this.currentPage);
history.pushState({}, "", `?${params.toString()}`);
}
handleURLParams() {
const params = new URLSearchParams(window.location.search);
this.searchQuery = params.get("search") || "";
this.selectedCategory = params.get("category") || "";
this.currentPage = parseInt(params.get("page"), 10) || 1;
this.filterResults(false);
}
}
// Initialize the pagination system
document.addEventListener("DOMContentLoaded", () => {
new BlogPagination(
"#blog-container",
"#pagination-controls",
"#search-input",
"#category-filter",
6,
);
});
3. Rendering Recent Posts
For individual post pages, exclude the current post from the recent posts list:
function renderRecentPosts() {
const recentContainer = document.querySelector("#recent-post");
if (!recentContainer) return;
const currentPageUrl = window.location.pathname;
const recentPosts = window.data
.filter((post) => !currentPageUrl.includes(post.pageLink))
.sort((a, b) => new Date(b.date) - new Date(a.date))
.slice(0, 3);
recentContainer.innerHTML = recentPosts
.map(
(post) => `
<div class="col-md-4 mb-4">
<div class="card h-100">
<a class="link-light d-block text-decoration-none" href="${post.pageLink}">
<img src="${post.image}" class="card-img-top" alt="${post.title}">
<div class="card-body">
<h5 class="card-title">${post.title}</h5>
<p class="card-text">${post.excerpt}</p>
</div>
</a>
</div>
</div>
`,
)
.join("");
}
document.addEventListener("DOMContentLoaded", renderRecentPosts);
Now Your Blog is Fully Dynamic!
✔ Pagination, search, and filtering
✔ Excludes current post from recent posts
✔ Works with Bootstrap 5
Ready to build your own blog?