🦈

Webアプリの「一覧に戻る」

2024/04/19に公開

想定するWebアプリ

  • Webブラウザで利用する業務用アプリケーション
    • サーバ側も自分たちで実装する前提
  • 「検索 / 一覧」「登録 / 修正」「照会 / 詳細」などの複数の画面間を遷移する
  • 一覧には検索条件フォームやページャ(pagination)を置く
  • 「詳細 ⇔ 修正」など一覧に戻るまで複数回遷移する可能性がある
  • 画面は単一タブ内で遷移し、新しいタブは開かない


一覧から登録や詳細へ遷移する


詳細から一覧や修正画面へ遷移する。削除すると一覧へ戻る


修正して保存すると詳細へ戻る

こんな感じで一覧に戻ってほしい

  • 別画面に移動する直前の「入力していた検索条件」や「見ていたページ番号」を維持した上で、情報は最新に更新してほしい
    • 修正した内容は反映して欲しいし、削除したレコードは消えて欲しい
    • 情報が最新になった結果、並び順やページ番号が変わる可能性は一旦保留
  • 単に一覧の URL へ遷移すると情報は最新になるが、検索条件やページ番号はリセットされてしまう

他ではどうやってんの

  • 通販サイトでは「詳細の時点で別タブで開く」や「ブラウザバックで戻る」が多い
    メニューボタンで一覧に戻ると検索条件などはクリアされてしまう
  • 単純なブラウザバックだと情報が更新されない
  • ブラウザ履歴の操作では複数回の遷移を挟んだ場合に適切に動作するか不明
    • 単に一覧⇔詳細を行き来するだけならいいけど、詳細の先があったらどうなるのか

今回はどうしたのか

方針

  • 一覧をリクエストする際に URL 引数をセッションに保存する
  • 一覧へ戻る際は、別のルートを経由し、保存した URL 引数を付加してから一覧へリダイレクトする

アプリケーションの構成

実装の抜粋

参考

Routing
Middleware
pagination/withQueryString

実装

routes/web.php
Route::prefix('admin')->middleware('auth')->group(function () {
    // 顧客マスタ
    Route::controller(CustomerController::class)->group(function () 
    {
        // 「一覧」ルートにミドルウェアを設定する
        Route::get('/customers/index', 'index')->middleware('saveParams')
            ->name('admin.customers.index');
        // 「一覧」に戻るルートは別に設ける
        Route::get('/customers/_back_', function() {
            // 保存した引数を付加してリダイレクトする
            return redirectWithRestoredParams("admin.customers.index");
        })->name('admin.customers.back');
        
        Route::get('/customers/show/{id}', 'show')->name('admin.customers.show');
        Route::get('/customers/edit/{id?}', 'edit')->name('admin.customers.edit');
        Route::post('/customers', 'store')->name('admin.customers.store');
        Route::post('/customers/delete', 'delete')->name('admin.customers.delete');
    });
});
app/Http/Middleware/SaveParameters.php
// alias "saveParams" で登録する
public function handle(Request $request, Closure $next): Response
{
    // Path をキー名にして、引数をセッションに保存する
    $path = $request->path();
    $request->session()->put('savedParams_' . $path, $request->all());
    return $next($request);
}
app/Helpers/helpers.php
// ここでは helper 関数として登録したがなんでも可
function redirectWithRestoredParams($routeName)
{
    // URL から path を取得
    $path = Str::substr(parse_url(route($routeName))['path'], 1); 

    $sessionKey = "savedParams_{$path}";

    // セッションに保存した引数を付加してリダイレクトする
    return redirect()->route($routeName, Session::get($sessionKey));
}
app/Http/Controllers/CustomerController.php
public function index(Request $request): View
{
    $code = $request->input('code');
    $name = $request->input('name');

    $customers = Customer::when($code, function ($query, $code) {
        $query->where('code', 'LIKE', "$code%");
    })->when($name, function ($query, $name) {
        $query->where('name', 'LIKE', "%$name%");
    })
        ->orderBy('code', 'ASC')
        ->paginate(5)
        ->withQueryString();
        // withQueryString でクエリ文字列を毎回出力してもらうことで、
        // SaveParameters で検索条件とページ番号を保存する

    $conditions = [
        'code' => $code,
        'name' => $name,
    ];

    return view('admin.customers.index', compact('customers', 'conditions'));
}

public function delete(CustomerDeleteRequest $request): RedirectResponse
{
    // 削除に成功したら、一覧へ戻る用のルートへリダイレクトする
    return redirect()->route('admin.customers.back');
}
app/resources/views/..../edit.blade.php
<!-- 各画面で一覧に戻る route は戻る用のルートを指定する -->
<div> 
    <a href="{{ route('admin.customers.back') }}">一覧</a>
</div>

まとめ

  • 機能ごとに設定するだけでロジックは必要はない
  • 一覧に戻ってくるまで何回でもどこにでも遷移してよい
  • ブラウザバックされてもそれぞれのURLで正しく動く
  • クライアント側は何もしなくてよい
  • スクロール位置はどうにもならない

Discussion