Grouping
Grouping membagi baris tabel ke dalam band dengan heading — misalnya order berdasarkan status, user berdasarkan role, atau task berdasarkan project. Gunakan ketika baris dapat dikelompokkan ke dalam beberapa kategori dan menampilkan semuanya sebagai satu daftar panjang membuat struktur data sulit dibaca.
Grouping adalah presentation, bukan aggregation. Fitur ini tidak mengubah record apa yang dikembalikan query, sehingga pagination tetap bekerja dengan cara yang sama.
Contoh minimal tabel yang dikelompokkan
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
use PandaPanel\Tables\Columns\TextColumn;
use PandaPanel\Tables\Group;
use PandaPanel\Tables\TableSchema;
return $table
->columns([
TextColumn::make('reference')->searchable(),
TextColumn::make('status'),
])
->groups([
Group::make('status')
->label('Status')
->titleUsing(static fn (Model $record): string => Str::headline($record->status)),
])
->defaultGroup('status');2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Pengguna memilih grouping dari kontrol tabel; pilihan tersebut disimpan di URL sebagai ?group=status.
TableSchema
| Method | Signature | Default |
|---|---|---|
groups() | groups(array $groups): self | [] |
defaultGroup() | defaultGroup(string $name): self | null |
getGroups() | getGroups(): list<Group> | — |
getGroup() | getGroup(string $name): ?Group | — |
getDefaultGroup() | getDefaultGroup(): ?string | — |
Satu tabel dapat mendeklarasikan beberapa cara untuk mengelompokkan baris, tetapi hanya satu yang aktif pada satu waktu.
Group
use Illuminate\Database\Eloquent\Model;
use PandaPanel\Tables\Enums\SortDirection;
use PandaPanel\Tables\Group;
Group::make('project_id')
->label('Project')
->column('project_id')
->direction(SortDirection::Descending)
->titleUsing(static fn (Model $record): string => $record->project?->name ?? 'Unassigned')
->descriptionUsing(static fn (Model $record): ?string => $record->project?->code);2
3
4
5
6
7
8
9
10
| Method | Signature | Default |
|---|---|---|
make() | static make(string $name): self | — |
label() | label(string $label): self | Str::headline() dari nama |
column() | column(string $column): self | nama grup |
direction() | direction(SortDirection $direction): self | SortDirection::Ascending |
titleUsing() | titleUsing(Closure $callback): self | raw key, atau Ungrouped ketika kosong |
descriptionUsing() | descriptionUsing(Closure $callback): self | null |
Kedua closure menerima record dan mengembalikan string (?string untuk description). Nilainya di-resolve di server, sehingga sebuah key dapat diubah menjadi nama yang mudah dibaca tanpa frontend perlu mengetahui caranya.
Method pembaca: getName(), getLabel(), getColumn(), keyFor(Model $record): string, titleFor(Model $record): string, descriptionFor(Model $record): ?string, applySort(Builder $query): void, toArray(): array.
toArray() hanya mengirim name dan label. Title ikut pada row karena nilainya dapat bergantung pada setiap record.
Menentukan band untuk sebuah baris
$group->keyFor($record); // the raw value of the group column, cast to stringKey menggunakan raw value, bukan title. Dua record dengan key yang sama berada dalam band yang sama meskipun title closure menghasilkan title berbeda. Nilai non-scalar diperlakukan sebagai string kosong.
Setiap row yang diserialisasi membawa informasi grupnya:
$schema->toRow($record, $group);
// [
// 'key' => 12,
// 'group' => ['key' => '3', 'title' => 'Apollo', 'description' => null],
// 'cells' => [...],
// 'cellMeta' => [...],
// 'actions' => [...],
// ]2
3
4
5
6
7
8
9
group bernilai null ketika tabel tidak sedang dikelompokkan. Frontend menggambar heading band setiap kali key berubah dari row sebelumnya.
Dampak grouping terhadap query
Grouping mengubah satu hal: ordering. Row harus tiba secara berdekatan agar dapat ditampilkan dalam band yang sama, sehingga group aktif selalu diurutkan sebelum sort tabel lainnya.
// TableQuery::applySort(), in order
$group?->applySort($query); // order by the group column, ascending by default
// then the requested sort, the custom sort, the relation sort, or the default2
3
Grouping tidak mengubah result set menjadi satu row per grup. Sebuah band dapat terbagi di dua halaman seperti rangkaian row lainnya. Ini adalah perilaku yang benar untuk server-paginated table; menggabungkan seluruh hasil menjadi grup utuh akan mengharuskan server mengambil semua record terlebih dahulu.
Grup aktif
$tableQuery->activeGroup(); // ?Group
$tableQuery->state()['group']; // ?string2
Resolusi mengikuti aturan state tabel lainnya:
| Request | Hasil |
|---|---|
?group=status yang menunjuk grup terdaftar | grup tersebut |
?group=deleted_at yang tidak terdaftar | null — diabaikan, bukan error |
?group= | null — nilai kosong eksplisit menonaktifkan grouping |
tidak ada key group | defaultGroup(), atau null |
Kasus nilai kosong eksplisit memungkinkan tabel yang memiliki defaultGroup() tetap dapat ditampilkan tanpa grouping.
Grouping disimpan bersama sort: persistSortInSession() juga mencakup group, karena keduanya merupakan satu keputusan tentang susunan baris.
Summary per grup
use PandaPanel\Tables\Columns\NumberColumn;
use PandaPanel\Tables\Summaries\Count;
use PandaPanel\Tables\Summaries\Sum;
$table->columns([
NumberColumn::make('total')->summarize([Sum::make(), Count::make()]),
]);2
3
4
5
6
7
Ketika tabel dikelompokkan dan kolom mendeklarasikan summarizer, ListRecords juga mengirim groupSummaries:
$schema->groupSummaries($query, array_values($records->items()), $group);
// ['3' => ['total' => [['name' => 'sum', 'label' => 'Sum', 'value' => '1,240', ...]]]]2
3
Struktur di-key oleh group key, lalu nama kolom, lalu daftar figure. Figure setiap band dihitung berdasarkan seluruh band, bukan hanya row pada halaman saat ini. Alasannya sama dengan total tabel: total band yang berubah saat berpindah halaman akan menjadi angka berbeda dengan label yang sama. Biayanya satu query per band yang tampil di layar, yang biasanya hanya beberapa. Summarizer dengan perPage() tetap dihitung dari record yang sedang ditampilkan.
groupSummaries() mengembalikan array kosong jika tidak ada kolom yang memiliki summarizer. Lihat Summary.
Pengujian
use App\Models\Task;
use Illuminate\Http\Request;
use PandaPanel\Tables\Columns\TextColumn;
use PandaPanel\Tables\Group;
use PandaPanel\Tables\TableQuery;
use PandaPanel\Tables\TableSchema;
$group = Group::make('project_id');
$schema = TableSchema::make()
->columns([TextColumn::make('name')])
->groups([$group]);
$request = Request::create('/', 'GET', ['group' => 'project_id']);
$request->setLaravelSession(app('session.store'));
$tableQuery = new TableQuery($schema, $request);
$records = $tableQuery->paginate(Task::query());
$keys = array_map(
static fn ($record): string => $schema->toRow($record, $group)['group']['key'],
$records->items(),
);
// Rows arrive together, because the group sorts before anything else: two
// tasks of one project, then the task of the other.
expect($tableQuery->activeGroup()?->getName())->toBe('project_id')
->and($keys)->toBe(['1', '1', '2']);2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
Hal yang perlu diperhatikan
- Sebuah band dapat melewati batas halaman. Ini adalah perilaku yang benar untuk server-paginated table, bukan bug. Jika band harus selalu utuh, lakukan pagination per band: filter ke satu band dan hilangkan grouping.
Group::make()tidak memeriksa apakah kolom benar-benar ada. Kolom yang tidak dikenal akan menjadiorder byyang ditolak database. Berbeda dengan sort column, validasi grup dilakukan terhadap group declaration, bukan terhadap seluruh kolom schema.titleUsing()tidak memengaruhi pembentukan band. Dua record dengan key sama berada dalam satu band apa pun title-nya; dua record dengan key berbeda tetap dua band meskipun title-nya sama.- Grouping menambahkan
order bysebelum sort pilihan pengguna. Tabel yang diurutkan berdasarkancreated_atdan dikelompokkan berdasarkanstatusakan diurutkan berdasarkan status terlebih dahulu. Inilah yang menjaga band tetap contiguous. descriptionUsing()dievaluasi per row meskipun hanya row pertama setiap band yang menampilkannya. Jaga closure tetap murah dan jangan menjalankan query di dalamnya.
Lihat juga
- Dasar-dasar TableSchema
- Summary
- Pengurutan
- Tab — scope atas query, sedangkan grouping adalah cara menampilkannya
- Pagination
- State tabel yang dipertahankan
- Referensi API tabel