概要
以下、Vue.jsのUIライブラリであるVuetifyの話です。
v-data-table
に描画したいデータをサーバから取得した際、受け取ったデータそのままだと扱いにくいのでなんとかしたいケースがあったので、その時のメモです。
基本的にサーバの返却値を修正してもらうのが諸事情で面倒くさいのでクライアント側でなんとかしたい、という前提での話です。
公式の1番簡単な使い方を基準に考えます。
※items-per-pageとclassは今回の話に不要なので除去しています。
<template>
<v-data-table
:headers="headers"
:items="desserts"
></v-data-table>
</template>
Ajaxで受け取ったデータでdessertsを随時上書きするイメージで、そのデータが扱いにくいという状況です。
ケースに応じて調査してでてきたもの、力技でなんとかした対応を紹介します。
ケース1 チェックボックス(show-select)を使うとき、item-key用の一意のデータがない
<template>
<v-data-table
v-model="selected"
:headers="headers"
:items="desserts"
item-key="name"
show-select
></v-data-table>
</template>
<script>
export default {
data () {
return {
selected: [],
headers: [
{ text: '名前', value: 'name', sortable: false },
{ text: '数値', value: 'value', sortable: false }
],
desserts: [
{
name: 'data1', value: 159,
name: 'data1', value: 200,
name: 'data3', value: 333,
}
]
}
}
}
</script>
※data内で初期化してる部分がサーバからデータを受信したものとして見てください。
チェックボックスをテーブルに表示したい場合、show-select
した上でitem-key
にitemsのいずれかのkeyをセットする必要があります。
このとき、keyには一意の値がくるものを指定する必要があります。
コード1ではitem-key="name"
として一意のkeyとしてnameが指定されていますが、サーバから取得したdessertsはnameの値がdata1で被っています。
この状況は嬉しくありません。
item-key
で指定したkeyの値が同一の場合、同じものと見なされてしまうためこの状況で数値が159か200の行のチェックボックスをクリックすると、クリックしなかったほうの行のチェックボックスまでチェックがついてしまうためです。
ちなみにこれはsingle-select
がどちらの場合でも両方にチェックがつきます。
こうならないためには一意の値をもつkeyを指定したいわけですが、サーバから返却されたデータにDBのPK的な固有の識別番号がないケースでは対応できません。
というわけでこれの対応が以下です。
<template>
<v-data-table
v-model="selected"
:headers="headers"
:items="indexedItems"
item-key="id"
show-select
></v-data-table>
</template>
<script>
//~途中省略~
computed: {
indexedItems () {
return this.desserts.map((item, index) => ({
id: index,
...item
}))
}
}
}
</script>
mapを使ってindexをそのままidとして持たせ、そのidをitem-key
で指定する形ですね。
こちらは公式のFeature Requestからの知見です。スマートですね。
ケース2 検索OKのテーブルで計算された値を使用したい
<template>
<v-data-table
:headers="headers"
:items="items"
:search="search"
></v-data-table>
</template>
<script>
export default {
data () {
return {
search: '',
headers: [
{ text: '名前', value: 'name', sortable: false },
{ text: '容量', value: 'capacity', sortable: false }
],
items: [
{
name: 'data1', value: 10485760,
name: 'data2', value: 20971520,
name: 'data3', value: 31457280,
}
]
}
}
}
</script>
サーバから返却された容量の値がByte単位で、MBにしたいとします。
1024で2回割った値を使いたいわけなので、ケース1のようにcomputedを使ってあげればいいのはお分かりかと思います。
<template>
<v-text-field
v-model="search"
></v-text-field>
<v-data-table
:headers="headers"
:items="mbItems"
:search="search"
></v-data-table>
</template>
<script>
//~途中省略~
data () {
return {
search: '',
headers: [
{ text: '名前', value: 'name', sortable: false },
{ text: '容量', value: 'mb', sortable: false }
],
}
},
computed: {
mbItems () {
return this.items.map((item) => ({
mb: item.capacity / 1024 / 1024,
...item
}))
}
}
}
</script>
computedは算出プロパティなんだからここでやるのが当然だろ、とお思いかもしれません。
ただv-data-table
では以下のようにすることができます。
<template>
<v-text-field
v-model="search"
></v-text-field>
<v-data-table
:headers="headers"
:items="items"
:search="search"
>
<template v-slot:item.capacity="{ item }">
{{ item.capacity / 1024 / 1024}}
</template>
</v-data-table>
</template>
v-data-table
がもつv-slot
のうちitem.<name>
を使って容量のカラムだけtemplate側で計算するわけですね。
これでも表示は同じになるのですが、データの検索ができるテーブルではv-slot
での計算はよろしくありません。
なぜかと言うとtemlate側のv-slot
で計算された値は、v-data-table
がもつsearch
オプションでの検索にひっかかってくれないからです。
コード2の例だとMB単位で10,20,30のデータが表示されるわけですが、検索用のv-text-field
に30といれても3行目がひっかかりません。
searchの値(30)を使ってitemsに対して検索をかけるため、
ちなみに上記の例だと10,20ではそれぞれ1行目,2行目がひっかかります。これは計算前の元からの値に含まれているためです。
このため、v-slot
で描画時に計算するようにして10,20などで検索してみてOK!と軽く判断すると実はちゃんと検索がきいていない、ということになりかねません。
なので、検索可のテーブルに表示するデータを加工する場合はcomputedでやりましょう。(これが言いたかった)
私が最初v-slot
でやっていて実は検索がきかなくて頭を抱えたのは内緒
まとめ
-
show-select
を使うテーブルでは、mapを使ってidを一意のデータとすることができます - データ検索ができるテーブルでは、計算済みデータを表示するときはcomputedでやりましょう