ForgeVision Engineer Blog

フォージビジョン エンジニア ブログ

Laravel+Vue.jsでかんたんにページング付きの検索テーブルを実装する

ソリューション技術部の近藤(id:kazumeat)です。主にWebアプリ開発をしています。

Vue.jsで管理システムを構築しているときに、さくっと検索結果一覧画面が作れないかなと思って 色々コンポーネントを探していた時に見つけたVuetable-2の紹介です。

以下の点を条件として探しました。

  • デザイン(css)を独自に当てることができること
  • ページングができること
  • ページングした時に、サーバにアクセスし、対象のデータだけ取得すること

Vuetable-2

特定のデザインで構成されているわけではないため、すでにデザインが決まっているページでも 簡単に導入することができます。 もちろん、各種CSSフレームワークとの連携も可能です。 Vuetable-2の作者の方が公開されているSandboxではsemantic-uiを使用しています。 下記サンプルのような感じで、結構色々と機能があり、カスタマイズもできます。 タイトルに[WIP]と付いていますが、一通り動作しています。

Vuetable-2: WIP - CodeSandbox

Vuetable-2の日本語の紹介記事はこちら。日本語記事が全然ないので貴重です。 こちらの記事ではLaravelでの実装部分も記載されています。

LaravelとVuetable-2でページネーション付きテーブルを使ってみる - Qiita

Vue-tables-2

ちなみに、名前が激似の別コンポーネントが存在しますが、こちらはVuetable-2とは全くの関係ないのでご注意を。

matfish2/vue-tables-2: Vue.js 2 grid components

下記紹介記事にもある通り、全件データを取得した後は、クライアントで完結する形のテーブル表示コンポーネントです。 そこまでデータ数が多くない場合は使ってみてもいいかもしれないですね。

Vuejsでテーブル表示 vue-tables-2 - Qiita

検索結果一覧を実装する

いわゆる、検索条件を入力し、検索ボタンをクリックすると、検索結果がページング付きのテーブルで表示される、という画面を作ります。 こんな画面です。

解説内容としては、Vuetable-2の紹介のみとなります。

f:id:kazumeat:20181010174454j:plain

  • バックエンドはLaravel
    • Laravelである必要は全くありませんが、親和性が高いです。
    • 検索結果データを取得するAPIを実装します
  • フロントエンドはVue.js

Dockerで開発環境を作る

まずはVue.jsで開発するための環境を作ります。

Dockerfile

FROM node:10.11.0-alpine

RUN apk update && \
    apk add git && \
    yarn global add @vue/cli
ENV HOST 0.0.0.0

WORKDIR /app

docker-compose.yml

version: '3'
services:
  vuetable-2-demo:
    tty: true
    privileged: true
    volumes:
      - .:/app
    ports:
      - 8080:8080
    container_name: demo

各種バージョン

/app # node -v
v10.11.0
/app # yarn -v
1.9.4
/app # vue -V
3.0.5

vue.jsのアプリを新規に作成します

/app # vue create vuetable-demo
<設定はデフォルト>
/app # cd vuetable-demo
/app # yarn install
/app # yarn serve

http://localhost:8080 にアクセスしてVue.jsのホーム画面表示を確認

vuetable-2をインストール

  • インストール
/app/vuetable-demo # yarn install vuetable-2@next
  • vuetable-2は、現時点で最新の下記バージョンを使用します。
info Direct dependencies
└─ vuetable-2@2.0.0-beta.3

Laravelでデータ取得API作成

Laravelでの具体的なAPI実装方法は割愛しますが、Vuetable-2はLaravelのPaginateメソッドが吐き出すJson形式をそのまま使うことができます。

参考:https://readouble.com/laravel/5.5/ja/pagination.html

{
   "total": 50,
   "per_page": 15,
   "current_page": 1,
   "last_page": 4,
   "first_page_url": "http://laravel.app?page=1",
   "last_page_url": "http://laravel.app?page=4",
   "next_page_url": "http://laravel.app?page=2",
   "prev_page_url": null,
   "path": "http://laravel.app",
   "from": 1,
   "to": 15,
   "data":[
        {
            // 結果のオブジェクト
        },
        {
            // 結果のオブジェクト
        }
   ]
}

こちらのJsonをカスタマイズなしに、Vuetable-2に食わせることができます。 もちろんLaravelを使わずとも、このJson形式でなくとも、Json形式はカスタマイズして食わせることもできますのでご安心を。

vuetable-2/Data-Transformation.md at master · ratiw/vuetable-2

Vuetable-2を使ってみる

Vue.jsでの実装に戻ります。

Vuetable-2コンポーネントを設置

各種プロパティの説明はこちらにあります。

https://ratiw.github.io/vuetable-2/#/Vuetable-Properties

List.vue

  • 検索条件入力部分のコードは割愛します
  • Vuetable-2コンポーネントを設置します
  • 以下は今回私が使用したプロパティになります
<template>
  <div>
    <vuetable ref="vuetable"
      :api-url="https://hogehoge/api"
      :http-fetch="myFetch"
      :fields="fields"
      :append-params="moreParams"
      pagination-path=""
      @vuetable:pagination-data="onPaginationData"
    >
    </vuetable>
 
    <vuetable-pagination ref="pagination"
      @vuetable-pagination:change-page="onChangePage"
    >
    </vuetable-pagination>
  </div>
</template>

<script>
import axios from 'axios'
import {Vuetable, VuetablePagination} from 'vuetable-2'
import FieldsDef from "~/components/FieldsDef.js";

export default {
  components: {
    Vuetable,
    VuetablePagination,
  },
  data () {
    return {
      fields: FieldsDef,  // テーブルの項目は別ファイルに定義
      moreParams: {sort: 'created_at', order: 'desc'}  //後述
    }
  },
  methods: {
    myFetch (apiUrl, httpOptions) {
      return this.$axios.get(apiUrl, httpOptions);
    },
    onSearch () {
      this.$nextTick(function() {
        this.$refs.vuetable.refresh();  //:http-fetchに設定されたmethod(myFetch)が呼ばれる(後述)
      });
    },
    onPaginationData (paginationData) {
      this.$refs.pagination.setPaginationData(paginationData);
    },
    onChangePage (page) {
      this.$refs.vuetable.changePage(page);
    }
  }
}
</script>

components/FieldsDef.js

  • テーブルに必要な列を定義しているファイルです
  • hiddenフィールド(visible: false)を追加したり、フォーマットしたり、ボタンを設置するための列を追加したりしてます
export default [
  {
    name: 'id',
    visible: false
  },
  {
    name: 'number',
    title: '番号'
  },
  {
    name: 'amount',
    title: '金額',
    sortField: 'amount',
    formatter (value) {
      if(value) {
        return value.toLocaleString()+'円';
      }
    },
  },
  {
    name: 'date',
    title: '日時',
    sortField: 'date',
    formatter (value) {
      if(value) {
        return moment(value).format("YYYY/MM/DD HH:mm");
      }
    },
  },
  {
    name: 'status',
    title: 'ステータス',
    sortField: 'status'
  },
  {
    name: 'canceled_at',
    title: '取消日時',
    sortField: 'canceled_at',
    formatter (value) {
      if(value) {
        return moment(value).format("YYYY/MM/DD HH:mm");[f:id:kazumeat:20181010174454j:plain]
      }
    },
  },
  {
    name: 'actions',   //編集ボタンなどを表示する列
    title: ''
  }
]

Vuetable-2プロパティ

以下、サンプルでは使用されていなかったプロパティを紹介します。

http-fetch

今回実際にアクセスするAPIには、トークンの指定が必要でした。 そのため、Vuetable-2でAPIにアクセスする前にひと手間必要になります。 http-fetchにはひと手間の処理を追加するためのメソッドを指定します。(myFetch) そこで、アクセスヘッダーにトークンをセットします。

  methods: {
    myFetch (apiUrl, httpOptions) {
      // APIリクエスト時にトークンを付与する
      this.$axios.onRequest(config => {
        config.headers.common['Content-Type'] = 'application/json';
        if (this.$store.state.token) {
          config.headers.common['Authorization'] = 'Bearer ' + this.$store.state.token;
        }
      });
      return this.$axios.get(apiUrl, httpOptions);
    },
    onSearch () {
      this.$nextTick(function() {
        this.$refs.vuetable.refresh();  //:http-fetchに設定されたmethod(myFetch)が呼ばれる
      });
    },
  },

APIを呼ぶトリガーとしては検索ボタンを用意し、ボタンがクリックされるとonSearchメソッドが呼ばれます。

append-params

今回は検索条件もAPIパラメータに付与したかったので、こちらもひと手間を加えるために append-paramsでデータを追加しています。

  methods: {
    onSearch () {
      this.moreParams = {sort: 'created_at', order: 'desc', status: 'hoge'};  // <- 動的に検索条件などを付与する
      this.$nextTick(function() {
        this.$refs.vuetable.refresh();
      });
    }
  },

ページングコンポーネント、表示上のバグ

vuetable-2@2.0.0-beta.3 には、ページングコンポーネント部分にバグがあります。 例えば、ページは10ページまでしかないのに、11、12ページが見た目上できてしまうバグです。クリックしても何も起こらず、その他の機能については問題なく動作するため、影響的には低いです。すでにissueが上がっていますが、まだ取り込まれていないのでご注意ください。

https://github.com/ratiw/vuetable-2/pull/515

公式チュートリアル

Vuetable-2の作者の方が、丁寧にチュートリアルのページを用意してくれています(^^) https://github.com/ratiw/vuetable-2-tutorial/wiki

また、作者のratiwさんはめちゃめちゃ親切な方で、質問に対してはとても丁寧に返答を頂けます。困ったことがあったら質問してみましょう!