Ketika membangun dashboard admin profesional, kenyamanan pengguna menjadi prioritas utama. Pengguna tidak ingin menunggu lama atau melihat halaman berkedip setiap kali melakukan pencarian atau mengganti filter. Di sinilah konsep AJAX dengan loading animation dan debounce sangat membantu.
Fitur loading animation memberikan umpan balik visual kepada pengguna bahwa data sedang diproses, sementara debounce mencegah permintaan AJAX dikirim terlalu sering saat pengguna mengetik cepat di kolom pencarian. Hasilnya? Aplikasi terasa lebih cepat, ringan, dan profesional — seperti pengalaman dashboard SaaS kelas atas.
Struktur Database
Masih sama seperti artikel sebelumnya, kita gunakan tabel products sebagai contoh data.
CREATE TABLE products ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100), category VARCHAR(50), price DECIMAL(10,2), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP );
File config.php (Koneksi Database)
<?php
$mysqli = new mysqli("localhost", "root", "", "db_toko");
if ($mysqli->connect_error) {
die("Koneksi gagal: " . $mysqli->connect_error);
}
?>
File fetch_data.php (Handler AJAX)
<?php
include 'config.php';
$limit = 5;
$page = isset($_POST['page']) ? $_POST['page'] : 1;
$search = isset($_POST['search']) ? $mysqli->real_escape_string($_POST['search']) : '';
$filter = isset($_POST['filter']) ? $mysqli->real_escape_string($_POST['filter']) : '';
$offset = ($page - 1) * $limit;
$where = "WHERE 1=1";
if ($search != '') {
$where .= " AND name LIKE '%$search%'";
}
if ($filter != '') {
$where .= " AND category = '$filter'";
}
$query = "SELECT * FROM products $where LIMIT $offset, $limit";
$result = $mysqli->query($query);
$totalQuery = $mysqli->query("SELECT COUNT(*) as total FROM products $where");
$total = $totalQuery->fetch_assoc()['total'];
$totalPages = ceil($total / $limit);
$output = '<table class="table table-bordered">
<thead><tr><th>ID</th><th>Nama</th><th>Kategori</th><th>Harga</th></tr></thead><tbody>';
if ($result->num_rows > 0) {
while ($row = $result->fetch_assoc()) {
$output .= "<tr>
<td>{$row['id']}</td>
<td>{$row['name']}</td>
<td>{$row['category']}</td>
<td>Rp " . number_format($row['price'], 0, ',', '.') . "</td>
</tr>";
}
} else {
$output .= "<tr><td colspan='4' class='text-center text-muted'>Tidak ada data ditemukan</td></tr>";
}
$output .= "</tbody></table>";
$output .= '<div class="pagination text-center">';
for ($i = 1; $i <= $totalPages; $i++) {
$active = ($i == $page) ? 'active' : '';
$output .= "<button class='btn btn-sm btn-outline-primary m-1 page-link $active' data-page='$i'>$i</button>";
}
$output .= '</div>';
echo $output;
?>
File index.php (Tampilan + Loading + Debounce)
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Pagination + Search + Filter AJAX dengan Loading & Debounce</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
.page-link.active { background-color: #0d6efd; color: white; }
#loading {
display: none;
text-align: center;
padding: 20px;
}
.spinner-border {
width: 3rem;
height: 3rem;
}
</style>
</head>
<body class="p-4">
<div class="container">
<h3 class="mb-4 text-center">Data Produk (AJAX + Pagination + Filter + Loading)</h3>
<div class="row mb-3">
<div class="col-md-4 mb-2">
<input type="text" id="search" class="form-control" placeholder="Cari produk...">
</div>
<div class="col-md-4 mb-2">
<select id="filter" class="form-select">
<option value="">Semua Kategori</option>
<option value="Elektronik">Elektronik</option>
<option value="Fashion">Fashion</option>
<option value="Makanan">Makanan</option>
</select>
</div>
</div>
<div id="loading">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Memuat...</span>
</div>
</div>
<div id="data-container"></div>
</div>
<script src="https://code.jquery.com/jquery-3.6.4.min.js"></script>
<script>
let debounceTimer;
function load_data(page = 1) {
var search = $('#search').val();
var filter = $('#filter').val();
$('#loading').show();
$.ajax({
url: "fetch_data.php",
type: "POST",
data: { page: page, search: search, filter: filter },
success: function(data) {
$('#loading').hide();
$('#data-container').html(data);
}
});
}
// Fungsi debounce agar tidak kirim request berulang saat user mengetik
function debounceLoad() {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(() => load_data(1), 400);
}
$(document).ready(function() {
load_data();
$('#search').on('input', debounceLoad);
$('#filter').on('change', function() {
load_data(1);
});
$(document).on('click', '.page-link', function() {
var page = $(this).data('page');
load_data(page);
});
});
</script>
</body>
</html>
Penjelasan Fitur Tambahan
Loading Spinner
Memberi efek visual agar pengguna tahu data sedang dimuat, meningkatkan UX.
Debounce Function
Menunda pemanggilan AJAX selama 400ms setelah user berhenti mengetik, mencegah server overload.
Real-time Update
Data otomatis diperbarui tanpa reload halaman, cocok untuk tabel besar dan admin panel.
Kelebihan Teknik Ini
✅ Tidak membebani server dengan request berulang
✅ Menampilkan data cepat tanpa flicker halaman
✅ UX profesional seperti aplikasi modern (Shopee, admin panel Laravel)
✅ Mudah diadaptasi untuk API REST atau backend lain
Kesimpulan
Kombinasi Pagination + Search + Filter AJAX dengan tambahan loading animation dan debounce menghasilkan sistem manajemen data yang cepat, elegan, dan efisien. Teknik ini sangat disarankan untuk proyek web modern seperti dashboard admin, aplikasi inventori, atau toko online. Dengan pengalaman pengguna yang lebih lembut dan interaktif, web kamu akan terasa seperti aplikasi profesional.