はじめに
RailsAPIとVuetifyでページネーションを作りました。
gemをどれにしようか調べてみたところ、Pagyがやたらとシンプル!軽い!ということらしいので、Pagyを使いました。
環境、使用技術
- Rails 5.2.4.2
- Pagy 3.8.1
- Vue.js 4.3.1
- Vuetify 2.2.21
- axios 0.19.2
Vuetifyやaxiosは他のものでも置き換え可能かなと思います。
Rails側
Pagyの初期設定
How To | Pagyに書いてある通りです。
毎度おなじみ$ bundle install
を実行し、config/initializers/pagy.rb
に設定ファイルを作成します。
テンプレートをコピペして、必要なところだけコメントアウトを外します。
1
|
Pagy::VARS[:items] = 3 # 1ページに3件取得する
|
コントローラ
PostmanでAPIを叩いてレスポンスを確認してみます。
このように、userのデータが3件ずつ取得できていました(シリアライザーを使っているので、カラム名がキャメルケースになっています)。
しかし、これだけでは現在のページや総ページ数がわかりません。フロント側のページネーションコンポーネントではそれらのデータが必要なので、追加で記述していきます。
ヘッダーにページの情報を入れる
引用:Headers | Pagy
この記述により、レスポンスヘッダーに以下の情報が格納されます。
KEY |
|
Link |
最初・最後のページ、前・次のページのリンク |
Current-Page |
現在のページ番号 |
Page-Items |
1 ページの user の数 |
Total-Pages |
全てのページ数 |
Total-Count |
全ての user の数 |
“Link"の中身(実際は一行)↓
<http://127.0.0.1:3000/api/v1/users?page=1>; rel="first",
<http://127.0.0.1:3000/api/v1/users?page=1>; rel="prev",
<http://127.0.0.1:3000/api/v1/users?page=3>; rel="next",
<http://127.0.0.1:3000/api/v1/users?page=3>; rel="last"
これでRails側の処理は終わりです。
共通化する場合は、after_action
を使う方法もあります(see 公式)。
Vue側
Vue-routerは使っていません。
テンプレート部分
Pagination component — Vuetify.jsを少しカスタマイズします。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
<template>
<div class="text-center">
<v-pagination
v-model="currentPage"
:length="page.totalPages"
></v-pagination>
</div>
</template>
<script>
export default {
data() {
return {
requestUrl: "/api/v1/users",
page: {
currentPage: 1,
totalPages: 5,
},
};
},
};
</script>
|
これでひとまずページネーションを表示することはできましたが、まだ、ボタンを押してもpage.currentPage
の値が変わるだけです。
ボタンを押したときの挙動
コンポーネントから@input
イベントを受け取り、changePage
メソッドで処理を行います。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
<template>
<div class="text-center">
<v-pagination
v-model="currentPage"
:length="page.totalPages"
+ @input="changePage"
></v-pagination>
</div>
</template>
<script>
export default {
data () {
return {
+ requestUrl: "/api/v1/users",
page: {
currentPage: 1,
totalPages: 5,
}
}
},
+ methods: {
+ changePage(val) {
+ // 処理
+ }
+ }
}
</script>
|
1
2
3
4
5
6
7
8
9
|
methods: {
async changePage(val) {
// "/api/v1/users?page=2"などにGETリクエストを送る
const response = await this.$axios.get(`${this.requestUrl}?page=${val}`)
// 受け取ったusersデータを格納する
const { users } = response.data
this.users = users
}
}
|
ページ読み込み時のデータ取得
mounted
で最初の画面描画時の動きを記述します。
1
2
3
4
5
6
7
8
9
10
|
async mounted() {
try {
// "/api/v1/users"にGETリクエストを送る
const response = await this.$axios.get(this.requestUrl)
// それぞれのdataにレスポンスの値を代入する
this.page.totalPages = Number(response.headers["total-pages"])
const { users } = response.data
this.users = users
}
}
|
最終的なコード
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
<template>
<!-- usersの表示部分。省略 -->
<div class="text-center">
<v-pagination
v-model="page.currentPage"
:length="page.totalPages"
@input="changePage"
/>
</div>
</template>
<script>
import goTo from "vuetify/es5/services/goto"; // しれっと追加している
export default {
data() {
return {
requestUrl: "/api/v1/users",
page: {
currentPage: 1,
totalPages: 1,
},
users: [],
};
},
async mounted() {
try {
const response = await this.$axios.get(this.requestUrl);
this.page.totalPages = Number(response.headers["total-pages"]);
const { users } = response.data;
this.users = users;
}
},
methods: {
async changePage(val) {
goTo(0); // ページ最上部までスクロール。Vuetifyのメソッド
const res = await this.$axios.get(`${this.requestUrl}?page=${val}`);
const { users } = res.data;
this.users = users;
},
},
};
</script>
|
ちなみに
追加でヘッダーに情報を渡す場合
以下のように書くことで追加できます。requestUrl
を初期値のdataで設定するのが難しい場合は、このようにヘッダーに渡して受け取る方法もあります。
1
2
3
4
5
6
|
def index
pagy, users = pagy(User.all)
pagy_headers_merge(pagy)
response.headers.merge!({ 'Request-Url' => request.path_info })
render json: users
end
|
参考リンク
rails APIでページネーションを実装する
【vue.js】Vuetifyで簡単ページネーション(Paginations)