🐉

Vuetify3 VDataTableの全選択チェックボックスを任意の列で利用する方法

はじめに

まだまだVuetify3の情報が少ない!
(Vuetify2からモノは良くなっていっていると思うのだけれど…)
というわけで、前回に引き続き、今回もVuetify3の記事です。
今回はVuetify3のVDataTableでチェックボックスを利用する方法として、公式の実装例で紹介されているものについて説明したあとに、公式の実装例で紹介されていない全選択チェックボックスを任意の列で利用する方法を紹介したいと思います。

全選択チェックボックス付きVDataTableの実装

まずは全選択チェックボックス付きのVDataTableを公式で紹介されているとおり、v-data-tableタグを差し込み、以下のように実装してみます。

App.vue
<script lang="ts">
  export default {
    data () {
      return {
        selected: [],
        items: [
          {
            name: '🍎 Apple',
            location: 'Washington',
            height: '0.1',
            base: '0.07',
            volume: '0.0001',
          },
          {
            name: '🍌 Banana',
            location: 'Ecuador',
            height: '0.2',
            base: '0.05',
            volume: '0.0002',
          },
          {
            name: '🍇 Grapes',
            location: 'Italy',
            height: '0.02',
            base: '0.02',
            volume: '0.00001',
          },
          {
            name: '🍉 Watermelon',
            location: 'China',
            height: '0.4',
            base: '0.3',
            volume: '0.03',
          },
          {
            name: '🍍 Pineapple',
            location: 'Thailand',
            height: '0.3',
            base: '0.2',
            volume: '0.005',
          },
          {
            name: '🍒 Cherries',
            location: 'Turkey',
            height: '0.02',
            base: '0.02',
            volume: '0.00001',
          },
          {
            name: '🥭 Mango',
            location: 'India',
            height: '0.15',
            base: '0.1',
            volume: '0.0005',
          },
          {
            name: '🍓 Strawberry',
            location: 'USA',
            height: '0.03',
            base: '0.03',
            volume: '0.00002',
          },
          {
            name: '🍑 Peach',
            location: 'China',
            height: '0.09',
            base: '0.08',
            volume: '0.0004',
          },
          {
            name: '🥝 Kiwi',
            location: 'New Zealand',
            height: '0.05',
            base: '0.05',
            volume: '0.0001',
          },
        ],
      }
    },
  }
</script>

<template>
  <v-app>
    <v-data-table
      v-model="selected"
      :items="items"
      item-value="name"
      show-select
    ></v-data-table>
  </v-app>
</template>

ながっ!
一見すると長いですが、よく見るとitemsプロパティで指定している配列が長いだけです。この配列は、ほとんどの場合、サーバーから取得することになるので、実際のソースコードはずっと短くなります。
ここでのポイントはVDataTableのPropsにあるshow-selectです。show-selectを指定することで、以下のような全選択チェックボックスと行ごとのチェックボックスを先頭の列に付けることができます。
全選択チェックボックス付きデータテーブル
デフォルトでは全選択チェックボックスで選択できるアイテムは現在のページのみですが、VDataTableのPropsにあるselect-strategyで'all'を指定すれば全選択チェックボックスで全ページのアイテムを選択できるように変更することもできます。

slotを利用したチェックボックス付きVDataTableの実装

公式で紹介されているチェックボックス付きVDataTableの実装方法は他にもあります。
それがVDataTableのslotを利用した方法です。VDataTableのslotを利用してVDataTableに任意の列に行ごとのチェックボックスを付ける場合は、公式で紹介されているとおり、以下のように実装します。

App.vue
<script lang="ts">
  export default {
    data: () => ({
      consoles: [
        {
          name: 'PlayStation 5',
          manufacturer: 'Sony',
          year: 2020,
          sales: '10M',
          exclusive: true,
        },
        {
          name: 'Xbox Series X',
          manufacturer: 'Microsoft',
          year: 2020,
          sales: '6.5M',
          exclusive: false,
        },
        {
          name: 'Nintendo Switch',
          manufacturer: 'Nintendo',
          year: 2017,
          sales: '89M',
          exclusive: true,
        },
        {
          name: 'PlayStation 4',
          manufacturer: 'Sony',
          year: 2013,
          sales: '116M',
          exclusive: true,
        },
        {
          name: 'Xbox One',
          manufacturer: 'Microsoft',
          year: 2013,
          sales: '50M',
          exclusive: false,
        },
        {
          name: 'Nintendo Wii',
          manufacturer: 'Nintendo',
          year: 2006,
          sales: '101M',
          exclusive: true,
        },
      ],
    }),
  }
</script>

<template>
  <v-app>
    <v-data-table :items="consoles">
      <template v-slot:item.exclusive="{ item }">
        <v-checkbox
          v-model="item.exclusive"
        ></v-checkbox>
      </template>
    </v-data-table>
  </v-app>
</template>

ここでのポイントはv-slot:item.exclusiveです。これにより、以下のようにexclusiveの列でチェックボックスが利用できるようになります。
(↓セガサターンがないやん…)
Slotsによるチェックボックス付きデータテーブル
当然、exclusiveをmanufacturerにすれば、manufacturerの列にチェックボックスを付けることもできます。

全選択チェックボックスを任意の列で利用する

さて、ここからが本題です。
公式で紹介されているのは、全選択チェックボックスと行ごとのチェックボックスを先頭の列に付ける方法と、任意の列に行ごとのチェックボックスを付ける方法です。
全選択チェックボックスを任意の列で利用したい場合には、どのようにすればよいでしょうか。どこか公式で説明がないか探していたところ、以下のような記述を見つけました。

The exception to this is reserved keys like data-table-select and data-table-expand which must be defined as key to work properly.

data-table-selectが予約キー…🤔
ここまでの公式のサンプルでdata-table-selectは出てきていませんでしたが、予約キーになっているということは、何か意味があるはずです。
しょうがない…ソースみるか………

VDataTableHeaders.tsx
if (column.key === 'data-table-select') {
  return slots['item.data-table-select']?.(slotProps) ?? (
   <VCheckboxBtn
     disabled={ !item.selectable }
     modelValue={ isSelected([item]) }
     onClick={ withModifiers(() => toggleSelect(item), ['stop']) }
    />
  )
}

VDataTableHeadersにdata-table-selectによる分岐がある…(ここで、Propsのheadersのkeyの型に文字列リテラルの'data-table-select'があることに気づく)
最初の実装例を以下のように書き換えます。

App.vue
<script lang="ts">
  export default {
    data () {
      return {
        selected: [],
        headers: [
          {
            title: "Name",
            key: "name",
          },
          {
            title: "Location",
            key: "location",
          },
          {
            title: "Height",
            key: "height",
          },
          {
            title: "Base",
            key: "base",
          },
          {
            key: "data-table-select",
          },
          {
            title: "Volume",
            key: "volume",
          },
        ],
        items: [
          {
            name: '🍎 Apple',
            location: 'Washington',
            height: '0.1',
            base: '0.07',
            volume: '0.0001',
          },
          {
            name: '🍌 Banana',
            location: 'Ecuador',
            height: '0.2',
            base: '0.05',
            volume: '0.0002',
          },
          {
            name: '🍇 Grapes',
            location: 'Italy',
            height: '0.02',
            base: '0.02',
            volume: '0.00001',
          },
          {
            name: '🍉 Watermelon',
            location: 'China',
            height: '0.4',
            base: '0.3',
            volume: '0.03',
          },
          {
            name: '🍍 Pineapple',
            location: 'Thailand',
            height: '0.3',
            base: '0.2',
            volume: '0.005',
          },
          {
            name: '🍒 Cherries',
            location: 'Turkey',
            height: '0.02',
            base: '0.02',
            volume: '0.00001',
          },
          {
            name: '🥭 Mango',
            location: 'India',
            height: '0.15',
            base: '0.1',
            volume: '0.0005',
          },
          {
            name: '🍓 Strawberry',
            location: 'USA',
            height: '0.03',
            base: '0.03',
            volume: '0.00002',
          },
          {
            name: '🍑 Peach',
            location: 'China',
            height: '0.09',
            base: '0.08',
            volume: '0.0004',
          },
          {
            name: '🥝 Kiwi',
            location: 'New Zealand',
            height: '0.05',
            base: '0.05',
            volume: '0.0001',
          },
        ],
      }
    },
  }
</script>

<template>
  <v-app>
    <v-data-table
      v-model="selected"
      :headers="headers"
      :items="items"
      item-value="name"
    ></v-data-table>
  </v-app>
</template>

できた!!
任意列全選択チェックボックス付きデータテーブル
なお、Vuetify2の場合もheadersのvalueに'data-table-select'を指定すれば、任意の列に全選択チェックボックスを配置できますので、data-table-selectを利用するのは、想定された実装方法と考えて良さそうです。

おまけ(slotでの置き換え)

ちなみに、このdata-table-selectも当然、slotでの置き換えが可能です。
slotで置き換えをして、チェックボックスにcolor="primary"を指定したい場合の実装例を以下に示します。

App.vue
<script lang="ts">
  import { withModifiers } from "vue";
  export default {
    data () {
      return {
        withModifiers: withModifiers,
        selected: [],
        headers: [
          {
            title: "Name",
            key: "name",
          },
          {
            title: "Location",
            key: "location",
          },
          {
            title: "Height",
            key: "height",
          },
          {
            title: "Base",
            key: "base",
          },
          {
            key: "data-table-select",
          },
          {
            title: "Volume",
            key: "volume",
          },
        ],
        items: [
          {
            name: '🍎 Apple',
            location: 'Washington',
            height: '0.1',
            base: '0.07',
            volume: '0.0001',
          },
          {
            name: '🍌 Banana',
            location: 'Ecuador',
            height: '0.2',
            base: '0.05',
            volume: '0.0002',
          },
          {
            name: '🍇 Grapes',
            location: 'Italy',
            height: '0.02',
            base: '0.02',
            volume: '0.00001',
          },
          {
            name: '🍉 Watermelon',
            location: 'China',
            height: '0.4',
            base: '0.3',
            volume: '0.03',
          },
          {
            name: '🍍 Pineapple',
            location: 'Thailand',
            height: '0.3',
            base: '0.2',
            volume: '0.005',
          },
          {
            name: '🍒 Cherries',
            location: 'Turkey',
            height: '0.02',
            base: '0.02',
            volume: '0.00001',
          },
          {
            name: '🥭 Mango',
            location: 'India',
            height: '0.15',
            base: '0.1',
            volume: '0.0005',
          },
          {
            name: '🍓 Strawberry',
            location: 'USA',
            height: '0.03',
            base: '0.03',
            volume: '0.00002',
          },
          {
            name: '🍑 Peach',
            location: 'China',
            height: '0.09',
            base: '0.08',
            volume: '0.0004',
          },
          {
            name: '🥝 Kiwi',
            location: 'New Zealand',
            height: '0.05',
            base: '0.05',
            volume: '0.0001',
          },
        ],
      }
    },
  }
</script>

<template>
  <v-app>
    <v-data-table
      v-model="selected"
      :headers="headers"
      :items="items"
      item-value="name"
    >
      <template v-slot:[`header.data-table-select`]="{ someSelected, allSelected, selectAll }">
        <v-checkbox-btn
          :modelValue="allSelected"
          :indeterminate="someSelected && !allSelected"
          @update:model-value="selectAll"
          color="primary"
          class="indeterminate-checkbox"
        />
      </template>
      <template v-slot:[`item.data-table-select`]="{ isSelected, toggleSelect, internalItem }">
        <v-checkbox-btn
          :modelValue="isSelected([internalItem])"
          :onclick="withModifiers(() => toggleSelect(internalItem), ['stop'])"
          color="primary"
        />
      </template>
    </v-data-table>
  </v-app>
</template>

<style scoped>
/* 部分選択時のマイナスアイコンを青くする */
.indeterminate-checkbox.v-checkbox-btn :deep(.v-selection-control__input) > .v-icon.mdi-minus-box {
  color: #1867c0;
  opacity: 1;
}
</style>

class="indeterminate-checkbox"は部分選択時に全選択チェックボックスのマイナスアイコンがグレーになってしまうので、マイナスアイコンを青くするために設定しているものです。不要な場合は消しても問題はありません。
※部分選択時に全選択チェックボックスのマイナスアイコンがグレーになるのはissueで「that is the expected output.(それが期待される出力です。)」と回答されているので仕様です。
任意列青い全選択チェックボックス付きデータテーブル

まとめ

以上が全選択チェックボックスを任意の列で利用する方法になります。
最終的なソースコードは以下に上げていますので、参考としてください。
https://github.com/y-kashima-iandc/vuetify-datatable-with-checkbox

参考文献

この記事は以下の情報を参考にして執筆しました。

株式会社アイアンドシー Tech Blog

Discussion