コンテンツにスキップ

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) でデフォルトの保存ボタンを非表示

EditAction::make()
    ->slideOver()
    ->modalSubmitAction(false) // デフォルトの保存ボタンを非表示

デフォルトの保存ボタンを非表示にし、カスタム保存ボタンに置き換えます。

2. extraModalFooterActions() でカスタムボタンを追加

->extraModalFooterActions(function (EditAction $editAction): array {
    // カスタム保存アクションを作成
    $saveAction = Action::make('saveWithConfirmation')
        // ...
    return [$saveAction];
})

スライドオーバーのフッターにカスタム保存ボタンを追加します。

3. cancelParentActions() で親アクションをキャンセル

$saveAction = Action::make('saveWithConfirmation')
    ->cancelParentActions() // 親のEditActionをキャンセル

ネストされたアクションが完了した際に、親の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,
    ]
);

EditActionprocess()メソッドを使用して、フォームデータを保存します。$data$recordを明示的に渡す必要があります。

重要: getRawData()ではなく、getMountedActionSchema()getState()を使用してフォームの状態を取得します。これにより、FileUploadコンポーネントの処理済みデータ(ファイルパス)が正しく取得されます。

動作の流れ

  1. スライドオーバーを開く: テーブルの編集ボタンをクリック
  2. フォームを編集: スライドオーバー内でフォームを編集
  3. 保存ボタンをクリック: カスタム保存ボタンをクリック
  4. 条件チェック: 有効から無効への変更かつ商品で使用されている場合
  5. 確認ダイアログを表示: ネストされたモーダル(確認ダイアログ)を表示
  6. OKをクリック: 確認ダイアログでOKをクリック
  7. 保存処理を実行: process()メソッドでデータを保存
  8. スライドオーバーを閉じる: cancelParentActions()により親のスライドオーバーが閉じる
  9. リストに戻る: resetTable()によりテーブルがリフレッシュされ、リスト画面に戻る

注意事項

getMountedActionSchema()getState() の使用

extraModalFooterActions()のクロージャ内では、$this->getMountedActionSchema(mountedAction: $editAction)$schema->getState()を使用して、フォームの状態を取得します。これにより、FileUploadコンポーネントの処理済みデータ(ファイルパス)が正しく取得されます。

重要: getRawData()は未処理の一時ファイルキーを返す可能性があるため、使用しないでください。

process() メソッドのパラメータ

process()メソッドを外部から呼び出す場合、$data$recordを明示的に渡す必要があります。これらは自動的に解決されません。

cancelParentActions() の重要性

cancelParentActions()を設定しないと、保存後にスライドオーバーが閉じず、リスト画面に戻りません。

参考