📐

Vuetifyのv-data-tableのカラムをツールチップ機能付きアイコンにする方法

9 min read

はじめに

Vuetifyのv-data-tableの左端にアイコン予定を表示し、ホバーしたら詳細が見れるようなものを作りたかったので実際にサンプルを作ってやってみました。

想定しているサンプルのユースケースは例えばこんな感じです(適当・後付け)

  • 小さなスーパーマーケットで利用
  • 健康志向のお客様が、必要なマクロ栄養素を簡単に把握できて、買いたくなる
  • リニューアル情報があれば気づきやすくしたい

そのためこんな情報設計とします

  • ファーストビューには収まらない前提で、なるべく多くの商品情報を表示する
    • 収まらないからにはフィルタできたりとかを充実させる
  • 商品の情報(名称やマクロ栄養素)はすべて表示する
  • リニューアル予定があるかどうか気づきやすくする
    • いつされるのか、どうなるのかをわかりやすくする

ただし、今回は筆者の都合上、追加情報は展開式の行じゃなくて、ツールチップで出します。

やってみたこと

nuxt.jsアプリを作成

$ create-nuxt-app
create-nuxt-app v3.5.2
✨  Generating Nuxt.js project in .
? Project name: 210714_vuetify-table-custom
? Programming language: TypeScript
? Package manager: Yarn
? UI framework: Vuetify.js
? Nuxt.js modules: (Press <space> to select, <a> to toggle all, <i> to invert selection)
? Linting tools: (Press <space> to select, <a> to toggle all, <i> to invert selection)
? Testing framework: None
? Rendering mode: Single Page App
? Deployment target: Server (Node.js hosting)
? Development tools: (Press <space> to select, <a> to toggle all, <i> to invert selection)
? What is your GitHub username? hirobel
? Version control system: Git

サンプル用ページを作成

$ cd 210714_vuetify-table-custom
$ touch page/table.vue

ツールチップが出る列を持ったテーブルを表示するサンプルコードを実装

  • <v-data-table>タグを利用してテーブルを作成します。
  • その上で、1列だけv-slotを使ってカスタマイズします。
  • 必要なサンプルデータはVuetify公式のData tablesの使い方ページから流用します。
  • v-ifを使って、データがある項目だけ表示するようにします。
$ vim page/table.vue
page/table.vue
<template>
  <div>
    <v-data-table
      :headers="headers"
      :items="desserts"
    >
      <template v-slot:item.scheduledAt="{ item }">
        <v-tooltip bottom v-if="item.scheduledAt" >
          <template v-slot:activator="{ on, attrs }">
            <v-icon
              dark
              v-bind="attrs"
              v-on="on"
            >
              mdi-clock-outline
            </v-icon>
          </template>
          <div>変更日:{{ item.scheduledAt }}</div>
          <div v-if="item.scheduledName">変更後の商品名:{{ item.scheduledName }}</div>
          <div v-if="item.scheduledCalories">変更後のカロリー:{{ item.scheduledCalories }}</div>
          <div v-if="item.scheduledFat">変更後の脂質:{{ item.scheduledFat }}</div>
          <div v-if="item.scheduledCarb">変更後の炭水化物:{{ item.scheduledCarb }}</div>
          <div v-if="item.scheduledProtein">変更後のタンパク質:{{ item.scheduledProtein }}</div>
          <div v-if="item.scheduledIron">変更後の鉄分:{{ item.scheduledIron }}</div>
        </v-tooltip>
      </template>
    </v-data-table>
  </div>
</template>

<script>
  export default {
    data () {
      return {
        headers: [
          { text: '', value: 'scheduledAt' }, // slotでカスタマイズするため、valueの指定は必須となる
          {
            text: 'Dessert (100g serving)',
            align: 'start',
            sortable: false,
            value: 'name',
          },
          { text: 'Calories', value: 'calories' },
          { text: 'Fat (g)', value: 'fat' },
          { text: 'Carbs (g)', value: 'carbs' },
          { text: 'Protein (g)', value: 'protein' },
          { text: 'Iron (%)', value: 'iron' },
        ],
        desserts: [
          {
            name: 'Frozen Yogurt',
            calories: 159,
            fat: 6.0,
            carbs: 24,
            protein: 4.0,
            iron: '1%',
            scheduledAt: "7/30",
            scheduledName: "Double Frozen Yogurt",
            scheduledCalories: "318",
            scheduledProtein: "8"
          },
          {
            name: 'Ice cream sandwich',
            calories: 237,
            fat: 9.0,
            carbs: 37,
            protein: 4.3,
            iron: '1%',
          },
          {
            name: 'Eclair',
            calories: 262,
            fat: 16.0,
            carbs: 23,
            protein: 6.0,
            iron: '7%',
          },
          {
            name: 'Cupcake',
            calories: 305,
            fat: 3.7,
            carbs: 67,
            protein: 4.3,
            iron: '8%',
          },
          {
            name: 'Gingerbread',
            calories: 356,
            fat: 16.0,
            carbs: 49,
            protein: 3.9,
            iron: '16%',
          },
          {
            name: 'Jelly bean',
            calories: 375,
            fat: 0.0,
            carbs: 94,
            protein: 0.0,
            iron: '0%',
          },
          {
            name: 'Lollipop',
            calories: 392,
            fat: 0.2,
            carbs: 98,
            protein: 0,
            iron: '2%',
          },
          {
            name: 'Honeycomb',
            calories: 408,
            fat: 3.2,
            carbs: 87,
            protein: 6.5,
            iron: '45%',
          },
          {
            name: 'Donut',
            calories: 452,
            fat: 25.0,
            carbs: 51,
            protein: 4.9,
            iron: '22%',
            scheduledAt: "8/2",
            scheduledName: "Protein Donut",
            scheduledCalories: "226",
            scheduledCarb: "25",            
            scheduledProtein: "10"            
          },
          {
            name: 'KitKat',
            calories: 518,
            fat: 26.0,
            carbs: 65,
            protein: 7,
            iron: '6%',
          },
        ],
      }
    },
  }
</script>

補足

サンプルコードの補足説明です
  • テーブル形式で表示したいので v-data-table を利用
    <v-data-table
     :headers="headers"
     :items="desserts"
  • item.<name>のslotを使って、一番左のカラムだけカスタマイズします
     <template v-slot:item.scheduledAt="{ item }">
       <v-tooltip bottom v-if="item.scheduledAt" >
         <template v-slot:activator="{ on, attrs }">
           <v-icon
             dark
             v-bind="attrs"
             v-on="on"
           >
             mdi-clock-outline
           </v-icon>
         </template>
         <div>変更日:{{ item.scheduledAt }}</div>
         <div v-if="item.scheduledName">変更後の商品名:{{ item.scheduledName }}</div>
         <div v-if="item.scheduledCalories">変更後のカロリー:{{ item.scheduledCalories }}</div>
         <div v-if="item.scheduledFat">変更後の脂質:{{ item.scheduledFat }}</div>
         <div v-if="item.scheduledCarb">変更後の炭水化物:{{ item.scheduledCarb }}</div>
         <div v-if="item.scheduledProtein">変更後のタンパク質:{{ item.scheduledProtein }}</div>
         <div v-if="item.scheduledIron">変更後の鉄分:{{ item.scheduledIron }}</div>
       </v-tooltip>
     </template>
  • 商品名や栄養素などのヘッダをここで定義
       headers: [
         { text: '', value: 'scheduledAt' }, // slotでカスタマイズするため、valueの指定は必須となる	
         {
           text: 'Dessert (100g serving)',
           align: 'start',
           sortable: false,
           value: 'name',
         },
         { text: 'Calories', value: 'calories' },
         { text: 'Fat (g)', value: 'fat' },
         { text: 'Carbs (g)', value: 'carbs' },
         { text: 'Protein (g)', value: 'protein' },
         { text: 'Iron (%)', value: 'iron' },
       ],
       desserts: [
         {
           name: 'Frozen Yogurt',
           calories: 159,
           fat: 6.0,
           carbs: 24,
           protein: 4.0,
           iron: '1%',
           scheduledAt: "7/30",
           scheduledName: "Double Frozen Yogurt",
           scheduledCalories: "318",
           scheduledProtein: "8"
         },
         {
           name: 'Ice cream sandwich',
           calories: 237,
           fat: 9.0,
           carbs: 37,
           protein: 4.3,
           iron: '1%',
         },
		:
       ],

動作確認

$ yarn dev

こんな感じでした

まとめ

  • Vuetifyのv-data-tableはitem.<name>のslotを使って、特定のカラムだけカスタマイズできる
  • カスタマイズの仕方によってはカラム内でツールチップを出すこともできる
    • ただし、ヘッダのvalueは必須となる