完成イメージ
bootstrap vueのコンポーネントを使うと、ブラウザバックやリロードをしたときに、URL上のページ番号と実際に表示されるページがちぐはぐになったりする。
今回は、自作する方法を解説。
ちなみにveutifyとかのフレームワークならこんなことしなくても、既存のコンポーネントで対応できるから該当する人だけ読んでくれ。
Laravel側の設定
コントローラー
//StockController.php
namespace App\\Http\\Controllers;
use App\\Models\\Stock;
use App\\Http\\Resources\\StockResource;
class StockController extends Controller
{
public function index(){
$stocks =StockResource::collection(Stock::orderBy('created_at', 'desc')->paginate(10));
return $stocks;
}
ごめん、Resourcesつかってるからちょっとややこしいよな
とりあえず記事一覧のレコードがとれたらなんでもいいと思う。
paginate(10))
が重要。
1ページ当たりの表示件数は10件ですよ
って意味になる。
リソース
//プロジェクトディレクトリ\\app\\Http\\Resources\\StockResource.php
<?php
namespace App\\Http\\Resources;
use Carbon\\Carbon;
use Illuminate\\Http\\Resources\\Json\\JsonResource;
class StockResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \\Illuminate\\Http\\Request $request
* @return array
*/
public function toArray($request)
{
return parent::toArray($request);
}
}
api
//api.php
<?php
use Illuminate\\Http\\Request;
use Illuminate\\Support\\Facades\\Route;
use App\\Http\\Controllers\\StockController;//追記
/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| is assigned the "api" middleware group. Enjoy building your API!
|
*/
Route::get('/stocks', [StockController::class, 'index']);//追記
Vue側
コード全体
//StockArhive.vue
<template>
<div>
<h1>{{title}}</h1>
<b-row>
<b-col cols="6" sm="3" class="b-col" v-for="stock in stocks" :key="stock.id">
//中略
</b-col>
</b-row>
<div class="text-center">
現在のページ:{{current_page}}<br>
トータルページ数:{{length}}<br>
トータル記事数:{{totalStocksPer}}<br>
<nav aria-label="Page navigation example">
<ul class="pagination justify-content-center">
<li class="page-item"><button class="page-link" @click="changePage(1)">
«</button></li>
<li class="page-item"><button class="page-link" @click="changePage(previous)">
‹</button></li>
<li role="separator" class="page-item disabled bv-d-xs-down-none" v-if="current_page > 2"><span
class="page-link">…</span>
</li>
<li class="page-item"><button class="page-link" v-if="current_page -2 > 0"
@click="changePage(current_page - 2)">{{current_page -2}}</button></li>
<li class="page-item"><button class="page-link" v-if="current_page -1 > 0"
@click="changePage(current_page - 1)">{{current_page -1}}</button></li>
<li class="page-item active"><button class="page-link"
@click="changePage(current_page)">{{current_page}}</button></li>
<li class="page-item"><button class="page-link" v-if="current_page +1 <= length"
@click="changePage(current_page + 1)">{{current_page +1}}</button></li>
<li class="page-item"><button class="page-link" v-if="current_page +2 < length"
@click="changePage(current_page + 2)">{{current_page +2}}</button></li>
<li role="separator" class="page-item disabled bv-d-xs-down-none" v-if="current_page +1 < length">
<span class="page-link">…</span>
</li>
<li class="page-item"><button class="page-link" @click="changePage(next)">
›</button></li>
<li class="page-item"><button class="page-link" @click="changePage(length)">
»</button></li>
</ul>
</nav>
</div>
</div>
</template>
<script>
import Header from "../layout/Header";
import Footer from "../layout/Footer";
export default {
components: {
Header,
Footer,
},
data() {
return {
title: 'Archive',
stocks: null,
current_page: null,
lists: [],
length: null,
parPage: null,
totalStocksPer: null,
pages: null,
previous: null,
next: null,
}
},
mounted() {
this.current_page = Number(this.$route.query.page) || 1
this.showArchive()
},
methods: {
async showArchive() {
const result = await axios.get(/api/stocks?page=${this.current_page}
);
const stocks = result.data;
this.stocks = stocks.data;
this.parPage = stocks.meta.per_page //1ページ当たりの表示件数
this.totalStocksPer = stocks.meta.total //全部でアイテムが何個あるか
this.length = stocks.meta.last_page //総ページ数を取得
this.makePagenation()
},
makePagenation() {
this.pages = []
for (let i = 1; i < this.length + 1; i++) {
//ページ番号とリンク先をオブジェクトで追加
this.pages.push({
no: i,
})
}
//1個前のページ
if (this.current_page !== 1) {
this.previous = this.current_page - 1
} else {
this.previous = 1
}
//次のページ
if (this.current_page !== this.length) {
this.next = this.current_page + 1
} else {
this.next = this.length
}
},
changePage(number) {
this.current_page = number
this.showArchive()
window.history.pushState({
number
},
Page${number}
,
${window.location.origin}/stocks?page=${number}
)
this.moveToTop()
},
moveToTop() {
window.scrollTo({
top: 0,
});
},
}
}
</script>
ばりめんどかった
解説
showArchive()メソッドについて
async showArchive() {
const result = await axios.get(/api/stocks?page=${this.current_page}
);
const stocks = result.data;
this.stocks = stocks.data;
this.parPage = stocks.meta.per_page //1ページ当たりの表示件数
this.totalStocksPer = stocks.meta.total //全部でアイテムが何個あるか
this.length = stocks.meta.last_page //総ページ数を取得
this.makePagenation()
},
api経由で記事一覧を取得している。
コントローラーに書いた内容が返ってきているだけ。
コントローラーに書いた「paginate()」って関数が
1ページ当たりの表示件数とかなどなどいろいろな情報を教えてくれる。
makePagenation()メソッドについて
makePagenation() {
this.pages = []
for (let i = 1; i < this.length + 1; i++) {
//ページ番号とリンク先をオブジェクトで追加
this.pages.push({
no: i,
})
}
//1個前のページ
if (this.current_page !== 1) {
this.previous = this.current_page - 1
} else {
this.previous = 1
}
//次のページ
if (this.current_page !== this.length) {
this.next = this.current_page + 1
} else {
this.next = this.length
}
},
ページネーション作成に必要な情報を生成している。
今開いているページの1つ前のページは、何番?
次のページは何番?
みたいな情報をつかさどる。
changePage()メソッドについて
changePage(number) {
this.current_page = number
this.showArchive()
window.history.pushState({
number
},
Page${number}
,
${window.location.origin}/stocks?page=${number}
)
this.moveToTop()
},
ページネーション上の移動したいページ番号のボタンがクリックされたら、その番号を取得してそのページのURLに移動する。
たとえば「3」がクリックされたら、3ページにあたる
ドメイン/stocks?page=3
のURLに移動する。
moveToTop()関数について
moveToTop() {
window.scrollTo({
top: 0,
});
},
一番上までスクロールするだけ。
ページ送りした後に、スクロールバーの位置がそのままになってしまうので
この関数をわざわざ作って上までスクロールし直している。
html部分について
<ul class="pagination justify-content-center">
<li class="page-item"><button class="page-link" @click="changePage(1)">
«</button></li>
<li class="page-item"><button class="page-link" @click="changePage(previous)">
‹</button></li>
<li role="separator" class="page-item disabled bv-d-xs-down-none" v-if="current_page > 2"><span
class="page-link">…</span>
</li>
<li class="page-item"><button class="page-link" v-if="current_page -2 > 0"
@click="changePage(current_page - 2)">{{current_page -2}}</button></li>
<li class="page-item"><button class="page-link" v-if="current_page -1 > 0"
@click="changePage(current_page - 1)">{{current_page -1}}</button></li>
<li class="page-item active"><button class="page-link"
@click="changePage(current_page)">{{current_page}}</button></li>
<li class="page-item"><button class="page-link" v-if="current_page +1 <= length"
@click="changePage(current_page + 1)">{{current_page +1}}</button></li>
<li class="page-item"><button class="page-link" v-if="current_page +2 < length"
@click="changePage(current_page + 2)">{{current_page +2}}</button></li>
<li role="separator" class="page-item disabled bv-d-xs-down-none" v-if="current_page +1 < length">
<span class="page-link">…</span>
</li>
<li class="page-item"><button class="page-link" @click="changePage(next)">
›</button></li>
<li class="page-item"><button class="page-link" @click="changePage(length)">
»</button></li>
</ul>
「<<」 先頭のページに飛ぶボタン
<li class="page-item"><button class="page-link" @click="changePage(1)">
«</button></li>
単純に1ページ目に飛ぶだけ
「<」 1ページ前に戻るボタン
<li class="page-item"><button class="page-link" @click="changePage(previous)">
‹</button></li>
存在しないページに飛ばないように、移動先のページに番号についてはmakePagenation() 関数側で設定済み
2個前のページ番号のボタン
<li class="page-item"><button class="page-link" v-if="current_page -2 > 0"
@click="changePage(current_page - 2)">{{current_page -2}}</button></li>
ただし「0ページ」や「-1ページ」や「-2ページ」など存在しないページボタンが表示されないようにv-ifで対策済み
1個前のページ番号のボタン
<li class="page-item"><button class="page-link" v-if="current_page -1 > 0"
@click="changePage(current_page - 1)">{{current_page -1}}</button></li>
ただし「0ページ」や「-1ページ」など存在しないページボタンが表示されないようにv-ifで対策済み
現在のページ
<li class="page-item active"><button class="page-link"
@click="changePage(current_page)">{{current_page}}</button></li>
クラスにactiveを付与することにより、デザインがほかのボタンとは変わっている。
1個次のページ番号のボタン
<li class="page-item"><button class="page-link" v-if="current_page +1 <= length"
@click="changePage(current_page + 1)">{{current_page +1}}</button></li>
ただし現在のページが最終ページなら表示しない
2個次のページ番号のボタン
<li class="page-item"><button class="page-link" v-if="current_page +2 < length"
@click="changePage(current_page + 2)">{{current_page +2}}</button></li>
ただし最終ページか、最終ページより1つ前のページなら表示しない
「>」 1ページ次に進むボタン
<li class="page-item"><button class="page-link" @click="changePage(next)">
›</button></li>
存在しないページに飛ばないように、移動先のページに番号についてはmakePagenation() 関数側で設定済み
「>>」最後のぺージに進むボタン
<li class="page-item"><button class="page-link" @click="changePage(length)">
»</button></li>
最後のページが何ページにあたるのかはmakePagenation() 関数側で設定済み
以上だ。
bootstrap vueのページネーションコンポーネントはクソ
一応、bootstrap vue公式ドキュメントに従うならこういう書き方もできるが・・・
<template>
<div class="overflow-auto">
<b-pagination @change="changePage" v-model="current_page" :total-rows="totalStocksPer"
:per-page="parPage" align="center">
</b-pagination>
</div>
</template>
おそらくブラウザバックやリロードされることが想定されていない。
こんな風に表示内容とURL、ページネーションがバラバラになる。
公式ドキュメント見ても、なんかテーブルをページ送りするサンプルが書かれていたからそういうことなんでしょう。