diff --git a/packages/forms/docs/02-building-forms.md b/packages/forms/docs/02-building-forms.md index 29cd7eff54..5a3cee8506 100644 --- a/packages/forms/docs/02-building-forms.md +++ b/packages/forms/docs/02-building-forms.md @@ -49,9 +49,14 @@ class EditPost extends Component implements Forms\Contracts\HasForms { use Forms\Concerns\InteractsWithForms; + public $title = ''; + public $content = ''; + protected function getFormSchema(): array // [tl! add:start] { return [ + Forms\Components\TextInput::make('title')->required(), + Forms\Components\MarkdownEditor::make('content'), // ... ]; } // [tl! add:end] @@ -89,12 +94,12 @@ class EditPost extends Component implements Forms\Contracts\HasForms public Post $post; - public $title; - public $content; + public $title = ''; + public $content = ''; public function mount(): void // [tl! add:start] { - $this->fill([ + $this->form->fill([ 'title' => $this->post->title, 'content' => $this->post->content, ]); @@ -103,7 +108,7 @@ class EditPost extends Component implements Forms\Contracts\HasForms protected function getFormSchema(): array { return [ - Forms\Components\TextInput::make('title'), + Forms\Components\TextInput::make('title')->required(), Forms\Components\MarkdownEditor::make('content'), ]; } @@ -135,19 +140,20 @@ class CreatePost extends Component implements Forms\Contracts\HasForms { use Forms\Concerns\InteractsWithForms; - public $title; - public $content; + public $title = ''; + public $content = ''; public function mount(): void // [tl! add:start] { - $this->fill(); + $this->form->fill(); } // [tl! add:end] protected function getFormSchema(): array { return [ Forms\Components\TextInput::make('title') - ->default('Status Update'), // [tl! add:start] + ->default('Status Update') // [tl! add] + ->required(), Forms\Components\MarkdownEditor::make('content'), ]; } @@ -159,10 +165,274 @@ class CreatePost extends Component implements Forms\Contracts\HasForms } ``` -## Validation +## Getting data from forms -$this->form->getState() +To get all form data in an array, call the `getState()` method on your form. + +```php +required(), + Forms\Components\MarkdownEditor::make('content'), + ]; + } + + public function create(): void // [tl! add:start] + { + Post::create($this->form->getState()); + } // [tl! add:end] + + public function render(): View + { + return view('create-post'); + } +} +``` + +When `getState()` is run: + +1) Validation rules are checked, and if errors are present, the form is not submitted. +2) Any pending file uploads are stored permanently in the filesystem. +3) [Field relationships](#working-with-field-relationships), if they are defined, are saved. + +## Using Eloquent model binding + +Using Eloquent model binding for fields works out of the box, using Livewire's dot-syntax: + +```php +required(), // [tl! add:start] + Forms\Components\MarkdownEditor::make('post.content'), // [tl! add:end] + ]; + } + + public function render(): View + { + return view('edit-post'); + } +} +``` ## Using multiple forms -getForms() +By default, the `InteractsWithForms` trait only handles one form per Livewire component. To change this, you can override the `getForms()` method to return more than one form, each with a unique name: + +```php +required(), + Forms\Components\MarkdownEditor::make('post.content'), + ]; + } + + protected function getAuthorFormSchema(): array + { + return [ + Forms\Components\TextInput::make('author.name')->required(), + Forms\Components\TextInput::make('author.email')->email()->required(), + ]; + } + + public function save(): void + { + $this->post->update( + $this->postForm->getState(), + ); + + $this->author->update( + $this->authorForm->getState(), + ); + } + + protected function getForms(): array // [tl! add:start] + { + return [ + 'postForm' => $this->makeForm()->schema($this->getPostFormSchema()), + 'authorForm' => $this->makeForm()->schema($this->getAuthorFormSchema()), + ]; + } // [tl! add:end] + + public function render(): View + { + return view('edit-post'); + } +} +``` + +## Working with field relationships + +Some fields, such as the `BelongsToSelect`, `SpatieMediaLibraryFileUpload` and `SpatieLaravelTagsInput` require you to pass an Eloquent model instance to the form directly. This allows them to attach Eloquent relationships automatically when the form is saved. + +Pass a model instance to a form using the `getFormModel()` method: + +```php +required(), + Forms\Components\MarkdownEditor::make('post.content'), + Forms\Components\SpatieTagsInput::make('post.tags'), + ]; + } + + protected function getFormModel(): Post // [tl! add:start] + { + return $this->post; + } // [tl! add:end] + + public function render(): View + { + return view('edit-post'); + } +} +``` + +Alternatively, you may pass the model instance to the field that requires it directly, using the `model()` method: + +```php +required(), + Forms\Components\MarkdownEditor::make('post.content'), + Forms\Components\SpatieTagsInput::make('post.tags')->model($this->post), // [tl! add] + ]; + } + + public function render(): View + { + return view('edit-post'); + } +} +``` + +Now, [when `getState()` is called on this form](#getting-data-from-forms), any relationships are saved automatically to this model. + +### Saving field relationships manually + +In some cases, the model instance is not available until the form has been submitted. For example, in a form that creates a post, the post model instance cannot be passed to the form before it has been submitted. + +In this situation, you may call the `model()` and `saveRelationships()` methods on the form after the instance has been created: + +```php +required(), + Forms\Components\MarkdownEditor::make('content'), + Forms\Components\SpatieTagsInput::make('tags'), + ]; + } + + public function create(): void + { + $post = Post::create($this->form->getState()); + + $this->form->model($post)->saveRelationships(); // [tl! add] + } + + public function render(): View + { + return view('create-post'); + } +} +``` diff --git a/packages/forms/src/Components/Concerns/BelongsToModel.php b/packages/forms/src/Components/Concerns/BelongsToModel.php index 8832abd203..1766cb2c17 100644 --- a/packages/forms/src/Components/Concerns/BelongsToModel.php +++ b/packages/forms/src/Components/Concerns/BelongsToModel.php @@ -8,7 +8,7 @@ trait BelongsToModel { protected $model = null; - public function model(Model | string | callable $model): static + public function model(Model | string | callable | null $model = null): static { $this->model = $model; diff --git a/packages/forms/src/Concerns/BelongsToModel.php b/packages/forms/src/Concerns/BelongsToModel.php index a3736dd97a..14b84a74d6 100644 --- a/packages/forms/src/Concerns/BelongsToModel.php +++ b/packages/forms/src/Concerns/BelongsToModel.php @@ -18,7 +18,7 @@ trait BelongsToModel public function saveRelationships(): void { foreach ($this->getComponents() as $component) { - if ($component->getModel()) { + if ($component->getModel() instanceof Model) { $component->saveRelationships(); } diff --git a/packages/forms/src/Concerns/InteractsWithForms.php b/packages/forms/src/Concerns/InteractsWithForms.php index 81d7e087d4..10b9be176b 100644 --- a/packages/forms/src/Concerns/InteractsWithForms.php +++ b/packages/forms/src/Concerns/InteractsWithForms.php @@ -3,6 +3,7 @@ namespace Filament\Forms\Concerns; use Filament\Forms\ComponentContainer; +use Illuminate\Database\Eloquent\Model; use Illuminate\Validation\ValidationException; use Livewire\WithFileUploads; use SplFileInfo; @@ -176,6 +177,11 @@ trait InteractsWithForms return $this->cachedForms; } + protected function getFormModel(): Model | string | null + { + return null; + } + protected function getFormSchema(): array { return []; @@ -184,7 +190,9 @@ trait InteractsWithForms protected function getForms(): array { return [ - 'form' => $this->makeForm()->schema($this->getFormSchema()), + 'form' => $this->makeForm() + ->schema($this->getFormSchema()) + ->model($this->getFormModel()), ]; }