LoginSignup
11
14

More than 5 years have passed since last update.

vue-router + apollo + vuetify でページネーションを作る

Posted at

ページネーションの実装も、vue-routerとapollo、vuetifyを使うと比較的簡単に実装できました。

デモはこちら。
https://vue-sample-blog.firebaseapp.com/pagingDemo
全コードはこちら。
https://github.com/kawamataryo/vue-sample-blog/blob/master/src/views/PagingDemo.vue

このようなページネーションを作成します。
output1.gif

HeadLessCMS + GraphQL + Vue.js でSPA(ブログ)を作ってみる
で作成したブログを元に使っています。

前提

下のようなGraphQLのスキーマを想定します

#sample blog post
type Post {
    #The status of the record
    status: Status!
    id: ID!
    createdAt: DateTime!#The time the record was updated
    updatedAt: DateTime!
    title: String
    description: String
    content: String
    thumbnail: Asset
}

ページング用のクエリの作成

postモデルのレコード数(投稿総数)とページ単位での取得用のクエリを用意します。

graphql.js
import gql from 'graphql-tag'

// postモデルのレコード数を取得
export const MAX_POST_COUNT = gql`
    query maxPostCount{
        postsConnection {
            aggregate {
                count
            }
        }
    }
`

// ページ単位で取得
export const FEACH_POST_BY_PAGE = gql`
    query feachPostByPage($displayUnit: Int, $page: Int) {
        posts(first: $displayUnit, skip: $page, orderBy: createdAt_DESC) {
            id
            title
            content
            description
            createdAt
            thumbnail {
                url
            }
        }
    }
`

簡単にクエリの説明です。
まず総数の取得は、、

    query maxPostCount  {
        # model名の複数形+Connection
        postsConnection  {
            # 集計の意味
            aggregate {
                # 総数を取得
                count 
            }
        }
    }

page単位の取得は、、

    query feachPostByPage($displayUnit: Int, $skip: Int) {
        # first = 最初から何件取得するか? $displayUnitで指定
     # skip = 抜かす件数 $skipで指定
        # orderBy = 並び順 createdAtの降順
        posts(first: $displayUnit, skip: $skip, orderBy: createdAt_DESC) {
            ...
        }
    }

なお、この部分はお使いのGraphQLのAPIサーバーの定義により異なるかもしれません。
GraphCMSの場合は、上記で取得できました。

vuetifyへの組み込み

サンプルコード

あとはvietifyのテンプレートに流し込めば終了です。


<template>
  <div>
    <v-container grid-list-xl>
      <!-- ローディングアイコン-->
      <div class="text-xs-center" v-if="loading">
        <v-progress-circular
            :size="50"
            color="primary"
            indeterminate
        ></v-progress-circular>
      </div>
      <!--投稿一覧-->
      <v-layout row wrap v-if="!loading">
        <PostCard
            v-for="post in posts"
            v-bind:key="post.id"
            :post=post
        ></PostCard>
      </v-layout>
      <!--ページネーション-->
      <div class="text-xs-center mt-5">
        <v-pagination
            v-model="pageNumber"
            :length="pageLength"
        ></v-pagination>
      </div>
    </v-container>
  </div>
</template>

<script>
  import HomeMainVisual from '../components/HomeMainVisual'
  import PostCard from '../components/PostCard'
  import {FEACH_POST_BY_PAGE, MAX_POST_COUNT} from "../constants/graphql"

  export default {
    name: "PostList",
    components: {
      PostCard,
      HomeMainVisual
    },
    data: () => ({
      pageNumber: 1, // 表示するページ番号
      pageDisplayUnit: 3, // 1ページの表示件数
      maxPostCount: 0, // 投稿総数(初期化で0)
      posts: [], // 投稿一覧
      loading: 0, // 通信の状態 apolloで制御
    }),
    mounted() {
      // マウント後に、vue-routerでクエリーパラムを取得
      // 取得したクエリーパラムを表示ページに設定
      let queryPage = this.$route.query.page
      this.pageNumber = queryPage != null ? parseInt(queryPage) : 1
    },
    watch: {
      // watchでpageNumberの状態を監視
      // pageNumberがページネーションのクリックで変化したときに、
      // vue-routerでページを変化させる
      pageNumber: function (newNumber) {
        this.$router.push({name: 'PagingDemo', query: {page: newNumber}})
      }
    },
    computed: {
      // 投稿総数を、1ページの表示件数で割ることで総ページ数を計算
      pageLength: function () {
        return Math.ceil(this.maxPostCount / this.pageDisplayUnit)
      }
    },
    apollo: {
      // 投稿総数の取得
      maxPostCount: {
        query: MAX_POST_COUNT,
        update(data) {
          return data.postsConnection.aggregate.count
        }
      },
      // ページごとの投稿を取得
      posts: {
        query: FEACH_POST_BY_PAGE,
        variables() {
          return {
            page: (this.$route.query.page - 1) * this.pageDisplayUnit,
            displayUnit: this.pageDisplayUnit
          }
        }
      },
    }

  }
</script>

<style>
  .v-pagination__navigation .v-icon {
    font-size: 1rem !important;
  }
</style>

解説

vuetifyとapolloのみでも見た目的にはページングはOKなのですが、URLが変化せず、
実質ajaxで読み込むスライドショーのような状態になってしまいます。
なので、vue-routerを連動させることで、プラウザの戻る、進むの対応、URLでのページ単位のアクセスを実装しています。

それを担っているのが、mountと、watchの処理です。

まず、vueのライフサイクルでDOMにアクセスが可能になるmoutedで、pageNumber(表示ページ)をURLのクエリパラーメーターより取得して設定しています。
(三項分岐で判定し、パラメーターがない場合は1ページ目として設定)

    mounted() {
      let queryPage = this.$route.query.page
      this.pageNumber = queryPage != null ? parseInt(queryPage) : 1
    },

さらにwatchでpageNumberの状態を監視。
pageNumberがページネーションのUIコンポーネントのクリックで変化したときに、vue-routerでページを変化させるという処理を実行しています。

    watch: {
      pageNumber: function (newNumber) {
        this.$router.push({name: 'PagingDemo', query: {page: newNumber}})
      }
    },

あとは、それぞれの変数に、データをバインディングして、Apolloで取得。表示という流れです。

おまけ

1件あたりの表示件数を設定しているpageDisplayUnitもリアクティブにすれば、このようなUXも表現もできます。
(使い所は不明)

スライダーで変更する1ページの表示数に追随して、ページリンクも変化しています。
jQueryで実装しようとすると大変だと思うのですが、vueならとても簡易に実装できて驚愕です。

output1.gif

11
14
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
14