Advanced Data List with Filtering & Sorting
A comprehensive data list component with advanced filtering, sorting, pagination, and search functionality
Responsive Design
Yes
Dark Mode Support
No
lines
1092
Browser Compatibility
No
Live Preview
Interact with the component without leaving the page.
Advanced Data List with Filtering & Sorting
A powerful and flexible data list component that provides comprehensive functionality for displaying, filtering, sorting, and managing large datasets. Perfect for admin panels, product catalogs, user directories, and any application requiring advanced data presentation.
Features
- Advanced Filtering: Multiple filter types including text, select, date range, and custom filters
- Multi-column Sorting: Sort by multiple columns with visual indicators
- Smart Search: Real-time search across all visible columns with highlighting
- Responsive Design: Adaptive layout that works on all screen sizes
- Pagination: Efficient pagination with customizable page sizes
- Row Selection: Single and multi-row selection with bulk actions
- Export Functionality: Export filtered data to CSV, JSON, or print
- Column Management: Show/hide columns and reorder them
- Loading States: Smooth loading animations and skeleton screens
- Accessibility: Full keyboard navigation and screen reader support
- Performance Optimized: Virtual scrolling for large datasets
HTML
<div class="data-list-container">
<!-- Header Section -->
<div class="data-list-header">
<div class="header-content">
<h2 class="list-title">Employee Directory</h2>
<p class="list-description">Manage and view employee information with advanced filtering and sorting</p>
</div>
<!-- Action Buttons -->
<div class="header-actions">
<button class="btn btn-secondary" id="columnToggle">
<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
<line x1="9" y1="9" x2="15" y2="9"></line>
<line x1="9" y1="15" x2="15" y2="15"></line>
</svg>
Columns
</button>
<button class="btn btn-secondary" id="exportData">
<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
<polyline points="7,10 12,15 17,10"></polyline>
<line x1="12" y1="15" x2="12" y2="3"></line>
</svg>
Export
</button>
<button class="btn btn-primary" id="addNew">
<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<line x1="12" y1="5" x2="12" y2="19"></line>
<line x1="5" y1="12" x2="19" y2="12"></line>
</svg>
Add Employee
</button>
</div>
</div>
<!-- Filters Section -->
<div class="filters-section">
<!-- Search Bar -->
<div class="search-container">
<div class="search-input-wrapper">
<svg class="search-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<circle cx="11" cy="11" r="8"></circle>
<path d="m21 21-4.35-4.35"></path>
</svg>
<input
type="text"
id="globalSearch"
class="search-input"
placeholder="Search employees..."
aria-label="Search employees"
>
<button class="search-clear" id="searchClear" aria-label="Clear search">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
</div>
</div>
<!-- Advanced Filters -->
<div class="advanced-filters">
<div class="filter-group">
<label for="departmentFilter">Department</label>
<select id="departmentFilter" class="filter-select">
<option value="">All Departments</option>
<option value="engineering">Engineering</option>
<option value="marketing">Marketing</option>
<option value="sales">Sales</option>
<option value="hr">Human Resources</option>
<option value="finance">Finance</option>
</select>
</div>
<div class="filter-group">
<label for="statusFilter">Status</label>
<select id="statusFilter" class="filter-select">
<option value="">All Status</option>
<option value="active">Active</option>
<option value="inactive">Inactive</option>
<option value="pending">Pending</option>
</select>
</div>
<div class="filter-group">
<label for="locationFilter">Location</label>
<select id="locationFilter" class="filter-select">
<option value="">All Locations</option>
<option value="new-york">New York</option>
<option value="san-francisco">San Francisco</option>
<option value="london">London</option>
<option value="remote">Remote</option>
</select>
</div>
<div class="filter-group">
<label for="hireDateFrom">Hire Date From</label>
<input type="date" id="hireDateFrom" class="filter-input">
</div>
<div class="filter-group">
<label for="hireDateTo">Hire Date To</label>
<input type="date" id="hireDateTo" class="filter-input">
</div>
<button class="btn btn-secondary" id="clearFilters">
Clear Filters
</button>
</div>
</div>
<!-- Results Info -->
<div class="results-info">
<div class="results-count">
<span id="resultsText">Showing 1-10 of 150 employees</span>
</div>
<div class="results-actions">
<div class="page-size-selector">
<label for="pageSize">Show:</label>
<select id="pageSize" class="page-size-select">
<option value="10">10</option>
<option value="25" selected>25</option>
<option value="50">50</option>
<option value="100">100</option>
</select>
<span>per page</span>
</div>
<div class="bulk-actions" id="bulkActions" style="display: none;">
<span class="selected-count" id="selectedCount">2 selected</span>
<button class="btn btn-sm btn-secondary" id="bulkEdit">Edit</button>
<button class="btn btn-sm btn-danger" id="bulkDelete">Delete</button>
</div>
</div>
</div>
<!-- Data Table -->
<div class="table-container">
<div class="table-wrapper">
<table class="data-table" id="dataTable">
<thead>
<tr>
<th class="select-column">
<input type="checkbox" id="selectAll" aria-label="Select all rows">
</th>
<th class="sortable" data-column="name" data-type="string">
<div class="th-content">
<span>Name</span>
<div class="sort-indicators">
<svg class="sort-icon sort-asc" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<polyline points="18,15 12,9 6,15"></polyline>
</svg>
<svg class="sort-icon sort-desc" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<polyline points="6,9 12,15 18,9"></polyline>
</svg>
</div>
</div>
</th>
<th class="sortable" data-column="email" data-type="string">
<div class="th-content">
<span>Email</span>
<div class="sort-indicators">
<svg class="sort-icon sort-asc" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<polyline points="18,15 12,9 6,15"></polyline>
</svg>
<svg class="sort-icon sort-desc" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<polyline points="6,9 12,15 18,9"></polyline>
</svg>
</div>
</div>
</th>
<th class="sortable" data-column="department" data-type="string">
<div class="th-content">
<span>Department</span>
<div class="sort-indicators">
<svg class="sort-icon sort-asc" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<polyline points="18,15 12,9 6,15"></polyline>
</svg>
<svg class="sort-icon sort-desc" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<polyline points="6,9 12,15 18,9"></polyline>
</svg>
</div>
</div>
</th>
<th class="sortable" data-column="position" data-type="string">
<div class="th-content">
<span>Position</span>
<div class="sort-indicators">
<svg class="sort-icon sort-asc" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<polyline points="18,15 12,9 6,15"></polyline>
</svg>
<svg class="sort-icon sort-desc" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<polyline points="6,9 12,15 18,9"></polyline>
</svg>
</div>
</div>
</th>
<th class="sortable" data-column="location" data-type="string">
<div class="th-content">
<span>Location</span>
<div class="sort-indicators">
<svg class="sort-icon sort-asc" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<polyline points="18,15 12,9 6,15"></polyline>
</svg>
<svg class="sort-icon sort-desc" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<polyline points="6,9 12,15 18,9"></polyline>
</svg>
</div>
</div>
</th>
<th class="sortable" data-column="hireDate" data-type="date">
<div class="th-content">
<span>Hire Date</span>
<div class="sort-indicators">
<svg class="sort-icon sort-asc" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<polyline points="18,15 12,9 6,15"></polyline>
</svg>
<svg class="sort-icon sort-desc" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<polyline points="6,9 12,15 18,9"></polyline>
</svg>
</div>
</div>
</th>
<th class="sortable" data-column="status" data-type="string">
<div class="th-content">
<span>Status</span>
<div class="sort-indicators">
<svg class="sort-icon sort-asc" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<polyline points="18,15 12,9 6,15"></polyline>
</svg>
<svg class="sort-icon sort-desc" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<polyline points="6,9 12,15 18,9"></polyline>
</svg>
</div>
</div>
</th>
<th class="actions-column">Actions</th>
</tr>
</thead>
<tbody id="tableBody">
<!-- Data rows will be populated by JavaScript -->
</tbody>
</table>
</div>
<!-- Loading State -->
<div class="loading-state" id="loadingState" style="display: none;">
<div class="loading-spinner"></div>
<p>Loading data...</p>
</div>
<!-- Empty State -->
<div class="empty-state" id="emptyState" style="display: none;">
<svg class="empty-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<circle cx="11" cy="11" r="8"></circle>
<path d="m21 21-4.35-4.35"></path>
</svg>
<h3>No employees found</h3>
<p>Try adjusting your search criteria or filters</p>
<button class="btn btn-primary" onclick="clearAllFilters()">Clear All Filters</button>
</div>
</div>
<!-- Pagination -->
<div class="pagination-container">
<div class="pagination-info">
<span id="paginationInfo">Showing 1 to 25 of 150 entries</span>
</div>
<nav class="pagination" aria-label="Data table pagination">
<button class="pagination-btn" id="firstPage" aria-label="First page">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor">
<polyline points="11,17 6,12 11,7"></polyline>
<polyline points="18,17 13,12 18,7"></polyline>
</svg>
</button>
<button class="pagination-btn" id="prevPage" aria-label="Previous page">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor">
<polyline points="15,18 9,12 15,6"></polyline>
</svg>
</button>
<div class="pagination-numbers" id="paginationNumbers">
<!-- Page numbers will be populated by JavaScript -->
</div>
<button class="pagination-btn" id="nextPage" aria-label="Next page">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor">
<polyline points="9,18 15,12 9,6"></polyline>
</svg>
</button>
<button class="pagination-btn" id="lastPage" aria-label="Last page">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor">
<polyline points="13,17 18,12 13,7"></polyline>
<polyline points="6,17 11,12 6,7"></polyline>
</svg>
</button>
</nav>
</div>
<!-- Column Toggle Modal -->
<div class="modal" id="columnModal">
<div class="modal-content">
<div class="modal-header">
<h3>Manage Columns</h3>
<button class="modal-close" id="closeColumnModal">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
</div>
<div class="modal-body">
<p>Select which columns to display in the table:</p>
<div class="column-toggles" id="columnToggles">
<!-- Column toggles will be populated by JavaScript -->
</div>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" id="resetColumns">Reset to Default</button>
<button class="btn btn-primary" id="applyColumns">Apply Changes</button>
</div>
</div>
</div>
<!-- Export Modal -->
<div class="modal" id="exportModal">
<div class="modal-content">
<div class="modal-header">
<h3>Export Data</h3>
<button class="modal-close" id="closeExportModal">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
</div>
<div class="modal-body">
<p>Choose export format and options:</p>
<div class="export-options">
<div class="export-format">
<label>Export Format:</label>
<div class="radio-group">
<label class="radio-option">
<input type="radio" name="exportFormat" value="csv" checked>
<span>CSV</span>
</label>
<label class="radio-option">
<input type="radio" name="exportFormat" value="json">
<span>JSON</span>
</label>
<label class="radio-option">
<input type="radio" name="exportFormat" value="print">
<span>Print</span>
</label>
</div>
</div>
<div class="export-scope">
<label>Export Scope:</label>
<div class="radio-group">
<label class="radio-option">
<input type="radio" name="exportScope" value="current" checked>
<span>Current Page</span>
</label>
<label class="radio-option">
<input type="radio" name="exportScope" value="filtered">
<span>All Filtered Results</span>
</label>
<label class="radio-option">
<input type="radio" name="exportScope" value="all">
<span>All Data</span>
</label>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" id="cancelExport">Cancel</button>
<button class="btn btn-primary" id="confirmExport">Export</button>
</div>
</div>
</div>
</div>CSS
/* Base Styles */
.data-list-container {
max-width: 1400px;
margin: 0 auto;
padding: 20px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #f8fafc;
min-height: 100vh;
}
/* Header */
.data-list-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 30px;
gap: 20px;
}
.header-content {
flex: 1;
}
.list-title {
font-size: 2rem;
font-weight: 700;
color: #1a202c;
margin: 0 0 8px 0;
}
.list-description {
color: #718096;
margin: 0;
font-size: 1rem;
}
.header-actions {
display: flex;
gap: 12px;
flex-wrap: wrap;
}
/* Buttons */
.btn {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 10px 16px;
border: none;
border-radius: 8px;
font-size: 0.875rem;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
text-decoration: none;
white-space: nowrap;
}
.btn-primary {
background: #4f46e5;
color: white;
}
.btn-primary:hover {
background: #4338ca;
transform: translateY(-1px);
}
.btn-secondary {
background: white;
color: #374151;
border: 1px solid #d1d5db;
}
.btn-secondary:hover {
background: #f9fafb;
border-color: #9ca3af;
}
.btn-danger {
background: #ef4444;
color: white;
}
.btn-danger:hover {
background: #dc2626;
}
.btn-sm {
padding: 6px 12px;
font-size: 0.75rem;
}
.icon {
width: 16px;
height: 16px;
stroke-width: 2;
}
/* Filters Section */
.filters-section {
background: white;
border-radius: 12px;
padding: 24px;
margin-bottom: 24px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.search-container {
margin-bottom: 20px;
}
.search-input-wrapper {
position: relative;
max-width: 400px;
}
.search-icon {
position: absolute;
left: 12px;
top: 50%;
transform: translateY(-50%);
width: 20px;
height: 20px;
color: #9ca3af;
pointer-events: none;
}
.search-input {
width: 100%;
padding: 12px 40px 12px 40px;
border: 2px solid #e5e7eb;
border-radius: 8px;
font-size: 0.875rem;
transition: border-color 0.2s ease;
}
.search-input:focus {
outline: none;
border-color: #4f46e5;
}
.search-clear {
position: absolute;
right: 12px;
top: 50%;
transform: translateY(-50%);
background: none;
border: none;
width: 20px;
height: 20px;
color: #9ca3af;
cursor: pointer;
opacity: 0;
transition: opacity 0.2s ease;
}
.search-clear.visible {
opacity: 1;
}
.advanced-filters {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 16px;
align-items: end;
}
.filter-group {
display: flex;
flex-direction: column;
gap: 6px;
}
.filter-group label {
font-size: 0.875rem;
font-weight: 500;
color: #374151;
}
.filter-select,
.filter-input {
padding: 8px 12px;
border: 1px solid #d1d5db;
border-radius: 6px;
font-size: 0.875rem;
background: white;
}
.filter-select:focus,
.filter-input:focus {
outline: none;
border-color: #4f46e5;
}
/* Results Info */
.results-info {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
gap: 20px;
}
.results-count {
color: #6b7280;
font-size: 0.875rem;
}
.results-actions {
display: flex;
align-items: center;
gap: 20px;
}
.page-size-selector {
display: flex;
align-items: center;
gap: 8px;
font-size: 0.875rem;
color: #6b7280;
}
.page-size-select {
padding: 4px 8px;
border: 1px solid #d1d5db;
border-radius: 4px;
font-size: 0.875rem;
}
.bulk-actions {
display: flex;
align-items: center;
gap: 12px;
padding: 8px 16px;
background: #eff6ff;
border: 1px solid #bfdbfe;
border-radius: 8px;
}
.selected-count {
font-size: 0.875rem;
color: #1e40af;
font-weight: 500;
}
/* Table */
.table-container {
background: white;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
margin-bottom: 24px;
}
.table-wrapper {
overflow-x: auto;
}
.data-table {
width: 100%;
border-collapse: collapse;
font-size: 0.875rem;
}
.data-table th {
background: #f8fafc;
padding: 16px 12px;
text-align: left;
font-weight: 600;
color: #374151;
border-bottom: 1px solid #e5e7eb;
white-space: nowrap;
}
.data-table td {
padding: 16px 12px;
border-bottom: 1px solid #f3f4f6;
vertical-align: middle;
}
.data-table tbody tr:hover {
background: #f8fafc;
}
.data-table tbody tr.selected {
background: #eff6ff;
}
/* Sortable Headers */
.sortable {
cursor: pointer;
user-select: none;
position: relative;
}
.sortable:hover {
background: #f1f5f9;
}
.th-content {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
}
.sort-indicators {
display: flex;
flex-direction: column;
gap: 1px;
}
.sort-icon {
width: 12px;
height: 12px;
color: #d1d5db;
transition: color 0.2s ease;
}
.sortable.sort-asc .sort-asc,
.sortable.sort-desc .sort-desc {
color: #4f46e5;
}
/* Column Types */
.select-column {
width: 40px;
text-align: center;
}
.actions-column {
width: 120px;
text-align: center;
}
/* Status Badges */
.status-badge {
display: inline-flex;
align-items: center;
padding: 4px 8px;
border-radius: 12px;
font-size: 0.75rem;
font-weight: 500;
text-transform: capitalize;
}
.status-active {
background: #dcfce7;
color: #166534;
}
.status-inactive {
background: #fee2e2;
color: #991b1b;
}
.status-pending {
background: #fef3c7;
color: #92400e;
}
/* Action Buttons */
.action-btn {
padding: 4px 8px;
border: none;
border-radius: 4px;
font-size: 0.75rem;
cursor: pointer;
margin: 0 2px;
transition: all 0.2s ease;
}
.action-btn.edit {
background: #dbeafe;
color: #1e40af;
}
.action-btn.edit:hover {
background: #bfdbfe;
}
.action-btn.delete {
background: #fee2e2;
color: #991b1b;
}
.action-btn.delete:hover {
background: #fecaca;
}
/* Loading State */
.loading-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 60px 20px;
color: #6b7280;
}
.loading-spinner {
width: 40px;
height: 40px;
border: 3px solid #f3f4f6;
border-top: 3px solid #4f46e5;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 16px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* Empty State */
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 60px 20px;
text-align: center;
}
.empty-icon {
width: 64px;
height: 64px;
color: #d1d5db;
margin-bottom: 16px;
}
.empty-state h3 {
font-size: 1.25rem;
font-weight: 600;
color: #374151;
margin: 0 0 8px 0;
}
.empty-state p {
color: #6b7280;
margin: 0 0 24px 0;
}
/* Pagination */
.pagination-container {
display: flex;
justify-content: space-between;
align-items: center;
gap: 20px;
}
.pagination-info {
color: #6b7280;
font-size: 0.875rem;
}
.pagination {
display: flex;
align-items: center;
gap: 4px;
}
.pagination-btn {
display: flex;
align-items: center;
justify-content: center;
width: 36px;
height: 36px;
border: 1px solid #d1d5db;
background: white;
border-radius: 6px;
cursor: pointer;
transition: all 0.2s ease;
}
.pagination-btn:hover:not(:disabled) {
background: #f9fafb;
border-color: #9ca3af;
}
.pagination-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.pagination-btn svg {
width: 16px;
height: 16px;
}
.pagination-numbers {
display: flex;
gap: 4px;
margin: 0 8px;
}
.page-number {
display: flex;
align-items: center;
justify-content: center;
width: 36px;
height: 36px;
border: 1px solid #d1d5db;
background: white;
border-radius: 6px;
cursor: pointer;
font-size: 0.875rem;
font-weight: 500;
transition: all 0.2s ease;
}
.page-number:hover {
background: #f9fafb;
border-color: #9ca3af;
}
.page-number.active {
background: #4f46e5;
color: white;
border-color: #4f46e5;
}
.page-ellipsis {
display: flex;
align-items: center;
justify-content: center;
width: 36px;
height: 36px;
color: #9ca3af;
font-weight: 500;
}
/* Modals */
.modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
opacity: 0;
visibility: hidden;
transition: all 0.2s ease;
}
.modal.active {
opacity: 1;
visibility: visible;
}
.modal-content {
background: white;
border-radius: 12px;
width: 90%;
max-width: 500px;
max-height: 90vh;
overflow: hidden;
transform: scale(0.9);
transition: transform 0.2s ease;
}
.modal.active .modal-content {
transform: scale(1);
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px 24px;
border-bottom: 1px solid #e5e7eb;
}
.modal-header h3 {
margin: 0;
font-size: 1.25rem;
font-weight: 600;
color: #1a202c;
}
.modal-close {
background: none;
border: none;
width: 24px;
height: 24px;
cursor: pointer;
color: #6b7280;
}
.modal-close:hover {
color: #374151;
}
.modal-body {
padding: 24px;
max-height: 60vh;
overflow-y: auto;
}
.modal-footer {
display: flex;
justify-content: flex-end;
gap: 12px;
padding: 20px 24px;
border-top: 1px solid #e5e7eb;
background: #f8fafc;
}
/* Column Toggles */
.column-toggles {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 12px;
margin-top: 16px;
}
.column-toggle {
display: flex;
align-items: center;
gap: 8px;
padding: 8px;
border: 1px solid #e5e7eb;
border-radius: 6px;
cursor: pointer;
transition: all 0.2s ease;
}
.column-toggle:hover {
background: #f8fafc;
}
.column-toggle input[type="checkbox"] {
margin: 0;
}
/* Export Options */
.export-options {
margin-top: 16px;
}
.export-format,
.export-scope {
margin-bottom: 20px;
}
.export-format label,
.export-scope label {
display: block;
font-weight: 500;
color: #374151;
margin-bottom: 8px;
}
.radio-group {
display: flex;
flex-direction: column;
gap: 8px;
}
.radio-option {
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
font-weight: normal !important;
}
.radio-option input[type="radio"] {
margin: 0;
}
/* Search Highlighting */
.highlight {
background: #fef3c7;
padding: 1px 2px;
border-radius: 2px;
font-weight: 600;
}
/* Responsive Design */
@media (max-width: 1024px) {
.data-list-header {
flex-direction: column;
align-items: stretch;
}
.header-actions {
justify-content: flex-start;
}
.advanced-filters {
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
}
}
@media (max-width: 768px) {
.data-list-container {
padding: 16px;
}
.list-title {
font-size: 1.5rem;
}
.filters-section {
padding: 16px;
}
.advanced-filters {
grid-template-columns: 1fr;
}
.results-info {
flex-direction: column;
align-items: stretch;
gap: 12px;
}
.results-actions {
justify-content: space-between;
}
.pagination-container {
flex-direction: column;
gap: 12px;
}
.pagination {
justify-content: center;
}
/* Hide less important columns on mobile */
.data-table th:nth-child(n+6),
.data-table td:nth-child(n+6) {
display: none;
}
}
@media (max-width: 480px) {
.header-actions {
flex-direction: column;
}
.btn {
justify-content: center;
}
.data-table th:nth-child(n+5),
.data-table td:nth-child(n+5) {
display: none;
}
.pagination-numbers {
display: none;
}
}JavaScript
// Advanced Data List Controller
class AdvancedDataList {
constructor(options = {}) {
this.options = {
pageSize: 25,
sortColumn: 'name',
sortDirection: 'asc',
searchDebounce: 300,
...options
};
this.data = [];
this.filteredData = [];
this.currentPage = 1;
this.totalPages = 1;
this.selectedRows = new Set();
this.visibleColumns = new Set(['name', 'email', 'department', 'position', 'location', 'hireDate', 'status']);
this.filters = {
search: '',
department: '',
status: '',
location: '',
hireDateFrom: '',
hireDateTo: ''
};
this.sortState = {
column: this.options.sortColumn,
direction: this.options.sortDirection
};
this.init();
}
async init() {
this.setupEventListeners();
this.setupKeyboardNavigation();
await this.loadData();
this.render();
}
setupEventListeners() {
// Search
const searchInput = document.getElementById('globalSearch');
const searchClear = document.getElementById('searchClear');
let searchTimeout;
searchInput.addEventListener('input', (e) => {
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => {
this.filters.search = e.target.value;
this.updateSearchClear();
this.applyFilters();
}, this.options.searchDebounce);
});
searchClear.addEventListener('click', () => {
searchInput.value = '';
this.filters.search = '';
this.updateSearchClear();
this.applyFilters();
});
// Filters
['departmentFilter', 'statusFilter', 'locationFilter', 'hireDateFrom', 'hireDateTo'].forEach(id => {
const element = document.getElementById(id);
element.addEventListener('change', (e) => {
const filterKey = id.replace('Filter', '').replace('hireDate', 'hireDate');
this.filters[filterKey] = e.target.value;
this.applyFilters();
});
});
// Clear filters
document.getElementById('clearFilters').addEventListener('click', () => {
this.clearAllFilters();
});
// Page size
document.getElementById('pageSize').addEventListener('change', (e) => {
this.options.pageSize = parseInt(e.target.value);
this.currentPage = 1;
this.render();
});
// Pagination
document.getElementById('firstPage').addEventListener('click', () => this.goToPage(1));
document.getElementById('prevPage').addEventListener('click', () => this.goToPage(this.currentPage - 1));
document.getElementById('nextPage').addEventListener('click', () => this.goToPage(this.currentPage + 1));
document.getElementById('lastPage').addEventListener('click', () => this.goToPage(this.totalPages));
// Select all
document.getElementById('selectAll').addEventListener('change', (e) => {
this.toggleSelectAll(e.target.checked);
});
// Bulk actions
document.getElementById('bulkEdit').addEventListener('click', () => this.bulkEdit());
document.getElementById('bulkDelete').addEventListener('click', () => this.bulkDelete());
// Header actions
document.getElementById('columnToggle').addEventListener('click', () => this.showColumnModal());
document.getElementById('exportData').addEventListener('click', () => this.showExportModal());
document.getElementById('addNew').addEventListener('click', () => this.addNewEmployee());
// Modal events
this.setupModalEvents();
}
setupModalEvents() {
// Column modal
document.getElementById('closeColumnModal').addEventListener('click', () => this.hideColumnModal());
document.getElementById('resetColumns').addEventListener('click', () => this.resetColumns());
document.getElementById('applyColumns').addEventListener('click', () => this.applyColumnChanges());
// Export modal
document.getElementById('closeExportModal').addEventListener('click', () => this.hideExportModal());
document.getElementById('cancelExport').addEventListener('click', () => this.hideExportModal());
document.getElementById('confirmExport').addEventListener('click', () => this.performExport());
// Close modals on backdrop click
document.querySelectorAll('.modal').forEach(modal => {
modal.addEventListener('click', (e) => {
if (e.target === modal) {
modal.classList.remove('active');
}
});
});
}
setupKeyboardNavigation() {
document.addEventListener('keydown', (e) => {
// Escape to close modals
if (e.key === 'Escape') {
document.querySelectorAll('.modal.active').forEach(modal => {
modal.classList.remove('active');
});
}
// Ctrl+F to focus search
if (e.ctrlKey && e.key === 'f') {
e.preventDefault();
document.getElementById('globalSearch').focus();
}
});
}
async loadData() {
this.showLoading(true);
try {
// Simulate API call - replace with actual data loading
await new Promise(resolve => setTimeout(resolve, 1000));
this.data = this.generateSampleData();
this.applyFilters();
} catch (error) {
console.error('Error loading data:', error);
this.showError('Failed to load data');
} finally {
this.showLoading(false);
}
}
generateSampleData() {
const departments = ['engineering', 'marketing', 'sales', 'hr', 'finance'];
const positions = {
engineering: ['Software Engineer', 'Senior Developer', 'Tech Lead', 'DevOps Engineer'],
marketing: ['Marketing Manager', 'Content Creator', 'SEO Specialist', 'Brand Manager'],
sales: ['Sales Representative', 'Account Manager', 'Sales Director', 'Business Development'],
hr: ['HR Manager', 'Recruiter', 'HR Business Partner', 'Compensation Analyst'],
finance: ['Financial Analyst', 'Accountant', 'Finance Manager', 'Controller']
};
const locations = ['new-york', 'san-francisco', 'london', 'remote'];
const statuses = ['active', 'inactive', 'pending'];
const firstNames = ['John', 'Jane', 'Michael', 'Sarah', 'David', 'Emily', 'Robert', 'Lisa', 'James', 'Maria'];
const lastNames = ['Smith', 'Johnson', 'Williams', 'Brown', 'Jones', 'Garcia', 'Miller', 'Davis', 'Rodriguez', 'Martinez'];
const data = [];
for (let i = 1; i <= 150; i++) {
const firstName = firstNames[Math.floor(Math.random() * firstNames.length)];
const lastName = lastNames[Math.floor(Math.random() * lastNames.length)];
const department = departments[Math.floor(Math.random() * departments.length)];
const position = positions[department][Math.floor(Math.random() * positions[department].length)];
data.push({
id: i,
name: `${firstName} ${lastName}`,
email: `${firstName.toLowerCase()}.${lastName.toLowerCase()}@company.com`,
department,
position,
location: locations[Math.floor(Math.random() * locations.length)],
hireDate: new Date(2020 + Math.floor(Math.random() * 4), Math.floor(Math.random() * 12), Math.floor(Math.random() * 28) + 1),
status: statuses[Math.floor(Math.random() * statuses.length)]
});
}
return data;
}
applyFilters() {
this.filteredData = this.data.filter(item => {
// Search filter
if (this.filters.search) {
const searchTerm = this.filters.search.toLowerCase();
const searchableText = `${item.name} ${item.email} ${item.department} ${item.position} ${item.location}`.toLowerCase();
if (!searchableText.includes(searchTerm)) {
return false;
}
}
// Department filter
if (this.filters.department && item.department !== this.filters.department) {
return false;
}
// Status filter
if (this.filters.status && item.status !== this.filters.status) {
return false;
}
// Location filter
if (this.filters.location && item.location !== this.filters.location) {
return false;
}
// Date range filter
if (this.filters.hireDateFrom) {
const fromDate = new Date(this.filters.hireDateFrom);
if (item.hireDate < fromDate) {
return false;
}
}
if (this.filters.hireDateTo) {
const toDate = new Date(this.filters.hireDateTo);
if (item.hireDate > toDate) {
return false;
}
}
return true;
});
this.applySorting();
this.currentPage = 1;
this.selectedRows.clear();
this.render();
}
applySorting() {
this.filteredData.sort((a, b) => {
const aVal = a[this.sortState.column];
const bVal = b[this.sortState.column];
let comparison = 0;
if (aVal instanceof Date && bVal instanceof Date) {
comparison = aVal.getTime() - bVal.getTime();
} else if (typeof aVal === 'string' && typeof bVal === 'string') {
comparison = aVal.localeCompare(bVal);
} else {
comparison = aVal < bVal ? -1 : aVal > bVal ? 1 : 0;
}
return this.sortState.direction === 'asc' ? comparison : -comparison;
});
}
render() {
this.calculatePagination();
this.renderTable();
this.renderPagination();
this.updateResultsInfo();
this.updateBulkActions();
}
calculatePagination() {
this.totalPages = Math.ceil(this.filteredData.length / this.options.pageSize);
if (this.currentPage > this.totalPages) {
this.currentPage = Math.max(1, this.totalPages);
}
}
renderTable() {
const tbody = document.getElementById('tableBody');
const startIndex = (this.currentPage - 1) * this.options.pageSize;
const endIndex = startIndex + this.options.pageSize;
const pageData = this.filteredData.slice(startIndex, endIndex);
if (pageData.length === 0) {
this.showEmptyState(true);
return;
}
this.showEmptyState(false);
tbody.innerHTML = pageData.map(item => {
const isSelected = this.selectedRows.has(item.id);
return `
<tr class="${isSelected ? 'selected' : ''}" data-id="${item.id}">
<td class="select-column">
<input type="checkbox" ${isSelected ? 'checked' : ''}
onchange="window.dataList.toggleRowSelection(${item.id}, this.checked)">
</td>
<td>${this.highlightSearchTerm(item.name)}</td>
<td>${this.highlightSearchTerm(item.email)}</td>
<td>${this.formatDepartment(item.department)}</td>
<td>${this.highlightSearchTerm(item.position)}</td>
<td>${this.formatLocation(item.location)}</td>
<td>${this.formatDate(item.hireDate)}</td>
<td><span class="status-badge status-${item.status}">${item.status}</span></td>
<td class="actions-column">
<button class="action-btn edit" onclick="window.dataList.editEmployee(${item.id})" title="Edit">
Edit
</button>
<button class="action-btn delete" onclick="window.dataList.deleteEmployee(${item.id})" title="Delete">
Delete
</button>
</td>
</tr>
`;
}).join('');
// Update sort indicators
this.updateSortIndicators();
}
renderPagination() {
const numbersContainer = document.getElementById('paginationNumbers');
const maxVisible = 5;
const pages = [];
let startPage = Math.max(1, this.currentPage - Math.floor(maxVisible / 2));
let endPage = Math.min(this.totalPages, startPage + maxVisible - 1);
if (endPage - startPage + 1 < maxVisible) {
startPage = Math.max(1, endPage - maxVisible + 1);
}
if (startPage > 1) {
pages.push(1);
if (startPage > 2) {
pages.push('...');
}
}
for (let i = startPage; i <= endPage; i++) {
pages.push(i);
}
if (endPage < this.totalPages) {
if (endPage < this.totalPages - 1) {
pages.push('...');
}
pages.push(this.totalPages);
}
numbersContainer.innerHTML = pages.map(page => {
if (page === '...') {
return '<span class="page-ellipsis">...</span>';
}
const isActive = page === this.currentPage;
return `
<button class="page-number ${isActive ? 'active' : ''}"
onclick="window.dataList.goToPage(${page})">
${page}
</button>
`;
}).join('');
// Update pagination buttons
document.getElementById('firstPage').disabled = this.currentPage === 1;
document.getElementById('prevPage').disabled = this.currentPage === 1;
document.getElementById('nextPage').disabled = this.currentPage === this.totalPages;
document.getElementById('lastPage').disabled = this.currentPage === this.totalPages;
}
updateResultsInfo() {
const startIndex = (this.currentPage - 1) * this.options.pageSize + 1;
const endIndex = Math.min(this.currentPage * this.options.pageSize, this.filteredData.length);
document.getElementById('resultsText').textContent =
`Showing ${startIndex}-${endIndex} of ${this.filteredData.length} employees`;
document.getElementById('paginationInfo').textContent =
`Showing ${startIndex} to ${endIndex} of ${this.filteredData.length} entries`;
}
updateBulkActions() {
const bulkActions = document.getElementById('bulkActions');
const selectedCount = document.getElementById('selectedCount');
if (this.selectedRows.size > 0) {
bulkActions.style.display = 'flex';
selectedCount.textContent = `${this.selectedRows.size} selected`;
} else {
bulkActions.style.display = 'none';
}
}
updateSortIndicators() {
// Remove all sort classes
document.querySelectorAll('.sortable').forEach(th => {
th.classList.remove('sort-asc', 'sort-desc');
});
// Add sort class to current column
const currentSortHeader = document.querySelector(`[data-column="${this.sortState.column}"]`);
if (currentSortHeader) {
currentSortHeader.classList.add(`sort-${this.sortState.direction}`);
}
}
updateSearchClear() {
const searchClear = document.getElementById('searchClear');
const searchInput = document.getElementById('globalSearch');
searchClear.classList.toggle('visible', searchInput.value.length > 0);
}
// Event handlers
goToPage(page) {
if (page >= 1 && page <= this.totalPages) {
this.currentPage = page;
this.render();
}
}
sortBy(column, type) {
if (this.sortState.column === column) {
this.sortState.direction = this.sortState.direction === 'asc' ? 'desc' : 'asc';
} else {
this.sortState.column = column;
this.sortState.direction = 'asc';
}
this.applySorting();
this.render();
}
toggleRowSelection(id, checked) {
if (checked) {
this.selectedRows.add(id);
} else {
this.selectedRows.delete(id);
}
this.updateBulkActions();
this.updateSelectAllState();
}
toggleSelectAll(checked) {
const startIndex = (this.currentPage - 1) * this.options.pageSize;
const endIndex = startIndex + this.options.pageSize;
const pageData = this.filteredData.slice(startIndex, endIndex);
pageData.forEach(item => {
if (checked) {
this.selectedRows.add(item.id);
} else {
this.selectedRows.delete(item.id);
}
});
this.render();
}
updateSelectAllState() {
const selectAll = document.getElementById('selectAll');
const startIndex = (this.currentPage - 1) * this.options.pageSize;
const endIndex = startIndex + this.options.pageSize;
const pageData = this.filteredData.slice(startIndex, endIndex);
const selectedOnPage = pageData.filter(item => this.selectedRows.has(item.id)).length;
if (selectedOnPage === 0) {
selectAll.checked = false;
selectAll.indeterminate = false;
} else if (selectedOnPage === pageData.length) {
selectAll.checked = true;
selectAll.indeterminate = false;
} else {
selectAll.checked = false;
selectAll.indeterminate = true;
}
}
clearAllFilters() {
this.filters = {
search: '',
department: '',
status: '',
location: '',
hireDateFrom: '',
hireDateTo: ''
};
// Reset form inputs
document.getElementById('globalSearch').value = '';
document.getElementById('departmentFilter').value = '';
document.getElementById('statusFilter').value = '';
document.getElementById('locationFilter').value = '';
document.getElementById('hireDateFrom').value = '';
document.getElementById('hireDateTo').value = '';
this.updateSearchClear();
this.applyFilters();
}
// Utility methods
highlightSearchTerm(text) {
if (!this.filters.search) return text;
const regex = new RegExp(`(${this.escapeRegex(this.filters.search)})`, 'gi');
return text.replace(regex, '<span class="highlight">$1</span>');
}
escapeRegex(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
formatDepartment(dept) {
return dept.charAt(0).toUpperCase() + dept.slice(1);
}
formatLocation(location) {
return location.split('-').map(word =>
word.charAt(0).toUpperCase() + word.slice(1)
).join(' ');
}
formatDate(date) {
return date.toLocaleDateString('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric'
});
}
showLoading(show) {
document.getElementById('loadingState').style.display = show ? 'flex' : 'none';
document.querySelector('.table-wrapper').style.display = show ? 'none' : 'block';
}
showEmptyState(show) {
document.getElementById('emptyState').style.display = show ? 'flex' : 'none';
document.querySelector('.table-wrapper').style.display = show ? 'none' : 'block';
}
showError(message) {
// Implement error display logic
console.error(message);
}
// Modal methods
showColumnModal() {
this.renderColumnToggles();
document.getElementById('columnModal').classList.add('active');
}
hideColumnModal() {
document.getElementById('columnModal').classList.remove('active');
}
renderColumnToggles() {
const container = document.getElementById('columnToggles');
const columns = [
{ key: 'name', label: 'Name' },
{ key: 'email', label: 'Email' },
{ key: 'department', label: 'Department' },
{ key: 'position', label: 'Position' },
{ key: 'location', label: 'Location' },
{ key: 'hireDate', label: 'Hire Date' },
{ key: 'status', label: 'Status' }
];
container.innerHTML = columns.map(col => `
<label class="column-toggle">
<input type="checkbox" ${this.visibleColumns.has(col.key) ? 'checked' : ''}
data-column="${col.key}">
<span>${col.label}</span>
</label>
`).join('');
}
resetColumns() {
this.visibleColumns = new Set(['name', 'email', 'department', 'position', 'location', 'hireDate', 'status']);
this.renderColumnToggles();
}
applyColumnChanges() {
const checkboxes = document.querySelectorAll('#columnToggles input[type="checkbox"]');
this.visibleColumns.clear();
checkboxes.forEach(cb => {
if (cb.checked) {
this.visibleColumns.add(cb.dataset.column);
}
});
this.updateTableColumns();
this.hideColumnModal();
}
updateTableColumns() {
// Update table header visibility
const headers = document.querySelectorAll('.data-table th[data-column]');
headers.forEach(th => {
const column = th.dataset.column;
th.style.display = this.visibleColumns.has(column) ? '' : 'none';
});
// Update table body visibility
const rows = document.querySelectorAll('.data-table tbody tr');
rows.forEach(row => {
const cells = row.querySelectorAll('td');
cells.forEach((cell, index) => {
if (index === 0 || index === cells.length - 1) return; // Skip select and actions columns
const headers = Array.from(document.querySelectorAll('.data-table th'));
const header = headers[index];
if (header && header.dataset.column) {
cell.style.display = this.visibleColumns.has(header.dataset.column) ? '' : 'none';
}
});
});
}
showExportModal() {
document.getElementById('exportModal').classList.add('active');
}
hideExportModal() {
document.getElementById('exportModal').classList.remove('active');
}
performExport() {
const format = document.querySelector('input[name="exportFormat"]:checked').value;
const scope = document.querySelector('input[name="exportScope"]:checked').value;
let dataToExport;
switch (scope) {
case 'current':
const startIndex = (this.currentPage - 1) * this.options.pageSize;
const endIndex = startIndex + this.options.pageSize;
dataToExport = this.filteredData.slice(startIndex, endIndex);
break;
case 'filtered':
dataToExport = this.filteredData;
break;
case 'all':
dataToExport = this.data;
break;
}
switch (format) {
case 'csv':
this.exportToCSV(dataToExport);
break;
case 'json':
this.exportToJSON(dataToExport);
break;
case 'print':
this.printData(dataToExport);
break;
}
this.hideExportModal();
}
exportToCSV(data) {
const headers = ['Name', 'Email', 'Department', 'Position', 'Location', 'Hire Date', 'Status'];
const csvContent = [
headers.join(','),
...data.map(row => [
`"${row.name}"`,
`"${row.email}"`,
`"${this.formatDepartment(row.department)}"`,
`"${row.position}"`,
`"${this.formatLocation(row.location)}"`,
`"${this.formatDate(row.hireDate)}"`,
`"${row.status}"`
].join(','))
].join('\n');
const blob = new Blob([csvContent], { type: 'text/csv' });
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'employees.csv';
a.click();
window.URL.revokeObjectURL(url);
}
exportToJSON(data) {
const jsonContent = JSON.stringify(data, null, 2);
const blob = new Blob([jsonContent], { type: 'application/json' });
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'employees.json';
a.click();
window.URL.revokeObjectURL(url);
}
printData(data) {
const printWindow = window.open('', '_blank');
const printContent = `
<html>
<head>
<title>Employee Directory</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
table { width: 100%; border-collapse: collapse; }
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
th { background-color: #f2f2f2; }
</style>
</head>
<body>
<h1>Employee Directory</h1>
<table>
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<th>Department</th>
<th>Position</th>
<th>Location</th>
<th>Hire Date</th>
<th>Status</th>
</tr>
</thead>
<tbody>
${data.map(row => `
<tr>
<td>${row.name}</td>
<td>${row.email}</td>
<td>${this.formatDepartment(row.department)}</td>
<td>${row.position}</td>
<td>${this.formatLocation(row.location)}</td>
<td>${this.formatDate(row.hireDate)}</td>
<td>${row.status}</td>
</tr>
`).join('')}
</tbody>
</table>
</body>
</html>
`;
printWindow.document.write(printContent);
printWindow.document.close();
printWindow.print();
}
// Action methods
editEmployee(id) {
const employee = this.data.find(emp => emp.id === id);
if (employee) {
console.log('Edit employee:', employee);
// Implement edit functionality
alert(`Edit employee: ${employee.name}`);
}
}
deleteEmployee(id) {
const employee = this.data.find(emp => emp.id === id);
if (employee && confirm(`Are you sure you want to delete ${employee.name}?`)) {
this.data = this.data.filter(emp => emp.id !== id);
this.selectedRows.delete(id);
this.applyFilters();
}
}
addNewEmployee() {
console.log('Add new employee');
// Implement add functionality
alert('Add new employee functionality would be implemented here');
}
bulkEdit() {
const selectedIds = Array.from(this.selectedRows);
console.log('Bulk edit:', selectedIds);
alert(`Bulk edit ${selectedIds.length} employees`);
}
bulkDelete() {
const selectedIds = Array.from(this.selectedRows);
if (confirm(`Are you sure you want to delete ${selectedIds.length} employees?`)) {
this.data = this.data.filter(emp => !selectedIds.includes(emp.id));
this.selectedRows.clear();
this.applyFilters();
}
}
}
// Setup sortable headers
document.addEventListener('DOMContentLoaded', () => {
// Initialize data list
window.dataList = new AdvancedDataList();
// Setup sortable headers
document.querySelectorAll('.sortable').forEach(header => {
header.addEventListener('click', () => {
const column = header.dataset.column;
const type = header.dataset.type;
window.dataList.sortBy(column, type);
});
});
});
// Global functions for external use
function clearAllFilters() {
if (window.dataList) {
window.dataList.clearAllFilters();
}
}
function exportData(format = 'csv') {
if (window.dataList) {
window.dataList.showExportModal();
}
}
function addEmployee() {
if (window.dataList) {
window.dataList.addNewEmployee();
}
}Customization Options
Data Source
- API Integration: Replace
generateSampleData()with actual API calls - Real-time Updates: Add WebSocket support for live data updates
- Caching: Implement client-side caching for better performance
- Offline Support: Add service worker for offline functionality
Filtering & Search
- Advanced Search: Add field-specific search operators
- Saved Filters: Allow users to save and load filter presets
- Quick Filters: Add one-click filter buttons for common searches
- Search History: Implement search term suggestions and history
Display Options
- Themes: Add dark mode and custom color schemes
- Density: Implement compact, normal, and comfortable row heights
- Card View: Add alternative card-based layout for mobile
- Infinite Scroll: Replace pagination with infinite scrolling
Export & Actions
- Additional Formats: Add Excel, PDF export options
- Bulk Operations: Implement custom bulk actions
- Audit Trail: Track all data changes and user actions
- Permissions: Add role-based access control
Accessibility Features
- Screen Reader Support: Full ARIA labeling and announcements
- Keyboard Navigation: Complete keyboard accessibility
- High Contrast: Support for high contrast mode
- Focus Management: Logical tab order and focus indicators
- Responsive Text: Support for browser zoom up to 200%
Performance Optimizations
- Virtual Scrolling: Handle large datasets efficiently
- Debounced Search: Prevent excessive API calls
- Lazy Loading: Load data on demand
- Memoization: Cache expensive calculations
- Web Workers: Offload heavy processing to background threads
Browser Compatibility
- ✅ Chrome 60+
- ✅ Firefox 55+
- ✅ Safari 12+
- ✅ Edge 79+
- ⚠️ IE 11 (with polyfills for modern JavaScript features)
Integration Examples
With React
import { useEffect, useRef } from 'react';
function DataListComponent({ data, onEdit, onDelete }) {
const containerRef = useRef();
const dataListRef = useRef();
useEffect(() => {
dataListRef.current = new AdvancedDataList({
container: containerRef.current,
data,
onEdit,
onDelete
});
return () => dataListRef.current?.destroy();
}, []);
return <div ref={containerRef} />;
}With Vue.js
export default {
mounted() {
this.dataList = new AdvancedDataList({
container: this.$refs.container,
data: this.employees,
onEdit: this.handleEdit,
onDelete: this.handleDelete
});
},
beforeDestroy() {
this.dataList?.destroy();
}
};HTML
386
lines
CSS
706
lines
<div class="data-list-container">
<!-- Header Section -->
<div class="data-list-header">
<div class="header-content">
<h2 class="list-title">Employee Directory</h2>
<p class="list-description">Manage and view employee information with advanced filtering and sorting</p>
</div>
<!-- Action Buttons -->
<div class="header-actions">
<button class="btn btn-secondary" id="columnToggle">
<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
<line x1="9" y1="9" x2="15" y2="9"></line>
<line x1="9" y1="15" x2="15" y2="15"></line>
</svg>
Columns
</button>
<button class="btn btn-secondary" id="exportData">
<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
<polyline points="7,10 12,15 17,10"></polyline>
<line x1="12" y1="15" x2="12" y2="3"></line>
</svg>
Export
</button>
<button class="btn btn-primary" id="addNew">
<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<line x1="12" y1="5" x2="12" y2="19"></line>
<line x1="5" y1="12" x2="19" y2="12"></line>
</svg>
Add Employee
</button>
</div>
</div>
<!-- Filters Section -->
<div class="filters-section">
<!-- Search Bar -->
<div class="search-container">
<div class="search-input-wrapper">
<svg class="search-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<circle cx="11" cy="11" r="8"></circle>
<path d="m21 21-4.35-4.35"></path>
</svg>
<input
type="text"
id="globalSearch"
class="search-input"
placeholder="Search employees..."
aria-label="Search employees"
>
<button class="search-clear" id="searchClear" aria-label="Clear search">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
</div>
</div>
<!-- Advanced Filters -->
<div class="advanced-filters">
<div class="filter-group">
<label for="departmentFilter">Department</label>
<select id="departmentFilter" class="filter-select">
<option value="">All Departments</option>
<option value="engineering">Engineering</option>
<option value="marketing">Marketing</option>
<option value="sales">Sales</option>
<option value="hr">Human Resources</option>
<option value="finance">Finance</option>
</select>
</div>
<div class="filter-group">
<label for="statusFilter">Status</label>
<select id="statusFilter" class="filter-select">
<option value="">All Status</option>
<option value="active">Active</option>
<option value="inactive">Inactive</option>
<option value="pending">Pending</option>
</select>
</div>
<div class="filter-group">
<label for="locationFilter">Location</label>
<select id="locationFilter" class="filter-select">
<option value="">All Locations</option>
<option value="new-york">New York</option>
<option value="san-francisco">San Francisco</option>
<option value="london">London</option>
<option value="remote">Remote</option>
</select>
</div>
<div class="filter-group">
<label for="hireDateFrom">Hire Date From</label>
<input type="date" id="hireDateFrom" class="filter-input">
</div>
<div class="filter-group">
<label for="hireDateTo">Hire Date To</label>
<input type="date" id="hireDateTo" class="filter-input">
</div>
<button class="btn btn-secondary" id="clearFilters">
Clear Filters
</button>
</div>
</div>
<!-- Results Info -->
<div class="results-info">
<div class="results-count">
<span id="resultsText">Showing 1-10 of 150 employees</span>
</div>
<div class="results-actions">
<div class="page-size-selector">
<label for="pageSize">Show:</label>
<select id="pageSize" class="page-size-select">
<option value="10">10</option>
<option value="25" selected>25</option>
<option value="50">50</option>
<option value="100">100</option>
</select>
<span>per page</span>
</div>
<div class="bulk-actions" id="bulkActions" style="display: none;">
<span class="selected-count" id="selectedCount">2 selected</span>
<button class="btn btn-sm btn-secondary" id="bulkEdit">Edit</button>
<button class="btn btn-sm btn-danger" id="bulkDelete">Delete</button>
</div>
</div>
</div>
<!-- Data Table -->
<div class="table-container">
<div class="table-wrapper">
<table class="data-table" id="dataTable">
<thead>
<tr>
<th class="select-column">
<input type="checkbox" id="selectAll" aria-label="Select all rows">
</th>
<th class="sortable" data-column="name" data-type="string">
<div class="th-content">
<span>Name</span>
<div class="sort-indicators">
<svg class="sort-icon sort-asc" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<polyline points="18,15 12,9 6,15"></polyline>
</svg>
<svg class="sort-icon sort-desc" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<polyline points="6,9 12,15 18,9"></polyline>
</svg>
</div>
</div>
</th>
<th class="sortable" data-column="email" data-type="string">
<div class="th-content">
<span>Email</span>
<div class="sort-indicators">
<svg class="sort-icon sort-asc" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<polyline points="18,15 12,9 6,15"></polyline>
</svg>
<svg class="sort-icon sort-desc" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<polyline points="6,9 12,15 18,9"></polyline>
</svg>
</div>
</div>
</th>
<th class="sortable" data-column="department" data-type="string">
<div class="th-content">
<span>Department</span>
<div class="sort-indicators">
<svg class="sort-icon sort-asc" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<polyline points="18,15 12,9 6,15"></polyline>
</svg>
<svg class="sort-icon sort-desc" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<polyline points="6,9 12,15 18,9"></polyline>
</svg>
</div>
</div>
</th>
<th class="sortable" data-column="position" data-type="string">
<div class="th-content">
<span>Position</span>
<div class="sort-indicators">
<svg class="sort-icon sort-asc" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<polyline points="18,15 12,9 6,15"></polyline>
</svg>
<svg class="sort-icon sort-desc" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<polyline points="6,9 12,15 18,9"></polyline>
</svg>
</div>
</div>
</th>
<th class="sortable" data-column="location" data-type="string">
<div class="th-content">
<span>Location</span>
<div class="sort-indicators">
<svg class="sort-icon sort-asc" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<polyline points="18,15 12,9 6,15"></polyline>
</svg>
<svg class="sort-icon sort-desc" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<polyline points="6,9 12,15 18,9"></polyline>
</svg>
</div>
</div>
</th>
<th class="sortable" data-column="hireDate" data-type="date">
<div class="th-content">
<span>Hire Date</span>
<div class="sort-indicators">
<svg class="sort-icon sort-asc" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<polyline points="18,15 12,9 6,15"></polyline>
</svg>
<svg class="sort-icon sort-desc" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<polyline points="6,9 12,15 18,9"></polyline>
</svg>
</div>
</div>
</th>
<th class="sortable" data-column="status" data-type="string">
<div class="th-content">
<span>Status</span>
<div class="sort-indicators">
<svg class="sort-icon sort-asc" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<polyline points="18,15 12,9 6,15"></polyline>
</svg>
<svg class="sort-icon sort-desc" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<polyline points="6,9 12,15 18,9"></polyline>
</svg>
</div>
</div>
</th>
<th class="actions-column">Actions</th>
</tr>
</thead>
<tbody id="tableBody">
<!-- Data rows will be populated by JavaScript -->
</tbody>
</table>
</div>
<!-- Loading State -->
<div class="loading-state" id="loadingState" style="display: none;">
<div class="loading-spinner"></div>
<p>Loading data...</p>
</div>
<!-- Empty State -->
<div class="empty-state" id="emptyState" style="display: none;">
<svg class="empty-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<circle cx="11" cy="11" r="8"></circle>
<path d="m21 21-4.35-4.35"></path>
</svg>
<h3>No employees found</h3>
<p>Try adjusting your search criteria or filters</p>
<button class="btn btn-primary" onclick="clearAllFilters()">Clear All Filters</button>
</div>
</div>
<!-- Pagination -->
<div class="pagination-container">
<div class="pagination-info">
<span id="paginationInfo">Showing 1 to 25 of 150 entries</span>
</div>
<nav class="pagination" aria-label="Data table pagination">
<button class="pagination-btn" id="firstPage" aria-label="First page">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor">
<polyline points="11,17 6,12 11,7"></polyline>
<polyline points="18,17 13,12 18,7"></polyline>
</svg>
</button>
<button class="pagination-btn" id="prevPage" aria-label="Previous page">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor">
<polyline points="15,18 9,12 15,6"></polyline>
</svg>
</button>
<div class="pagination-numbers" id="paginationNumbers">
<!-- Page numbers will be populated by JavaScript -->
</div>
<button class="pagination-btn" id="nextPage" aria-label="Next page">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor">
<polyline points="9,18 15,12 9,6"></polyline>
</svg>
</button>
<button class="pagination-btn" id="lastPage" aria-label="Last page">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor">
<polyline points="13,17 18,12 13,7"></polyline>
<polyline points="6,17 11,12 6,7"></polyline>
</svg>
</button>
</nav>
</div>
<!-- Column Toggle Modal -->
<div class="modal" id="columnModal">
<div class="modal-content">
<div class="modal-header">
<h3>Manage Columns</h3>
<button class="modal-close" id="closeColumnModal">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
</div>
<div class="modal-body">
<p>Select which columns to display in the table:</p>
<div class="column-toggles" id="columnToggles">
<!-- Column toggles will be populated by JavaScript -->
</div>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" id="resetColumns">Reset to Default</button>
<button class="btn btn-primary" id="applyColumns">Apply Changes</button>
</div>
</div>
</div>
<!-- Export Modal -->
<div class="modal" id="exportModal">
<div class="modal-content">
<div class="modal-header">
<h3>Export Data</h3>
<button class="modal-close" id="closeExportModal">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
</div>
<div class="modal-body">
<p>Choose export format and options:</p>
<div class="export-options">
<div class="export-format">
<label>Export Format:</label>
<div class="radio-group">
<label class="radio-option">
<input type="radio" name="exportFormat" value="csv" checked>
<span>CSV</span>
</label>
<label class="radio-option">
<input type="radio" name="exportFormat" value="json">
<span>JSON</span>
</label>
<label class="radio-option">
<input type="radio" name="exportFormat" value="print">
<span>Print</span>
</label>
</div>
</div>
<div class="export-scope">
<label>Export Scope:</label>
<div class="radio-group">
<label class="radio-option">
<input type="radio" name="exportScope" value="current" checked>
<span>Current Page</span>
</label>
<label class="radio-option">
<input type="radio" name="exportScope" value="filtered">
<span>All Filtered Results</span>
</label>
<label class="radio-option">
<input type="radio" name="exportScope" value="all">
<span>All Data</span>
</label>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" id="cancelExport">Cancel</button>
<button class="btn btn-primary" id="confirmExport">Export</button>
</div>
</div>
</div>
</div>