ITプロパートナーズの新卒採用サービス「intee」のエンジニアの五藤です。
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コンポーネントてどんなの?
( element-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のコンポーネントを配置することも出来ます。
[いろんなタグを配置してみるとこんな感じ]
こんな感じで、画像やアイコン、タグ、ボタンなどを各カラムにポチポチ配置して、可読性の高い画面をデザイナー抜きで簡単に作ることが出来ちゃいます。 もちろんボタンに導線を作ることもできるので、
- ボタンを押すとチェックリストにチェックが付く
- ボタンを押すとモーダルを表示してデータ編集ができる
といった処理を組むことも簡単です。 ちなみにモーダルも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.[カラム名] で各レコードの情報が参照できる
です。このやり方を使うと、カラムの中で自由にレコード情報を表示ことができるため、 先程あげたような
- 画像を貼り付けたり
- カラム内で改行して複数の情報を表示したり
- アクションボタンを配置したり
とかなり応用が効くようになります。
テーブルの行番号を取得したい。
行番号は 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> をご活用ください!