Components

FormBuilder

Basics

Fields and components in FormBuilder are used within forms that are processed by FormBuilder. Thanks to FormBuilder, fields are displayed and filled with data. FormBuilder is used on the edit page, as well as for relationship fields such as HasOne. You can also use FormBuilder on your own pages, in modal windows, or even outside of MoonShine.

use MoonShine\UI\Components\FormBuilder;
 
TableBuilder::make(
string $action = '',
FormMethod $method = FormMethod::POST,
FieldsContract|iterable $fields = [],
mixed $values = []
)
<x-moonshine::form name="crud-edit">
<x-moonshine::form.input
name="title"
placeholder="Title"
value=""
/>
 
<x-slot:buttons>
<x-moonshine::form.button type="reset">Cancel</x-moonshine::form.button>
<x-moonshine::form.button class="btn-primary">Submit</x-moonshine::form.button>
</x-slot:buttons>
</x-moonshine::form>

Basic Usage

Example usage of FormBuilder:

FormBuilder::make(
action:'/crud/update',
method: FormMethod::POST,
fields: [
Hidden::make('_method')->setValue('put')
Text::make('Text')
],
values: ['text' => 'Value']
)
 
// or
 
FormBuilder::make()
->action('/crud/update')
->method(FormMethod::POST)
->fields([
Hidden::make('_method')->setValue('put')
Text::make('Text')
])
->fill(['text' => 'Value'])
  • action - handler
  • method - request type,
  • fields - fields and components.
  • values - field values.

Basic Methods

Fields

The method fields() is used to declare form fields and components:

fields(FieldsContract|Closure|iterable $fields)
FormBuilder::make('/crud/update')
->fields([
Heading::make('Title'),
Text::make('Text'),
])

Form Name

The method name() allows you to set a unique name for the form, through which events can be triggered.

FormBuilder::make('/crud/update')
->name('main-form')

Fill Fields

The method fill() is used to fill fields with values:

fill(mixed $values = [])
FormBuilder::make('/crud/update')
->fields([
Heading::make('Title'),
Text::make('Text'),
])
->fill(['text' => 'value'])

Type Cast

The method cast() is used to cast form values to a specific type. Since fields work with primitive types by default:

cast(DataCasterContract $cast)
use MoonShine\Laravel\TypeCasts\ModelCaster;
 
FormBuilder::make('/crud/update')
->fields([
Heading::make('Title'),
Text::make('Text'),
])
->values(
['text' => 'value'],
)
->cast(new ModelCaster(User::class))

In this example, we cast the data to the User model format using ModelCaster.

For more detailed information, refer to the TypeCasts section.

Fill and Type Cast

The method fillCast() allows you to cast data to a specific type and fill them with values at the same time:

fillCast(mixed $values, DataCasterContract $cast)
use MoonShine\TypeCasts\ModelCaster;
 
FormBuilder::make('/crud/update')
->fields([
Heading::make('Title'),
Text::make('Text'),
])
->fillCast(
['text' => 'value'],
new ModelCaster(User::class)
)

or

use MoonShine\TypeCasts\ModelCaster;
 
FormBuilder::make('/crud/update')
->fields([
Heading::make('Title'),
Text::make('Text'),
])
->fillCast(
User::query()->first(),
new ModelCaster(User::class)
)

Buttons

Form buttons can be modified and added.

To configure the "submit" button, use the method submit().

submit(string $label, array $attributes = [])
  • label - button name,
  • attributes - additional attributes
FormBuilder::make('/crud/update')
->submit(label: 'Click me', attributes: ['class' => 'btn-primary'])

The method hideSubmit() allows you to hide the submit button.

FormBuilder::make('/crud/update')
->hideSubmit()

To add new buttons based on ActionButton, use the method buttons()

buttons(iterable $buttons = [])
FormBuilder::make('/crud/update')
->buttons([
ActionButton::make('Delete', route('name.delete'))
])

Attribute Configuration

You can set any HTML attributes for the form using the method customAttributes().

FormBuilder::make()
->customAttributes(['class' => 'custom-form'])

Asynchronous Mode

If you need to submit the form asynchronously, use the method async().

async(
Closure|string|null $url = null,
string|array|null $events = null,
?AsyncCallback $callback = null,
)
  • url - request URL (by default, the request is sent to the action URL),
  • events - events triggered after a successful request,
  • callback - JS callback function after receiving a response.
FormBuilder::make('/crud/update')
->async()

After a successful request, you can trigger events by adding the events parameter.

FormBuilder::make('/crud/update')
->name('main-form')
->async(events: [
AlpineJs::event(JsEvent::TABLE_UPDATED, 'crud-table'),
AlpineJs::event(JsEvent::FORM_RESET, 'main-form'),
])

Event list for FormBuilder:

  • JsEvent::FORM_SUBMIT - submit the form,
  • JsEvent::FORM_RESET - reset the form values by its name,

The async() method must come after the name() method!

Calling Methods

asyncMethod() allows you to specify the method name in the resource and call it asynchronously when submitting the FormBuilder without the need to create additional controllers.

FormBuilder::make()->asyncMethod('updateSomething'),
// With notification
public function updateSomething(MoonShineRequest $request): MoonShineJsonResponse
{
// $request->getResource();
// $request->getResource()->getItem();
// $request->getPage();
 
return MoonShineJsonResponse::make()->toast('My message', ToastType::SUCCESS);
}
 
// Redirect
public function updateSomething(MoonShineRequest $request): MoonShineJsonResponse
{
return MoonShineJsonResponse::make()->redirect('/');
}
 
// Redirect
public function updateSomething(MoonShineRequest $request): RedirectResponse
{
return back();
}
 
// Exception
public function updateSomething(MoonShineRequest $request): void
{
throw new \Exception('My message');
}

Reactivity

By default, fields inside the form are reactive, but if the form is outside the resource, then reactivity will not be available, as the form does not know where to send requests. In the case of using the form outside of resources, you can specify the reactive URL yourself:

FormBuilder::make()->reactiveUrl(fn(FormBuilder $form) => $form->getCore()->getRouter()->getEndpoints()->reactive($page, $resource, $extra))

Validation

Displaying Validation Errors

By default, validation errors are displayed at the top of the form.

The method errorsAbove(bool $enable = true) is used to control the display of validation errors at the top of the form. It allows you to enable or disable this feature.

FormBuilder::make('/crud/update')
->errorsAbove(false)

Pre-Cognitive Validation

If you need to perform pre-cognitive validation first, you need the method precognitive().

FormBuilder::make('/crud/update')
->precognitive()

Multiple Forms Simultaneously

If you have multiple forms on one page and they are not in async mode, you also need to specify a name for the errorBag in FormRequest or in Controller:

Learn more about errorBag naming

FormBuilder::make(route('multiple-forms.one'))
->name('formOne'),
 
FormBuilder::make(route('multiple-forms.two'))
->name('formTwo'),
 
FormBuilder::make(route('multiple-forms.three'))
->name('formThree')
 
class FormOneFormRequest extends FormRequest
{
protected $errorBag = 'formOne';
 
// ..
}
 
class FormTwoFormRequest extends FormRequest
{
protected $errorBag = 'formTwo';
 
// ..
}
 
class FormThreeFormRequest extends FormRequest
{
protected $errorBag = 'formThree';
 
// ..
}

Apply

The method apply() in FormBuilder iterates over all form fields and calls their apply methods.

apply(
Closure $apply,
?Closure $default = null,
?Closure $before = null,
?Closure $after = null,
bool $throw = false,
)
  • $apply - callback function;
  • $default - apply for the default field;
  • $before - callback function before applying;
  • $after - callback function after applying;
  • $throw - throw exceptions.

Examples

You need to save data from all fields of the FormBuilder in the controller:

$form->apply(fn(Model $item) => $item->save());

A more complex option, specifying events before and after saving:

$form->apply(
static fn(Model $item) => $item->save(),
before: function (Model $item) {
if (! $item->exists) {
$item = $this->beforeCreating($item);
}
 
if ($item->exists) {
$item = $this->beforeUpdating($item);
}
 
return $item;
},
after: function (Model $item) {
$wasRecentlyCreated = $item->wasRecentlyCreated;
 
$item->save();
 
if ($wasRecentlyCreated) {
$item = $this->afterCreated($item);
}
 
if (! $wasRecentlyCreated) {
$item = $this->afterUpdated($item);
}
 
return $item;
},
throw: true
);

Dispatch Events

To dispatch JavaScript events, you can use the method dispatchEvent().

dispatchEvent(array|string $events)
FormBuilder::make()
->dispatchEvent(AlpineJs::event(JsEvent::OFF_CANVAS_TOGGLED, 'default')),

By default, when calling an event with a request, all form data will be sent. If the form is large, you may need to exclude a set of fields. Exclude can be done through the exclude parameter:

->dispatchEvent(
AlpineJs::event(JsEvent::OFF_CANVAS_TOGGLED, 'default'),
exclude: ['text', 'description']
)

You can also completely exclude data from being sent through the withoutPayload parameter:

->dispatchEvent(
AlpineJs::event(JsEvent::OFF_CANVAS_TOGGLED, 'default'),
withoutPayload: true
)

Submit Event

To submit the form, you can call the Submit event.

AlpineJs::event(JsEvent::FORM_SUBMIT, 'componentName')

Example of calling the event on the form page

protected function formButtons(): ListOf
{
return parent::formButtons()->add(ActionButton::make('Save')->dispatchEvent(AlpineJs::event(JsEvent::FORM_SUBMIT, $this->uriKey())));
}

For additional information on JS events, refer to the Events section.

Using in Blade

Basics

Forms can be created using the moonshine::form component.

<x-moonshine::form
name="crud-form"
:errors="$errors"
precognitive
>
<x-moonshine::form.input
name="title"
placeholder="Title"
value=""
/>
<x-slot:buttons>
<x-moonshine::form.button type="reset">Cancel</x-moonshine::form.button>
<x-moonshine::form.button class="btn-primary">Submit</x-moonshine::form.button>
</x-slot:buttons>
</x-moonshine::form>