在製作網頁或是應用程式的圖形使用者介面時,如果需要顯示比較大量的資料,通常製作「換頁」、「分頁」功能。但是若是做了這樣的功能,就必須要讓使用者能夠有方法進行「跳頁」的動作。要讓使用者能夠在圖形介面上進行跳頁,比較簡單的方式就是弄個下拉式選單把所有頁碼列出來讓使用者選,或者乾脆放上一個文字輸入方塊直接讓使用者自行輸入要查詢的頁碼。當然,比較潮的方式還是弄一個數字分頁導覽列(Pagination Bar),會更方便操作。不過數字分頁導覽列的產生其實有點麻煩,因為通常要考慮到目前使用者正在查看的頁數、總頁數和圖形畫面上能夠顯示的頁碼數量的空間。在Wordpress中,甚至還提供了一個paginate_links
函數,專門用來產生數字分頁導覽列。
下圖正是一個Wordpress產生的數字分頁導覽列:
我們可以發現,這個數字分頁導覽列有著5種不同的項目(按鈕),列表如下:
- 上一頁:在目前頁碼不是第一頁的時候出現。
- 其它頁碼:顯示頁碼並提供超連結。
- 目前頁碼:加強顯示頁碼,但不提供超連結。
- 省略:顯示
...
,表示一個或多個連續的頁碼。當最兩端頁碼的數量和中間(目前頁碼)兩側的頁碼數量已達到上限時,其餘的頁碼就會被省略。(其實如果只有一個頁碼的話不該省略的才對,這是paginate_links
函數實作方式的問題) - 下一頁:在目前頁碼不是最後一頁的時候出現。
雖然Wordpress的paginate_links
函數還是有它的問題在,但它確實是一個方便的函數。所以在Rust上當然也要有一個類似的功能啦!而且還能跨平台使用呢!
Paginator
「Paginator」是筆者開發的套件,用來在網頁或是其它使用者介面上產生數字分頁導覽列。
在Rust上使用Paginator
Crates.io
Cargo.toml
使用方法
剛才提到的數字分頁導覽列「項目」,在paginator
這個crate中是以PageItem
列舉來代表。
Paginator
或是PaginatorIter
結構體的builder
關聯函數可以用來建立PaginatorBuilder
結構實體,用以安全地產生Paginator
或是PaginatorIter
結構實體。PaginatorIter
結構實體可以用來產生不同「目前頁碼」的Paginator
結構實體,而Paginator
結構實體則有paginate
方法,可以回傳一個Vec<PageItem>
結構實體。按照順序走訪Vec<PageItem>
結構實體中的PageItem
列舉的變體,即可產生出數字分頁導覽列。
以下是一個產生出總頁數為5
,目前頁數為1
的數字分頁導覽列的例子:
use paginator::{Paginator, PageItem};
use core::fmt::Write;
let paginator = Paginator::builder(5).current_page(1).build_paginator().unwrap();
let mut html = String::new();
for page_item in paginator.paginate() {
match page_item {
PageItem::Prev(page) => {
// `PageItem::Prev` variant is used when the `has_prev` option is not set to `YesNoDepends::No`.
html.write_fmt(format_args!("<li><a href=\"/page/{page}\">«</a></li>", page = page)).unwrap();
}
PageItem::Page(page) => {
html.write_fmt(format_args!("<li><a href=\"/page/{page}\">{page}</a></li>", page = page)).unwrap();
}
PageItem::CurrentPage(page) => {
html.write_fmt(format_args!("<li>{page}</li>", page = page)).unwrap();
}
PageItem::Ignore => {
html.push_str("<li>...</li>");
}
PageItem::Next(page) => {
// `PageItem::Next` variant is used when the `has_next` option is not set to `YesNoDepends::No`.
html.write_fmt(format_args!("<li><a href=\"/page/{page}\">»</a></li>", page = page)).unwrap();
}
_ => {
// `PageItem::ReservedPrev` or `PageItem::ReservedNext` variant is used only when the `has_prev` option or the `has_next` option is set to `YesNoDepends::Yes`.
}
}
}
而以下是一個產生出總頁數為2
,且目前頁數為1
或2
的數字分頁導覽列的例子:
use paginator::{Paginator, PageItem};
let mut paginator_iter = Paginator::builder(2).build_paginator_iter().unwrap();
for page_item in paginator_iter.next().unwrap().paginate() {
// current_page == 1
}
for page_item in paginator_iter.next().unwrap().paginate() {
// current_page == 2
}
數字分頁規則
在建立出Paginator
結構實體前,有一個重要的max_item_count
選項可以透過PaginatorBuilder
結構實體來設定。這個max_item_count
選項能夠限制數字分頁導覽列中項目的數量,會在paginate
時,將離目前頁面比較遠的多餘頁碼以PageItem::Ignore
變體來取代。另外,也可以設定保留任意數量的最前或是最後的頁碼,讓它們不會被省略。
詳情可以參考以下程式碼:
use paginator::{Paginator, page_items_to_string};
let mut p = Paginator::builder(8).max_item_count(9).start_size(1).end_size(1).build_paginator_iter().unwrap();
assert_eq!("1* 2 3 4 5 6 7 8 >", page_items_to_string(p.next().unwrap().paginate().as_slice()));
assert_eq!("< 1 2* 3 4 5 ... 8 >", page_items_to_string(p.next().unwrap().paginate().as_slice()));
assert_eq!("< 1 2 3* 4 5 ... 8 >", page_items_to_string(p.next().unwrap().paginate().as_slice()));
assert_eq!("< 1 2 3 4* 5 ... 8 >", page_items_to_string(p.next().unwrap().paginate().as_slice()));
assert_eq!("< 1 ... 4 5* 6 7 8 >", page_items_to_string(p.next().unwrap().paginate().as_slice()));
assert_eq!("< 1 ... 4 5 6* 7 8 >", page_items_to_string(p.next().unwrap().paginate().as_slice()));
assert_eq!("< 1 ... 4 5 6 7* 8 >", page_items_to_string(p.next().unwrap().paginate().as_slice()));
assert_eq!("< 1 2 3 4 5 6 7 8*", page_items_to_string(p.next().unwrap().paginate().as_slice()));
在JavaScript/TypeScript上使用Paginator (pagination-bar-generator)
由於npm上「paginator」這個名稱已經被別人使用,所以改取名為pagination-bar-generator
。
npmjs.com
npm 安裝指令
使用方法
import { Paginator } from "pagination-bar-generator";
const paginator = Paginator.builder(5).currentPage(1).buildPaginator();
let html = "";
for (const pageItem of paginator.paginate()) {
if (pageItem.isPrev()) {
html += `<li><a href="/page/${pageItem.pageNumber}">«</a></li>`;
} else if (pageItem.isPage()) {
html += `<li><a href="/page/${pageItem.pageNumber}">${pageItem.pageNumber}</a></li>`;
} else if (pageItem.isCurrentPage()) {
html += `<li>${pageItem.pageNumber}</li>`;
} else if (pageItem.isIgnore()) {
html += `<li>...</li>`;
} else if (pageItem.isNext()) {
html += `<li><a href="/page/${pageItem.pageNumber}">»</a></li>`;
} else {
// `PageItem.ReservedPrev` or `PageItem.ReservedNext` variant is used only when the `hasPrev` option or the `hasNext` option is set to `YesNoDepends.Yes`.
}
}