Filamentでモーダル内にモーダルを表示する方法
概要
Filamentでは、スライドオーバー(モーダル)内で確認ダイアログ(モーダル)を表示する必要がある場合があります。例えば、編集フォームの保存ボタンを押した際に、条件によって確認ダイアログを表示したい場合などです。
この記事では、EditActionのスライドオーバー内で、カスタム保存ボタンを作成し、そのボタンに確認ダイアログを表示する実装方法を説明します。
実装例
要件
- ブランド編集のスライドオーバー内で保存ボタンを押した際
- 有効から無効への変更時、かつ商品で使用されている場合のみ
- 確認ダイアログを表示する
実装コード
<?php
declare(strict_types=1);
namespace App\Admin\Resources\BrandResource\Pages;
use App\Admin\Resources\BrandResource;
use App\Models\Brand;
use Filament\Actions\Action;
use Filament\Actions\EditAction;
use Filament\Resources\Pages\ListRecords;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Model;
class ListBrands extends ListRecords
{
protected static string $resource = BrandResource::class;
public function table(Table $table): Table
{
return parent::table($table)
->recordUrl(null)
->recordActions([
EditAction::make()
->slideOver()
->modalSubmitAction(false) // デフォルトの保存ボタンを非表示
->extraModalFooterActions(function (EditAction $editAction): array {
// 現在の状態と新しい状態を取得
$record = $editAction->getRecord();
$schema = $this->getMountedActionSchema(mountedAction: $editAction);
$formData = $schema->getState();
$currentIsActive = (bool) ($record->is_active ?? false);
$newIsActive = (bool) ($formData['is_active'] ?? false);
$productCount = $record->products()->count();
// カスタム保存アクションを作成
$saveAction = Action::make('saveWithConfirmation')
->label('保存')
->color('primary')
->cancelParentActions() // 親のEditActionをキャンセル
->action(function () use ($editAction): void {
$record = $editAction->getRecord();
// フォームの状態を取得(FileUploadの処理済みデータ)
$schema = $this->getMountedActionSchema(mountedAction: $editAction);
$formData = $schema->getState();
// EditActionの保存処理を実行
$editAction->process(
function (array $data, Model $record): void {
$record->update($data);
},
[
'data' => $formData,
'record' => $record,
]
);
// テーブルをリフレッシュしてリストに戻る
$this->resetTable();
});
// 条件によって確認ダイアログを設定
if ($currentIsActive && ! $newIsActive && $productCount > 0) {
$saveAction
->requiresConfirmation()
->modalHeading('変更の確認')
->modalDescription("このブランドは{$productCount}件の商品で使用されています。無効にして良いですか。")
->modalSubmitActionLabel('保存');
}
return [$saveAction];
}),
]);
}
}
重要なポイント
1. modalSubmitAction(false) でデフォルトの保存ボタンを非表示
デフォルトの保存ボタンを非表示にし、カスタム保存ボタンに置き換えます。
2. extraModalFooterActions() でカスタムボタンを追加
->extraModalFooterActions(function (EditAction $editAction): array {
// カスタム保存アクションを作成
$saveAction = Action::make('saveWithConfirmation')
// ...
return [$saveAction];
})
スライドオーバーのフッターにカスタム保存ボタンを追加します。
3. cancelParentActions() で親アクションをキャンセル
ネストされたアクションが完了した際に、親のEditAction(スライドオーバー)を自動的にキャンセルします。これにより、保存後にスライドオーバーが閉じ、リスト画面に戻ります。
4. requiresConfirmation() で確認ダイアログを表示
if ($currentIsActive && ! $newIsActive && $productCount > 0) {
$saveAction
->requiresConfirmation()
->modalHeading('変更の確認')
->modalDescription("このブランドは{$productCount}件の商品で使用されています。無効にして良いですか。")
->modalSubmitActionLabel('保存');
}
条件によって、確認ダイアログを表示するかどうかを動的に設定します。
5. process() メソッドで保存処理を実行
// フォームの状態を取得(FileUploadの処理済みデータ)
$schema = $this->getMountedActionSchema(mountedAction: $editAction);
$formData = $schema->getState();
$editAction->process(
function (array $data, Model $record): void {
$record->update($data);
},
[
'data' => $formData,
'record' => $record,
]
);
EditActionのprocess()メソッドを使用して、フォームデータを保存します。$dataと$recordを明示的に渡す必要があります。
重要: getRawData()ではなく、getMountedActionSchema()とgetState()を使用してフォームの状態を取得します。これにより、FileUploadコンポーネントの処理済みデータ(ファイルパス)が正しく取得されます。
動作の流れ
- スライドオーバーを開く: テーブルの編集ボタンをクリック
- フォームを編集: スライドオーバー内でフォームを編集
- 保存ボタンをクリック: カスタム保存ボタンをクリック
- 条件チェック: 有効から無効への変更かつ商品で使用されている場合
- 確認ダイアログを表示: ネストされたモーダル(確認ダイアログ)を表示
- OKをクリック: 確認ダイアログでOKをクリック
- 保存処理を実行:
process()メソッドでデータを保存 - スライドオーバーを閉じる:
cancelParentActions()により親のスライドオーバーが閉じる - リストに戻る:
resetTable()によりテーブルがリフレッシュされ、リスト画面に戻る
注意事項
getMountedActionSchema() と getState() の使用
extraModalFooterActions()のクロージャ内では、$this->getMountedActionSchema(mountedAction: $editAction)と$schema->getState()を使用して、フォームの状態を取得します。これにより、FileUploadコンポーネントの処理済みデータ(ファイルパス)が正しく取得されます。
重要: getRawData()は未処理の一時ファイルキーを返す可能性があるため、使用しないでください。
process() メソッドのパラメータ
process()メソッドを外部から呼び出す場合、$dataと$recordを明示的に渡す必要があります。これらは自動的に解決されません。
cancelParentActions() の重要性
cancelParentActions()を設定しないと、保存後にスライドオーバーが閉じず、リスト画面に戻りません。