Excerpts
by Kuligaposten 2025-02-16
Creating a Searchable, Paginated Excerpt Viewer & PDF Generator with JavaScript
Creating a Searchable, Paginated Excerpt Viewer & PDF Generator with JavaScript
A JSON-driven approach using JavaScript, Bootstrap, and jsPDF
Introduction
If you have a large collection of articles, documentation, or blog posts, you might want to:
✅ Display short excerpts for quick browsing
✅ Let users toggle between excerpt & full content
✅ Provide search functionality for easy filtering
✅ Implement pagination to navigate large datasets
✅ Allow users to generate PDFs of content
In this guide, we’ll build a fully interactive, JSON-driven UI that supports search, pagination, and PDF generation—all powered by plain JavaScript
Pagination dynamically updates the displayed excerpts while ensuring search results remain accessible. It prevents performance issues when handling large datasets.
Technologies Used
- JavaScript (ES6) – Handles data rendering & interactivity
- Bootstrap 5 – Provides styling & responsive layout
- jsPDF – Generates PDFs dynamically
- JSON Data – Stores and retrieves content dynamically
Step 1: JSON-Driven Data
Instead of hardcoding content in HTML, we use a structured JSON dataset:
window.data = [
{
category: 'JavaScript',
title: 'Understanding Closures',
content:
'A closure is a function that remembers variables from its enclosing scope even after execution...',
},
{
category: 'CSS',
title: 'Flexbox vs Grid Layout',
content:
'Flexbox is a one-dimensional layout system, whereas Grid allows precise two-dimensional placement...',
},
{
category: 'Security',
title: 'Understanding CORS',
content:
'Cross-Origin Resource Sharing (CORS) allows restricted resources on a webpage to be accessed from another domain...',
},
];
✔ Structured data format for easy updates
✔ Can be fetched from an API or a static JSON file
Step 2: Search & Pagination Setup
We implement client-side search & pagination to make navigating large datasets smooth.
🔹 The Pagination & Search Class
class PaginatedSearch {
constructor(data, itemsPerPage = 6) {
this.data = data;
this.itemsPerPage = itemsPerPage;
this.currentPage = 1;
this.filteredData = data; // Stores search results
this.searchInput = document.getElementById('searchInput');
// Attach search event listener
this.searchInput.addEventListener('input', this.searchData.bind(this));
this.renderCarCards();
}
// 🔍 Search Functionality
searchData() {
const query = this.searchInput.value.toLowerCase();
this.filteredData = this.data.filter(
(item) =>
item.title.toLowerCase().includes(query) ||
item.content.toLowerCase().includes(query)
);
this.currentPage = 1; // Reset to page 1 after search
this.renderCarCards();
}
// 📄 Render Cards with Pagination
renderCarCards() {
const excerptCards = document.getElementById('excerptCards');
excerptCards.innerHTML = '';
const startIndex = (this.currentPage - 1) * this.itemsPerPage;
const paginatedItems = this.filteredData.slice(
startIndex,
startIndex + this.itemsPerPage
);
let htmlString = '';
if (paginatedItems.length === 0) {
htmlString = `<p class="text-center text-muted">No results found.</p>`;
} else {
paginatedItems.forEach((item, index) => {
const dataIndex = startIndex + index;
htmlString += `<div class="col mb-4 gy-0">
<div class="d-flex flex-column bg-info-subtle p-4 rounded-4 shadow h-100">
<span class="badge rounded-pill bg-primary mb-2">${item.category}</span>
<h4>${item.title}</h4>
<p class="toggle-text flex-grow-1 excerpt" data-index="${dataIndex}" data-state="half">
${item.content.slice(0, Math.floor(item.content.length / 2)).trim() + '...'}
</p>
<button class="btn btn-sm btn-outline-primary mt-2 generate-pdf" data-index="${dataIndex}">
Generate PDF
</button>
</div>
</div>`;
});
}
excerptCards.innerHTML = htmlString.trim();
this.renderPaginationControls();
}
renderPaginationControls() {
const pagination = document.getElementById('pagination');
pagination.innerHTML = '';
const totalPages = Math.ceil(this.filteredData.length / this.itemsPerPage);
if (totalPages <= 1) return;
let paginationHTML = `<nav><ul class="pagination justify-content-center">`;
for (let i = 1; i <= totalPages; i++) {
paginationHTML += `<li class="page-item ${i === this.currentPage ? 'active' : ''}">
<a class="page-link" href="#" data-page="${i}">${i}</a></li>`;
}
paginationHTML += `</ul></nav>`;
pagination.innerHTML = paginationHTML;
document.querySelectorAll('.page-link').forEach((button) => {
button.addEventListener('click', (event) => {
event.preventDefault();
this.currentPage = parseInt(event.target.getAttribute('data-page'));
this.renderCarCards();
});
});
}
}
✔ Dynamically updates the search results & pagination
✔ Keeps everything fast with client-side filtering
Step 3: Toggling Full Content
function toggleContent(element, index) {
const isHalfContent = element.getAttribute('data-state') !== 'full';
element.style.opacity = 0;
setTimeout(() => {
element.textContent = isHalfContent
? window.data[index].content
: window.data[index].content
.slice(0, Math.floor(window.data[index].content.length / 2))
.trim() + '...';
element.setAttribute('data-state', isHalfContent ? 'full' : 'half');
element.style.opacity = 1;
}, 250);
}
✔ Smooth content transition using opacity
✔ Prevents text from being interpreted as HTML
Step 4: Generating PDFs
function generatePdf(index) {
const { jsPDF } = window.jspdf;
const doc = new jsPDF();
const item = window.data[index];
const title = item.title.replace(/</g, '<').replace(/>/g, '>');
const content = item.content.replace(/</g, '<').replace(/>/g, '>');
doc.setProperties({
title: title,
subject: item.category,
author: 'Kuligaposten',
});
doc.setFont('helvetica', 'bold');
doc.setFontSize(16);
doc.text(title, 10, 20);
doc.setFont('helvetica', 'normal');
doc.setFontSize(12);
doc.text(content, 10, 30, { maxWidth: 180 });
const pdfBlob = doc.output('blob');
const pdfUrl = URL.createObjectURL(pdfBlob);
window.open(pdfUrl, '_blank'); // Open in new tab
}
✔ Opens PDF in a new tab to bypass Chrome sandbox restrictions
✔ Formats title & content properly for readability
📌 How to use it
// Initialize the PaginatedSearch instance globally
let paginatedSearchInstance;
// Initial setup
document.addEventListener('DOMContentLoaded', () => {
// Create the instance of PaginatedSearch
paginatedSearchInstance = new PaginatedSearch(window.data);
});
The Final Result
✔ JSON-driven content
✔ Client-side search & pagination
✔ Smooth excerpt toggling
✔ Instant PDF generation
With this script, you can efficiently display, search, and manage excerpts while allowing users to generate PDFs. This approach keeps the UI clean and user-friendly, making it ideal for blogs, documentation sites, and educational resources.
This script is lightweight, efficient, and easy to integrate into any blog or documentation site!
Since Chrome blocks Blob URLs in sandboxed iframes, we use window.open() to ensure the PDF always loads properly.
👉 Click an excerpt to expand the full content, or generate a PDF for offline reading.
| Column 1 | Column 2 |
|---|---|
| Cell 1 | Cell 2 |
| Cell 3 | Cell 4 |
| Cell 1 | Cell 2 |
| Cell 3 | Cell 4 |
| Cell 1 | Cell 2 |
| Cell 3 | Cell 4 |
| Cell 1 | Cell 2 |
| Cell 3 | Cell 4 |
| Cell 1 | Cell 2 |
| Cell 3 | Cell 4 |
| Cell 1 | Cell 2 |
| Cell 3 | Cell 4 |
| Cell 1 | Cell 2 |
| Cell 3 | Cell 4 |
| Cell 1 | Cell 2 |
| Cell 3 | Cell 4 |