Table API Example - Server-side Pagination & Filtering
Introduction
This example demonstrates how to use the Table Manager with a PHP REST API that supports server-side pagination, searching and filtering.
Key Features:
- Server-side Pagination: Reduces client load by paginating on the server
- Server-side Search: Search runs on the server and supports large datasets
- Server-side Filtering: Filtering performed server-side
- Server-side Sorting: Sorting performed server-side
- Real-time Updates: Real-time updates from the API
- Performance: Improved performance for large datasets
Live Demo
This table loads data from a PHP REST API with server-side pagination, search and filters.
ID | Name | Position | Department | Status | Salary | Join Date |
---|
HTML Code
Example HTML markup for creating an API-backed table
<table class="table border fullwidth"
data-table="api-employees"
data-source="api/employees.php"
data-page-size="10"
data-search-columns="name,email,position,department"
data-allow-row-modification="false"
data-show-checkbox="true">
<thead>
<tr>
<th data-field="id" data-sort="id">ID</th>
<th data-field="name" data-sort="name">Name</th>
<th data-field="position" data-sort="position" data-filter="true" data-type="select" data-show-all="true" data-all-value="" data-label="Position" data-format="lookup">Position</th>
<th data-field="department" data-sort="department" data-filter="true" data-type="select" data-show-all="true" data-all-value="" data-label="Department" data-format="lookup">Department</th>
<th data-field="email" data-sort="email">Email</th>
<th data-field="status" data-sort="status" data-filter="true" data-type="select" data-show-all="true" data-all-value="" data-label="Status" data-format="lookup">Status</th>
<th data-field="salary" data-sort="salary" data-format="currency">Salary</th>
<th data-field="join_date" data-sort="join_date">Join Date</th>
</tr>
</thead>
<tbody></tbody>
</table>
Data Attributes (explained):
Attribute | Description | Example |
---|---|---|
data-table | Table identifier used to reference the instance | api-employees |
data-source | URL of the API endpoint | api/employees.php |
data-page-size | Items per page | 10 |
data-search-columns | name,email,position,department | |
events | Register lightweight listeners using EventManager.on('table:render', handler) and EventManager.on('table:selectionChange', handler) | - | data-format | Display format | lookup, currency |
PHP API Endpoint
API endpoint that supports pagination, searching, and filtering
<?php
header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *');
// Receive parameters from the query string
$page = isset($_GET['page']) ? (int)$_GET['page'] : 1;
$pageSize = isset($_GET['pageSize']) ? (int)$_GET['pageSize'] : 10;
$search = isset($_GET['search']) ? trim($_GET['search']) : '';
$sort = isset($_GET['sort']) ? $_GET['sort'] : '';
$order = isset($_GET['order']) ? $_GET['order'] : 'asc';
// Initialize example event listeners on DOM ready
// Filters
$department = isset($_GET['department']) ? $_GET['department'] : '';
$position = isset($_GET['position']) ? $_GET['position'] : '';
$status = isset($_GET['status']) ? $_GET['status'] : '';
// Minimal example listeners for the demo (use modern TableManager events)
EventManager.on('table:render', function(payload) {
if (payload.tableId === 'api-employees') {
console.info('api-employees rendered', payload.data || []);
}
});
// Selection changes are emitted as table:selectionChange
EventManager.on('table:selectionChange', function(payload) {
if (payload.tableId === 'api-employees') {
console.info('selection changed', payload.selectedRows || []);
}
});
8 => 'HR Staff'
];
$statuses = [
0 => 'Working',
1 => 'On Leave',
2 => 'Resigned',
3 => 'Probation'
];
// Employee data (example)
$employees = [
[
'id' => 1,
'name' => 'Somchai Jaidee',
'position' => 0,
'department' => 0,
'email' => 'somchai@example.com',
'status' => 0,
'salary' => 45000,
'join_date' => '2023-01-15'
],
// ... other data
];
// Filter data
$filteredData = $employees;
// Search
if (!empty($search)) {
$filteredData = array_filter($filteredData, function ($employee) use ($search, $departments, $positions, $statuses) {
$name = $employee['name'];
$email = $employee['email'];
$positionName = $positions[$employee['position']] ?? '';
$departmentName = $departments[$employee['department']] ?? '';
$statusName = $statuses[$employee['status']] ?? '';
return stripos($name, $search) !== false ||
stripos($email, $search) !== false ||
stripos($positionName, $search) !== false ||
stripos($departmentName, $search) !== false ||
stripos($statusName, $search) !== false;
});
}
// Filter by department
if (!empty($department)) {
$filteredData = array_filter($filteredData, function ($employee) use ($department) {
return $employee['department'] == $department;
});
}
// Filter by position
if (!empty($position)) {
$filteredData = array_filter($filteredData, function ($employee) use ($position) {
return $employee['position'] == $position;
});
}
// Filter by status
if (!empty($status)) {
$filteredData = array_filter($filteredData, function ($employee) use ($status) {
return $employee['status'] == $status;
});
}
// Sorting
if (!empty($sort)) {
usort($filteredData, function ($a, $b) use ($sort, $order) {
$aVal = $a[$sort];
$bVal = $b[$sort];
if ($order === 'desc') {
return $bVal <=> $aVal;
}
return $aVal <=> $bVal;
});
}
// Calculate pagination
$totalRecords = count($filteredData);
$totalPages = ceil($totalRecords / $pageSize);
$offset = ($page - 1) * $pageSize;
$paginatedData = array_slice($filteredData, $offset, $pageSize);
// Prepare filter options as arrays (prefer array for stable ordering)
$filterDepartments = [];
$filterDepartments[] = ['value' => '', 'text' => 'All'];
foreach ($departments as $k => $v) {
$filterDepartments[] = ['value' => $k, 'text' => $v];
}
$filterPositions = [];
$filterPositions[] = ['value' => '', 'text' => 'All'];
foreach ($positions as $k => $v) {
$filterPositions[] = ['value' => $k, 'text' => $v];
}
$filterStatuses = [];
$filterStatuses[] = ['value' => '', 'text' => 'All'];
foreach ($statuses as $k => $v) {
$filterStatuses[] = ['value' => $k, 'text' => $v];
}
// Build response (use array for filters to preserve ordering)
$response = [
'data' => array_values($paginatedData),
'meta' => [
'total' => $totalRecords,
'page' => $page,
'pageSize' => $pageSize,
'totalPages' => $totalPages,
'hasNext' => $page < $totalPages,
'hasPrev' => $page > 1
],
'filters' => [
'department' => $filterDepartments,
'position' => $filterPositions,
'status' => $filterStatuses
]
];
echo json_encode($response, JSON_UNESCAPED_UNICODE);
?>
JavaScript Events
JavaScript examples for handling API table events
// Example: preferred event handlers for the API-backed table
function setupApiTableEvents() {
// Listen to render events to get the currently displayed page and meta
EventManager.on('table:render', function(payload) {
if (payload.tableId === 'api-employees') {
exampleLog('API table rendered', { data: payload.data, total: payload.totalRecords, filtered: payload.filteredRecords });
}
});
// Listen to selection changes
EventManager.on('table:selectionChange', function(payload) {
if (payload.tableId === 'api-employees') {
exampleLog('Selection changed', payload.selectedRows);
}
});
}
// Call on DOM ready
document.addEventListener('DOMContentLoaded', function() {
if (window.EventManager && typeof EventManager.on === 'function') {
setupApiTableEvents();
}
});
// Function to refresh API table data
function refreshApiTable() {
const tableId = 'api-employees';
const table = TableManager.state.tables.get(tableId);
if (table) {
TableManager.loadData(tableId, table.config.source);
}
}
// Function to change page size
function changePageSize(size) {
const tableId = 'api-employees';
const table = TableManager.state.tables.get(tableId);
if (table) {
table.config.params.pageSize = size;
table.config.params.page = 1; // reset to first page
TableManager.loadData(tableId, table.config.source);
}
}
Features of the API Example
Server-side Pagination
Server-side pagination reduces client load and improves performance
Server-side Search
Server-side search supports large datasets and lookup field searches
Server-side Filtering
Server-side filtering supports multiple filter types
Server-side Sorting
Server-side sorting supports multi-field sorting
Real-time Updates
Real-time updates from the API
Performance
Improved performance for large datasets
API Parameters
Parameters supported by the API
Parameter | Type | Description | Example |
---|---|---|---|
page | integer | Page number to request | 1, 2, 3... |
pageSize | integer | Items per page | 10, 25, 50 |
search | string | Search term | e.g. somchai |
sort | string | Field to sort by | name, salary, join_date |
order | string | Sort direction | asc, desc |
department | string | Filter by department | 0, 1, 2... |
position | string | Filter by position | 0, 1, 2... |
status | string | Filter by status | 0, 1, 2... |
Response Format
API response format
{
"data": [
{
"id": 1,
"name": "Somchai Jaidee",
"position": 0,
"department": 0,
"email": "somchai@example.com",
"status": 0,
"salary": 45000,
"join_date": "2023-01-15"
}
],
"meta": {
"total": 30,
"page": 1,
"pageSize": 10,
"totalPages": 3,
"hasNext": true,
"hasPrev": false
},
"filters": {
"department": {
"": "All",
"0": "Development",
"1": "Systems Analysis"
},
"position": {
"": "All",
"0": "Programmer",
"1": "Analyst"
},
"status": {
"": "All",
"0": "Working",
"1": "On Leave"
}
}
}
Advanced Features
Advanced features for the API
1. Custom API Headers
// Set custom headers for API
const tableConfig = {
source: 'api/employees.php',
headers: {
'Authorization': 'Bearer your-token-here',
'X-API-Key': 'your-api-key',
'Content-Type': 'application/json'
}
};
TableManager.init(document.querySelector('#myTable'), tableConfig);
2. API Response Transformation
// Function to transform API response
function transformApiResponse(data) {
return {
data: data.employees.map(emp => ({
...emp,
fullName: `${emp.firstName} ${emp.lastName}`,
salaryFormatted: new Intl.NumberFormat('th-TH', {
style: 'currency',
currency: 'THB'
}).format(emp.salary)
})),
meta: data.pagination
};
}
3. API Caching
// API cache system
const apiCache = new Map();
function getCachedData(key) {
const cached = apiCache.get(key);
if (cached && Date.now() - cached.timestamp < 300000) { // 5 minutes
return cached.data;
}
return null;
}
function setCachedData(key, data) {
apiCache.set(key, {
data: data,
timestamp: Date.now()
});
}
4. Real-time Updates
// Real-time updates system
function setupRealTimeUpdates(tableId) {
const eventSource = new EventSource('api/employees-stream.php');
eventSource.onmessage = function(event) {
const data = JSON.parse(event.data);
if (data.type === 'update') {
// Update table data
const table = TableManager.state.tables.get(tableId);
if (table) {
const existingData = table.data;
const updatedData = existingData.map(item =>
item.id === data.employee.id ? data.employee : item
);
TableManager.setData(tableId, updatedData);
}
}
};
}