Belgeler · DataTables
Tablolar, PHP ile.
DataTables bir tabloya arama, sıralama ve sayfalama ekler. Küçük veride tarayıcıda, büyük veride PHP ile sunucuda çalışır.
01CDN ile kurulum
<link rel="stylesheet" href="https://cdn.datatables.net/2.1.8/css/dataTables.dataTables.min.css" integrity="sha384-bK2L0Q3Sn2fB8YZTIjKejpTUJXd8xNtKn4RcIm7KM3EX3orMlOI31hwtNH0iyky/" crossorigin="anonymous">
<script src="https://code.jquery.com/jquery-3.7.1.min.js" integrity="sha384-1H217gwSVyLSIfaLxHbE7dRb3v4mYCKbpQvzx0cegeju1MVsGrX5xXxAvs/HgeFs" crossorigin="anonymous" defer></script>
<script src="https://cdn.datatables.net/2.1.8/js/dataTables.min.js" integrity="sha384-MgwUq0TVErv5Lkj/jIAgQpC+iUIqwhwXxJMfrZQVAOhr++1MR02yXH8aXdPc3fk0" crossorigin="anonymous" defer></script>DataTables 2 hâlâ jQuery’ye ihtiyaç duyar; sıralama önemlidir: önce jQuery, sonra DataTables.
02Tablo HTML’i
<section class="ad-panel ad-dt">
<table id="siparisler">
<thead><tr><th>Sipariş</th><th>Müşteri</th><th>Tarih</th><th>Tutar</th><th>Durum</th></tr></thead>
<tbody>
<tr>
<td><strong>#FK-10480</strong></td>
<td>Ayşe Kaya</td>
<td data-order="2026-10-24T09:42">24.10.2026 09:42</td>
<td data-order="2340">₺2.340</td>
<td data-search="Bekliyor"><span class="fk-badge fk-badge-warning">Bekliyor</span></td>
</tr>
</tbody>
</table>
</section>| Öznitelik | Ne işe yarar? |
|---|---|
data-order | Sıralamada görünen metin yerine bu değer kullanılır (tarih, para) |
data-search | Aramada rozetin HTML’i yerine düz metin kullanılır |
.ad-dt | DataTables’ı fkdev temasına bürür (arama hapı, sayfalama, satırlar) |
03Başlatma ve Türkçe
const dt = new DataTable('#siparisler', {
pageLength: 10,
lengthMenu: [10, 25, 50],
order: [[2, 'desc']],
columnDefs: [{ orderable: false, searchable: false, targets: -1 }],
language: {
decimal: ',', thousands: '.',
info: '_TOTAL_ siparişten _START_–_END_ arası',
infoEmpty: 'Gösterilecek sipariş yok',
infoFiltered: '(toplam _MAX_ sipariş içinde)',
lengthMenu: 'Sayfada _MENU_ kayıt',
search: '',
searchPlaceholder: 'Sipariş no, müşteri, şehir…',
zeroRecords: 'Aramana uyan sipariş bulunamadı',
paginate: { first: '«', last: '»', next: '›', previous: '‹' }
}
});
document.querySelectorAll('input[name="durum"]').forEach((r) => {
r.addEventListener('change', () => {
dt.column(5).search(r.value ? '^' + r.value + '$' : '', true, false).draw();
});
});04Sunucu taraflı: PHP + PDO
Binlerce satırda tüm tabloyu sayfaya basmak yerine DataTables her sayfa için PHP’ye istek atar; PHP yalnızca o 10 satırı döndürür.
new DataTable('#siparisler', {
serverSide: true,
processing: true,
ajax: { url: '/api/siparisler.php', type: 'GET' },
order: [[2, 'desc']],
columns: [null, null, null, null, { className: 'dt-type-numeric' }, null]
});<?php
declare(strict_types=1);
require __DIR__ . '/../bootstrap.php';
require_login(['admin', 'editor']);
header('Content-Type: application/json; charset=utf-8');
header('Cache-Control: no-store');
$columns = [
0 => 'o.code',
1 => 'c.name',
2 => 'o.created_at',
3 => 'c.city',
4 => 'o.total',
5 => 'o.status',
];
$draw = (int) ($_GET['draw'] ?? 0);
$start = max(0, (int) ($_GET['start'] ?? 0));
$length = (int) ($_GET['length'] ?? 10);
$length = $length < 1 || $length > 100 ? 10 : $length;
$search = trim((string) ($_GET['search']['value'] ?? ''));
$orderCol = (int) ($_GET['order'][0]['column'] ?? 2);
$orderDir = strtolower((string) ($_GET['order'][0]['dir'] ?? 'desc')) === 'asc' ? 'ASC' : 'DESC';
$orderBy = $columns[$orderCol] ?? 'o.created_at';
$from = ' FROM orders o JOIN customers c ON c.id = o.customer_id ';
$where = '';
$params = [];
if ($search !== '') {
$where = ' WHERE (o.code LIKE :q1 OR c.name LIKE :q2 OR c.city LIKE :q3 OR o.status LIKE :q4) ';
$like = '%' . addcslashes($search, '%_\\') . '%';
foreach ([':q1', ':q2', ':q3', ':q4'] as $k) {
$params[$k] = $like;
}
}
$status = (string) ($_GET['columns'][5]['search']['value'] ?? '');
$allowed = ['Bekliyor', 'Hazırlanıyor', 'Kargoda', 'Teslim edildi', 'İade'];
$status = trim($status, '^$');
if (in_array($status, $allowed, true)) {
$where .= ($where ? ' AND ' : ' WHERE ') . ' o.status = :status ';
$params[':status'] = $status;
}
$total = (int) db()->query('SELECT COUNT(*) FROM orders')->fetchColumn();
$stmt = db()->prepare('SELECT COUNT(*)' . $from . $where);
$stmt->execute($params);
$filtered = (int) $stmt->fetchColumn();
$sql = 'SELECT o.code, c.name, o.created_at, c.city, o.total, o.status'
. $from . $where
. " ORDER BY $orderBy $orderDir LIMIT :start, :length";
$stmt = db()->prepare($sql);
foreach ($params as $k => $v) {
$stmt->bindValue($k, $v);
}
$stmt->bindValue(':start', $start, PDO::PARAM_INT);
$stmt->bindValue(':length', $length, PDO::PARAM_INT);
$stmt->execute();
$data = [];
foreach ($stmt as $row) {
$data[] = [
e($row['code']),
e($row['name']),
e(date('d.m.Y H:i', strtotime($row['created_at']))),
e($row['city']),
number_format((float) $row['total'], 2, ',', '.') . ' ₺',
e($row['status']),
];
}
echo json_encode([
'draw' => $draw,
'recordsTotal' => $total,
'recordsFiltered' => $filtered,
'data' => $data,
], JSON_UNESCAPED_UNICODE);| İstek parametresi | Anlamı |
|---|---|
draw | İstek sayacı; aynen geri döndürülür |
start / length | Kaçıncı satırdan, kaç satır |
search[value] | Genel arama kutusu |
order[0][column] / [dir] | Sıralanan sütun ve yön |
columns[i][search][value] | Sütun filtresi (durum segmenti) |
{
"draw": 3,
"recordsTotal": 12840,
"recordsFiltered": 214,
"data": [["#FK-10480", "Ayşe Kaya", "24.10.2026 09:42", "İstanbul", "2.340,00 ₺", "Bekliyor"]]
}05Güvenlik notları
- Sütun adını asla istekten alma.
order[0][column]bir sayıdır; onu kendi$columnsdizinde karşılığa çevir (beyaz liste). - Yönü sabitle: yalnızca
ASCya daDESC. - Limiti sınırla:
lengthen fazla 100; yoksa birilength=1000000ile sunucunu yorar. - Hazırlanmış sorgu: arama değeri her zaman parametre olarak bağlanır;
LIKEiçindeki%ve_kaçırılır. - Çıktıyı kaçır: hücreye giden her metin
e()(htmlspecialchars) ile kaçırılır; yoksa müşteri adına yazılan bir betik admin panelinde çalışır. - Yetki: uç nokta
require_login(['admin', 'editor'])ile korunur.
06Performans
CREATE INDEX idx_orders_created ON orders (created_at);
CREATE INDEX idx_orders_status ON orders (status, created_at);Sıraladığın ve filtrelediğin her sütunun indeksi olsun. LIKE '%kelime%' indeks kullanamaz; çok büyük tablolarda FULLTEXT indeks ve MATCH … AGAINST kullan.
07Satır işlemleri (silme)
document.querySelector('#siparisler').addEventListener('click', async (e) => {
const btn = e.target.closest('[data-sil]');
if (!btn) return;
if (!(await FK.confirm({ title: 'Sipariş silinsin mi?', ok: 'Sil', danger: true }))) return;
const res = await fetch('/api/siparis-sil.php', {
method: 'POST',
headers: { 'X-CSRF-Token': document.querySelector('meta[name="csrf"]').content },
body: new URLSearchParams({ id: btn.dataset.sil })
});
if (res.ok) { dt.row(btn.closest('tr')).remove().draw(false); FK.toast('Silindi', 'success'); }
else FK.toast('Silinemedi', 'danger');
});