サービスの管理画面で Vue + element.ui を活用する(table編)

ITプロパートナーズの新卒採用サービス「intee」のエンジニアの五藤です。

intee.jp

inteeの技術スタックは、バックエンドがPHP(CakePHP3)、フロントエンドがVue.jsを採用しています。 ユーザーが見る画面はもちろんの事、弊社スタッフが利用する管理画面に関しても、新しく実装する画面に関してはほとんどVueを使って管理画面を構築しています。

「何で管理画面のフロントエンドをリッチにする必要が?」

って思われるかもしれませんが、管理オペレーションが複雑になってくると、

  • 一度に複数のユーザーに対して操作したり、複数のレコードを追加する際にいちいち画面遷移したくない
  • 複雑なリレーションのデータ登録を、モーダルなどを用いて効率的にやりたい

などなど、オペレーション最適化の面で、リッチなフロントエンドを提供するメリットは意外と多く、営業サイドから 「この画面をVueを使ってサクサク使えるようにしてほしい」 という要望を受けることもしばしばだったります。

そんな中で、UI/コンポーネントライブラリとして愛用しているのが、Element です!

Element - The world's most popular Vue UI framework

各種フォーム部品やモーダルやアラート、簡単なグラフ表示などの、リッチな画面を作るのに必要なコンポーネントが揃っているニクいやつです。しかも、Vueのコンポーネントとして実装されているため、

<el-tag type="success">Elementに用意されてるめっちゃきれいなタグUI</el-tag>

という感じに、HTMLライクな書き方で簡単にコンポーネントを配置できるのも嬉しいところです。

今回は、その中でも、管理画面の画面設計の中核を占める <el-table> の使い方や、実装上のポイントについてまとめます!

tableコンポーネントてどんなの?

スクリーンショット 2018-11-28 10.07.36.pngelement-ui公式 より引用 )

簡単に言うと、管理画面でよくある、「データ一覧をテーブル形式で表示する」事ができるコンポーネントです。 これだけ書くと凄くシンプルなので、elementのtableのすごい所をガンガン紹介します!

el-tableを使うと捗るポイント

データ投入が簡単で、いい感じに表示してくれる

データ設定の方法は簡単で、配列形式のデータをVueのdata属性に格納して、<el-table> のプロパティとして指定するだけ。

(データ側)

data: {
          tableData: [{
            date: '2016-05-03',
            name: 'Tom',
            address: 'No. 189, Grove St, Los Angeles'
          }, {
            date: '2016-05-02',
            name: 'Tom',
            address: 'No. 189, Grove St, Los Angeles'
          }, {
            date: '2016-05-04',
            name: 'Tom',
            address: 'No. 189, Grove St, Los Angeles'
          }, {
            date: '2016-05-01',
            name: 'Tom',
            address: 'No. 189, Grove St, Los Angeles'
          }]
        }

(コンポーネント)

<el-table :data="tableData">

これだけで、tableDataプロパティに格納した配列データを、<el-table>内で使用することが出来ますし、el-dataの値を切り替えれば、リアルタイムでテーブル上の値も更新されます。

また、dataに格納する値は、

<el-table :data="tableData">

      <!-- 日付データを表示するカラム -->     
      <el-table-column
        prop="date"
        label="Date"
        width="180">
      </el-table-column>

      <!-- 名前データを表示するカラム -->
      <el-table-column
        prop="name"
        label="Name"
        width="180">
      </el-table-column>

<el-table>

という感じで、table側で自由に各カラムを抽出、表示できます。 dataに設定された全カラムが勝手に出力されるのではなく、コンポーネント側で出したい情報を自由にピックできる のがポイントで、実際の想定シーンとしては、

  • dataプロパティには、サーバーサイド側の参照APIをまるっと格納させ、<el-table>側で、必要なデータを必要な形でレイアウトする

というやり方で、 汎用的なAjaxの戻り値を、そのまま<el-table>にぶちこめる のが実装上は凄く楽だったりします。

各カラムの表示内容が、タグベースで柔軟に設定できる

各カラムに表示する値は、HTML形式で動的に設定可能で、更に言うと、他のelementのコンポーネントを配置することも出来ます。

[いろんなタグを配置してみるとこんな感じ]

f:id:itpro_goto:20181203220644p:plain
いろんなタグを配置してみた

こんな感じで、画像やアイコン、タグ、ボタンなどを各カラムにポチポチ配置して、可読性の高い画面をデザイナー抜きで簡単に作ることが出来ちゃいます。 もちろんボタンに導線を作ることもできるので、

  • ボタンを押すとチェックリストにチェックが付く
  • ボタンを押すとモーダルを表示してデータ編集ができる

といった処理を組むことも簡単です。 ちなみにモーダルもelementで用意されています。

http://element.eleme.io/#/en-US/component/dialog

いたれりつくせりですね。

・・・どうですか?管理画面に<el-table>使いたくなりませんか?

実際に使う上でのtips

<el-table>の素晴らしさがわかったところで、実際に作っていく上でのポイントをいくつか書いていきます。

基本的なデータフロー

前項でも述べましたが、<el-table>に格納するデータは、Ajaxの戻り値をそのまま格納するのがわかりやすいので、 「データを取得する共通処理」を一つの共通メソッドにまとめておき、表示・更新したいタイミングで呼び出す のが基本的な実装パターンになります。以下はデータ読み込み処理のコード例です。

// レコード取得を実施
getModels:function(){
  var that = this
  this.isLoading = true // (1)ローディング表示をON
  $.ajax({ // jQuery ajax使ってますが他のライブラリでも対して変わらないはず
    url:this.apiUrl,
    type:'GET',
    data:this.condition // (2)GETパラメータをプロパティ
  }).done(function(data){
    // API戻り値をtableDataに格納
    that.tableData = data.models 
    that.isLoading = false
  })
},

いくつかポイントを上げると、

  • APIを呼び出すタイミングでローディング用のプロパティをONにし、読み込み後にOFFにすることでローディングを実装
    • ローディングステータスは<el-table v-loading="loading"> という感じで、v-loadingディレクティブにbindすることで、プロパティと同期できます。
  • 検索クエリとページネーションはプロパティにまとめておくと、そのまま渡せる(検索ボックス側でthis.conditionを編集する作りにすると、検索側と読み込み側が疎結合になるので扱いやすい

という感じです。

データ表示の基本2タイプ

表示カラムは<el-table-column> を使うといいましたが、2つの記法があります。

el-table-column::prop で簡単カラム指定

<el-table-column prop="full_name" label="氏名">
</el-table-column>

propプロパティに直接Objectのキーを指定すると、文字列としてデータが表示されます。

template slot-scope="scope" で細かく指定

もう少しリッチに各カラムのデータを書きたい場合は、このように書きます。

<el-table-column label="タグ一覧">
  <template slot-scope="scope">
       <el-tag v-for="tag in scope.row.tags" :key="tag.id" type="success">{{tag.name}}<el-tag>    
  </temlate>    
</el-table-column>

ポイントとしては

  • <template slot-scope="scope"> で囲んだ中に、そのカラムで表示したいHTMLを配置
  • scope.row.[カラム名] で各レコードの情報が参照できる

です。このやり方を使うと、カラムの中で自由にレコード情報を表示ことができるため、 先程あげたような

f:id:itpro_goto:20181203220644p:plain

  • 画像を貼り付けたり
  • カラム内で改行して複数の情報を表示したり
  • アクションボタンを配置したり

とかなり応用が効くようになります。

テーブルの行番号を取得したい。

行番号は scope.$index で取得できます。

<el-table-column width="50" fixed>
  <template slot-scope="scope" label="No">
    {{ scope.$index}}
  </template>
</el-table-column>

発展パターンとして、ページネーションを実装している場合のレコード番号は、

<el-table-column width="50" fixed>
  <template slot-scope="scope" label="No">
    {{ getRecordSequence(scope.$index)}}
  </template>
</el-table-column>

というふうに外部メソッドにscope.$index を投げて、

getRecordSequence(index){
  if(!this.paginate.page || !this.paginate.perPage){
    return index + 1
  }
  return (this.paginate.page - 1) * this.paginate.perPage + index + 1
},

こんな感じで、ページネーションの値と組み合わせて算出したりもします。

ページネーション

<el-table> はページングのための特別な仕組みを持たないため、

  • dataプロパティに「ページ情報」と「総ページ数」を用意しておき、API更新のタイミングで同期する
  • 画面側のページング表示、操作は<el-paginate> コンポーネントを別途配置して、こちらもAPI更新と紐付ける

というやり方で実装します。

データ構造

data:{
  condition:{
    page: 1, // 現在のページ数(conditionに含めることでクエリパラメータとして渡される
  },
  pageCount: 1, //ページ数を取得
},
methods: {
  getModels:function(){
    var that = this
    $.ajax({ // jQuery ajax使ってますが他のライブラリでも対して変わらないはず
      url:this.apiUrl,
      type:'GET',
      data:this.condition 
  }).done(function(data){
    that.tableData = data.models 
    that.isLoading = false
    that.pageCount = data.paginate.pageCount //総ページ情報を更新
    that.paginate = data.paginate //現在ページ情報を更新
  })
},
            }

ページネーション部品

<el-pagination
  @current-change = "getModels"
  :current-page.sync="condition.page"
  :page-count="pageCount">
</el-pagination>

通化のパターン

いろいろな画面で<el-table>を使うようになってくると、必要な処理を共通化したい!と思う人もいるかも知れません。結論からいうと、共通化はcomponentよりもmixinで行うのが個人的にはおすすめです。 mixin側で、

  • APIとの通信処理
  • 取得したレコード一覧や検索条件、ページネーションを保持するためのdataオブジェクト

を共通化して持っておくと、このパターンのテーブル表示を量産する上では役に立ちます。 こんな感じでしょうか。

// 管理画面の汎用テーブルプラグイン

var dataTableMixin = {
    data:{
        getUrl: '/get', // データ取得用のAPI。必要に応じて実装側でオーバーライドする。
        models: [], // 取得されたレコード配列を保持
        // 検索条件を保持。値を変更することで、レコード取得APIにリクエストクエリとして渡される
        condition:{
            page: 1, // ページ情報も検索条件の一つとして保持する
        },
        pageCount: 1, // APIから返却された総ページ数の値を保持
        isLoading: false // ローディング状態を保持
    },
    mounted:function(){
        this.getModels()
    },
    methods:{
        // ページネーション切り替え
        changeCurrentPage: function(page){
           this.condition.page = page
           this.getModels()
        },

        // レコード取得を実施
        getModels:function(){
            var that = this
            this.isLoading = true
            $.ajax({ // jQuery ajax使ってますが他のライブラリでも対して変わらないはず
                url:this.getUrl,
                type:'GET',
                data:this.condition
            }).done(function(data){
                that.models = data.models
                that.isLoading = false
                // ページネーション関連情報をセット
                that.pageCount = data.paginate.pageCount
                that.paginate = data.paginate
                that.afterGet()
            })
        },
        // レコード取得後に独自処理を追加するためのフックメソッド
        afterGet:function(){
            return //OVERRIDE ME
        },
    }
}

いかがでしたでしょうか?ぜひ管理画面もリッチな <el-table> をご活用ください!