Pencarian
Kotak pencarian tabel mencocokkan term terhadap kolom yang mendeklarasikan dirinya sebagai searchable. Gunakan ketika daftar sudah cukup panjang sehingga scrolling bukan lagi cara yang masuk akal untuk menemukan sebuah row. Sorting dan filtering menjawab "data yang mana, dalam urutan apa"; search menjawab "record ini ada di mana".
Contoh minimal
<?php
declare(strict_types=1);
namespace App\Panels\Admin\Resources\Users\Tables;
use PandaPanel\Tables\Columns\TextColumn;
use PandaPanel\Tables\TableSchema;
final class UsersTable
{
public static function configure(TableSchema $table): TableSchema
{
return $table
->columns([
TextColumn::make('name')->searchable(),
TextColumn::make('email')->searchable(),
])
->searchPlaceholder('Search by name or email...');
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Kotak pencarian muncul karena setidaknya satu kolom searchable. Ketika pengguna mengetik, frontend menulis ?search= ke URL dan server mencocokkan term terhadap name dan email.
Mendeklarasikan kolom searchable
use PandaPanel\Tables\Columns\Column;
Column::searchable(bool $searchable = true, ?array $columns = null, bool $individually = false): static
Column::isSearchable(): bool
Column::isIndividuallySearchable(): bool
Column::getSearchColumns(): array // list<string>; [$name] unless $columns was given2
3
4
5
6
| Argumen | Tipe | Default | Arti |
|---|---|---|---|
$searchable | bool | true | apakah term boleh dicocokkan terhadap kolom ini |
$columns | list<string>|null | null | kolom database yang dicari jika berbeda dari nama atribut |
$individually | bool | false | juga menyediakan kotak pencarian khusus untuk kolom ini pada header row kedua |
use PandaPanel\Tables\Columns\TextColumn;
// The displayed value comes from `name`; the term is matched against two columns.
TextColumn::make('name')->searchable(columns: ['first_name', 'last_name']),
// Searchable and given its own box.
TextColumn::make('reference')->searchable(individually: true),
// Explicitly not searchable, for a column a shared configuration made searchable.
TextColumn::make('notes')->searchable(false),2
3
4
5
6
7
8
9
10
searchable() adalah deklarasi, bukan implementasi perilaku. Query layer membacanya sebagai whitelist; inilah yang mencegah ?search= mencapai kolom yang tidak pernah ditawarkan tabel.
Cara tabel memproses term
use PandaPanel\Tables\TableSchema;
TableSchema::isSearchable(): bool // true when any column declared itself searchable
TableSchema::getSearchColumns(): array // the local names, deduplicated
TableSchema::getSearchRelations(): array // the dotted ones2
3
4
5
Term di-trim, dipotong maksimal 255 karakter, dan dianggap tidak ada jika hasilnya kosong. Setelah itu, untuk setiap kata ditambahkan satu grup where(...):
where (name like ? or email like ?)Kondisi selalu berada di dalam group, sehingga orWhere tidak pernah memperluas constraint yang sudah diterapkan filter. Wildcard LIKE \, %, dan _ di-escape dari term. Karena itu ?search=% mencari tanda persen literal, bukan membuat database memindai semua row.
Memecah term
use PandaPanel\Tables\TableSchema;
TableSchema::splitSearchTerms(bool $split = true): self // on by default
TableSchema::shouldSplitSearchTerms(): bool2
3
4
Aktif secara default: setiap kata dari term multi-kata harus cocok di suatu tempat. Dengan demikian, "ada lovelace" dapat menemukan record ketika nama depan berada pada satu kolom dan nama belakang berada pada kolom lain. Setiap kata memiliki group sendiri dan semua group digabung menggunakan AND, sehingga penambahan kata mempersempit hasil, bukan memperluasnya.
use PandaPanel\Tables\Columns\TextColumn;
use PandaPanel\Tables\TableSchema;
// Split (default): "Apollo Lunar" matches the project named Apollo with a task named Lunar.
TableSchema::make()->columns([
TextColumn::make('name')->searchable(),
TextColumn::make('tasks.name')->searchable(),
]);
// Off: the phrase is the value. "Lovelace Ada" then finds nothing.
TableSchema::make()
->columns([TextColumn::make('reference')->searchable()])
->splitSearchTerms(false);2
3
4
5
6
7
8
9
10
11
12
13
Nonaktifkan pemisahan ketika frasa secara keseluruhan memang merupakan nilainya — misalnya reference, serial number, atau alamat.
Pencarian per kolom
Kolom yang mendeklarasikan individually: true mendapatkan kotak pencarian sendiri di header row kedua. Constraint tersebut di-AND dengan seluruh state lain dan menjawab "dari row yang tersisa, mana yang memiliki term ini pada kolom tersebut".
use PandaPanel\Tables\Columns\TextColumn;
use PandaPanel\Tables\TableSchema;
TableSchema::make()->columns([
TextColumn::make('name')->searchable(individually: true),
TextColumn::make('email')->searchable(),
]);2
3
4
5
6
7
State dikirim melalui parameter tersendiri:
?search=Apollo&columnSearch[name]=TwoHanya kolom yang secara eksplisit menyediakan kotak pencarian individual yang dapat dicari dengan cara ini. Request yang menyebut kolom lain tidak menjalankan pencarian, dan state yang dikembalikan juga tidak memuatnya:
use PandaPanel\Tables\TableSchema;
TableSchema::getIndividuallySearchableColumns(): array // list<Column>
TableSchema::getIndividuallySearchableColumn(string $name): ?Column2
3
4
use PandaPanel\Tables\TableQuery;
$state = (new TableQuery($schema, $request))->state();
$state['columnSearches']; // ['name' => 'Apo'] — the invented and blank ones are gone2
3
4
5
Setiap term per kolom di-trim, dipotong maksimal 255 karakter, dan mengikuti aturan pemisahan term yang sama dengan pencarian tabel secara keseluruhan.
Mencari melalui relation
Nama searchable yang mengandung titik dicocokkan menggunakan orWhereHas, bukan LIKE. Nama tersebut bukan kolom pada tabel utama, dan memperlakukannya sebagai kolom lokal akan menghasilkan SQL error, bukan sekadar hasil kosong.
use PandaPanel\Tables\Columns\TextColumn;
TextColumn::make('author.name')->label('Author')->searchable(),2
3
Titik terakhir memisahkan relation path dari kolom. Jadi author.profile.city mencari city melalui relation author.profile. Tabel yang hanya memiliki searchable relation tetap melaporkan searchable: true. Lihat Kolom relasi.
Kapan request pencarian dikirim
Biaya pencarian diketahui oleh server, sehingga server pula yang menentukan kapan frontend sebaiknya meminta hasil.
use PandaPanel\Tables\TableSchema;
TableSchema::searchDebounce(int $milliseconds): self // default 300, floored at 0
TableSchema::searchOnBlur(bool $onBlur = true): self // default false
TableSchema::searchPlaceholder(string $placeholder): self2
3
4
5
$table
->searchDebounce(750) // a table over a large join wants the user to finish typing
->searchOnBlur() // ask when the field loses focus, not while typing
->searchPlaceholder('Search by name, email, or reference...');2
3
4
Debounce negatif di-clamp menjadi 0, yang berarti request dikirim langsung. Ini hanya masuk akal untuk tabel cukup kecil sehingga biaya pencarian tidak menjadi masalah. Placeholder default adalah Search....
Ketiga konfigurasi tersebut, bersama searchable dan individualSearchColumns, ikut di dalam definisi tabel:
$schema->toArray();
// [
// 'searchable' => true,
// 'searchPlaceholder' => 'Search...',
// 'searchDebounce' => 300,
// 'searchOnBlur' => false,
// 'individualSearchColumns' => ['name'],
// ...
// ]2
3
4
5
6
7
8
9
Mengingat term pencarian
use PandaPanel\Tables\TableSchema;
TableSchema::persistSearchInSession(bool $persist = true): self // off by default
TableSchema::persistsSearchInSession(): bool2
3
4
Nonaktif secara default. Kembali ke sebuah tabel dan mendapati hasil masih terfilter oleh sesuatu yang diketik kemarin dapat membingungkan, kecuali tabel tersebut memang merupakan workspace yang digunakan terus-menerus.
Dua aturan membuat pemulihan state aman. Pertama, request selalu menang ketika menyatakan sesuatu, termasuk nilai kosong; jika tidak, menghapus pencarian akan langsung dibatalkan oleh session yang mengingatnya. Kedua, nilai yang diingat melewati validasi yang sama seperti nilai baru. Session key dibangun dari panel id dan resource slug, bukan dari input request, dan tabel tanpa session middleware cukup tidak mengingat state tanpa menghasilkan error. Lihat State yang dipertahankan.
Pencarian pada array table
PandaPanel\Tables\ArrayTableData menerapkan whitelist yang sama pada record yang tidak berasal dari database, menggunakan PHP:
use Illuminate\Http\Request;
use PandaPanel\Tables\ArrayTableData;
$data = ArrayTableData::make($schema, $records, $request);
$page = $data->paginate();
return ['rows' => $data->rows($page), 'state' => $data->state()];2
3
4
5
6
7
Pencocokan menggunakan substring case-insensitive terhadap TableSchema::getSearchColumns(). Ada tiga perbedaan yang disengaja dari query layer: relation tidak dicari, term tidak dipecah, dan tidak ada per-column search. Lihat Tabel data array.
Catatan
- Schema adalah whitelist.
?search=hanya mencapai kolom yang dideklarasikan, sedangkan?columnSearch[password]=tidak mencapai apa pun.statemengembalikan apa yang benar-benar diterapkan server, bukan apa yang diminta client. - Term per kolom mempersempit pencarian global, tidak pernah memperluasnya. Mencari "Apollo" lalu "Two" pada kolom nama hanya menyisakan row yang cocok dengan keduanya.
- Search tidak mempersempit record lookup. Search berada di
TableQuery::paginate(), bukanResource::query(), sehingga record yang tidak tampil akibat search masih dapat dibuka melalui URL jika user memang berhak mengaksesnya. LIKEbukan full-text search. Setiap term menjadi%escaped-term%, yang tidak dapat menggunakan B-tree index biasa. Pada tabel besar, gunakan database index yang memang dibuat untuk pencarian atau search service, bukan sekadar menaikkan debounce.- Relation search menambah subquery untuk setiap searchable relation dan setiap kata. Term tiga kata terhadap dua relation column berarti enam
whereHas. Ini adalah biaya convenience tersebut dan layak diukur sebelum membuat terlalu banyak relation searchable. searchable()pada kolom dengan nilai computed biasanya tidak berguna. Term dicocokkan terhadap kolom database dengan nama tersebut; nilai yang hanya muncul setelahformatUsing()tidak memiliki kolom untuk dicari. Arahkan pencarian ke kolom nyata melalui argumencolumns:.