Field Tags
PandaPanel\Forms\Components\TagsInput adalah list berisi string pendek yang diketik user. Gunakan field ini ketika value belum diketahui sebelumnya — misalnya keyword, label, atau alias. Jika daftar value sudah diketahui, gunakan Select atau checkbox list karena field tersebut dapat memvalidasi terhadap whitelist, sedangkan TagsInput tidak.
Form minimal
<?php
declare(strict_types=1);
namespace App\Panels\Admin\Resources\Posts\Forms;
use PandaPanel\Forms\Components\TagsInput;
use PandaPanel\Forms\FormSchema;
final class PostForm
{
public static function configure(FormSchema $schema): FormSchema
{
return $schema->schema([
TagsInput::make('keywords')
->maxTags(10)
->maxLength(30)
->placeholder('Type and press Enter'),
]);
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Rule menjadi keywords => nullable|array|max:10 dan keywords.* => string|max:30. Value disimpan sebagai PHP array.
Apa yang dibatasi, dan mengapa
Tidak ada whitelist pada field ini karena memang value-nya tidak diketahui sebelumnya. Yang dibatasi adalah jumlah tag dan panjang setiap tag, karena array tanpa batas dari sebuah form berarti write tanpa batas.
| Batas | Method | Default | Rule yang dihasilkan |
|---|---|---|---|
| Jumlah tag | maxTags() | null (tanpa batas) | max:N pada field |
| Panjang setiap tag | maxLength() | 50 | max:N pada field.* |
Element rules selalu ada: ['string', 'max:'.$maxLength]. Laravel tidak menginfer rule untuk element dari rule array utama, sehingga FormSchema mengeluarkannya secara eksplisit di keywords.*.
Menyimpan array
Value field berupa array saat masuk maupun keluar. Jika model menyimpan tags dalam satu column, cast attribute tersebut:
protected function casts(): array
{
return ['keywords' => 'array'];
}2
3
4
Jika ingin menyimpan sebagai joined string, gunakan dehydrateStateUsing(). Keputusan format penyimpanan tetap berada di schema, bukan di field:
TagsInput::make('keywords')
->separator(',')
->dehydrateStateUsing(static fn (mixed $value): string => is_array($value)
? implode(',', $value)
: '');2
3
4
5
Ketika separator(',') digunakan, string tersimpan akan di-split kembali menjadi tags saat hydration, sehingga round trip tetap konsisten.
Method
suggestions(array $suggestions): self
Menerima list<string>, default []. Suggestions dirender sebagai <datalist> pada text box: berfungsi sebagai typed-ahead completion, bukan constraint.
use PandaPanel\Forms\Components\TagsInput;
TagsInput::make('keywords')->suggestions(['php', 'laravel', 'vue', 'inertia']);2
3
Suggestion yang sudah dipilih tidak ditawarkan lagi. Suggestions tidak ikut masuk validation — user tetap boleh mengetik value lain dan mengabaikan suggestion bukan error. Jika list tersebut harus menjadi constraint, TagsInput bukan field yang tepat.
Suggestions bersifat statis. Tidak ada callback atau lookup endpoint, sehingga jika suggestions berasal dari database, schema-lah yang harus membangunnya:
use App\Models\Post;
TagsInput::make('keywords')->suggestions(
Post::query()->distinct()->pluck('primary_keyword')->all(),
);2
3
4
5
maxTags(int $max): self
Default null, berarti tidak ada batas jumlah tag. Menambahkan max:N pada rule field dan menghentikan browser menerima tag baru ketika limit tercapai — input dinonaktifkan pada batas tersebut, bukan membuang input user secara silent. Nilai di bawah 1 di-clamp menjadi 1.
TagsInput::make('keywords')->maxTags(5);maxLength(int $length): self
Default 50. Menentukan panjang maksimum satu tag, masuk ke element rules dan attribute maxlength pada input. Nilai di bawah 1 di-clamp menjadi 1.
TagsInput::make('keywords')->maxLength(24);Berbeda dari TextInput::maxLength(), method ini tidak menerima null. Selalu ada limit per-tag karena string tanpa batas di dalam array tanpa batas berarti dua level unbounded write.
separator(string $separator): self
Default null. Memecah satu input menjadi beberapa tag, sehingga paste daftar menjadi praktis — "red, green" menjadi dua tag, bukan satu string yang mengandung koma.
TagsInput::make('keywords')->separator(',');Separator kosong akan memecah setiap karakter menjadi tag tersendiri. Karena itu separator('') ditolak dan property dikembalikan ke null.
Separator juga digunakan saat form dihydrate. castForForm() akan melakukan explode terhadap string berdasarkan separator, sehingga column berisi comma-separated string dapat round-trip menjadi tags.
type(): FieldType
Mengembalikan FieldType::TagsInput, diserialisasi sebagai 'tags_input'.
Bentuk serialized payload
TagsInput::make('keywords')->maxTags(10)->separator(',')->toArray(null, 'create') menambahkan empat key ke payload dasar field:
| Key | Type | Default |
|---|---|---|
suggestions | string[] | [] |
maxTags | number | null | null |
maxLength | number | 50 |
separator | string | null | null |
Value dinormalisasi oleh castForForm() sebelum dikirim: string di-split jika separator diatur, semua entry dipetakan menjadi string, dan empty entries dibuang. Value yang bukan array dan bukan string menjadi [].
Behavior control
TagsInputField.vue adalah text box dengan committed tags yang ditampilkan sebagai removable chips.
- Enter menyimpan draft menjadi tag. Blur pada input juga melakukan commit, sehingga tag yang sudah diketik tidak hilang hanya karena user berpindah focus.
- Backspace pada text box kosong menghapus tag terakhir.
- Tag kosong, duplikat, melebihi
maxLength, atau melewatimaxTagsditolak di browser daripada dikirim hanya untuk menerima error. - Tombol remove pada chip ikut disabled ketika
disabled()berlaku.
Semua behavior browser tersebut bukan security/validation boundary. Seluruh batas tetap diterapkan kembali oleh server rules.
Hal yang perlu diperhatikan
Duplikat ditolak control, bukan rules. Browser tidak menambahkan tag yang sudah ada, tetapi crafted request
['a', 'a']tetap lolos validation. Rule Laraveldistinctseharusnya diterapkan pada element, sedangkanelementRules()di field ini fixed pada['string', 'max:N'];rules()menambah rule ke field utama, bukan element. Deduplicate saat dehydration jika dibutuhkan:phpTagsInput::make('keywords') ->dehydrateStateUsing(static fn (mixed $value): array => is_array($value) ? array_values(array_unique(array_map(strval(...), $value))) : []);1
2
3
4maxTagsadalahmax:Npada array, sehingga Laravel menghitung jumlah element. Ini bukan character limit.maxLengthadalah batas karakter dan berada di element rules.required()menolak empty list. Laravelrequiredgagal untuk Countable dengan length nol, sehingga required tags field mewajibkan minimal satu tag. Biasanya ini behavior yang tepat; gunakanrequired(false)jika empty set valid.Separator adalah convenience client-side yang juga berdampak server-side. Browser menggunakannya saat paste, dan hydration menggunakannya untuk split stored string. Jika column sudah menyimpan array, separator tidak mengubah apa pun karena
castForForm()hanya split string.Suggestions bukan options. Suggestions tidak membatasi value dan tidak divalidasi terhadap daftar tersebut. Jika hanya known values yang boleh dipilih, gunakan Select dengan
multiple(), atau checkbox list.Tags bukan relation. Field ini menulis array ke column. Untuk menyimpan tags sebagai related records, gunakan
Selectdengan->relationship(...), yang melakukan sync pivot setelah record disimpan.