この記事では、Inertiaのレスポンスに毎回同じデータを含める方法やフラッシュメッセージの使い方について解説します。
前回の記事ではInertia.jsでのバリデーションについて解説しました。このシリーズ記事ではInertia.jsの使い方を解説していますので以前の記事と併せてお読みください。
アプリ全体で使用する設定値やフラッシュメッセージをInertiaレスポンスに含めたいんですが、毎回コントローラーで渡すのは無駄が多い気がするんです。
LaravelのミドルウェアでInertiaレスポンスに毎回含めたいデータを指定することができますよ。
アプリ全体で使用する設定値を毎回のレスポンスに含める
バックエンドのLaraveで設定した値を、フロントエンドのVueで使いたい場合、毎回コントローラーで値を渡すのは効率が悪いため、ミドルウェアで一律で設定するとすべてのレスポンスに含まれるようになります。
ここでは、例としてこのシリーズ記事で使用しているサンプルのブックマーク数の最大値を、.envファイルで設定し、それをVue側で使えるようにするという設定を行います。
.envファイルで設定値を設定
//.envファイルに追記
MAX_BOOKMARKS=20
configとして扱えるようにする
「config/app.php」に次の行を追記します。
return [
//略
'max_bookmarks' => env('MAX_BOOKMARKS'),
];
これで、Laravelの中で次のコードで.envで設定した「MAX_BOOKMARKS」の値を使えるようになります。
config('app.max_bookmarks')
Inertiaのミドルウェアで設定値を返す
「app/Http/Middleware/HandleInertiaRequests.php」がInertiaのミドルウェアファイルです。毎回のリクエスト時にこの内容が読み込まれます。
今回はその中のshare()メソッドの返り値にsetting配列を追加し、max_bookmarksキーでブックマーク数の最大値をセットします。
//(略)
public function share(Request $request)
{
return array_merge(parent::share($request), [
'setting' => [
'max_bookmarks' => config('app.max_bookmarks'),
]
]);
}
//(略)
この設定値はInertiaレスポンスの中でこのような形で含まれるようになります。
"props": {
"setting":{
"max_bookmarks":"20"
}
}
Vueコンポーネントでの設定値の使用
propsに渡された値はVueコンポーネントからは次のように参照できます。
import { usePage } from '@inertiajs/vue3'
const page = usePage()
console.log(page.props.setting.max_bookmarks)
渡された設定値を利用してブックマークの登録数をプログレスバーで表示するサンプルです。
<div class="flex w-full">
<progress v-if="page.props.setting.max_bookmarks" :value="bookmarks.length" :max="page.props.setting.max_bookmarks" class="w-full flex-grow">{{ bookmarks.length }}</progress>
<div class="w-20">{{ bookmarks.length }} / {{ page.props.setting.max_bookmarks }}</div>
</div>
フラッシュメッセージを利用する
フラッシュメッセージとは、一回のみ表示されるメッセージのことで、何かのアクションに対する応答を表示する場合などに用います。
コントローラーでフラッシュメッセージを設定
フラッシュメッセージの設定方法はいくつかありますが、ここではwith()メソッドで追加する方法を用います。
//コントローラーの削除メソッド
public function destroy ($id)
{
Bookmark::destroy($id);
return to_route('bookmark.index')->with('message', '削除しました'); //削除後ブックマーク一覧ページへリダイレクト
}
ブックマーク削除後のレスポンスにwith()メソッドをチェインしています。
これでセッションのmessageに「削除しました」というメッセージが入ります。
ミドルウェアでレスポンスにフラッシュメッセージを含める
先ほど設定値をミドルウェアで返すようにした際と同様に「app/Http/Middleware/HandleInertiaRequests.php」のshare()メソッドの返り値にflash配列を追加します。
flash配列にはmessageキーにフラッシュメッセージの内容をセッションから取り出してセットしています。
//(略)
public function share(Request $request)
{
return array_merge(parent::share($request), [
//(略)
'flash' => [
'message' => fn () => $request->session()->get('message')
],
]);
}
//(略)
このフラッシュメッセージは次のようなJSONデータとして渡されます。
"props": {
"flash": {
"message": "削除しました"
}
}
Vueコンポーネントでのフラッシュメッセージの使用
propsにflashとして渡された値はVueコンポーネントからは次のように参照できます。
import { usePage } from '@inertiajs/vue3'
const page = usePage()
console.log(page.props.flash.message)
渡されたフラッシュメッセージを親レイアウトで表示するサンプルです。
<div v-if="page.props.flash.message" class="bg-green-200 p-2 m-1">
{{ page.props.flash.message }}
</div>
サンプルソース
今回のサンプルソースです。完全なソースはGitHubをご覧ください。
ミドルウェア
<?php
namespace App\Http\Middleware;
use Illuminate\Http\Request;
use Inertia\Middleware;
class HandleInertiaRequests extends Middleware
{
/**
* The root template that's loaded on the first page visit.
*
* @see https://inertiajs.com/server-side-setup#root-template
* @var string
*/
protected $rootView = 'app';
/**
* Determines the current asset version.
*
* @see https://inertiajs.com/asset-versioning
* @param \Illuminate\Http\Request $request
* @return string|null
*/
public function version(Request $request): ?string
{
return parent::version($request);
}
/**
* Defines the props that are shared by default.
*
* @see https://inertiajs.com/shared-data
* @param \Illuminate\Http\Request $request
* @return array
*/
public function share(Request $request): array
{
return array_merge(parent::share($request), [
'setting' => [
'max_bookmarks' => config('app.max_bookmarks'),
],
'flash' => [
'message' => fn () => $request->session()->get('message')
],
]);
}
}
Vueコンポーネント(ブックマーク上限数の表示)
<script setup>
import { reactive, computed } from "vue";
import { Link, router, useForm, usePage } from '@inertiajs/vue3'
import Layout from '@/Pages/Layout.vue'
const page = usePage()
const props = defineProps({
bookmarks: {
type: Array
},
errors: {
type: Object
}
});
const form = useForm({
title: null,
url: null,
});
const submit = () => {
form
.transform((data) => ({
...data,
title: data.title ? data.title : '無題',
}))
.post(route('bookmark.store'));
}
</script>
<template>
<Layout title="ブックマーク一覧">
<h2 class="text-lg border-b p-2">
<Link :href="route('bookmark.index')" class="text-blue-700 underline">
ブックマーク一覧
</Link>
</h2>
<ul class="list-disc list-inside p-2">
<li v-for="bookmark in bookmarks" class="list-none flex justify-between">
<a :href="bookmark.url" target=_blank class="text-blue-700" >{{ bookmark.title }}</a>
<Link as="button" method="delete" :href="route('bookmark.delete',bookmark.id)" preserve-scroll class="border border-red-400 m-1 p-1 text-sm text-red-400">削除</Link>
</li>
</ul>
<div class="flex w-full">
<progress v-if="page.props.setting.max_bookmarks" :value="bookmarks.length" :max="page.props.setting.max_bookmarks" class="w-full flex-grow">{{ bookmarks.length }}</progress>
<div class="w-20">{{ bookmarks.length }} / {{ page.props.setting.max_bookmarks }}</div>
</div>
<template v-if="bookmarks.length < page.props.setting.max_bookmarks">
<form @submit.prevent="submit">
<h3 class="border-l-4 p-1">ブックマーク追加</h3>
<div class="flex w-full">
<div class="w-20 text-right bg-gray-200 text-sm p-1 m-1">タイトル</div>
<div class="w-40">
<input type=text v-model="form.title" size=15 class="p-1 m-1 text-sm w-full border" />
</div>
<div v-if="form.errors.title" class="p-1 m-1 text-sm text-red-400">{{ form.errors.title }}</div>
</div>
<div class="flex w-full">
<div class="w-20 text-right bg-gray-200 text-sm p-1 m-1">URL</div>
<div class="w-40">
<input type=text v-model="form.url" size=15 class="p-1 m-1 text-sm w-full border" />
</div>
<div v-if="form.errors.url" class="p-1 m-1 text-sm text-red-400">{{ form.errors.url }}</div>
</div>
<button type="submit" class="border border-gray-400 m-1 p-1 text-sm" :disabled="form.processing" v-bind:class="{'cursor-not-allowed': form.processing}">ブックマーク追加</button>
<button v-if="form.processing" @click="form.cancel()" type="button" class="border bg-red-200 m-1 p-1 text-sm">送信中止</button>
<button @click="form.reset()" type="reset" class="border bg-gray-200 m-1 p-1 text-sm" :disabled="!form.isDirty" v-bind:class="{'cursor-not-allowed': !form.isDirty}">リセット</button>
<button @click="form.defaults()" type="button" class="border bg-gray-200 m-1 p-1 text-sm" :disabled="!form.isDirty" v-bind:class="{'cursor-not-allowed': !form.isDirty}">初期値として設定</button>
<div v-if="form.recentlySuccessful" class="p-1 m-1 bg-green-200">送信が完了しました。</div>
</form>
</template>
<template v-else>
ブックマークが上限まで追加されています。
</template>
</Layout>
</template>
コントローラー(フラッシュメッセージを作成)
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Inertia\Inertia;
use App\Models\Bookmark;
use Illuminate\Support\Facades\Validator;
class BookmarkController extends Controller
{
public function index ()
{
return Inertia::render('Bookmark/Index',['bookmarks' => fn() => Bookmark::all()]);
}
//検索メソッド
public function search ($queryWord)
{
return Inertia::render('Bookmark/Index',[
'bookmarks' => Bookmark::Where(
'title', 'like', '%'.$queryWord.'%'
)->orWhere(
'url', 'like', '%'.$queryWord.'%'
)->get()
]);
}
//追加メソッド
public function store (Request $request)
{
$validator = Validator::make($request->all(), [
'title' => ['required', 'string', 'max:255'],
'url' => ['required', 'url', 'max:255', 'unique:App\Models\Bookmark,url'],
])->validate();
$bookmark = new Bookmark;
$bookmark->title = $request->title;
$bookmark->url = $request->url;
$bookmark->save();
return to_route('bookmark.index')->with('message', '追加しました'); //追加後ブックマーク一覧ページへリダイレクト
}
//削除メソッド
public function destroy ($id)
{
Bookmark::destroy($id);
return to_route('bookmark.index')->with('message', '削除しました'); //削除後ブックマーク一覧ページへリダイレクト
}
}
親レイアウトVueコンポーネント(フラッシュメッセージ表示)
<script setup>
import { ref } from "vue";
import { Link, Head, usePage } from '@inertiajs/vue3'
const page = usePage()
defineProps({ title: String })
const searchWord = ref('');
</script>
<template>
<Head :title="title" />
<main>
<header class="bg-gray-100">
<Link href="/" class="text-blue-700 underline m-2">Home</Link>
<Link href="/HelloWorld" class="text-blue-700 underline m-2">HelloWorld</Link>
<Link :href="route('bookmark.index')" class="text-blue-700 underline m-2">ブックマーク一覧</Link>
<input type=text name=search v-model="searchWord" size=15 class="p-1 m-1 text-sm" />
<Link as="button" method="get" :href="route('bookmark.search',{queryWord: searchWord})" preserve-state class="border border-gray-400 m-1 p-1 text-sm">ブックマーク検索</Link>
</header>
<article class="max-w-screen-sm">
<div v-if="page.props.flash.message" class="bg-green-200 p-2 m-1">
{{ page.props.flash.message }}
</div>
<slot />
</article>
</main>
</template>
Comments