Is it possible to manage different resources (models) on each Step inside a Wizard?

All models have some basic relationships between them and the state should be maintained between the steps. I already figured out how to create a record after the first step using afterValidation(), but im struggling to pass the created model's id to the next step of the wizard in order to populate a Select component with a ->relationship(). Thanks in advace.
5 Replies
prowler
prowlerOP6mo ago
Anyone, please? @awcodes - when searching for 'wizard' here in discord i saw that you wrote to someone - You’re trying to use the wizard as a multiple form instead of steps in a form so it going to require a more granular custom approach.. Do you mind sharing any direction for that? Im struggling to manage a wizard with 3 steps where each step should handle a different form for a different resource, but still no success. sorry if pinging you is considered rude
awcodes
awcodes6mo ago
Sorry, I’ve never done anything with the wizard like this. I’d have to see the code to even start to try to put it all together in my head.
prowler
prowlerOP6mo ago
sadly i can't share the whole repo so i'll paste some crucial pieces to understand the overall concept - I have a resource called RfqResource and its create page with a wizard and these are the steps -
protected function getSteps(): array
{
return [
Step::make('RFQ')
->schema($this->getRfqFormSchema())
->afterValidation(function ($state, callable $set, callable $get) {
$this->rfq = Rfq::create($state);
})->model(Rfq::class),
Step::make('Assemblies')
->schema($this->getAssemblyFormSchema(1))
->afterStateUpdated(function ($state, callable $set) {
$set('rfq_id', $this->rfq?->id);
})
->afterValidation(function ($state, callable $set) {
$this->assembly = Assembly::create($state);
$set('assembly_id', $this->assembly?->id);

})->model(Assembly::class),
Step::make('BOM')
->schema($this->getBomFormSchema())
->afterStateHydrated(function ($state, callable $set) {
$set('assembly_id', $this->assembly?->id);
})->model(Bom::class),
];
}
protected function getSteps(): array
{
return [
Step::make('RFQ')
->schema($this->getRfqFormSchema())
->afterValidation(function ($state, callable $set, callable $get) {
$this->rfq = Rfq::create($state);
})->model(Rfq::class),
Step::make('Assemblies')
->schema($this->getAssemblyFormSchema(1))
->afterStateUpdated(function ($state, callable $set) {
$set('rfq_id', $this->rfq?->id);
})
->afterValidation(function ($state, callable $set) {
$this->assembly = Assembly::create($state);
$set('assembly_id', $this->assembly?->id);

})->model(Assembly::class),
Step::make('BOM')
->schema($this->getBomFormSchema())
->afterStateHydrated(function ($state, callable $set) {
$set('assembly_id', $this->assembly?->id);
})->model(Bom::class),
];
}
each of these getBomFormSchema() functions basically returns an array with the whole scheme, for example - $this->getRfqFormSchema() returns an array -
[
Forms\Components\Section::make('General Information')
->columns()
->icon('heroicon-o-information-circle')
->schema([
Forms\Components\TextInput::make('name')
->required()
->maxLength(255),
Forms\Components\Select::make('user_id')
->relationship('user','name')
->preload()
->searchable(),
Forms\Components\Select::make('customer_id')
->relationship('customer','name')
->preload()
->searchable(), // ... more fields
]
[
Forms\Components\Section::make('General Information')
->columns()
->icon('heroicon-o-information-circle')
->schema([
Forms\Components\TextInput::make('name')
->required()
->maxLength(255),
Forms\Components\Select::make('user_id')
->relationship('user','name')
->preload()
->searchable(),
Forms\Components\Select::make('customer_id')
->relationship('customer','name')
->preload()
->searchable(), // ... more fields
]
And on the assembly form there's a select field with a relationship to rfq -
Select::make('rfq_id')
->options(fn () => Rfq::all()->pluck('name', 'id')->toArray())
->required()
Select::make('rfq_id')
->options(fn () => Rfq::all()->pluck('name', 'id')->toArray())
->required()
(the reason i didn't use ->relationship('rfq','name') because it tries to load this relationship on the rfq create page and obviously its wrong because rfq doesn't have a relationship called rfq as well) I hoped I could somehow patch it by creating a real rfq on the first step -
->afterValidation(function ($state, callable $set, callable $get) {
$this->rfq = Rfq::create($state);
})->model(Rfq::class)
->afterValidation(function ($state, callable $set, callable $get) {
$this->rfq = Rfq::create($state);
})->model(Rfq::class)
And then pass this rfq's id to the next step like this -
->afterStateUpdated(function ($state, callable $set) {
$set('rfq_id', $this->rfq?->id);
})
->afterStateUpdated(function ($state, callable $set) {
$set('rfq_id', $this->rfq?->id);
})
But sadly it doesn't work and because both forms share some attributes with the same name (e.g. name) then when moving to step 2 it basically derives the name of the rfq from step 1 because the wizard sees all these forms as one big form and not as separate ones. Hope i was clear and provided enough info
awcodes
awcodes6mo ago
Still not totally following the relationships here. Seems like the assembly relationship is backwards to me. Ie, you’re assigning an rfq to assemblies instead of attaching assemblies to an rfq.
prowler
prowlerOP6mo ago
Assembly.php -
public function rfq(): BelongsTo
{
return $this->belongsTo(Rfq::class);
}
public function rfq(): BelongsTo
{
return $this->belongsTo(Rfq::class);
}
Rfq.php
public function assemblies(): HasMany
{
return $this->hasMany(Assembly::class);
}
public function assemblies(): HasMany
{
return $this->hasMany(Assembly::class);
}
Basically, on the second step there should be some sort of a repeater for the assemblies while each of them should automatically derive the rfq's id that was created on the first step so eventually i won't have this field visible to the user since the rfq was already 'chosen' on the previous step - Assembly.php - getForm() function -
Select::make('rfq_id')
->options(fn () => Rfq::all()->pluck('name', 'id')->toArray())
->required(), //this is here only for debugging purposes atm

Forms\Components\TextInput::make('name')
->maxLength(255),
Forms\Components\TextInput::make('number')
->required()
->maxLength(255),
// more fields...
Select::make('rfq_id')
->options(fn () => Rfq::all()->pluck('name', 'id')->toArray())
->required(), //this is here only for debugging purposes atm

Forms\Components\TextInput::make('name')
->maxLength(255),
Forms\Components\TextInput::make('number')
->required()
->maxLength(255),
// more fields...
Thanks again @awcodes for your time. It's not taken for granted! Also, for some reason, im now missing the 'previous' button on the wizard. Only Next and Cancel are there i managed to solve it with some public variables on the CreateRfq itself which then I pass between each step and attaching this relationship on the step's afterValidation() function, for example -
Step::make('RFQ')
->schema($this->getRfqFormSchema())
->afterValidation(function ($state, callable $set, callable $get) {
$this->rfq = Rfq::create($state);
$this->rfqId = $this->rfq->id;

})->model(Rfq::class),
Step::make('Assemblies')
->schema($this->getAssemblyFormSchema($this->rfqId))
->afterValidation(function ($state, callable $set) {
$this->assembly = Assembly::create($state);
$this->assembly->rfq_id = $this->rfqId;
logger("Assembly {$this->assembly->name} ({$this->assembly->id}) was created and its parent rfq is {$this->assembly->rfq->name}");
})->model(Assembly::class),
Step::make('RFQ')
->schema($this->getRfqFormSchema())
->afterValidation(function ($state, callable $set, callable $get) {
$this->rfq = Rfq::create($state);
$this->rfqId = $this->rfq->id;

})->model(Rfq::class),
Step::make('Assemblies')
->schema($this->getAssemblyFormSchema($this->rfqId))
->afterValidation(function ($state, callable $set) {
$this->assembly = Assembly::create($state);
$this->assembly->rfq_id = $this->rfqId;
logger("Assembly {$this->assembly->name} ({$this->assembly->id}) was created and its parent rfq is {$this->assembly->rfq->name}");
})->model(Assembly::class),
actually no.. its still not working. ugh. on the last step it creates another new rfq and being redirected to its edit page with no assembly(ies) attached to it. how can i prevent the submit on the last step, or at least control it more granularly?
Want results from more Discord servers?
Add your server