JavaScript Notes
Build a fully interactive sortable and filterable table using vanilla JavaScript. Learn DOM manipulation, Array sort/filter methods, event delegation, and dynamic rendering in this hands-on intermediate project.
Introduction
Tables are one of the most common UI components in web applications — from admin dashboards to e-commerce product listings. A static HTML table is useless when users need to find specific data quickly or organize columns by name, price, or date.
In this project, you will build a Sortable & Filterable Table using pure vanilla JavaScript. You will learn:
- How to store data in a JavaScript array and render a table dynamically
- How to sort columns in ascending/descending order on header click
- How to filter rows in real time as the user types in a search box
- How to use event delegation for efficient event handling
Summary: We will build a table that sorts when you click a column header (A→Z or Z→A) and filters in real-time as you type in the search box — without any page reload!
HTML Table Structure
We need a search input and a table with clickable headers:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Sortable & Filterable Table</title>
<style>
body { font-family: 'Segoe UI', sans-serif; background: #f0f4f8; padding: 2rem; }
.container { max-width: 800px; margin: 0 auto; }
.search-box {
width: 100%; padding: 0.75rem 1rem; font-size: 1rem;
border: 2px solid #cbd5e0; border-radius: 8px; margin-bottom: 1rem;
}
.search-box:focus { border-color: #4299e1; outline: none; }
table { width: 100%; border-collapse: collapse; background: white; border-radius: 8px; overflow: hidden; }
th { background: #2d3748; color: white; padding: 0.85rem 1rem; cursor: pointer; user-select: none; text-align: left; }
th:hover { background: #4a5568; }
th .sort-icon { margin-left: 0.5rem; font-size: 0.75rem; }
td { padding: 0.75rem 1rem; border-bottom: 1px solid #e2e8f0; }
tr:hover td { background: #ebf8ff; }
.no-results { text-align: center; padding: 2rem; color: #718096; }
</style>
</head>
<body>
<div class="container">
<h1>Sortable & Filterable Table</h1>
<input type="text" class="search-box" id="searchInput" placeholder="Search by name, city, or department..." />
<table id="dataTable">
<thead><tr id="tableHead"></tr></thead>
<tbody id="tableBody"></tbody>
</table>
</div>
<script src="app.js"></script>
</body>
</html>Notice: The <thead> and <tbody> are empty — JavaScript will fill them dynamically. This is the data-driven approach.
JavaScript — Step by Step
Data Array
Our data source is an array of objects. Each object represents one row:
Note: This is our "source of truth" — all sorting and filtering will happen on this array, not on the DOM.
Render Function
The heart of the application — reads data, applies sort/filter, updates DOM:
function renderTable() {
let processedData = filterData(employees, state.searchQuery);
if (state.sortColumn) {
processedData = sortData(processedData, state.sortColumn, state.sortDirection);
}
renderHeaders();
renderBody(processedData);
}// On initial call: Headers rendered, all 10 rows displayed
Sort by Column
Sorting handles both strings and numbers correctly:
// sortData(employees, "salary", "desc") // → Vikram (120000), Meera (110000), Arjun (95000), Karan (91000)...
Important: We use [...data].sort() instead of data.sort() to avoid mutating the original array.
Filter by Search Input
The filter checks if any column value contains the search text:
// filterData(employees, "mumbai")
// → [{ name: "Aarav Sharma", city: "Mumbai"... }, { name: "Karan Mehta", city: "Mumbai"... }]How it works: Object.values(row) gets all values, .some() returns true if ANY value contains the query, .filter() keeps only matching rows.
Sort Direction Toggle
Clicking the same column header flips the direction:
function handleSort(column) {
if (state.sortColumn === column) {
state.sortDirection = state.sortDirection === "asc" ? "desc" : "asc";
} else {
state.sortColumn = column;
state.sortDirection = "asc";
}
renderTable();
}// Click "Name" → asc (Aarav, Ananya, Arjun...) // Click "Name" again → desc (Vikram, Sneha, Rahul...) // Click "Salary" → new column, resets to asc
Render Headers and Body
DOM rendering with sort indicators:
Event Listeners with Delegation
One listener on the parent handles all header clicks:
// User types "eng" in search → state.searchQuery = "eng" // → renderTable() → Only Engineering rows shown (4 rows) // Clear search → All 10 rows return with current sort preserved
Complete Code
Full app.js combining all pieces:
// === COMPLETE APP BEHAVIOR === // Page Load → 10 rows displayed, no sort, no filter // Click "Age" → Rows: 25, 27, 28, 29, 31, 33, 34, 36, 38, 42 (▲) // Click "Age" again → Rows: 42, 38, 36, 34, 33, 31, 29, 28, 27, 25 (▼) // Type "design" → Only 2 rows: Sneha Gupta, Divya Nair // Clear search → All 10 rows return with current sort preserved
Key Takeaways
1. Data-Driven Rendering Never store state inside the DOM. Keep one JavaScript array as the "source of truth" and re-render from it. This prevents bugs where DOM and data go out of sync.
2. Immutable Operations with Spread Always use [...data].sort() instead of data.sort(). The spread creates a copy so your original array stays untouched, making filter and sort independent.
3. Array.filter() for Search Array.filter() creates a new array with only passing elements. Combined with Object.values() and includes(), it enables multi-column search in 3 lines.
4. Array.sort() Comparator The comparator returns negative (a first), zero (equal), or positive (b first). Multiplying by -1 reverses direction elegantly.
5. Event Delegation Attach one listener to the parent element and use event.target.closest("th") to identify the clicked header. Works even after re-renders.
6. Template Literals for HTML Using .map().join("") with backtick strings is the cleanest way to generate HTML from arrays without a framework.
7. State Object Pattern Centralizing sort column, direction, and search query in one object makes the application predictable and debuggable.
8. Separation of Concerns Code split into layers: Data → Processing (sort/filter) → Rendering → Event Handling. Each function does one thing, making it testable and extendable.
FAQ
Q1: How can I add pagination to this table?
Add currentPage and rowsPerPage to state. After filtering and sorting, slice the array:
const start = (state.currentPage - 1) * state.rowsPerPage;
const paginatedData = processedData.slice(start, start + state.rowsPerPage);Render pagination buttons that update state.currentPage and call renderTable().
Q2: Why innerHTML instead of createElement? Is it unsafe?
For hardcoded data (not user-generated), innerHTML with template literals is simpler and safe. Modern browsers optimize it well for bulk updates. For user-generated content in production, sanitize input or use textContent for data values.
Q3: How to use API data instead of a hardcoded array?
Replace the hardcoded array with fetch:
let employees = [];
async function loadData() {
const response = await fetch("https://api.example.com/employees");
employees = await response.json();
renderTable();
}
// Call loadData() in initializeApp instead of immediate renderTable()The sort/filter/render code works identically because it operates on the array regardless of data source.
Exam Focus
Revise definitions, diagrams, examples, and short-answer points for JavaScript Sortable Filterable Table — DOM & Array Methods Project 2026.
Interview Use
Prepare one clear explanation, one practical example, and one common mistake for this JavaScript Master Course topic.
Search Terms
javascript-complete-guide, javascript master course, javascript, complete, guide, mini, projects, sortable
Related JavaScript Master Course Topics