💭

Laravel 8でInertia+Vuetifyでテンプレート対応 - Laravel JetStream - お試し

2021/10/07に公開

はじめに

経緯

Laravel 8でInertia+Vuetifyを使ってみるの続きです。

前回作成したDataTables.vueは、<v-app>を直接記述していました。
これにLaravel JetStreamのInertiaのレイアウト構成を適用してみます。

Inertiaのレイアウト構成について

InertiaでHTTPレスポンスを生成してくれるInertia::render($component, $props)$componentは、resources/js/Pages以下のVueコンポーネントを指定します。ここにメインコンテンツのコンポーネントを配置することになります。

Pagesディレクトリと同じ階層には、Layoutsディレクトリがあり、ここに画面レイアウトを構成するAppLayout.vueというVueコンポーネントが配置されていました。つまりここにテンプレートとなるVueコンポーネントを配置することになります。

テンプレートの適用には、Vueのスロットが利用されています。(スロットについてはこちら

Inertiaのレイアウト構成でVuetifyを組む

既存のコンポーネントを適当に利用し、レイアウト構成を組んでみます。

レイアウトコンポーネント準備

テンプレートと適当なメニューを作成します。

  1. メニュー(Drawer)
    Vuetifyの<v-navigation-drawer>を利用したメニューを作ります。

    Commonsディレクトリを作成し、Drawer.vueというファイルを配置します。
    内容はこんな感じにしました。

    resources/js/Commons/Drawer.vue
    <template>
      <v-navigation-drawer
          id="commons-drawer"
          v-model="drawer"
          expand-on-hover
          permanent
          app>
        <v-layout column>
          <v-list-item class="px-3">
    	<v-list-item-avatar size="32" color="grey"></v-list-item-avatar>
    	<v-list-item-title class="title">メニュー</v-list-item-title>
          </v-list-item>
    
          <v-divider/>
    
          <template v-for="(link, index) in links">
    	<inertia-link :key="index" :href="route(link.name)" as="v-list-item">
    	  <v-list-item-action>
    	    <v-icon>{{ link.icon }}</v-icon>
    	  </v-list-item-action>
    	  <v-list-item-content>
    	    <v-list-item-title v-text="link.title" />
    	  </v-list-item-content>
    	</inertia-link>
          </template>
    
        </v-layout>
      </v-navigation-drawer>
    </template>
    
    <script>
    
    export default {
      data () {
        return {
          drawer: true,
          links: [{
    	name: 'welcome',
    	icon: 'mdi-home',
    	title: 'Welcome'
          }, {
    	name: 'dashboard',
    	icon: 'mdi-view-dashboard',
    	title: 'Dashboard'
          }, {
    	name: 'data-tables',
    	icon: 'mdi-view-list',
    	title: 'DataTables'
          }],
        }
      }
    }
    </script>
    
    <style scoped>
    </style>
    
  2. レイアウトテンプレート

    次にレイアウトです。
    Layoutsディレクトリに、VueLayout.vueという名称で作成します。

    ここで全体を<v-app>で括ります。
    そこに先ほどのDrawer.vueを配置します。ついでに<v-app-bar>も配置します(空っぽです)

    そしてメインコンテンツです。<v-main><slot></slot>を配置します。

    resources/js/Layouts/VueLayout.vue
    <template>
      <v-app>
        <commons-drawer/>
    
        <v-app-bar app/>
    
        <v-main>
          <v-container fluid>
    	<slot></slot>
          </v-container>
        </v-main>
    
      </v-app>
    </template>
    
    <script>
    import CommonsDrawer from '@/Commons/Drawer'
    
    export default {
      name: 'VueLayout',
      components: {CommonsDrawer},
    }
    </script>
    
    <style scoped>
    </style>
    
  3. ルートに名前を設定
    Welcome画面のルート設定にname('welcome')を設定します。

    routes/web.php
    Route::get('/', function () {
        return Inertia::render('Welcome', []);
    })->name('welcome');
    

データテーブル画面の準備・表示

前回作成したDataTables.vueを表示してみます。

  1. テンプレート適用

    DataTables.vueを修正し、テンプレートを適用します。

    <script>タグ内の適当な位置に以下を追加します。

    resources/js/Pages/DataTables.vue
    <script>
    import VueLayout from '@/Layouts/VueLayout'components: {
        VueLayout,
      },

    <template>内の<v-app><v-main>の括りを<vue-layout>に変更します。

    resources/js/Pages/DataTables.vue
    <template>
      <vue-layout>
        <v-card flat tile></v-card>
      </vue-layout>
    </template>
    
    参考:DataTables.vue全体
    resources/js/Pages/DataTables.vue
    <template>
      <vue-layout>
        <v-card flat tile>
          <v-app-bar flat outlined>
    	<v-toolbar-title>サンプルテーブル</v-toolbar-title>
          </v-app-bar>
          <v-data-table
    	  :headers="headers"
    	  :items="desserts"
    	  :items-per-page="5"
    	  class="elevation-1"
          ></v-data-table>
        </v-card>
      </vue-layout>
    </template>
    
    <script>
    import VueLayout from '@/Layouts/VueLayout'
    
    export default {
      name: 'DataTables',
      components: {
        VueLayout,
      },
      props: ['desserts'],
      data () {
        return {
          headers: [
    	{
    	  text: 'Dessert (100g serving)',
    	  align: 'start',
    	  sortable: false,
    	  value: 'name',
    	},
    	{ text: 'Calories', value: 'calories' },
    	{ text: 'Fat (g)', value: 'fat' },
    	{ text: 'Carbs (g)', value: 'carbs' },
    	{ text: 'Protein (g)', value: 'protein' },
    	{ text: 'Iron (%)', value: 'iron' },
          ],
        }
      },
    }
    </script>
    
    <style scoped>
    </style>
    
  2. 確認
    ブラウザで確認してみます。
    http://localhost/app02/data-tables

    レイアウトが適用されていると思います。

    こんな感じで、スロットを利用することで簡単にレイアウトを構成できました。

その他の画面にレイアウトを適用

せっかくなので、メニューからリンクされている他の画面にもレイアウトを適用します。
Dashboard.vueコンポーネントを直接編集するところですが、別のやり方として編集せずにラップするようにします。

  1. Dashboard画面

    まずはダッシュボード画面です。
    PagesディレクトリにVueDashboard.vueというコンポーネントを作成します。
    内容は以下のとおりです。

    resources/js/Pages/VueDashboard.vue
    <template>
      <vue-layout>
        <dashboard/>
      </vue-layout>
    </template>
    
    <script>
    import VueLayout from '@/Layouts/VueLayout'
    import Dashboard from '@/Pages/Dashboard'
    
    export default {
      name: 'VueDashboard',
      components: {
        VueLayout,
        Dashboard,
      }
    }
    </script>
    
    <style scoped>
    </style>
    

    ルート設定の対象コンポーネントを変更します。

    routes/web.php
    return Inertia::render('Dashboard');return Inertia::render('VueDashboard');
    
  2. Welcome画面

    続いてウェルカム画面です。
    本来はこのテンプレートを適用するような画面ではないですが便宜上適用します。

    こちらもPagesディレクトリの下にVueWelcome.vueを以下の内容で作成します。
    こちらはデータの受け渡しがありますので、v-bindの指定が必要です。

    resources/js/Pages/VueWelcome.vue
    <template>
      <vue-layout>
        <welcome v-bind="$props"/>
      </vue-layout>
    </template>
    
    <script>
    import VueLayout from '@/Layouts/VueLayout'
    import Welcome from '@/Pages/Welcome'
    
    export default {
      name: 'VueWelcome',
      components: {
        VueLayout,
        Welcome,
      },
      props: {
        canLogin: Boolean,
        canRegister: Boolean,
        laravelVersion: String,
        phpVersion: String,
      }
    }
    </script>
    
    <style scoped>
    </style>
    

    ルート設定のWelcome画面の対象コンポーネントを変更し、

    routes/web.php
    return Inertia::render('Welcome');return Inertia::render('VueWelcome');
    

    URIも/から/welcomeに調整します。

    routes/web.php
    Route::get('/', function () {Route::get('/welcome', function () {
    

    /へのアクセスは/dashboardリダイレクトします。

    routes/web.php
    Route::get('/', function () {
        return redirect('/dashboard');
    });
    
    参考:web.php全体
    routes/web.php
    <?php
    
    use Illuminate\Foundation\Application;
    use Illuminate\Support\Facades\Route;
    use Inertia\Inertia;
    
    Route::get('/', function () {
        return redirect('/dashboard');
    });
    
    Route::get('/welcome', function () {
        return Inertia::render('VueWelcome', [
    	'canLogin' => Route::has('login'),
    	'canRegister' => Route::has('register'),
    	'laravelVersion' => Application::VERSION,
    	'phpVersion' => PHP_VERSION,
        ]);
    })->name('welcome');
    
    Route::middleware(['auth:sanctum', 'verified'])->get('/dashboard', function () {
        return Inertia::render('VueDashboard');
    })->name('dashboard');
    
    Route::get('/data-tables', [\App\Http\Controllers\AnyController::class, 'responseDataTables'])->name('data-tables');
    

これで他の画面にもレイアウトが適用できたかと思います。
※Dashboardを表示するにはログインが必要です。

まとめ

Inertia + Vue + Vuetify でレイアウト構成を組んでみました。

VuetifyでSPAを作成するのと同じ感覚で構築できる、かと思いきや、Vuex(状態管理を行うライブラリ)を利用したSPAに慣れすぎていると、UIまわりの状態管理をどのようにすれば良いのか悩みます。(Vuexについてこちら)
例えば、今回のレイアウトのメニュー(Drawer)のオン・オフなどです。

Vuexを使えば簡単に管理できますが、このケースでVuexを使うのは行き過ぎに思えます。もっと軽量で良いのです。

少し調べたら、InertiaにShared dataという仕組みがあり、ページ間でデータを共有できるようです。実際にWelcome画面でも$page.props.userというようにShared dataへのアクセスが行われています。
ただ、この仕組みは、値の参照こそ簡単ですが、値をセットするのが面倒に思えます。

となると、普通にCookieで良いのではないか、と思う次第です。
というわけで、使ったことのないvue-cookiesのことでも調べてみようか、と思い始めた検証でした。

こんなところで。

Discussion