今回達成すること
タグ一覧ページにVuetify2のdata-tableを導入して、レイアウトを完成させます。
下の3つの機能をdata-tableで実現していきます。
- デフォルトで投稿数の多い順に並べてタグを表示する
- タグ名で検索できる
- ページべネーションでタグ一覧を切り分ける
最終的に↓このようになります。
タグ一覧ページの完成イメージ
Contentfulでタグ作成を行う
何がなくとも、基となるタグを作成しましょう。
Countentfulで、投稿に関連付くタグを20個くらい作成してください。
Countentfulの操作を忘れた方は、以下の記事にタグの作成方法を書いています。
目次)タグを作ってみよう からご覧ください。
トップページのレイアウト崩れを修正する
ある程度タグが関連付くと、トップページの記事カードのレイアウトが崩れます。
これはv-card-text
に設定したheightが原因です。
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」を基本に作成します。
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の形ができました!
【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
}
}
}
-
tag.fields.postcount = this.$store.getters.associatePosts(tag).length
tag.fieldsに新しいプロパティ
postcount
を追加しています。associatePostsは、引数に渡したタグの関連する記事を返します。
associatePostsは、こちらの記事で作成しています。
console.logで覗くとpostcount
が追加されていますね。
それではブラウザを確認してみま....。
おやおや、まだタグは表示されていません。
【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を文字列で指定します。
おおっ!タグが表示されました。
【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!
【step5】投稿の多い順に並び変える
さて、次は投稿の多い順にタグを並び替えます。
とは言っても簡単で、sort-by
とsort-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
だけで渡すことができます。
投稿の多い順に並びましたね。
【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が非表示になりました。
べネーションを追加する
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>
耳のようなものが出てきました。
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>
ページべネーションが出てきました。
クリックすると次ページに移動していますね。よし!
べネーションのボタンの数を制御する
べネーションをカスタマイズしていきましょう。
今のままだと問題があります。
先ほど指定したitemsPerPage: 20
を、試しに1に変更してみてください。
itemsPerPageを20から1に変更したべネーション
流石にボタンが多すぎますね。今後タグが増えてきたらこうなるということです。
そこでボタンの数を制御する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に戻してください。
ベーネションをオサレに
最後です。
ここから先は好みの問題ですのでご自由にカスタマイズしてください。
べネーションのボタンを丸く、矢印を別の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 /
以上でタグ一覧ページは完成です!
最終的な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タグ)にリンクをつける方法です。
スマホの場合に、ユーザーが誤ってタップしてしまうのでおすすめはしません。
完成イメージ
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側でリンクをつけるメソッドです。
参考
-
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チェックボックスをグリーンに変更しています。
完成イメージ
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、高さをデフォルトより少し高くしました。
完成イメージ
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を使わなくても、プロパティだけでおおよそのカスタマイズが可能です。
できるだけシンプルに、
どうしてもという部分だけ使いましょう。
参考
(おしまい)