在製作網頁或是應用程式的圖形使用者介面時,如果需要顯示比較大量的資料,通常製作「換頁」、「分頁」功能。但是若是做了這樣的功能,就必須要讓使用者能夠有方法進行「跳頁」的動作。要讓使用者能夠在圖形介面上進行跳頁,比較簡單的方式就是弄個下拉式選單把所有頁碼列出來讓使用者選,或者乾脆放上一個文字輸入方塊直接讓使用者自行輸入要查詢的頁碼。當然,比較潮的方式還是弄一個數字分頁導覽列(Pagination Bar),會更方便操作。不過數字分頁導覽列的產生其實有點麻煩,因為通常要考慮到目前使用者正在查看的頁數、總頁數和圖形畫面上能夠顯示的頁碼數量的空間。在Wordpress中,甚至還提供了一個paginate_links函數,專門用來產生數字分頁導覽列。



下圖正是一個Wordpress產生的數字分頁導覽列:

paginator

我們可以發現,這個數字分頁導覽列有著5種不同的項目(按鈕),列表如下:

  • 上一頁:在目前頁碼不是第一頁的時候出現。
  • 其它頁碼:顯示頁碼並提供超連結。
  • 目前頁碼:加強顯示頁碼,但不提供超連結。
  • 省略:顯示...,表示一個或多個連續的頁碼。當最兩端頁碼的數量和中間(目前頁碼)兩側的頁碼數量已達到上限時,其餘的頁碼就會被省略。(其實如果只有一個頁碼的話不該省略的才對,這是paginate_links函數實作方式的問題)
  • 下一頁:在目前頁碼不是最後一頁的時候出現。

雖然Wordpress的paginate_links函數還是有它的問題在,但它確實是一個方便的函數。所以在Rust上當然也要有一個類似的功能啦!而且還能跨平台使用呢!

Paginator

「Paginator」是筆者開發的套件,用來在網頁或是其它使用者介面上產生數字分頁導覽列。

Crates.io

https://crates.io/crates/paginator

Cargo.toml

paginator = "*"

使用方法

剛才提到的數字分頁導覽列「項目」,在paginator這個crate中是以PageItem列舉來代表。

Paginator或是PaginatorIter結構體的builder關聯函數可以用來建立PaginatorBuilder結構實體,用以安全地產生Paginator或是PaginatorIter結構實體。PaginatorIter結構實體可以用來產生不同「目前頁碼」的Paginator結構實體,而Paginator結構實體則有paginate方法,可以回傳一個Vec<PageItem>結構實體。按照順序走訪Vec<PageItem>結構實體中的PageItem列舉的變體,即可產生出數字分頁導覽列。

以下是一個產生出總頁數為5,目前頁數為1的數字分頁導覽列的例子:

extern crate paginator;

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,且目前頁數為12的數字分頁導覽列的例子:

extern crate paginator;

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變體來取代。另外,也可以設定保留任意數量的最前或是最後的頁碼,讓它們不會被省略。

詳情可以參考以下程式碼:

extern crate paginator;

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()));