ブログ構築 7. タグ機能の構築 #02
2019年10月25日に公開

【Nuxt.js × Contentful】タグに関連付いたブログ記事を表示する

今回達成すること

今回は、

  1. タグページの作成
  2. タグの名前を表示
  3. 関連付いた記事の表示
  4. パンくずリストの表示
  5. タグ一覧ページへのリンクの設定

を行います。

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

2019-10-20 14-06-40

まずはブランチの作成

前回の編集をコミットしていない場合は、コミットしておきましょう。

$ git commit -am "top_page_add_tags"

新規ブランチを作成します。ブランチ名は「tag」とします。

$ git checkout -b tag
$ git branch
...
* tag

これでブランチの作成と移動が完了しました。

OK、次に進みましょう!

tagファイルの作成

タグ一覧ページと、タグの個別ページを作成します。

Nuxt.jsプロジェクトのルートディレクトリで、下のコマンドを実行してください。

$ mkdir pages/tags && touch pages/tags/_slug.vue && touch pages/tags/index.vue

このように、tagsディレクトリとindex.vue、_slug.vueファイルが作成されます。

app
 L pages
   L tags
     L _slug.vue
     L index.vue

タグの名前を表示する

まずはタグの名前を表示してみましょう。

pages/tags/_slug.vueを編集します。

pages/tags/_slug.vue
<template>
  <div>
    <h1>{{ tag.fields.name }}</h1>
  </div>
</template>

<script>
export default {
  asyncData({ payload, params, error, store, env }) {
    let tag = payload
    if (!tag) {
      for (let i = 0; i < store.state.posts.length; i++) {
        const tags = store.state.posts[i].fields.tags
        if (tags) tag = tags.find(tag => tag.fields.slug === params.slug)
        if (tag) break
      }
    }
    if (tag) {
      return { tag }
    } else {
      error({ statusCode: 400 })
    }
  }
}
</script>
  • let tag = payload

    本番環境では、payloadにタグが格納されています。

    もし、tagが存在しない場合のみ、for文でtagを検索しています。

  • for (let i = 0; i < store.state.posts.length; i++) { ...

    Vuexに保存されたpostsをループで回し、さらにpostオブジェクトに関連付いたtagsを、findメソッドで検索しています。

  • if (tag) break

    タグを見つけ次第、ループから抜けます。

トップページにリンクを設定する

続いてトップページに表示されているタグにリンクを設定します。

画像でいうところの赤い四角のところですね。

2019-10-19 20-47-43

pages/index.vueを開いてtoの部分を書き換えます。

pages/index.vue
<template>
  ...
  <v-card-text
    style="height: 64px;"
  >
    <template v-if="post.fields.tags">
      <v-chip
        v-for="(tag) in post.fields.tags"
        :key="tag.sys.id"
        :to="linkTo('tags', tag)" <!-- toの部分を書き換える -->
        small
        label
        outlined
        class="ma-1"
      >
  ...        
</template>

編集が完了したら、トップページのタグをクリックしてみてください。

URLは「tags/設定したslug」、ページにはタグの名前が表示されていると思います。

2019-10-20 13-48-38

これでタグの名前は表示できました。次へ進みましょう。

(コラム)for検索 VS API通信 タグ取得はどちらが速いか?

ちなみに、下記のようにContentfulのclient.getEntriesを使って、タグを取得することもできます。

これは上のコードと全く同じ結果を返します。

pages/tags/_slug.vue (Contentfulから取得する場合)
<template>
  <div>
    <h1>{{ tag.fields.name }}</h1>
  </div>
</template>

<script>
import client from '~/plugins/contentful'
export default {
	async asyncData({ payload, params, error, store, env }) {
    const tag = payload || await client.getEntries({
      content_type: 'tag',
      'fields.slug': params.slug
    }).then(res => res.items[0]).catch(console.error)
    if (tag) {
      return { tag }
    } else {
      error({ statusCode: 400 })
    }
  }
}
</script>

ではfor文とclient、どちらが速いのでしょうか?

筆者は、for文の方が画面遷移の速度が速く感じました。

なので、今回はfor文でタグを検索する方法を採用しています。

ただこれは、今時点の記事の数、タグ、カテゴリーの数での速度です。

記事やタグの数が増えてきた場合は、また別の結果になる可能性もあります。

今後コンテンツが増えてきたら検討の予知があると言うことを、頭の隅っこに置いておくといいですよ。

(コラム終わり)

タグに関連する記事の取得

次は、タグに関連付く記事を取得していきます。

pages/tags/_slug.vueにコードを追加していきましょう。

pages/tags/_slug.vue
<template>
  <div>
    <h1>{{ tag.fields.name }}</h1>
    <!-- 追記 -->
    <div
      v-for="(post, i) in relatedPosts"
      :key="i"
    >
      {{ post.fields.title }}
    </div>
    <!-- 追記終了 -->
  </div>
</template>

<script>
import client from '~/plugins/contentful'		// 追記
  
export default {
  async asyncData({ payload, params, error, store, env }) {			// asyncの追記
		...
    
    if (tag) {
      // 追記
      const relatedPosts = await client.getEntries({
        content_type: env.CTF_BLOG_POST_TYPE_ID,
        'fields.tags.sys.id': tag.sys.id
      }).then(res => res.items).catch(console.error)

     return { tag, relatedPosts } 	// relatedPostsの追記
    } else {
      error({ statusCode: 400 })
    }
  }
}
</script>
  • 'fields.tags.sys.id': tag.sys.id

    Contentfulでは、関連付くフィールド(ここで言うtags)のIDで、取得するコンテンツを絞り込むことができます。blogPostモデルのうち、tag.sys.idが一致するpostを取得しています。

さて、編集が完了したらタグページをリロードしてみてください。

下画像のように、関連付く記事のタイトルが表示されます。

2019-10-20 13-53-10

これで関連付く記事の取得は完了です。

パンくずリストのセット

次はパンくずリストをセットしましょう。

パンくずリストは、この記事でグローバルコンポーネントに登録しましたね。

【Nuxt.js × Contentful】カテゴリー記事一覧ページを作成する

pages/tags/_slug.vueにcomputedを追加します。

pages/tags/_slug.vue
<template>
  <div>
    <breadcrumbs :add-items="addBreads" />   <!-- 追記 -->
    ...
  </div>
</template>

<script>

export default {
	// 追記
  computed: {
    addBreads() {
      return [{ icon: 'mdi-tag-outline', text: 'タグ一覧', to: '/tags' }]
    }
  },
	...
}
</script>

タグ一覧のナビが表示されましたね。

2019-10-20 13-59-37

あれ、でもクリックできない。。。なんで?

パンくずリストがクリックできない

結論を先に言うと、Vuetifyのv-breadcrumbs-itemコンポーネントのactive-classプロパティに「v-breadcrumbs__item–disabled」クラスがセットされているためです。

このクラスがついていると、リンクは使用禁止状態になります。

なぜ勝手にクラスがセットされるのか?

このactive-classプロパティは、「/tags」も「/tags/ruby」もアクティブと見なすためです。

つまりアクティブリンクの判定を、URLの完全一致で行なっていないのです。

解決方法

URLの完全一致でアクティブリンクを判定するにはexact-active-classプロパティを使います。

active-classのクラスを空にして、exact-active-classプロパティに「v-breadcrumbs__item–disabled」をセットすることで解決します。

components/ui/breadcrumbs.vue
<template>
  <v-breadcrumbs :items="breadcrumbs">
    <template #item="props">
      <v-breadcrumbs-item
        :to="props.item.to"
        active-class=""                                     <!-- active-classを空にして -->
        exact-active-class="v-breadcrumbs__item--disabled" <!-- exact-active-classにセットする -->
      >
   ...
</template>

「タグ一覧」がクリックできるようになりました。

2019-10-20 14-06-40

タグ一覧ページを作る

まだタグ一覧ページを作っていないので、リンク先には何も表示されません。

pages/tags/index.vueを編集しましょう。

pages/tags/index.vue
<template>
  <div>
    <breadcrumbs />
    tags/index.vue
  </div>
</template>

<script>
export default {
}
</script>

どうですか?

「タグ一覧」のリンクから「/tags」のページに飛べるようになりました。

2019-10-20 14-10-59

おわりに

今回はここまで。

今回はタグページを作成し、関連付く記事のタイトルを表示していきました。

タグ一覧ページはリンクを確認するための仮作成なので、次回編集していきます。

さて次回は

タグ一覧ページを作り込んでいきましょう。

タグの一覧、タグに関連付いた投稿の数を表示していきます。

それではまた!

現在、カテゴリー「Rails apiとNuxt.jsでSPA開発」のデモアプリを構築中です。記事になるまでもう少々のお時間が必要です。ブログの更新が止まって申し訳ありません。デモアプリの進捗状況は こちらの記事 で随時お伝えしてまいります。
スポンサー広告
次の記事はこちらです
ブログ構築
1. 今回作るアプリケーション
#01
Nuxt.jsとContentfulで作るマイブログ
今日のTweet
スポンサー広告