Laravel 內建的 LengthAwarePaginator 與 Paginator 透過 Presenter 模式渲染分頁 HTML,預設輸出 Bootstrap 3 風格。本文示範如何建立自訂 Presenter 類別,分別擴展 paginate()(完整分頁)與 simplePaginate()(簡易上一頁/下一頁)樣式,並說明在 Blade 模板中套用方式。
Laravel 透過 Illuminate\Contracts\Pagination\Presenter 介面定義渲染規約:
interface Presenter {
public function render(); // 輸出 HTML
public function hasPages(); // 是否有分頁
}
LengthAwarePaginator::render() 內部實例化 BootstrapThreePresenter,我們只需實作自己的 Presenter 即可替換輸出樣式。
建立 app/Foundations/Pagination/CustomerPresenter.php:
<?php
namespace App\Foundations\Pagination;
use Illuminate\Contracts\Pagination\Presenter as PresenterContract;
use Illuminate\Contracts\Pagination\LengthAwarePaginator as PaginatorContract;
use Illuminate\Pagination\UrlWindow;
use Illuminate\Support\HtmlString;
use Illuminate\Pagination\BootstrapThreeNextPreviousButtonRendererTrait;
use Illuminate\Pagination\UrlWindowPresenterTrait;
class CustomerPresenter implements PresenterContract {
use BootstrapThreeNextPreviousButtonRendererTrait, UrlWindowPresenterTrait;
protected $paginator;
protected $window;
public function __construct(PaginatorContract $paginator, UrlWindow $window = null) {
$this->paginator = $paginator;
$this->window = is_null($window) ? UrlWindow::make($paginator) : $window->get();
}
public function hasPages() {
return $this->paginator->hasPages();
}
public function render() {
if ($this->hasPages()) {
return new HtmlString(sprintf(
'<ul class="pagination">%s %s %s</ul>',
$this->getPreviousButton('上一頁'), // 自訂文字
$this->getLinks(), // 頁碼連結
$this->getNextButton('下一頁') // 自訂文字
));
}
return '';
}
// 以下方法可視需求覆寫 HTML 結構
protected function getAvailablePageWrapper($url, $page, $rel = null) {
$rel = is_null($rel) ? '' : ' rel="' . $rel . '"';
return '<li><a href="' . htmlentities($url) . '"' . $rel . '>' . $page . '</a></li>';
}
protected function getDisabledTextWrapper($text) {
return '<li class="disabled hide"><span>' . $text . '</span></li>';
}
protected function getActivePageWrapper($text) {
return '<li class="active"><span>' . $text . '</span></li>';
}
protected function getDots() {
return $this->getDisabledTextWrapper('...');
}
protected function currentPage() { return $this->paginator->currentPage(); }
protected function lastPage() { return $this->paginator->lastPage(); }
}
{!! with(new \App\Foundations\Pagination\CustomerPresenter($categories))->render() !!}
繼承上述 Presenter,覆寫 render() 與 hasPages():
<?php
namespace App\Foundations\Pagination;
use Illuminate\Support\HtmlString;
use Illuminate\Contracts\Pagination\Paginator as PaginatorContract;
class CustomerSimplePresenter extends CustomerPresenter {
public function __construct(PaginatorContract $paginator) {
$this->paginator = $paginator;
}
public function hasPages() {
return $this->paginator->hasPages() && count($this->paginator->items()) > 0;
}
public function render() {
if ($this->hasPages()) {
return new HtmlString(sprintf(
'<ul class="pager">%s %s</ul>',
$this->getPreviousButton('上一篇'),
$this->getNextButton('下一篇')
));
}
return '';
}
}
{!! with(new \App\Foundations\Pagination\CustomerSimplePresenter($categories))->render() !!}
render() 中加入 <input type="number"> 並綁定 JS 跳轉。$this->paginator->total(), firstItem(), lastItem()。__('pagination.previous') 透過語系檔管理。ul class 與按鈕結構即可。paginate() 回傳 LengthAwarePaginator,需實作 LengthAwarePaginatorContract。simplePaginate() 回傳 Paginator,需實作 PaginatorContract。render(Presenter $presenter) 委派輸出,預設用 BootstrapThreePresenter。PresenterContract 兩個方法,極度彈性。掌握 Presenter 模式後,Laravel 分頁樣式客製化變得直觀且可維護。建立專屬 Presenter 類別、覆寫 render() 輸出所需 HTML,即可滿足任何設計需求,無需修改核心檔案,完美符合 SOLID 原則。