F
Filament17mo ago
John

Handling multiple forms, but one at a time

I changed my app's main workflow from using a customised Wizard to building my own component.
class RequestWorkflow extends Page implements HasForms
{
use InteractsWithForms;
use InteractsWithRecord;

protected static string $resource = RequestResource::class;

protected static string $view = 'filament.resources.request-resource.pages.request-workflow';

public array $data;

private array $steps = [];

public string $currentStep = 'student-data';

public function mount($record): void
{
$this->record = $this->resolveRecord($record);
$data = $this->getRecord()->attributesToArray();
$this->form->fill($data);
}

protected function getFormSchema(): array
{
return $this->getSteps()[$this->currentStep]->getSchema();
}

protected function getFormModel(): Model|string|null
{
return $this->record;
}

protected function getFormStatePath(): ?string
{
return 'data';
}

public function nextStep()
{
$this->submit();
$this->currentStep = 'arrangement'; // TODO: determine actual next step
}

public function getSteps()
{
return [
'student-data' => new StudentDataForm,
'arrangement' => new ArrangementForm,
];
}

public function setStep($step)
{
$this->currentStep = $step;
}

public function submit()
{
$data = $this->form->getState();
$this->getRecord()->update($data);
}
}
class RequestWorkflow extends Page implements HasForms
{
use InteractsWithForms;
use InteractsWithRecord;

protected static string $resource = RequestResource::class;

protected static string $view = 'filament.resources.request-resource.pages.request-workflow';

public array $data;

private array $steps = [];

public string $currentStep = 'student-data';

public function mount($record): void
{
$this->record = $this->resolveRecord($record);
$data = $this->getRecord()->attributesToArray();
$this->form->fill($data);
}

protected function getFormSchema(): array
{
return $this->getSteps()[$this->currentStep]->getSchema();
}

protected function getFormModel(): Model|string|null
{
return $this->record;
}

protected function getFormStatePath(): ?string
{
return 'data';
}

public function nextStep()
{
$this->submit();
$this->currentStep = 'arrangement'; // TODO: determine actual next step
}

public function getSteps()
{
return [
'student-data' => new StudentDataForm,
'arrangement' => new ArrangementForm,
];
}

public function setStep($step)
{
$this->currentStep = $step;
}

public function submit()
{
$data = $this->form->getState();
$this->getRecord()->update($data);
}
}
The form schema is determined dynamically, based on the current step. The nextStep action should save the current step and move on to the next. The problem however is, it uses the cached form, resulting in the wrong (old) form being rendered. (This does not occur when I switch tabs using setStep. I don't understand why.) Any advise on how to handle this?
Solution:
I'm not sure but I think you would need to call $this->form->fill( ... ) at the start of every step... If I understand correctly, all steps share the same state path?
Jump to solution
8 Replies
John
JohnOP17mo ago
An additional problem I'm having, is that a Repeater field isn't properly loaded if it's not loaded on the initial step. If I change the initial $currentStep to the one holding the Repeater, the current items are shown. If I start on any other step, and use setStep to navigate to it, simple input fields are shown just fine, but the Repeater is empty. This surely has to do with the Livewire / Filament lifecycle, but I wouldn't know how to fix this. Any help is appreciated. Same with accessors. They are only properly parsed on the initial step. I guess in mount() the form of the initial step is parsed and any accessors and relations are resolved. If I swap the form later, the form data isn't parsed anew. Can I refresh this somehow?
Solution
Patrick Boivin
Patrick Boivin17mo ago
I'm not sure but I think you would need to call $this->form->fill( ... ) at the start of every step... If I understand correctly, all steps share the same state path?
John
JohnOP17mo ago
I remember trying something like this, and messing up the lifecycle even more, where the displayed form was one click behind the selected tab. But...
public function setCurrentStepCode($step)
{
$this->currentStepCode = $step;
$this->form->fill($this->request->attributesToArray());
}
public function setCurrentStepCode($step)
{
$this->currentStepCode = $step;
$this->form->fill($this->request->attributesToArray());
}
This does seem to do the trick! Thanks a bunch!
Patrick Boivin
Patrick Boivin17mo ago
Nice! Not sure if I'm missing something but I suspect $this->request can cause issues eventually... just curious, where does it come from?
John
JohnOP17mo ago
Ah.. sry about that. That used to be $this->getRecord(), which I got from InteractsWithRecord. But since I only have to work with a \App\Models\Request I changed it to get autocomplete.
Patrick Boivin
Patrick Boivin17mo ago
lol, I thought this was related to request() 😄
John
JohnOP17mo ago
Yea, I understand the confusion. Thanks again!
John
JohnOP17mo ago
I've encountered the problem again, where the form stays the same when I switch steps. I also found a solution, so I'm just adding this to help others that might have the same issue. The problem comes up whenever you interact with the form before changing the current step. E.g. updating the record:
$this->request->update($this->form->getState());
$this->request->update($this->form->getState());
The cause is that the first time, the form is cached. The solution is to rebuild the form cache after changing the current step:
public function setCurrentStepCode($step): void
{
$this->currentStepCode = $step;

// * Rebuild forms cache, this is needed whenever $this->form is accessed before changing the current step
$this->cacheForms();

$this->form->fill($this->request->attributesToArray());
}
public function setCurrentStepCode($step): void
{
$this->currentStepCode = $step;

// * Rebuild forms cache, this is needed whenever $this->form is accessed before changing the current step
$this->cacheForms();

$this->form->fill($this->request->attributesToArray());
}
Want results from more Discord servers?
Add your server