作為一個網頁開發人員,你經常要用容易閱讀的格式把數據顯示給用戶,舉例說你要從數據庫讀取一份雇員名單,並在網頁上羅列出來,若果名單只有區區十多人,用一個簡單迴圈把所有人列印出來便好了,不是很簡單嗎?但若果你有五十名雇員又如何呢?一百人?一千人?把這麼多人一口氣羅列出來顯然不是一個好主意,Crayon Violent 在 PHP Freaks 寫了一篇教學文件,介紹如何透過 PHP 來實作分頁。
同一時間從數據庫扯出所有數據,你的用戶可能會一面呆等一面納悶,系統究竟發甚麼神經老是沒有輸出?千呼萬喚有結果了,得到的竟然是一本完完整整的「三國演義」,在一頁版面上列印出來!據說古人的確是把不論長短的文章寫在一匹絹布上,一種好像稱為卷軸的東西,詳情我也不清楚,但廿一世紀的你不會也這麼做吧?
聰明的做法是把一份長長的名單分割成很多小段,每次只從數據庫提取及顯示一段,這可以大幅降低伺服器的工作量,也提升網頁的下載速度,用戶也比較容易消化屏幕上的資料,不會一下子被你弄至消化不良,這種做法就是分頁。
一個基本的分頁函式初看起來可能又長又可怖,但其實你只要閉上眼睛,深吸一口氣,然後閱讀每一個分段,便會發現它其實是很簡單的東西。根據 Crayon 多年來在論壇上幫助別人的經驗,人們對分頁的最大困難在於搞不清這種技術叫作甚麼!既然我們搞定了最困難的部份,餘下的便易如反掌了,不是嗎?
首先,我們要在數據庫中建立一個資料表,並放進一些數據,這裏不會討論安裝數據庫、建立資料表等細節,若果你不清楚這些東西,這份教學文件並不適合你。資料表的內容並不重要,若果你已經有一個滿載數據的資料表可作為測試用途,歡迎把以下程式中資料表和欄位的名稱更改,為了方便說明,文中的資料表名稱是「numbers」,它有兩個欄位:「number」和「id」,兩者的類型都是 int,其中「id」是自動遞增的。
若果你打算用這些名稱自行建立資料表,不妨用以下的程式片段來產生測試用的數據:
1
2
3
4
5
6
7
8
9
10
|
<?php
$conn = mysql_connect('localhost','dbusername','dbpassword') or trigger_error("SQL", E_USER_ERROR);
$db = mysql_select_db('dbname',$conn) or trigger_error("SQL", E_USER_ERROR);
for ($x = 0; $x < 106; $x++) {
$number = rand(100,999);
$sql = "INSERT INTO numbers (number, id) VALUES ($number, '')";
$query = mysql_query($sql, $conn) or trigger_error("SQL", E_USER_ERROR);
}
?>
|
長話短說:這個程式會產生 106 個隨機的 3 位數,為甚麼是 106 個呢?這只是隨便選的,而且,分頁程式每次會顯示 10 筆資料,這樣便可以保證最後一頁少於 10 筆資料。為甚麼是 3 位數呢?為甚麼最後一頁要顯示 6 筆資料?這些全部都是隨意選擇的,沒有特別的原因,請相信我。
好了,正式開始前我們先看看偉大的分頁程式完整版本,很多教學文件把程式解剖成一小段一小段,讀者們被逼重複多次的複製和貼上,十分令人困擾,但偏偏與本文的宗旨互相矛盾,不是很諷刺嗎?
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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
|
<?php
// 數據庫連結資料
$conn = mysql_connect('localhost','dbusername','dbpass') or trigger_error("SQL", E_USER_ERROR);
$db = mysql_select_db('dbname',$conn) or trigger_error("SQL", E_USER_ERROR);
// 計算資料表中有多少列
$sql = "SELECT COUNT(*) FROM numbers";
$result = mysql_query($sql, $conn) or trigger_error("SQL", E_USER_ERROR);
$r = mysql_fetch_row($result);
$numrows = $r[0];
// 每頁顯示的列數
$rowsperpage = 10;
// 計算總共需要多少頁
$totalpages = ceil($numrows / $rowsperpage);
// 取得當前的頁數,或者顯示預設的頁數
if (isset($_GET["currentpage"]) && is_numeric($_GET["currentpage"])) {
// 把變量的類型轉換成 int
$currentpage = (int) $_GET["currentpage"];
} else {
// 預設的頁數
$currentpage = 1;
} // end if
// 若過當前的頁數大於頁數總數
if ($currentpage > $totalpages) {
// 把當前頁數設定為最後一頁
$currentpage = $totalpages;
} // end if
// 若果當前的頁數小於 1
if ($currentpage < 1) {
// 把當前頁數設定為 1
$currentpage = 1;
} // end if
// 根據當前頁數計算名單的起始位置
$offset = ($currentpage - 1) * $rowsperpage;
// 從數據庫取資料
$sql = "SELECT id, number FROM numbers LIMIT $offset, $rowsperpage";
$result = mysql_query($sql, $conn) or trigger_error("SQL", E_USER_ERROR);
// 若果還有資料的話...
while ($list = mysql_fetch_assoc($result)) {
// 列印資料
echo $list['id'] . " : " . $list['number'] . "\n";
} // end while
// 建立分頁連結
// 顯示的頁數範圍
$range = 3;
// 若果正在顯示第一頁,無需顯示「前一頁」連結
if ($currentpage < 1) {
// 使用 << 連結回到第一頁
echo " <a href=\"{$_SERVER[\"><<</a> ";
// 前一頁的頁數
$prevpage = $currentpage - 1;
// 使用 < 連結回到前一頁
echo " <a href=\"{$_SERVER[\"><</a> ";
} // end if
// 顯示當前分頁鄰近的分頁頁數
for ($x = (($currentpage - $range) - 1); $x < (($currentpage + $range) + 1); $x++) {
// 如果這是一個正確的頁數
if (($x > 0) && ($x <= $totalpages)) {
// 如果這一頁等於當前頁數...
if ($x == $currentpage) {
// 不使用連結, 但用高亮度顯示
echo " [<b>$x</b>] ";
// 如果這一頁不是當前頁數...
} else {
// 顯示連結
echo " <a href=\"{$_SERVER[\">$x</a> ";
} // end else
} // end if
} // end for
// 如果不是最後一頁, 顯示跳往下一頁及最後一頁的連結
if ($currentpage != $totalpages) {
// 下一頁的頁數
$nextpage = $currentpage + 1;
// 顯示跳往下一頁的連結
echo " <a href=\"{$_SERVER[\">></a> ";
// 顯示跳往最後一頁的連結
echo " <a href=\"{$_SERVER[\">>></a> ";
} // end if
// 完成建立分頁連結
?>
|
好了,就是這麼多代碼,它的基本構思是:
- 計算你的資料表有多少列
- 根據每頁你想顯示的列數,計算有多少分頁
- 照出當前的頁數
- 從數據庫提取需要顯示的資料
- 顯示資料
- 製作一些「第一頁」、「前一頁」、當前頁鄰近的分頁、「下一頁」和「最後一頁」的連結
上面的原碼有很多注釋,所以你應該可以弄清楚是怎麼回事,無論如何,下面我們會把它拆解分析,好,繼續前進。
1
2
3
|
// 數據庫連結資料
$conn = mysql_connect('localhost','dbusername','dbpass') or trigger_error("SQL", E_USER_ERROR);
$db = mysql_select_db('dbname',$conn) or trigger_error("SQL", E_USER_ERROR);
|
以上是幾行很顯然只是用來連接數據庫。
1
2
3
4
5
|
// 計算資料表中有多少列
$sql = "SELECT COUNT(*) FROM numbers";
$result = mysql_query($sql, $conn) or trigger_error("SQL", E_USER_ERROR);
$r = mysql_fetch_row($result);
$numrows = $r[0];
|
知道了資料表有多少列,我們才知道有多少分頁,因此,我們執行基本的 count(*) 查詢並把接過儲存在 $numrows。
1
2
3
4
|
// 每頁顯示的列數
$rowsperpage = 10;
// 計算總共需要多少頁
$totalpages = ceil($numrows / $rowsperpage);
|
$rowsperpage 是每頁顯示的列數,我們把總列數($numrows)除以每頁的列數($rowsperpage),便知道有多少分頁需要顯示,由於不會有半頁(除非給你的愛犬啃掉了),我們使用 celi() 來計算確實的頁數。
1
2
3
4
5
6
7
8
|
// 取得當前的頁數,或者顯示預設的頁數
if (isset($_GET['currentpage']) && is_numeric($_GET['currentpage'])) {
// 把變量的類型轉換成 int
$currentpage = (int) $_GET['currentpage'];
} else {
// 預設的頁數
$currentpage = 1;
} // end if
|
當你點擊一個分頁的連結時,目標分頁的頁數會透過 GET 方法傳到伺服器,這裏正是要取得這個分頁頁數。由於第一次進入這個程式時並不會傳送這個變量,所以我們必須檢查流覽器是否傳來這個變量,與及它是否一個整數,說不定有人在 URL 手動更改這個變量的數值。
如果接收到這個變量,又是一個數字的話,我們便把它強制轉換為 int(整數)類型,原因是倘若有人在 URL 中把這個變量設定為 9.75,它便會轉換成第 9 分頁,除非你是哈利波特,否則應該很清楚世界上沒有第 9.75 分頁。
如果輸入的不是數字,我們預設為第一頁。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
// 若過當前的頁數大於總頁數...
if ($currentpage > $totalpages) {
// 把當前頁數設定為最後一頁
$currentpage = $totalpages;
} // end if
// 若果當前的頁數小於 1...
if ($currentpage < 1) {
// 把當前頁數設定為 1
$currentpage = 1;
} // end if
下一步是檢查這是否一個有效的頁碼,原因是倘若我們的小說只有 400 頁,有一個不滿小說結局,或者不肯相信小說就此結束的人,幻想我們還有一個隱藏起來的第 401 頁,於是直接在 URL 中把頁數更改為 401,那時怎麼辦?所以啊……如果有人輸入一個負數的頁數,或者超過總頁數的數字,我們分別把它預設為 1 和總頁數。
```php
// 根據當前頁數計算名單的起始位置
$offset = ($currentpage - 1) * $rowsperpage;
|
我們總共有 106 筆資料,但每次只顯示 10 筆,所以要計算從哪一筆資料開始提取,例如第 8 頁的起始列是第 80 筆資料,不過事實上應該是第 70 筆,因為電腦總愛從零開始計算,因此我們把目前的頁數(8)減去一,乘以每頁的列數(10)。再強調一次,電腦是從零開始計算的,所以:
第一頁我們將會顯示第 0 - 9 列
第二頁我們將會顯示第 10 - 19 列
……如此類推……
1
2
3
|
// 從數據庫取資料
$sql = "SELECT id, number FROM numbers LIMIT $offset, $rowsperpage";
$result = mysql_query($sql, $conn) or trigger_error("SQL", E_USER_ERROR);
|
現在執行 SQL 查詢讀取資料表,從指定的起始列數開始讀,提取 10 列數據,由於我們的資料表只有 106 列,最後一頁只有六列。
1
2
3
4
5
|
// 若果還有資料的話...
while ($list = mysql_fetch_assoc($result)) {
// 列印資料
echo $list['id'] . " : " . $list['number'] . "\n";
} // end while
|
跟著我們利用一個簡單迴路顯示目前網頁的數據列,這裏沒有 table 或者 div 等花招,因為不是這篇文件的目的。下一步我們將建立導航欄…
1
2
3
4
5
6
7
8
9
|
// 若果正在顯示第一頁,無需顯示「前一頁」連結
if ($currentpage > 1) {
// 使用 << 連結回到第一頁
echo " <a href=\"{$_SERVER[\"><<</a> ";
// 前一頁的頁數
$prevpage = $currentpage - 1;
// 使用 < 連結回到前一頁
echo " <a href=\"{$_SERVER[\"><</a> ";
} // end if
|
首先要做的是製作一些類似「« <」的東西。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
// 顯示的頁數範圍
$range = 3;
// 顯示當前分頁鄰近的分頁頁數
for ($x = (($currentpage - $range) - 1); $x < (($currentpage + $range) + 1); $x++) {
// 如果這是一個正確的頁數...
if (($x > 0) && ($x <= $totalpages)) {
// 如果這一頁等於當前頁數...
if ($x == $currentpage) {
// 不使用連結, 但用高亮度顯示
echo " [<b>$x</b>] ";
// 如果這一頁不是當前頁數...
} else {
// 顯示連結
echo " <a href=\"{$_SERVER[\">$x</a> ";
} // end else
} // end if
} // end for
|
在這裏我們顯示一連串的網頁連結,它的概念是若果我們在第八頁,它會顯示:
1
|
<< < 5 6 7 [8] 9 10 11 > >>
|
我們使用了 $range 代表頁數範圍,它代表目前頁數的左邊和右邊該顯示多少個連結(不是總數量),我們目前在第八頁,$range 是 3,所以 8 的左邊顯示 5 6 7,右邊顯示 9 10 11,迴路從目前頁數減 $range 開始,遍歷至目前頁數加 $range。在迴路中,我們首先檢查當前的頁數是否合法,例如我們正在第一頁,當然不想連結顯示為 -2 -1 0,下一件事就是檢查要顯示的頁數是否目前的頁數,是的話便把它加粗並用方括號令它突顯出來,我們也無須使它成為連結。若果當前的頁數合法,又不是目前的頁數,便用這個數字做一個連結,就是這麼簡單。
1
2
3
4
5
6
7
8
9
10
11
|
// 如果不是最後一頁, 顯示跳往下一頁及最後一頁的連結
if ($currentpage != $totalpages) {
// 下一頁的頁數
$nextpage = $currentpage + 1;
// 顯示跳往下一頁的連結
echo " <a href=\"{$_SERVER[\">></a> ";
// 顯示跳往最後一頁的連結
echo " <a href="{$\_SERVER[\">>></a> ";
} // end if
/****** 完成建立分頁連結 ******/
?>
|
這部份負責製作「> »」連結,方法與製作「« <」如出一轍,只是方向相反罷了:如果我們不是在最後一頁,便需要製作「> »」連結,製作「>」只需把目前頁數加一,至於「»」則連結到 $totalpages 變量的值。
好了,製作分頁就只是這麼多東西,當然你可以加入一些花俏元素,例如加入一些按欄位排序的連結,天空才是你的極限!
祝大家編碼快樂!