Pagination
Setiap tabel panel dipaginasi oleh database. Schema menentukan pilihan ukuran halaman dan ukuran yang digunakan sebelum pengguna memilih apa pun; sisanya dibawa oleh URL. Gunakan panduan ini ketika tabel membutuhkan ukuran halaman berbeda, atau ketika Anda membuat tabel di luar resource index dan perlu memanggil paginator sendiri.
Contoh minimal
use PandaPanel\Tables\Columns\TextColumn;
use PandaPanel\Tables\TableSchema;
return $table
->columns([TextColumn::make('name')->sortable()])
->perPageOptions([25, 50, 100])
->defaultPerPage(50);2
3
4
5
6
7
Tabel akan terbuka dengan 50 baris per halaman dan menawarkan pilihan 25, 50, dan 100 pada kontrol ukuran halaman.
Dua deklarasi utama
| Method | Signature | Default |
|---|---|---|
perPageOptions() | perPageOptions(array $options): self | [10, 25, 50, 100] |
defaultPerPage() | defaultPerPage(int $perPage): self | 25 |
perPageOptions() membuang nilai yang tidak lebih besar dari nol, menghapus duplikasi, lalu mengurutkannya ascending. Urutan saat Anda menulis array tidak berpengaruh; frontend selalu menerima nilai dari kecil ke besar.
$table->perPageOptions([100, 25, 0, 25, -5]);
$table->getPerPageOptions(); // [25, 100]2
3
getDefaultPerPage() melakukan clamp: jika default yang dideklarasikan tidak termasuk di dalam pilihan, pilihan pertama akan digunakan. Jika tidak ada pilihan sama sekali, fallback-nya adalah 25.
$table->perPageOptions([10, 20])->defaultPerPage(15);
$table->getDefaultPerPage(); // 102
3
Bagian yang dikontrol URL
| Parameter | Dibaca sebagai | Jika nilainya ditolak |
|---|---|---|
?page= | max(1, (int) $value) | nilai di bawah 1 menjadi 1 |
?perPage= | harus cocok persis dengan perPageOptions() | fallback ke getDefaultPerPage() |
?perPage=100000 tidak dapat memaksa database mengambil semua baris. Nilai di luar pilihan yang dideklarasikan tidak di-clamp ke pilihan terdekat, tetapi diabaikan dan diganti default. state()['perPage'] melaporkan nilai yang benar-benar diterapkan, sehingga kontrol UI tidak pernah menampilkan ukuran yang tidak digunakan query.
Nomor halaman yang melebihi halaman terakhir bukan error. Laravel mengembalikan halaman kosong, lastPage tetap menunjukkan batas data, dan tombol next-page frontend sudah dinonaktifkan pada kondisi tersebut.
Melakukan pagination
use PandaPanel\Tables\TableQuery;
use PandaPanel\Tables\TableSchema;
$schema = OrderResource::table(TableSchema::make());
$query = OrderResource::query();
$tableQuery = new TableQuery($schema, request());
$records = $tableQuery->paginate($query); // LengthAwarePaginator2
3
4
5
6
7
8
9
| Method | Signature | Penggunaan |
|---|---|---|
constrain() | constrain(Builder $query): void | menerapkan seluruh state kecuali halaman dan ukuran halaman |
paginate() | paginate(Builder $query): LengthAwarePaginator | kasus umum |
paginateRelation() | paginateRelation(Relation $relation): LengthAwarePaginator | tabel relasi |
paginate() memanggil constrain() terlebih dahulu — column queries, base-query filters, search, filters, dan sort — kemudian menjalankan $query->paginate(perPage, page)->withQueryString(). Karena itu link yang dihasilkan tetap membawa state tabel lainnya.
Menggunakan constrain() tanpa pagination
$tableQuery->constrain($query);
$query->each(static fn ($record) => /* ... */);2
3
Method ini menerapkan seluruh state yang menentukan record mana yang dipilih dan dalam urutan apa, tetapi tidak menentukan jumlah record. Export adalah contoh yang tepat: file harus berisi record yang sama dengan daftar yang sedang dilihat pengguna, tetapi seluruhnya. ExportAction menggunakan method yang sama alih-alih menduplikasi logika, sehingga file export tidak mengambil data berbeda dari layar tempat export dijalankan.
paginateRelation()
$records = $tableQuery->paginateRelation($manager::relationForTable($owner));Method ini tidak dapat dipertukarkan begitu saja dengan paginate($relation->getQuery()) untuk relasi many-to-many. getQuery() mengembalikan underlying builder yang tidak mengetahui data pivot, sehingga pagination tersebut menghasilkan record tanpa relation pivot dan nilai kolom pivot akan terbaca sebagai null. BelongsToMany::paginate() memilih alias kolom pivot dan melakukan hydration setelahnya. State tetap diterapkan ke instance builder yang sama di dalam relation, sehingga search, filter, dan sort bekerja sama seperti pada query biasa.
Payload
ListRecords::pagination() mengirim enam counter dan tidak mengirim struktur tambahan lain:
[
'page' => 2,
'perPage' => 25,
'total' => 137,
'lastPage' => 6,
'from' => 26,
'to' => 50,
]2
3
4
5
6
7
8
Array link bawaan paginator sengaja tidak dikirim. Frontend membangun URL dari query string saat ini, sehingga URL tetap menjadi single source of truth untuk state tabel. Jika server juga mengirim link hasil render, link tersebut harus mengodekan ulang search, filter, sort, dan susunan kolom — berarti ada dua sumber kebenaran untuk state yang sama.
from dan to di-cast dari firstItem() dan lastItem(). Keduanya null pada halaman kosong sehingga dikirim sebagai 0.
Relation table dan table widget mengirim enam key yang sama, sehingga satu komponen pagination Vue dapat digunakan untuk ketiga konteks.
Lebih dari satu tabel dalam satu halaman
Halaman record dapat memiliki beberapa relation table, dan dashboard dapat memiliki beberapa table widget. Jika semuanya memakai ?page= yang sama, satu pagination akan menggerakkan semua tabel. Karena itu state setiap tabel disimpan di bawah namespace:
new TableQuery($schema, $request, namespace: 'relations.tasks');
// reads ?relations[tasks][page]=2&relations[tasks][perPage]=102
| Konteks | Namespace |
|---|---|
| resource index | tidak ada |
| relation manager | relations.{manager key} |
| table widget | widgets.{kebab-cased widget class} |
TableQuery::namespace() membaca namespace tersebut kembali, dan RelationTable mengirimnya ke frontend sebagai stateKey agar client menulis state ke lokasi yang sama.
Ukuran halaman pada table widget
PandaPanel\Widgets\TableWidget menggunakan ukuran halaman tetap alih-alih menampilkan kontrol pilihan:
final class RecentOrders extends TableWidget
{
protected static int $perPage = 5;
}2
3
4
Widget membangun schema lalu menerapkan perPageOptions([$perPage])->defaultPerPage($perPage), sehingga request yang meminta nilai lain akan fallback ke satu-satunya pilihan tersebut. Tabel dashboard memang ditujukan untuk dibaca sekilas.
Pagination pada data array
PandaPanel\Tables\ArrayTableData melakukan pagination di memory menggunakan deklarasi yang sama:
$data = ArrayTableData::make($schema, $records, $request);
$page = $data->paginate();
$data->pagination($page); // the same six keys
$data->rows($page); // serialized rows2
3
4
5
Implementasi ini membaca ?page= dan menggunakan getDefaultPerPage(), tetapi tidak membaca ?perPage=. Lihat Tabel data array.
Hal yang perlu diperhatikan
perPagetidak pernah disimpan di session. Search, sort, filters, dan columns dapat dipersist; ukuran halaman selalu dibaca dari request dan fallback ke default schema.paginate()meninggalkan limit dan offset pada builder yang diterimanya. Perhitungan apa pun dari builder tersebut setelah pagination dapat menggambarkan satu halaman sambil seolah-olah menggambarkan seluruh hasil. Karena ituTableSchema::summaries()melakukan clone, menghapus limit dan offset, serta menghapus ordering sebelum agregasi.defaultPerPage()di-clamp tanpa error. Jika ukuran halaman yang dipilih tidak ada dalamperPageOptions(), pilihan pertama digunakan.?page=tidak memiliki batas atas. Bookmark halaman 900 dari tabel yang kini hanya memiliki tiga halaman akan menampilkan halaman kosong, bukan redirect.- Tabel yang tidak memanggil
perPageOptions()tetap memiliki empat pilihan. Default-nya[10, 25, 50, 100]; panggilperPageOptions()untuk mempersempit pilihan, bukan untuk mengaktifkan pagination. - Grouping tidak mengubah mekanisme halaman. Satu band dapat terbagi di dua halaman; ini adalah perilaku yang konsisten untuk tabel dengan server-side pagination. Lihat Grouping.