はじめに
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件取得する
コントローラ
1
2
3
4
5
6
7
8
9
class Api::V1::UsersController < Api::V1::BaseController
+ include Pagy::Backend
def index
- users = User.all
+ pagy, users = pagy(User.all)
render json: users
end
end
PostmanでAPIを叩いてレスポンスを確認してみます。
このように、userのデータが3件ずつ取得できていました(シリアライザーを使っているので、カラム名がキャメルケースになっています)。
しかし、これだけでは現在のページや総ページ数がわかりません。フロント側のページネーションコンポーネントではそれらのデータが必要なので、追加で記述していきます。
ヘッダーにページの情報を入れる
1
2
3
4
5
6
7
8
9
10
11
+ require 'pagy/extras/headers'
class Api::V1::UsersController < Api::V1::BaseController
include Pagy::Backend
def index
pagy, users = pagy(User.all)
+ pagy_headers_merge(pagy)
render json: users
end
end
引用: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)