F
Filament2mo ago
nowak

How to add a CreateAction in the headerActions of resource A, to create a record in resource B?

I want to allow users to easily create a record in resource B, on the List page of resource A in its tables headerActions. I would also prefer to use the existing form from resource B as well as it's create lifecycle hooks, if possible. Basically, I want to open the existing create page of resource B in a modal by clicking an action button, which does the exact same as the existing create page does. Is this possible? I tried to use Filament\Actions\CreateAction on a tables headerActions just to test if this would open a create modal when clicked like this:
->headerActions([
\Filament\Actions\CreateAction::make()
->model(UserOrder::class)
->form([
TextInput::make('status_id')
->required()
->maxLength(255),
// ...
]),
]);
->headerActions([
\Filament\Actions\CreateAction::make()
->model(UserOrder::class)
->form([
TextInput::make('status_id')
->required()
->maxLength(255),
// ...
]),
]);
But this gives this error:
Method Filament\Actions\CreateAction::table does not exist.
Method Filament\Actions\CreateAction::table does not exist.
15 Replies
Diogo Pinto
Diogo Pinto2mo ago
->headerActions([
\Filament\Tables\Actions\Action::make('category')
->model(Category::class)
->form(CategoryResource::getFormSchema()),
)]
->headerActions([
\Filament\Tables\Actions\Action::make('category')
->model(Category::class)
->form(CategoryResource::getFormSchema()),
)]
In your other resource extract the schema to a new method like getFormSchema():
public static function form(Form $form): Form
{
return $form->schema(self::getFormSchema());
}

public static function getFormSchema(): array
{
return [
Forms\Components\TextInput::make('name')
->required()
->label('Category Name'),
];
}
public static function form(Form $form): Form
{
return $form->schema(self::getFormSchema());
}

public static function getFormSchema(): array
{
return [
Forms\Components\TextInput::make('name')
->required()
->label('Category Name'),
];
}
This should work. Importing the form directly passing a closure can mess up with livewire
nowak
nowakOP2mo ago
Thanks! But it does not seem to work in my case. I do have a fairly complex form in my other resource though, where it seems like myGrid component relationship causes an error. Here is some top level code which might help explain this. So my headActions now has this:
->headerActions([
\Filament\Tables\Actions\Action::make('userOrder')
->model(UserOrder::class)
->form(UserOrderResource::getFormSchema()),
)]
->headerActions([
\Filament\Tables\Actions\Action::make('userOrder')
->model(UserOrder::class)
->form(UserOrderResource::getFormSchema()),
)]
And my UserOrderResource has this form and getFormSchema:
public static function form(Form $form): Form
{
return $form->schema(self::getFormSchema())->columns(3);
}

public static function getFormSchema(): array
{
return [
Forms\Components\Group::make()
->schema([
Forms\Components\Section::make()
->schema(static::getDetailsFormSchema())
->columns(3),

Forms\Components\Section::make('Order items')
->headerActions([
])
->schema([
static::getItemsRepeater(),
static::getTotalPriceInput(),
]),
])
->columnSpan(['lg' => fn (?UserOrder $record) => $record === null ? 3 : 2]),
Forms\Components\Section::make()
->schema([
Forms\Components\Placeholder::make('created_at')
->label('Created at')
->content(fn (UserOrder $record): ?string => $record->created_at?->diffForHumans()),

Forms\Components\Placeholder::make('updated_at')
->label('Last modified at')
->content(fn (UserOrder $record): ?string => $record->updated_at?->diffForHumans()),
])
->columnSpan(['lg' => 1])
->hidden(fn (?UserOrder $record) => $record === null),
];
}
public static function form(Form $form): Form
{
return $form->schema(self::getFormSchema())->columns(3);
}

public static function getFormSchema(): array
{
return [
Forms\Components\Group::make()
->schema([
Forms\Components\Section::make()
->schema(static::getDetailsFormSchema())
->columns(3),

Forms\Components\Section::make('Order items')
->headerActions([
])
->schema([
static::getItemsRepeater(),
static::getTotalPriceInput(),
]),
])
->columnSpan(['lg' => fn (?UserOrder $record) => $record === null ? 3 : 2]),
Forms\Components\Section::make()
->schema([
Forms\Components\Placeholder::make('created_at')
->label('Created at')
->content(fn (UserOrder $record): ?string => $record->created_at?->diffForHumans()),

Forms\Components\Placeholder::make('updated_at')
->label('Last modified at')
->content(fn (UserOrder $record): ?string => $record->updated_at?->diffForHumans()),
])
->columnSpan(['lg' => 1])
->hidden(fn (?UserOrder $record) => $record === null),
];
}
The error I get, is this:
Call to undefined method App\Models\GroupOrder::detail()
Call to undefined method App\Models\GroupOrder::detail()
Where the GroupOrderResource is where my table has the headerAction to create the UserOrderResource record. The error comes from the getRelationship method in filament/packages/forms/src/Components/Grid.php:
public function getRelationship(): BelongsTo | HasOne | MorphOne | null
{
$name = $this->getRelationshipName();

if (blank($name)) {
return null;
}

return $this->getModelInstance()->{$name}();
}
public function getRelationship(): BelongsTo | HasOne | MorphOne | null
{
$name = $this->getRelationshipName();

if (blank($name)) {
return null;
}

return $this->getModelInstance()->{$name}();
}
Where my "detail" Grid component looks like this in my UserOrderResource form schema:
Grid::make('Detail')
->relationship('detail')
->hidden(condition: fn (Get $get, ?Model $record) => !$record || User::find($get('user_id')))
->schema([
TextInput::make('name_label')
->label('User')
->disabled(),
]),
Grid::make('Detail')
->relationship('detail')
->hidden(condition: fn (Get $get, ?Model $record) => !$record || User::find($get('user_id')))
->schema([
TextInput::make('name_label')
->label('User')
->disabled(),
]),
So it seems like this approach does not deal well with form components with relationships, when used in other resources tables headerActions. Any idea of how to work around this?
ChesterS
ChesterS2mo ago
Have a look at some of the answers here on how to add a form in a modal https://discord.com/channels/883083792112300104/1296766970896580649
nowak
nowakOP2mo ago
Thanks! But that thread is about editing records, where you can pass the record that needs to be edited. I need to create a record, where it seems like the relationships I have in my other resource form are seen as relationships on the model that the headerActions table belongs to. So if I try to fetch a form from my UserOrderResource, which has a Grid component with a relationship, in a headerAction on my GroupOrderResource table, the relationships are perceived as being related to the GroupOrder model, instead of the UserOrder model. I think that is my main issue here.
ChesterS
ChesterS2mo ago
I'm not sure what your setup is, but I've managed to use a CreateRecord form in another resource modal. Create pages really are just Livewire components. If you're only using resources then I'm not sure tbh, haven't tried it, but it's probably possible
nowak
nowakOP2mo ago
I am only using resources. Where this header action on the GroupOrderResource:
->headerActions([
\Filament\Tables\Actions\Action::make('userOrder')
->model(UserOrder::class)
->form(UserOrderResource::getFormSchema()),
)]
->headerActions([
\Filament\Tables\Actions\Action::make('userOrder')
->model(UserOrder::class)
->form(UserOrderResource::getFormSchema()),
)]
Gives me this error:
Call to undefined method App\Models\GroupOrder::detail()
Call to undefined method App\Models\GroupOrder::detail()
Because my Form Schema in UserOrderResource has this form component:
Grid::make('Detail')
->relationship('detail')
->hidden(condition: fn (Get $get, ?Model $record) => !$record || User::find($get('user_id')))
->schema([
TextInput::make('name_label')
->label('User')
->disabled(),
]),
Grid::make('Detail')
->relationship('detail')
->hidden(condition: fn (Get $get, ?Model $record) => !$record || User::find($get('user_id')))
->schema([
TextInput::make('name_label')
->label('User')
->disabled(),
]),
Which is strange, as I pass the ->model(UserOrder::class)
ChesterS
ChesterS2mo ago
Hmm not sure. I didn't know you could call ->relationship() on a Grid tbh 🤔 . FWIW, here's an example of creating a record that I have
Tables\Actions\Action::make('createRecord')
->modalContent(function (): Htmlable {
$component = CreatOrderForProperty::class;
return new HtmlString(Blade::render(<<<VIEW
@livewire($component::class, [
'issue_id' => $this->issue_id,
'lazy' => true
])
VIEW
));
}),
Tables\Actions\Action::make('createRecord')
->modalContent(function (): Htmlable {
$component = CreatOrderForProperty::class;
return new HtmlString(Blade::render(<<<VIEW
@livewire($component::class, [
'issue_id' => $this->issue_id,
'lazy' => true
])
VIEW
));
}),
Anyway, maybe someone else has a better idea 🤞
toeknee
toeknee2mo ago
You get that detail error because you are using the form on a different model being UserOrder I presume and detail relationship doesn't exist on the user order. Can you explain a little more of your approach here? For example, if you are in a user resource, you would tend to have the order relationship and can create others through there in the relationship. If you want to create an order without showing the orders, then you should be able to build an action rendering the form.
nowak
nowakOP2mo ago
The detail relationship is on the UserOrder model though:
public function detail()
{
return $this->hasOne(UserOrderDetail::class);
}
public function detail()
{
return $this->hasOne(UserOrderDetail::class);
}
My approach is that I have a GroupOrderResource, where I manage all GroupOrders (add couriers, set routes etc.) directly on the table. All UserOrders belong to a GroupOrder, and what I want to do, is add an action on this table, where I can easily create a new UserOrder. I know I could edit a GroupOrder -> UserOrder relations -> Create. But that is a few clicks and loading away, and if a GroupOrder does not exist yet, I can't use this approach (a GroupOrder is created after an initial UserOrder is created). So having an action to create a UserOrder directly on the GroupOrderResource would be very helpful. Interesting, what is your CreatOrderForProperty::class exactly?
ChesterS
ChesterS2mo ago
Just a Filament page
class CreatOrderForProperty extends CreateRecord
class CreatOrderForProperty extends CreateRecord
In my case it extends another page with small changes to it because I needed slightly different handleRecordCreation logic
nowak
nowakOP2mo ago
So I could basically use this page here:
class CreateUserOrder extends CreateRecord
class CreateUserOrder extends CreateRecord
ChesterS
ChesterS2mo ago
Yeah probably. There are some issues with this that I havent' figured oute yet. EG if you're showing this in a modal, instead of closing the modal, it will take you to the new record you created. It's like an iframe pretty much, so you see the breadcrumbs etc, but there are ways to hide that stuff Anyway, it's a pretty 'hacky' way to do it but it works ¯\_(ツ)_/¯
nowak
nowakOP2mo ago
Ahh, I get it.
ChesterS
ChesterS2mo ago
Let me know if you find a better way
nowak
nowakOP2mo ago
Will do! After looking into this again, I thought that it was weird that the native CreateAction didn't work. So I tried to add this to the List page instead:
protected function getHeaderActions(): array
{
return [
CreateAction::make('userOrder')
->label('Create User Order')
->model(UserOrder::class)
->form(UserOrderResource::getFormSchema())
->fillForm([
'user_id' => User::firstWhere(
'email',
config('meinrad.assistant_email')
)->id
])
->mutateFormDataUsing(function (array $data): array {
$mealType = MealType::find($data['meal_type_id']);
$deliveryDate = Carbon::createFromFormat(
'Y-m-d',
$data['delivery_date']
);
$orderDeadlineTime = Carbon::createFromFormat(
'H:i:s',
$mealType->order_deadline
);
$deadline = $deliveryDate->setTime(
$orderDeadlineTime->hour,
$orderDeadlineTime->minute,
$orderDeadlineTime->second
);
$data['deadline'] = $deadline->toDateTimeString();
return $data;
})
->after(function ($record) {
$detailCreator = app(CreatesUserOrderDetails::class);
$detailCreator->create($record);
$groupOrderCreator = app(CreatesGroupOrders::class);
$groupOrder = $groupOrderCreator->create($record);
if ($groupOrder) {
$updater = app(UpdatesGroupOrders::class);
$updater->update($groupOrder, []);
}
}),
];
}
protected function getHeaderActions(): array
{
return [
CreateAction::make('userOrder')
->label('Create User Order')
->model(UserOrder::class)
->form(UserOrderResource::getFormSchema())
->fillForm([
'user_id' => User::firstWhere(
'email',
config('meinrad.assistant_email')
)->id
])
->mutateFormDataUsing(function (array $data): array {
$mealType = MealType::find($data['meal_type_id']);
$deliveryDate = Carbon::createFromFormat(
'Y-m-d',
$data['delivery_date']
);
$orderDeadlineTime = Carbon::createFromFormat(
'H:i:s',
$mealType->order_deadline
);
$deadline = $deliveryDate->setTime(
$orderDeadlineTime->hour,
$orderDeadlineTime->minute,
$orderDeadlineTime->second
);
$data['deadline'] = $deadline->toDateTimeString();
return $data;
})
->after(function ($record) {
$detailCreator = app(CreatesUserOrderDetails::class);
$detailCreator->create($record);
$groupOrderCreator = app(CreatesGroupOrders::class);
$groupOrder = $groupOrderCreator->create($record);
if ($groupOrder) {
$updater = app(UpdatesGroupOrders::class);
$updater->update($groupOrder, []);
}
}),
];
}
Which works, but I have to add mutateFormDataUsing and after logic, as this is not inherited from the UserOrderResource Create page. So this action would be added to the page header instead of the table header, which is also a con, but CreateAction() just doesn't work from a tables headerActions([]) for some good reason probably. @ChesterS Maybe useful for you as well?
Want results from more Discord servers?
Add your server