Vuetify2のdata-tableの使い方を学んで、タグ一覧ページをレイアウト
  • 2019.11.01に公開
  • ブログ構築
  • 7. タグ機能の構築
  • No.4 / 4
「ブログ構築」では、Nuxt.js v2.14未満が使用されています。 Nuxt.jsはv2.13~14で大きなバージョンアップがありました。それに伴い、書き方も大きく変化しています。 v2.14以上で書かれた「ブログ構築」の続編は、カテゴリー「 ブログ構築TS 」 で公開しています。

今回達成すること

タグ一覧ページにVuetify2のdata-tableを導入して、レイアウトを完成させます。

下の3つの機能をdata-tableで実現していきます。

  • デフォルトで投稿数の多い順に並べてタグを表示する
  • タグ名で検索できる
  • ページべネーションでタグ一覧を切り分ける

最終的に↓このようになります。

タグ一覧ページの完成イメージ

最終的なtags/index.vueのコードまですっ飛ばす

2019-11-01 18-15-47

Contentfulでタグ作成を行う

何がなくとも、基となるタグを作成しましょう。

Countentfulで、投稿に関連付くタグを20個くらい作成してください。

Countentfulの操作を忘れた方は、以下の記事にタグの作成方法を書いています。

目次)タグを作ってみよう からご覧ください。

Contentfulにタグモデルを作成し関連付けを行う

トップページのレイアウト崩れを修正する

ある程度タグが関連付くと、トップページの記事カードのレイアウトが崩れます。

これはv-card-textに設定したheightが原因です。

2019-11-01 18-07-06

pages/index.vueを修正しておきましょう。

pages/index.vue
<template>
 <v-container fluid>
   ...
   <v-list-item three-line style="min-height: unset;">
     <v-list-item-subtitle>
       {{ post.fields.body }}
     </v-list-item-subtitle>
   </v-list-item>

   <!-- 削除する -->
   <!-- <v-card-text
     style="height: 64px;"
   > -->

   <v-card-text>		<!-- 書き換え -->

     <template v-if="post.fields.tags">
       <v-chip
         v-for="(tag) in post.fields.tags"
         :key="tag.sys.id"
         :to="linkTo('tags', tag)"
         small
         label
         outlined
         class="ma-1"
       >
   ...

それではタグ一覧ページの編集を開始します。

【step1】Vuetify2のdata-tableを表示する

最初はdata-tableを表示して、骨組みを作ります。

Vuetify2のdata-tableのサンプル「search」を基本に作成します。

Data table components - Vuetify

pages/tags/index.vueを以下のように編集します。

pages/tags/index.vue
<template>
  <div>
    <breadcrumbs :add-items="addBreads" />

    <v-container>
      <v-row
        justify="center"
      >
        <v-col
          cols="12"
          sm="10"
          md="8"
        >
          <v-card>
            <v-card-title>
              <v-text-field
                v-model="search"
                append-icon="mdi-magnify"
                label="Search"
                single-line
                hide-details
              />
            </v-card-title>

            <v-data-table
              :headers="headers"
              :items="tableItems"
              :search="search"
            ></v-data-table>
          </v-card>
        </v-col>
      </v-row>
    </v-container>
  </div>
</template>

<script>
import { mapState, mapGetters } from 'vuex'

export default {
  data: () => ({
    search: '',
    headers: [
      { text: 'タグ' },
      { text: '投稿数' }
    ]
  }),
  computed: {
    ...mapState(['tags']),
    ...mapGetters(['linkTo']),
    // 削除する
    // postCount() {
    //   return (currentTag) => {
    //     return this.$store.getters.associatePosts(currentTag).length
    //   }
    // },
    addBreads() {
      return [{ icon: 'mdi-tag-outline', text: 'タグ一覧', to: '/tags', disabled: true, iconColor: 'grey' }]
    },
    tableItems() {
      return []
    }
  }
}
</script>

"http://localhost:3000/tags"にアクセスすると。。。

data-tableの形ができました!

2019-10-31 23-34-08

【step2】タグアイテムを作成する

次は、表示するタグアイテムを作成します。

え?そのままthis.tagsを使えばええやんって思いました?

筆者も最初はそう思って実装しましたが、、、失敗しました。

そう、投稿数の多い順に並び替えができないのです。

この機能を実現するには、this.tagsと同じ配列内に、投稿数のデータを入れ込まなければなりません。

算出プロパティで実装しましょう。

pages/tags/index.vue
export default {
  ...
  computed: {
    ...
    tableItems() {
      const tags = []
      for (let i = 0; i < this.tags.length; i++) {
        const tag = this.tags[i]

        tag.fields.postcount = this.$store.getters.associatePosts(tag).length
        tags.push(tag)
      }
      return tags
    }
  }
}

console.logで覗くとpostcountが追加されていますね。

2019-10-31 23-48-32

それではブラウザを確認してみま....。

おやおや、まだタグは表示されていません。

【step3】data-tableのheadersを追加する

タグを表示するには、headersの配列にvalueのプロパティを追加しなければなりません。

valueには、表示したいオブジェクトのkeyを指定ます。

pages/tags/index.vue
export default {
  data: () => ({
    search: '',
    headers: [
      {
        text: 'タグ',
        align: 'left',
        value: 'fields.name'
      },
      {
        text: '投稿数',
        align: 'center',
        width: 150,
        value: 'fields.postcount'
      }
    ]
  }),
  ...
}
  • align:

    table内の表示位置を指定できます。

    指定できる値は「left」「center」「right」の3つです。

  • width:

    tableセルの幅を指定できます。

    数字の場合、ピクセル値を渡します。

    文字列でwidth: '150px'と指定することもできます。

  • value:

    表示したいオブジェクトのkeyを文字列で指定します。

おおっ!タグが表示されました。

2019-11-01 00-11-56

【step4】タグの名前にリンクをつける

続いてタグの名前にリンクをつけます。

Vuetify2のdata-tableは非常に柔軟な拡張性を持っています。

タグの名前にリンクをつけるには、v-data-tableのタグの中でslotを定義します。

pages/tags/index.vue
<template>
	...
    <v-data-table
      :headers="headers"
      :items="tableItems"
      :search="search"
    >
      
      <!-- v-data-tableのタグ内に追記 -->
      <template v-slot:item.fields.name="{ item }">
        <v-icon size="18">
          mdi-tag-outline
        </v-icon>

        <nuxt-link
          :to="linkTo('tags', item)"
        >
          {{ item.fields.name }}
        </nuxt-link>
      </template>
      <!-- 終わり -->
      
    </v-data-table>
 ...
</template>
  • <template v-slot:item.fields.name="{ item }">

    v-slotには、拡張したいセルのvalueを指定します。

    valueはheadersの配列で指定しましたね。

headers: [
  {
    text: 'タグ',
    align: 'left',
    value: 'fields.name'	// この値を指定する
  },
  {
    text: '投稿数',
    align: 'center',
    width: 150,
    value: 'fields.postcount' // この値を指定する
  }
]

タグの名前にリンクがつきました。

クリックしたらちゃんとタグの個別ページに遷移しますね。OK!

2019-11-01 09-02-37

【step5】投稿の多い順に並び変える

さて、次は投稿の多い順にタグを並び替えます。

とは言っても簡単で、sort-bysort-descのプロパティを追加するだけで実現できます。

pages/tasg/index.vue
<template>
	...
    <v-data-table
      :headers="headers"
      :items="tableItems"
      :search="search"
      :sort-by="sortBy"   <!-- 追記 -->
      sort-desc           <!-- 追記 -->
    >
	...
</template>

<script>
import { mapState, mapGetters } from 'vuex'

export default {
  data: () => ({
    search: '',
    sortBy: 'fields.postcount',   // 追記
    ...
  })
}
</script>
  • :sort-by="sortBy"

    sort-byプロパティに追加するのは、headersで指定したvalueの値です。

    ここでは'fields.postcount'を指定しています。

  • sort-desc

    true or false のブーリアン型を渡します。

    プロパティを指定しなければ小さい順に並びます。

    ちなみにtrueを渡す場合は、:sort-desc="true"としなくても、sort-descだけで渡すことができます。

投稿の多い順に並びましたね。

2019-11-01 15-43-08

【step6】1ページに表示するタグ数を変更する

続いてタグの表示数を変更しましょう。

items-per-pageに表示したい数字を渡します。

pages/tags/index.vue
<template>
  ...
    <v-data-table
      :headers="headers"
      :items="tableItems"
      :search="search"
      :sort-by="sortBy"
      :items-per-page="itemsPerPage"  <!-- 追記 -->
      sort-desc
    >
	...
</template>

<script>
import { mapState, mapGetters } from 'vuex'

export default {
  data: () => ({
    search: '',
    sortBy: 'fields.postcount',
    itemsPerPage: 20,    // 追記

</script>
  • :items-per-page="itemsPerPage"

    今回は20を渡しています。

これで1ページに表示されるタグの数が20個になりました。

【step7】ページべネーションを追加する

さあ、最後のstep、ページべネーションを追加します。

data-tableのfooterを非表示に

まず、hide-default-footerプロパティでfooterを非表示にしましょう。

pages/tags/index.vue
<template>
  ...
    <v-data-table
      :headers="headers"
      :items="tableItems"
      :search="search"
      :sort-by="sortBy"
      :items-per-page="itemsPerPage"
      sort-desc
      hide-default-footer           <!-- 追記 -->
    >
  ...
  
</template>

tableのfooterが非表示になりました。

2019-11-01 16-03-49

べネーションを追加する

v-data-tableのとじタグの下にべネーションを追加します。

pages/tags/index.vue
<template>
  ...
    </v-data-table>

    <!-- paginationの追記 -->
		<div class="text-center py-2">
      <v-pagination
        v-model="page"
        :length="pageCount"
      />
    </div>
	...
</template>

<script>
import { mapState, mapGetters } from 'vuex'

export default {
  data: () => ({
    search: '',
    sortBy: 'fields.postcount',
    itemsPerPage: 20,
    page: 1,         // 追記
    pageCount: 0,    // 追記

  ...
</script>

耳のようなものが出てきました。

2019-11-01 16-11-45

data-tableと連動させる

次は、べネーションとテーブルデータを連動させます。

data-table側page.sync@page-countを追記します。

pages/tags/index.vue
<template>
  ...
    <v-data-table
      :headers="headers"
      :items="tableItems"
      :search="search"
      :sort-by="sortBy"
      :items-per-page="itemsPerPage"
      :page.sync="page"                 <!-- 追記 -->
      sort-desc
      hide-default-footer
      @page-count="pageCount = $event"  <!-- 追記 -->
    >
  ...
</template>

ページべネーションが出てきました。

クリックすると次ページに移動していますね。よし!

2019-11-01 16-17-31

べネーションのボタンの数を制御する

べネーションをカスタマイズしていきましょう。

今のままだと問題があります。

先ほど指定したitemsPerPage: 20を、試しに1に変更してみてください。

itemsPerPageを20から1に変更したべネーション

2019-11-01 16-26-50

流石にボタンが多すぎますね。今後タグが増えてきたらこうなるということです。

そこでボタンの数を制御するtotal-visibleプロパティを設定します。

pages/tags/index.vue
<template>
  ...
    </v-data-table>

    <div class="text-center py-2">
      <v-pagination
        v-model="page"
        :length="pageCount"
        :total-visible="totalVisible"    <!-- 追記 -->
      />
    </div>
</template>

<script>
import { mapState, mapGetters } from 'vuex'

export default {
  data: () => ({
    search: '',
    sortBy: 'fields.postcount',
    itemsPerPage: 1,
    page: 1,
    pageCount: 0,
    totalVisible: 7,    // 追記
  ...
</script>
  • :total-visible="totalVisible"

    一番見栄えの良い、7を設定しています。

ボタンの数が減りましたね。確認が取れたらitemsPerPage: 1を、20に戻してください。

2019-11-01 16-34-01

ベーネションをオサレに

最後です。

ここから先は好みの問題ですのでご自由にカスタマイズしてください。

べネーションのボタンを丸く、矢印を別のiconに変更します。

pages/tags/index.vue
<template>
  ...
    </v-data-table>

    <div class="text-center py-2">
      <v-pagination
        v-model="page"
        :length="pageCount"
        :total-visible="totalVisible"
        circle						<!-- 追記 -->
        prev-icon="mdi-menu-left"	 <!-- 追記 -->
        next-icon="mdi-menu-right"	<!-- 追記 -->
      />
    </div>
   ...
</template>

ほお!オサレです \ o /

2019-11-01 16-49-45

以上でタグ一覧ページは完成です!

最終的なtags/index.vue

最終的なタグ一覧ページのコードです。

エラーが発生して動けない場合は、ここをコピペしてください。

pages/tags/index.vue
<template>
  <div>
    <breadcrumbs :add-items="addBreads" />

    <v-container>
      <v-row
        justify="center"
      >
        <v-col
          cols="12"
          sm="10"
          md="8"
        >
          <v-card>
            <v-card-title>
              <v-text-field
                v-model="search"
                append-icon="mdi-magnify"
                label="Search"
                single-line
                hide-details
              />
            </v-card-title>

            <v-data-table
              :headers="headers"
              :items="tableItems"
              :search="search"
              :sort-by="sortBy"
              :items-per-page="itemsPerPage"
              :page.sync="page"
              sort-desc
              hide-default-footer
              @page-count="pageCount = $event"
            >
              <template v-slot:item.fields.name="{ item }">
                <v-icon size="18">
                  mdi-tag-outline
                </v-icon>
                <nuxt-link
                  :to="linkTo('tags', item)"
                >
                  {{ item.fields.name }}
                </nuxt-link>
              </template>
            </v-data-table>

            <div class="text-center py-2">
              <v-pagination
                v-model="page"
                :length="pageCount"
                :total-visible="totalVisible"
                circle
                prev-icon="mdi-menu-left"
                next-icon="mdi-menu-right"
              />
            </div>
          </v-card>
        </v-col>
      </v-row>
    </v-container>
  </div>
</template>

<script>
import { mapState, mapGetters } from 'vuex'

export default {
  data: () => ({
    search: '',
    sortBy: 'fields.postcount',
    itemsPerPage: 20,
    page: 1,
    pageCount: 0,
    totalVisible: 7,
    headers: [
      {
        text: 'タグ',
        align: 'left',
        value: 'fields.name'
      },
      {
        text: '投稿数',
        align: 'center',
        width: 150,
        value: 'fields.postcount'
      }
    ]
  }),
  computed: {
    ...mapState(['tags']),
    ...mapGetters(['linkTo']),
    addBreads() {
      return [{ icon: 'mdi-tag-outline', text: 'タグ一覧', to: '/tags', disabled: true, iconColor: 'grey' }]
    },
    tableItems() {
      const tags = []
      for (let i = 0; i < this.tags.length; i++) {
        const tag = this.tags[i]

        tag.fields.postcount = this.$store.getters.associatePosts(tag).length
        tags.push(tag)
      }
      return tags
    }
  }
}
</script>

本番環境にpushする

ここまでの変更を本番環境にpushしておきましょう。

$ git commit -am "add_datatable_for_tags/index.vue"
$ git push

本番環境のURLでも確認してみてくださいね。

お疲れ様でした!

さて、次回は?

さてさて、次回からは各ページのレイアウトを完成させていきます。

モックアップの作成やscssの導入を行いますよ。

それではまたお会いしましょう。

あとがき

いやぁ、流石に長かったですね。

最後まで読んでいただいてありがとうございます。

最後に、data-tableコンポーネントの拡張方法を3つ紹介しています。

お時間があるときにゆっくりご覧ください。↓

【コラム】Vuetify2 data-table 拡張機能いろいろ

data-tableのいろいろな拡張機能をご紹介します。

1. 行全体にリンクをつけたい【v-slot:body】

tableの行(trタグ)にリンクをつける方法です。

スマホの場合に、ユーザーが誤ってタップしてしまうのでおすすめはしません。

完成イメージ

2019-11-01 10-35-17

pages/tags/index.vue
<template>

  <v-data-table
    :headers="headers"
    :items="tableItems"
    :search="search"
  >
    <template v-slot:body="{ items }">
      <tbody>
        <tr
          v-for="(item, i) in items"
          :key="i"
          class="pointer"
          @click="goLink(item)"
        >
          <td>
            {{ item.fields.name }}
          </td>
          <td class="text-center">
            {{ item.fields.postcount }}
          </td>
        </tr>
      </tbody>
    </template>

</template>

<script>
import { mapState, mapGetters } from 'vuex'

export default {

  methods: {
    goLink(item) {
      this.$router.push(this.linkTo('tags', item))
    }
  }
}
</script>
<style lang="css">
  .pointer:hover {
    cursor: pointer;
  }
</style>
  • @click="goLink(item)"

    nuxt-linkでtrタグを囲んでしまうと、レイアウトが崩れてしまいます。

    そこでクリックイベントでページ遷移する方法をとっています。

  • this.$router.push(this.linkTo('tags', item))

    $router.pushは、javascript側でリンクをつけるメソッドです。

    参考

    プログラムによるナビゲーション - Vue Router

  • cursor: pointer;

    table bodyをカスタマイズする場合、スタイリングを自分で行わなければなりません。

    マウスホーバーしたときに、ポインターになるようCSSを設定しています。

2. チェックボックスのカラーを変えたい【v-slot:item.data-table-select】

tableの横に表示するチェックボックスの色を変える拡張方法です。

header用とbody用、2パターンあります。

  • header = v-slot:header.data-table-select
  • body = v-slot:item.data-table-select

headerチェックボックスの色をパープルに、bodyチェックボックスをグリーンに変更しています。

完成イメージ

2019-11-01 12-46-57

pages/tags/index.vue
<template>
  ...
    <v-data-table
      v-model="selected"     <!-- 追記 -->
      :headers="headers"
      :items="tableItems"
      :search="search"
      show-select           <!-- 追記 -->
      item-key="sys.id"     <!-- 追記 -->
    >
      <!-- headerチェックボックス -->
      <template v-slot:header.data-table-select="{ on, props }">
        <v-simple-checkbox
          color="purple"
          v-bind="props"
          v-on="on"
        />
      </template>
      
      <!-- bodyチェックボックス -->
      <template v-slot:item.data-table-select="{ isSelected, select }">
        <v-simple-checkbox
          color="green"
          :value="isSelected"
          @input="select($event)"
        />
      </template>

      <template v-slot:item.fields.name="{ item }">
        <v-icon size="18">
          mdi-tag-outline
        </v-icon>

        <nuxt-link
          :to="linkTo('tags', item)"
        >
          {{ item.fields.name }}
        </nuxt-link>
      </template>
    </v-data-table>
  ...
</template>

<script>
import { mapState, mapGetters } from 'vuex'

export default {
  data: () => ({
    selected: [],			// 追記
    ...
  }),
	...
}
</script>
  • v-model="selected"

    このselectedの配列にチェックを入れたアイテムが保管されます。

3. プログレスバーをカスタマイズしたい【v-slot:progress】

プログレスバーとは、ロード中に表示されるのバーことです。

バーの色をred、高さをデフォルトより少し高くしました。

完成イメージ

2019-11-01 13-46-32

pages/tags/index.vue
<template>
  ...
    <v-data-table
      :headers="headers"
      :items="isLoading ? [] : tableItems" <!-- 変更 -->
      :search="search"
      :loading="isLoading"                 <!-- 追記 -->
      loading-text="Loading..."            <!-- 追記 -->
      item-key="sys.id"
    >
  		<!-- プログレスバーのカスタマイズ -->
      <template v-slot:progress>
        <v-progress-linear
          color="red"
          :height="5"
          indeterminate
        />
      </template>

      <template v-slot:item.fields.name="{ item }">
        <v-icon size="18">
          mdi-tag-outline
        </v-icon>

        <nuxt-link
          :to="linkTo('tags', item)"
        >
          {{ item.fields.name }}
        </nuxt-link>
      </template>
    </v-data-table>
	...
</template>

<script>
import { mapState, mapGetters } from 'vuex'

export default {
  data: () => ({
    isLoading: false, // 追記
    ...
  }),
	...

  // 追記
  created() {
    this.isLoading = true
  },
  mounted() {
    this.$nextTick().then((res) => {
      setTimeout(() => { res.isLoading = false }, 500)
    })
  }
}
</script>

他にもいろいろカスタマイズできますが、基本的にはv-slotを使わなくても、プロパティだけでおおよそのカスタマイズが可能です。

できるだけシンプルに、

どうしてもという部分だけ使いましょう。

参考

Data table -Vuetify

(おしまい)

あなたの力になれること
私自身が独学でプログラミングを勉強してきたので、一人で学び続ける苦しみは痛いほど分かります。そこで、当時の私がこんなのあったら良いのにな、と思っていたサービスを立ち上げました。周りに質問できる人がいない、答えの調べ方が分からない、ここを聞きたいだけなのにスクールは高額すぎる。そんな方に向けた単発・短期間メンターサービスを行っています。
独学プログラマのサービス
ブログ構築の投稿
1
  • 更新情報
  • /
  • #01
ブログ構築カテゴリーの記事修正、更新情報【2020/05/19追記: このカテゴリーの更新を一旦終了といたします】
2
  • 更新情報
  • /
  • #02
Nuxt.js v2.13.0新機能メモの公開とv2.12.2にダウングレードする方法
1
  • 今回作るアプリケーション
  • /
  • #01
Nuxt.jsとContentfulで作るマイブログ
1
  • 開発環境にNuxt.jsを立ち上げる
  • /
  • #01
Nuxt.jsを動かす環境を構築する
2
  • 開発環境にNuxt.jsを立ち上げる
  • /
  • #02
Nuxt.jsのプロジェクトを作成する
3
  • 開発環境にNuxt.jsを立ち上げる
  • /
  • #03
Hello Nuxtを表示する
1
  • Nuxt.jsアプリを公開する
  • /
  • #01
Nuxt.jsをデプロイする前の事前準備を行う
2
  • Nuxt.jsアプリを公開する
  • /
  • #02
Netlifyの初期セットアップとNuxt.jsのデプロイを行う
3
  • Nuxt.jsアプリを公開する
  • /
  • #03
NetlifyにデプロイしたNuxt.jsに独自ドメインを設定する
1
  • Contentfulのセットアップ
  • /
  • #01
【Nuxt.js Universal】Vuetify2.0にバージョンアップしよう
2
  • Contentfulのセットアップ
  • /
  • #02
【画像で説明】Contentfulの使い方。初期設定と各メニューについて学ぶ
3
  • Contentfulのセットアップ
  • /
  • #03
Contentfulにブログ記事モデルを作成していこう
4
  • Contentfulのセットアップ
  • /
  • #04
ContentfulからAPIを取得してNuxt.jsで記事一覧を表示する
1
  • ブログ記事周りの構築
  • /
  • #01
Nuxt.jsにContentfulのブログ記事を表示する
2
  • ブログ記事周りの構築
  • /
  • #02
Contentfulから取得した下書き記事を開発環境に表示する
3
  • ブログ記事周りの構築
  • /
  • #03
Nuxt.jsのgenerateプロパティに動的なルーティングを追加する
4
  • ブログ記事周りの構築
  • /
  • #04
【Nuxt.js】middlewareを活用しブログ記事取得のパフォーマンスを改善する
1
  • カテゴリーページの構築
  • /
  • #01
【Contentful】カテゴリーモデルとブログ記事モデルの関連付け
2
  • カテゴリーページの構築
  • /
  • #02
【Nuxt.js × Contentful】ブログ記事に関連付くカテゴリーを表示する
3
  • カテゴリーページの構築
  • /
  • #03
【Nuxt.js × Contentful】カテゴリー記事一覧ページを作成する
1
  • タグ機能の構築
  • /
  • #01
Contentfulにタグモデルを作成し関連付けを行う
2
  • タグ機能の構築
  • /
  • #02
【Nuxt.js × Contentful】タグに関連付いたブログ記事を表示する
3
  • タグ機能の構築
  • /
  • #03
Contentfulのincludesを使って関連モデルを取得しタグ一覧ページを作成する
4
  • タグ機能の構築
  • /
  • #04
Vuetify2のdata-tableの使い方を学んで、タグ一覧ページをレイアウト
1
  • Nuxt.jsブログカスタマイズ
  • /
  • #01
Twitterシェアボタン、フォローボタンの作り方【Nuxt.js Universalモード編】
2
  • Nuxt.jsブログカスタマイズ
  • /
  • #02
Contentfulの全文検索を使ったNuxt.jsブログ内検索の実装
独学プログラマ
独学でも、ここまでできるってよ。
CONTACT
Nuxt.js制作のご依頼は下記メールアドレスまでお送りください。