Field Disabled dan Hidden
Ada empat pertanyaan berbeda yang sengaja dipisahkan: apakah field tampil pada Page ini, apakah field tampil untuk record ini, apakah field dapat diedit, dan apakah field membawa value yang tidak pernah dilihat user. Gunakan API pada halaman ini ketika form berbeda antara create dan edit, atau ketika sebagian form hanya berlaku untuk record tertentu. Semua keputusan di halaman ini dibuat di server satu kali saat schema dibangun. Untuk kondisi yang harus bereaksi ketika user mengetik, lihat Field visibility.
Contoh minimal
use Illuminate\Database\Eloquent\Model;
use PandaPanel\Forms\Components\HiddenInput;
use PandaPanel\Forms\Components\TextInput;
TextInput::make('slug')->visibleOn(['edit']); // Page mana
TextInput::make('reference')->hiddenOn(['create']);
TextInput::make('email')->disabledOn(['edit']); // tampil, tetapi tidak dapat diedit
TextInput::make('reason')->visible( // record
static fn (?Model $record): bool => $record?->getAttribute('status') === 'rejected',
);
HiddenInput::make('source')->default('panel'); // membawa value, tetapi tidak ditampilkan2
3
4
5
6
7
8
9
10
11
12
13
Field yang hidden bukan sekadar tidak terlihat. Field tersebut tidak ada di payload, tidak memiliki validation rule, dan tidak ikut proses dehydration. Karena itu request yang mencoba mengirim key tersebut tidak dapat membuat field yang tersembunyi menjadi “ada”.
Menyembunyikan berdasarkan Page
Page berasal dari FormSchema::forPage(). Resource create page menggunakan 'create', edit page menggunakan 'edit', dan relation form menggunakan key yang sama.
| Method | Signature | Efek |
|---|---|---|
hiddenOn() | hiddenOn(list<string> $pages): static | Disembunyikan pada Page yang disebutkan |
visibleOn() | visibleOn(list<string> $pages): static | Disembunyikan pada semua Page yang tidak disebutkan |
use PandaPanel\Forms\Components\TextInput;
TextInput::make('slug')->hiddenOn(['create']);
TextInput::make('slug')->visibleOn(['edit']);2
3
4
Keduanya tersedia karena cara membacanya berbeda. Field yang hanya dimiliki proses create lebih jelas ditulis sebagai visibleOn(['create']) daripada harus menuliskan daftar seluruh Page lainnya.
Anda juga dapat melakukan branching langsung berdasarkan Page. Contoh resource password field:
use PandaPanel\Forms\Components\PasswordInput;
use PandaPanel\Forms\FormSchema;
public static function form(FormSchema $schema): FormSchema
{
return $schema->schema([
PasswordInput::make('password')->when(
$schema->getPage() === 'create',
static fn (PasswordInput $field): PasswordInput => $field->required(),
static fn (PasswordInput $field): PasswordInput => $field->optionalWhenFilled(),
),
]);
}2
3
4
5
6
7
8
9
10
11
12
13
Menyembunyikan berdasarkan record
use Illuminate\Database\Eloquent\Model;
use PandaPanel\Forms\Components\TextInput;
TextInput::make('rejection_reason')->visible(
static fn (?Model $record): bool => $record !== null && $record->getAttribute('status') === 'rejected',
);
TextInput::make('legacy_id')->hidden(
static fn (?Model $record): bool => $record === null,
);
TextInput::make('debug')->hidden(app()->isProduction());2
3
4
5
6
7
8
9
10
11
12
| Method | Signature |
|---|---|
hidden() | hidden(Closure(?Model): bool|bool $condition = true): static |
visible() | visible(Closure(?Model): bool|bool $condition = true): static |
Memanggil hidden() tanpa argument berarti selalu hidden. Memanggil visible() tanpa argument berarti selalu visible. Mengirim boolean biasa setara dengan mengirim Closure yang mengembalikan boolean tersebut.
Closure menerima record yang sedang digunakan untuk membangun form. Pada create page nilainya null. Closure dievaluasi satu kali ketika schema diserialisasi, sehingga tidak dapat bereaksi terhadap value yang sedang diketik user. Jika kondisi harus reaktif terhadap input, gunakan visibleWhen().
Urutan penggabungan keputusan visibility
Field::isHiddenOn(string $page, ?Model $record = null): bool memeriksa empat sumber dengan urutan paling ketat terlebih dahulu:
hidden()mengatakan ya → field hidden.visible()mengatakan tidak → field hidden.visibleOn()telah diset tetapi tidak menyebut Page ini → field hidden.hiddenOn()menyebut Page ini → field hidden.
Selain kondisi tersebut field ditampilkan. Satu keputusan ini menjadi sumber yang sama untuk rendering, validation, dan persistence sehingga ketiganya tidak dapat saling bertentangan.
use PandaPanel\Forms\Components\TextInput;
use PandaPanel\Forms\FormSchema;
$schema = FormSchema::make()->forPage('create')->schema([
TextInput::make('name'),
TextInput::make('slug')->visibleOn(['edit']),
]);
array_column($schema->toArray()['schema'], 'name'); // ['name']
$schema->validationRules(); // tidak ada key 'slug'
$schema->dehydrate(['slug' => 'injected']); // []2
3
4
5
6
7
8
9
10
11
Layout yang hanya berisi hidden fields tetap dirender. Container bukan field dan hanya hilang jika Anda sendiri menghapusnya. Child fields yang hidden tidak masuk ke schema, sehingga container tampil kosong alih-alih meninggalkan gap field.
Disabled
Field disabled tetap dirender, tetap menampilkan value, tetapi tidak dapat diedit melalui control browser.
| Method | Signature |
|---|---|
disabled() | disabled(bool $disabled = true): static |
disabledOn() | disabledOn(list<string> $pages): static |
isDisabledOn() | isDisabledOn(string $page, ?Model $record = null): bool |
use PandaPanel\Forms\Components\TextInput;
TextInput::make('email')->disabledOn(['edit']);
TextInput::make('reference')->disabled();2
3
4
disabled() hanya menerima boolean. Tidak ada versi Closure dan tidak ada disabledWhen(). Rendering read-only per-record belum didukung. Alternatif yang jujur adalah menyembunyikan field dengan Closure hidden(), atau menampilkan value melalui infolist.
Disabled adalah presentation, bukan jaminan keamanan write. Field tetap berada di schema. Karena itu value tetap dapat dikirim, divalidasi, dan dipersist seperti field lain bila request dimodifikasi secara manual. Jika value memang tidak boleh writable, nyatakan pada boundary persistence:
use Illuminate\Database\Eloquent\Model;
use PandaPanel\Forms\Components\TextInput;
TextInput::make('reference')
->disabled()
->dehydrated(false); // tidak pernah ditulis
TextInput::make('created_by')
->disabled()
->dehydrated(static fn (?Model $record): bool => $record === null); // hanya ditulis saat create2
3
4
5
6
7
8
9
10
Membawa value tanpa menampilkannya
PandaPanel\Forms\Components\HiddenInput tidak merender control, tetapi tetap merupakan field dalam semua aspek lainnya: value divalidasi dengan rules yang Anda deklarasikan dan dipersist seperti field biasa.
use PandaPanel\Forms\Components\HiddenInput;
HiddenInput::make('source')
->default('panel')
->rules(['in:panel,api']);2
3
4
5
Hidden adalah pilihan rendering, bukan trust boundary. Value tetap berasal dari browser. Jika value sama sekali tidak boleh berasal dari browser, jangan masukkan ke schema. Set value tersebut melalui mutateFormDataBeforeSave() atau handleRecordCreation().
Walaupun namanya mirip, HiddenInput dan hidden() memiliki behavior yang berlawanan: HiddenInput adalah field yang tetap ada dan ikut submit; hidden() menghapus field dari form sepenuhnya.
Yang tidak didukung
- Tidak ada Closure untuk
disabled()dan tidak adadisabledWhen(). Read-only merupakan deklarasi per-Page atau always-on. - Tidak ada
readOnly(). Gunakandisabled(). - Tidak ada
hiddenJs()atau mekanisme lain untuk mengirim executable code dari server. Reactive visibility menggunakan closed set dari comparison yang dideskripsikan. Lihat Field visibility. - Tidak ada helper visibility per-user. Baca user sendiri di dalam Closure
visible(), atau lakukan authorization pada Page. Menyembunyikan field bukan authorization.
Catatan
- Default dari hidden field tidak ditulis. Jika field tidak ada, field juga tidak ada dalam proses
dehydrate(). visibleOn()default-nyanull, bukan empty list. Field yang tidak pernah memanggilnya visible pada semua Page.visibleOn([])justru menyembunyikan field pada semua Page.- Custom page key didukung.
forPage('review')denganvisibleOn(['review'])merupakan pasangan yang valid. String Page tidak dibatasi hanyacreatedanedit. - Endpoint
form-statemembangun ulang schema menggunakan Page yang sama. Field yang disembunyikan Closurevisible()tetap hidden setelah live rebuild karena Closure dievaluasi kembali pada setiap serialization. PasswordInputtidak pernah mengirim value tersimpan kembali ke browser, baik disabled maupun tidak.formValue()selalu mengembalikannullsehingga hash password tidak pernah masuk Page payload.