Skip to content

Instantly share code, notes, and snippets.

@mirzap
Created April 1, 2025 09:00
Show Gist options
  • Save mirzap/ec6602ef1d6f4170406491eda22d43b0 to your computer and use it in GitHub Desktop.
Save mirzap/ec6602ef1d6f4170406491eda22d43b0 to your computer and use it in GitHub Desktop.
1 million rows table - Plain JS
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Paginated Events Table</title>
<style>
body {
font-family: Arial, sans-serif;
}
#controls {
width: 90%;
max-width: 1000px;
margin: 20px auto;
display: flex;
flex-wrap: wrap;
gap: 10px;
justify-content: space-between;
}
#controls > * {
flex: 1 1 22%;
}
#table-container {
width: 90%;
max-width: 1000px;
margin: auto;
overflow-x: auto;
}
table {
width: 100%;
border-collapse: collapse;
}
th, td {
padding: 8px;
border: 1px solid #ddd;
text-align: left;
font-size: 14px;
}
#pagination {
display: flex;
justify-content: center;
gap: 10px;
margin: 20px;
}
#pagination button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
</style>
</head>
<body>
<div id="controls">
<input type="text" id="searchInput" placeholder="Search by name, email or message" />
<select id="sortSelect">
<option value="id">Sort by ID</option>
<option value="cc">Sort by Country Code</option>
<option value="plan">Sort by Plan</option>
<option value="size">Sort by Size</option>
</select>
<select id="planFilter">
<option value="">All Plans</option>
<option value="free">Free</option>
<option value="pro">Pro</option>
<option value="enterprise">Enterprise</option>
</select>
<select id="sizeFilter">
<option value="">All Sizes</option>
<option value="s">S</option>
<option value="m">M</option>
<option value="l">L</option>
<option value="xl">XL</option>
</select>
<button onclick="applyFiltersAndSort()">Apply</button>
</div>
<div id="table-container">
<table>
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Email</th>
<th>Message</th>
<th>Country</th>
<th>Plan</th>
<th>Size</th>
</tr>
</thead>
<tbody id="table-body"></tbody>
</table>
</div>
<div id="pagination">
<button id="firstPageBtn" onclick="firstPage()">First</button>
<button id="prevPageBtn" onclick="prevPage()">Previous</button>
<span id="page-info"></span>
<button id="nextPageBtn" onclick="nextPage()">Next</button>
<button id="lastPageBtn" onclick="lastPage()">Last</button>
</div>
<script>
const events = [];
let currentEntries = events;
let currentPage = 0;
const pageSize = 100;
function add_events(input) {
const lines = input.split('\n');
for (let line of lines) {
if (line.trim()) {
try {
events.push(JSON.parse(line.trim()));
} catch (e) {
console.warn('Skipping malformed line:', line);
}
}
}
currentEntries = events;
renderTable();
}
function renderTable() {
const tbody = document.getElementById('table-body');
tbody.innerHTML = '';
const totalPages = Math.ceil(currentEntries.length / pageSize);
const start = currentPage * pageSize;
const end = Math.min(currentEntries.length, start + pageSize);
document.getElementById('page-info').textContent = `Page ${currentPage + 1} of ${totalPages}`;
document.getElementById('firstPageBtn').disabled = currentPage === 0;
document.getElementById('prevPageBtn').disabled = currentPage === 0;
document.getElementById('nextPageBtn').disabled = currentPage >= totalPages - 1;
document.getElementById('lastPageBtn').disabled = currentPage >= totalPages - 1;
for (let i = start; i < end; i++) {
const e = currentEntries[i];
const row = document.createElement('tr');
row.innerHTML = `
<td>${e.data.id}</td>
<td>${e.data.name}</td>
<td>${e.data.email}</td>
<td>${e.data.message}</td>
<td>${e.data.cc}</td>
<td>${e.data.plan}</td>
<td>${e.data.size}</td>
`;
tbody.appendChild(row);
}
}
function nextPage() {
const totalPages = Math.ceil(currentEntries.length / pageSize);
if (currentPage < totalPages - 1) {
currentPage++;
renderTable();
}
}
function prevPage() {
if (currentPage > 0) {
currentPage--;
renderTable();
}
}
function firstPage() {
currentPage = 0;
renderTable();
}
function lastPage() {
const totalPages = Math.ceil(currentEntries.length / pageSize);
currentPage = totalPages - 1;
renderTable();
}
function sortEntries(sort_by = 'id', ascending = true) {
const planOrder = ['free', 'pro', 'enterprise'];
const sizeOrder = ['s', 'm', 'l', 'xl'];
currentEntries = [...currentEntries].sort((a, b) => {
let compare = 0;
if (sort_by === 'id') compare = a.data.id - b.data.id;
else if (sort_by === 'cc') compare = a.data.cc.localeCompare(b.data.cc);
else if (sort_by === 'size') compare = sizeOrder.indexOf(a.data.size || 's') - sizeOrder.indexOf(b.data.size || 's');
else if (sort_by === 'plan') compare = planOrder.indexOf(a.data.plan) - planOrder.indexOf(b.data.plan);
return ascending ? compare : -compare;
});
}
function filterEntries({ type, plan, size }) {
currentEntries = events.filter(el => {
return (!type || el.type === type) &&
(!plan || el.data.plan === plan) &&
(!size || el.data.size === size);
});
}
function search(query) {
const q = query.toLowerCase();
currentEntries = currentEntries.filter(event => {
return (event.data.message || '').toLowerCase().includes(q) ||
(event.data.name || '').toLowerCase().includes(q) ||
(event.data.email || '').toLowerCase().includes(q);
});
}
function applyFiltersAndSort() {
const sortBy = document.getElementById('sortSelect').value;
const plan = document.getElementById('planFilter').value;
const size = document.getElementById('sizeFilter').value;
const searchQuery = document.getElementById('searchInput').value;
currentEntries = [...events];
filterEntries({ plan, size });
sortEntries(sortBy);
if (searchQuery) {
search(searchQuery);
}
currentPage = 0;
renderTable();
}
const simulatedInputLines = [];
for (let i = 0; i < 1000000; i++) {
simulatedInputLines.push(JSON.stringify({
type: "question",
data: {
cc: "fr",
email: `user${i + 1}@example.com`,
id: i + 1,
message: `Message number ${i + 1}`,
name: `User #${i + 1}`,
plan: ['free', 'pro', 'enterprise'][i % 3],
size: ['s', 'm', 'l', 'xl'][i % 4]
}
}));
}
function addInChunks(lines, chunkSize = 5000) {
let index = 0;
function processChunk() {
const chunk = lines.slice(index, index + chunkSize);
add_events(chunk.join('\n'));
index += chunkSize;
if (index < lines.length) {
setTimeout(processChunk, 0);
}
}
processChunk();
}
addInChunks(simulatedInputLines);
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment