DataTable: Vuetify3 の新コンポーネント (labs) を試す

このページは、下記ページへ移行しました。参考までに、記事の内容はのことしておきます。

- [Vuetify3 の基本](https://zenn.dev/bbled/books/vuetify3_book)

# 内容

DataTable は表形式のデータを表示するコンポーネントです。`v-table` よりも機能が豊富です。 

# 前提

Vuetify のバージョンは 3.4.6 で試しています。バージョンが上がると仕様は変更されるかもしれません。

# 公式

- [DataTable](https://vuetifyjs.com/en/components/date-tables/)

# 基本

データをテーブル形式で表示します。表示する行の数を指定すると、データ数が指定した数より多い時は、自動的にページ送り機能がつきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/6ee57586-fd49-7e44-7b4f-4297492ed33c.png)

```.vue
<template>
  <v-container>
    <p class="text-h3">DataTable</p>
    <v-data-table
      v-model:items-per-page="itemsPerPage"
      :headers="headers"
      :items="pref"
      :items-per-page-options="pages"
      items-per-page-text="表示行数"
      class="elevation-1"
    ></v-data-table>
  </v-container>
</template>

<script>
export default {
  data () {
    return {

      itemsPerPage: 5,
      pages: [
        {value: 5, title: '5'},
        {value: 10, title: '10'},
        {value: 20, title: '20'},
        {value: -1, title: '$vuetify.dataFooter.itemsPerPageAll'}
      ],
      headers: [
        {
          title: '番号',
          align: 'end',
          sortable: false,
          key: 'no',
        },
        { title: '都道府県名', align: 'start', key: 'pref_jp' },
        { title: '英語名', align: 'start', key: 'pref_en' },
        { title: '面積(km2)', align: 'end', key: 'area' },
        { title: '人口(千人)', align: 'end', key: 'population' },
      ],
      pref: [
        {no: 1,pref_jp: "北海道",pref_en: "Hokkaido",area: 83457,population: 5400},
        {no: 2,pref_jp: "青森",pref_en: "Aomori",area: 9645,population: 1321},
        {no: 3,pref_jp: "岩手",pref_en: "Iwate",area: 15279,population: 1284},
        {no: 4,pref_jp: "宮城",pref_en: "Miyagi",area: 6862,population: 2328},
        {no: 5,pref_jp: "秋田",pref_en: "Akita",area: 11636,population: 1037},
        {no: 6,pref_jp: "山形",pref_en: "Yamagata",area: 6652,population: 1131},
        {no: 7,pref_jp: "福島",pref_en: "Fukushima",area: 13783,population: 1935},
        {no: 8,pref_jp: "茨城",pref_en: "Ibaraki",area: 6096,population: 2919},
        {no: 9,pref_jp: "栃木",pref_en: "Tochigi",area: 6408,population: 1980},
        {no: 10,pref_jp: "群馬",pref_en: "Gumma",area: 6362,population: 1976},
        {no: 11,pref_jp: "埼玉",pref_en: "Saitama",area: 3768,population: 7239},
        {no: 12,pref_jp: "千葉",pref_en: "Chiba",area: 5082,population: 6197},
        {no: 13,pref_jp: "東京",pref_en: "Tokyo",area: 2104,population: 13390},
        {no: 14,pref_jp: "神奈川",pref_en: "Kanagawa",area: 2416,population: 9096},
        {no: 15,pref_jp: "新潟",pref_en: "Niigata",area: 10364,population: 2313},
        {no: 16,pref_jp: "富山",pref_en: "Toyama",area: 2046,population: 1070},
        {no: 17,pref_jp: "石川",pref_en: "Ishikawa",area: 4186,population: 1156},
        {no: 18,pref_jp: "福井",pref_en: "Fukui",area: 4190,population: 790},
        {no: 19,pref_jp: "山梨",pref_en: "Yamanashi",area: 4201,population: 841},
        {no: 20,pref_jp: "長野",pref_en: "Nagano",area: 13105,population: 2109},
        {no: 21,pref_jp: "岐阜",pref_en: "Gifu",area: 9768,population: 2041},
        {no: 22,pref_jp: "静岡",pref_en: "Shizuoka",area: 7255,population: 3705},
        {no: 23,pref_jp: "愛知",pref_en: "Aichi",area: 5116,population: 7455},
        {no: 24,pref_jp: "三重",pref_en: "Mie",area: 5762,population: 1825},
        {no: 25,pref_jp: "滋賀",pref_en: "Shiga",area: 3767,population: 1416},
        {no: 26,pref_jp: "京都",pref_en: "Kyoto",area: 4613,population: 2610},
        {no: 27,pref_jp: "大阪",pref_en: "Osaka",area: 1901,population: 8836},
        {no: 28,pref_jp: "兵庫",pref_en: "Hyogo",area: 8396,population: 5541},
        {no: 29,pref_jp: "奈良",pref_en: "Nara",area: 3691,population: 1376},
        {no: 30,pref_jp: "和歌山",pref_en: "Wakayama",area: 4726,population: 971},
        {no: 31,pref_jp: "鳥取",pref_en: "Tottori",area: 3507,population: 574},
        {no: 32,pref_jp: "島根",pref_en: "Shimane",area: 6708,population: 697},
        {no: 33,pref_jp: "岡山",pref_en: "Okayama",area: 7010,population: 1924},
        {no: 34,pref_jp: "広島",pref_en: "Hiroshima",area: 8480,population: 2833},
        {no: 35,pref_jp: "山口",pref_en: "Yamaguchi",area: 6114,population: 1408},
        {no: 36,pref_jp: "徳島",pref_en: "Tokushima",area: 4147,population: 764},
        {no: 37,pref_jp: "香川",pref_en: "Kagawa",area: 1862,population: 981},
        {no: 38,pref_jp: "愛媛",pref_en: "Ehime",area: 5679,population: 1395},
        {no: 39,pref_jp: "高知",pref_en: "Kochi",area: 7105,population: 738},
        {no: 40,pref_jp: "福岡",pref_en: "Fukuoka",area: 4847,population: 5091},
        {no: 41,pref_jp: "佐賀",pref_en: "Saga",area: 2440,population: 835},
        {no: 42,pref_jp: "長崎",pref_en: "Nagasaki",area: 4106,population: 1386},
        {no: 43,pref_jp: "熊本",pref_en: "Kumamoto",area: 7268,population: 1794},
        {no: 44,pref_jp: "大分",pref_en: "Oita",area: 5100,population: 1171},
        {no: 45,pref_jp: "宮崎",pref_en: "Miyazaki",area: 6795,population: 1114},
        {no: 46,pref_jp: "鹿児島",pref_en: "Kagoshima",area: 9045,population: 1668},
        {no: 47,pref_jp: "沖縄",pref_en: "Okinawa",area: 2277,population: 1421}
      ],
    }
  }
}
</script>
```

- `v-model:items-per-page`: １ページ内に表示するデータの行数を指定します。
- `headers`: テーブルのヘッダ（項目名など）のデータを指定します。
- `item`: データ本体を指定します。`headers` と項目数が一致しないと、表示が崩れます。
- `items-per-page-text`: 表示する行数を指定するメニューの左側に表示される文字列です。デフォルトでは `item per pages` になります。
- `items-per-page-options`: 表示する行数を選択するメニューの選択肢のリストを指定します。デフォルトでは 10, 20, 50, 100, ALL という選択肢のメニューになります。

`class="elevation-1"`を指定しないと、テーブルと背景の境（枠）がなくなります。`class="border"`　でも枠はつきます。

# 応用

## 項目ソート

DataTable はデフォルトで項目ごとのソート機能がついています。テーブルの最上段の項目名を押すことでソートが実行されます。ソートは、項目名を押すたびに、昇順、降順、先頭の項目の番号順、という順序でソート順序が入れ替わります。

面積で降順ソートすると下図のようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/5bfb871d-f091-1c8a-b172-17ae81d69948.png)

`multi-sort` prop を使うと、複数項目によるソートが可能になります。ソートを禁止したい場合は、近視したいレツの `header` に `sortable: false` を追加します。上の例では `key: no` の列に対するソートを禁止しています。

## 項目の上下の空白を指定: density

`density` prop を入れると各行の上下のスペースの空き具合を指定できます。`density="compact"` とすると、かなり詰まった状態になります。`comfortable`　だとやや詰まりになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/ae33c610-b90f-1ac6-117b-f66d7db42a48.png)


```.vue
<template>
  <v-container>
    <v-data-table
      density="compact"
      v-model:items-per-page="itemsPerPage"
      :headers="headers"
      :items="pref"
      :items-per-page-options="pages"
      items-per-page-text="表示行数"
      class="elevation-1"
    ></v-data-table>
  </v-container>
</template>
```

## 選択: show-select

`show-select` prop を使うと、行を選択するチェックボックスがつきます。デフォルトでは複数選択が許可されています。`select-strategy="single"` とすると複数選択を禁止できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/2de87f2b-6108-a09c-34b6-020ed65062ef.png)

```vue
<template>
  <v-container>
    <v-data-table
      density="compact"
      show-select
      v-model="selected"
      v-model:items-per-page="itemsPerPage"
      :headers="headers"
      :items="pref"
      item-value="no"
      :items-per-page-options="pages"
      items-per-page-text="表示行数"
      class="elevation-1"
    ></v-data-table>
    <div>
      {{ selected }}
    </div>
  </v-container>
</template>
```

`show-select` を指定するときは、同時に　`item-value` でデータを区別するためにつかう key を指定します。これを指定しないと、どのチェックボックスを選択しても、全部のデータが選択されます。

選択したデータは `v-model` で指定した変数に配列として入ります。配列に入るのは、選択した行の　`item-value` で指定した列の値です。

## 行にボタンを追加する: v-slot:item

`v-slot:item` を使うことで、各行の中にボタンやアイコンなどを表示する列を追加できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/9d6964e6-ffc4-3a52-6a34-9d4a5d1ea089.png)


```vue
<template>
  <v-container>
    <v-data-table
      density="compact"
      v-model:items-per-page="itemsPerPage"
      :headers="headers"
      :items="pref"
      item-value="no"
      :items-per-page-options="pages"
      items-per-page-text="表示行数"
      class="elevation-1"

    >
      <template v-slot:item.actions="{ item }">
        <v-icon
          size="small"
          @click="deleteItem(item.raw)"
        >
          mdi-delete
        </v-icon>
      </template>

    </v-data-table>
  </v-container>
</template>

<script>
export default {
  methods: {
    deleteItem(item){
      alert( item.no + "番のデータを削除します");
    }
  },
  data () {
    return {

      itemsPerPage: 5,
      selected: [],
      pages: [
        {value: 5, title: '5'},
        {value: 10, title: '10'},
        {value: 20, title: '20'},
        {value: -1, title: '$vuetify.dataFooter.itemsPerPageAll'}
      ],
      headers: [
        { title: '番号', align: 'end', sortable: false, key: 'no' },
        { title: '都道府県名', align: 'start', key: 'pref_jp' },
        { title: '英語名', align: 'start', key: 'pref_en' },
        { title: '面積(km2)', align: 'end', key: 'area' },
        { title: '人口(千人)', align: 'end', key: 'population' },
        { title: '操作', align: 'end', key: 'actions' },
      ],
      pref: [ // 省略
      ],
    }
  }
}
</script>
```

上の例では　`v-slot:item.actions` としていますが、この場合は　`headers` に　`key: 'actions'` という項目を持つデータ（項目）を追加しておく必要があります。`headers` の中に　　`v-slot:item.xxxx` の `xxxx` と一致する `key` をもつ項目がない場合は、`v-slot` で指定したボタンなどは表示されません。

## 行に他の項目から計算した項目を追加: v-slot:item

ボタンを追加したときと同じ方法で、行に表示する項目を追加できます。下の図では人口密度の項目を都度計算して表示させています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/818b703c-862f-bb66-6129-2a24be9fdda2.png)


```vue
<template>
  <v-container>
    <v-data-table
      density="compact"
      v-model:items-per-page="itemsPerPage"
      :headers="headers"
      :items="pref"
      item-value="no"
      :items-per-page-options="pages"
      items-per-page-text="表示行数"
      class="elevation-1"
    >
      <template v-slot:item.density="{ item }">
          {{ (item.columns.population*1000 / item.columns.area).toFixed(2) }}
      </template>
    </v-data-table>

  </v-container>
</template>

<script>
export default {
  methods: {
    deleteItem(item){
      alert( item.no + "番のデータを削除します");
    }
  },
  data () {
    return {
      search: ``,
      itemsPerPage: 5,
      selected: [],
      pages: [
        {value: 5, title: '5'},
        {value: 10, title: '10'},
        {value: 20, title: '20'},
        {value: -1, title: '$vuetify.dataFooter.itemsPerPageAll'}
      ],
      headers: [
        { title: '番号', align: 'end', sortable: false, key: 'no' },
        { title: '都道府県名', align: 'start', key: 'pref_jp' },
        { title: '英語名', align: 'start', key: 'pref_en' },
        { title: '面積(km2)', align: 'end', key: 'area' },
        { title: '人口(千人)', align: 'end', key: 'population' },
        { title: '人口密度', align: 'end', key: 'density', sortable: false },
      ],
pref: [ // 省略
      ],
    }
  }
}
</script>
```

この方法で追加した項目は、ソート機能が使えません（自力でソート機能を実装しない限り）。上の例では `sortable: false` を指定して、人口密度でのソートを禁止しています。


## 検索

`search` prop で文字列を指定すると、その文字列を含むデータだけを表示します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/2cc62e14-2370-3eff-2cb9-0c20fd9c2748.png)

```vue
<template>
  <v-container>
    <v-text-field
        v-model="search"
        append-icon="mdi-magnify"
        label="検索"
        single-line
        hide-details
      ></v-text-field>

    <v-data-table
      density="compact"
      v-model:items-per-page="itemsPerPage"
      :headers="headers"
      :items="pref"
      item-value="no"
      :search="search"
      :items-per-page-options="pages"
      items-per-page-text="表示行数"
      class="elevation-1"
    >
    </v-data-table>
  </v-container>
</template>
```

デフォルトでは、すべての列のデータを対象にフィルタ（検索）します。特定の列のデータを使ってフィルタしたいときは、フィルタする関数を自力で書く必要があります。詳しくは下記を参照してください。

- [Data table - Filtering
](https://vuetifyjs.com/en/components/data-tables/filtering/)

## サーバのデータを順次読み込む

サーバにあるデータの一部だけ表示しておいて、ページをめくっていったりスクロールさせていくと、残りが読み込まれて表示される、みたいなこともできます。

サーバがないとサンプルがかけないので、ここでは例示はしません。詳しくは下記を参照してください。

- [Data table - Server side tables](https://vuetifyjs.com/en/components/data-tables/server-side-tables/)