Behaviour

Live and dependent fields

Server closures load options and set values, and the browser only calls the server when a value they read changes.

Options and values often come from the database. A closure runs on the server, and Get reads other fields.

use Xelqun\Forms\Components\Select;
use Xelqun\Forms\State\Get;
use Xelqun\Forms\State\Set;

Select::make('country_id')
    ->options(Country::pluck('name', 'id'))
    ->afterStateUpdated(function (Get $get, Set $set): void {
        $set('state_id', null);
    });

Select::make('state_id')
    ->options(fn (Get $get): array => State::where('country_id', $get('country_id'))->pluck('name', 'id')->all())
    ->disabled(fn (Get $get): bool => blank($get('country_id')));

The package notices which values each closure reads. When country_id changes, the browser sends one small request and gets back a patch for what changed. Focus and cursor position stay put.

Sending changes to the server

live() sends a field's changes. Most of the time you don't need it, because a closure that reads a field already makes that field live, and so does afterStateUpdated().

TextInput::make('title')->live(onBlur: true);

TextInput::make('search')->live(debounce: 500);

dependsOn() declares what the tracking can't see, such as a read behind a branch.

TextInput::make('total')->dependsOn(['qty', 'price']);

Reacting to a change

afterStateUpdated() runs after someone changes the field. $state, $get, $set and $record can all be injected.

TextInput::make('title')
    ->live(onBlur: true)
    ->afterStateUpdated(fn (Set $set, ?string $state) => $set('slug', Str::slug($state)));

state() sets the field's own value from a closure.

Changing a value going in and out

Method What it does
formatStateUsing() Changes the value the form shows.
afterStateHydrated() Runs after the form is filled, with every field's state in place.
dehydrateStateUsing() Changes the value before it's saved.

Calculating in the browser

computed() calculates a value with no server call, and the server works it out again on submit.

TextInput::make('subtotal')->computed('qty * price')->disabled();
TextInput::make('total')->computed('sum(items.*.subtotal)')->disabled();

Expressions take +, -, *, / and parentheses, along with sum(), avg(), min(), max(), count() and round().