2013年5月6日 星期一

jQuery 套件 DataTables 的測試

ExtJS 框架以其起家的 DataGrid 元件聞名, 而 jQuery 框架雖然沒有內建類似 DataGrid 的功能, 但也有 dataTables 套件可用. 其實這是產品設計策略的差異, ExtJS 是一次到位, 提供全方位解決方案 (但是比較龐大, 學習曲線陡峭); 而 jQuery 則是保持一個精簡快捷的核心, 應用功能則盡量用 plugin 來實現, 所以是百花齊放. 以下先就 dataTables 這個套件的使用方式作簡要說明後, 再進行功能測試.

dataTables 是蘇格蘭 SpryMedia 工作室的 Mr.Allan Jardine 之作品, 可以結合 jQuery UI 的主題佈景, 在網頁上為資料表格呈現豐富的功能, 例如搜尋, 排序, 以及透過 Ajax 動態擷取表格資料, 重點是~~免費 (好用的話捐點小錢, 鼓勵 Allan 一下吧).
下載 dataTables 請連至下列網址, 目前版本為 1.9.4 :

http://www.datatables.net/download/

將 DataTables-1.9.4.zip 解壓縮後, 在 media/js 目錄下可以找到兩個 dataTables 程式, 其中 jquery.dataTables.js 原始檔 (約 370K), 而 jquery.dataTables.min.js 則是壓縮檔 (約 70 K), 要研究人家怎麼寫的, 請參考原始檔, 我們應用上只要將壓縮檔放到專案的 jquery 目錄即可, 與 jQuery 主檔放在一塊. 另外在 media/css 目錄下有一個  jquery.dataTables.css 檔, 此檔也是放到專案的 jquery 目錄下 :

匯入函式庫

<link href="jquery/themes/hot-sneaks/jquery-ui.css" rel="stylesheet">
<link href="jquery/jquery.dataTables.css" rel="stylesheet">
<script type="text/javascript" src="jquery/jquery.min.js"></script>
<script type="text/javascript" src="jquery/jquery-ui.min.js"></script>
<script type="text/javascript" src="jquery/jquery.dataTables.min.js"></script>


如果我們的專案是放在 Internet 上, 那麼也可以使用 微軟的 CDN 來匯入函式庫 :

從微軟 CDN 匯入函式庫

<link href="http://ajax.aspnetcdn.com/ajax/jquery.ui/1.10.2/themes/hot-sneaks/jquery-ui.css" rel="stylesheet">
<link href="http://ajax.aspnetcdn.com/ajax/jquery.dataTables/1.9.4/css/jquery.dataTables.css" rel="stylesheet">
<script type="text/javascript" src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-2.0.0.min.js"></script>
<script type="text/javascript" src="http://ajax.aspnetcdn.com/ajax/jquery.ui/1.10.2/jquery-ui.min.js"></script>
<script type="text/javascript" src="http://ajax.aspnetcdn.com/ajax/jquery.dataTables/1.9.4/jquery.dataTables.min.js"></script>


函式庫準備妥當後, 要讓 dataTables 能正常工作, 還有一個先決條件, 我們的 Table 元素裡一定要包含格式正確的 <thead> 與 <tbody> (但不要求有 <tfoot>), 例如 :

<table id="table1">
    <thead>
        <tr>
            <th>股票名稱</th>
            <th>股票名稱</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>台積電</td>
            <td>2330</td>
        </tr>
        <tr>
            <td>中華電</td>
            <td>2412</td>
        </tr>
    </tbody>
</table>

這樣只要取得 Table 元素的 jQuery 物件, 呼叫其 dataTable() 方法即可 (注意喔, 是單數的 dataTable, 不是複數的 dataTables) :

$(document).ready(function(){
    $("#table1").dataTable();
    });

測試範例 1 : http://tony1966.xyz/test/jquerytest/jquerytest/datatable_1.htm [看原始碼]

看到了嗎? 點一下各欄位表頭, 就會以該欄位值來排序, 也可以在右上角輸入關鍵字對整個表格做搜尋過濾, 還具有分頁功能 (但我們資料太少, 沒頁可分啦).
在範例 1 中, 我們在呼叫 dataTable() 方法時未傳入任何參數, 這樣 dataTables 套件會套用預設值, 預設是英文介面 (這是指 DataTables 的操作部分, 表格內容部分的語言仍然由我們決定). 要如何才能將 dataTables 的操作介面改成繁體中文呢 (也就是本地化, Localization)? 這就必須設定其參數物件中的 oLanguage 選項了. dataTables 的參數是一個物件實體, 每一個選項名稱就是其屬性. 在官網的 Usage/Internationalization 項下, 可以找到選項物件 oLanguage 的各屬性的說明, 但其中與本地化有關之屬性只有如下八個 (第八個還有跟分頁有關的 4 個次屬性), 下列便是繁體中文化的設定 :

var opt={"oLanguage":{"sProcessing":"處理中...",
                                     "sLengthMenu":"顯示 _MENU_ 項結果",
                                     "sZeroRecords":"沒有匹配結果",
                                     "sInfo":"顯示第 _START_ 至 _END_ 項結果,共 _TOTAL_ 項",
                                     "sInfoEmpty":"顯示第 0 至 0 項結果,共 0 項",
                                     "sInfoFiltered":"(從 _MAX_ 項結果過濾)",
                                     "sSearch":"搜索:",
                                     "oPaginate":{"sFirst":"首頁",
                                                          "sPrevious":"上頁",
                                                          "sNext":"下頁",
                                                          "sLast":"尾頁"}
                                     }
               };
$("#table1").dataTable(opt);

注意, 上面的中文字都可以改, 但 _MAX_, _TOTAL 等等都是變數, 不可以改或拿掉. 實際測試如範例 2. 有沒有注意到 dataTables 的選項名稱有一種規律, 凡是屬性值為字串者都以 "s" 開頭 (string), 而屬性值為物件者都以 "o" 開頭 (object)? 其實這是源自全錄公司的 Hungarian notation (匈牙利標示法), 從開頭的 1, 2 個字元, 就知道它的屬性值是哪種資料型態. 不過許多大師不太認同這種標示法, Linux 祖師爺 Linus Torvalds 甚至形容這種作法為 "腦殘 (brain damaged)". 我個人是覺得如果一個參數名稱可以放兩種以上資料類型時就會有點混亂. 
dataTables 所使用的字頭如下 :

 字頭saaafnfn 
 資料類型 stringobjectarray (1維)array (2維)integer float bolean node function

測試範例 2 : http://tony1966.xyz/test/jquerytest/datatable_2.htm [看原始碼]

如果網頁中有好幾個表格, 每一個都得設定 oLanguage 選項的本地化屬性會很煩, 其實只要把 oLanguage 的屬性值, 即 "{sProcessing .... }" 部分存成一個文字檔, 例如繁體中文化檔案 dataTables.zh-tw.txt 內容如下 :

{"sProcessing":"處理中...",
 "sLengthMenu":"顯示 _MENU_ 項結果",
 "sZeroRecords":"沒有匹配結果",
 "sInfo":"顯示第 _START_ 至 _END_ 項結果,共 _TOTAL_ 項",
 "sInfoEmpty":"顯示第 0 至 0 項結果,共 0 項",
 "sInfoFiltered":"(從 _MAX_ 項結果過濾)",
 "sSearch":"搜索:",
 "oPaginate":{"sFirst":"首頁",
                        "sPrevious":"上頁",
                        "sNext":"下頁",
                        "sLast":"尾頁"
                        }
 }

再將 oLanguage 的 sUrl 屬性設為此檔案位址即可 :

$(document).ready(function(){ 
    var opt={"oLanguage":{"sUrl":"dataTables.arabic.txt"}};
    $("#table1").dataTable(opt);
    });

dataTables 已經為各語言製作了本地化選項值, 可以在 Plug-ins/internationalization 下找到, 請點選語言名稱下的 Show details, 再複製其內容, 然後存成 utf-8 格式之純文字檔案, 再上傳到伺服器即可. 範例 3 是以這種方式對繁體中文, 日文, 以及阿拉伯文進行本地化. 

測試範例 3-1 (繁體) : http://tony1966.xyz/test/jquerytest/datatable_3_1.htm [看原始碼]
測試範例 3-2 (日文) : http://tony1966.xyz/test/jquerytest/datatable_3_2.htm [看原始碼]
測試範例 3-3 (阿拉伯文) : http://tony1966.xyz/test/jquerytest/datatable_3_3.htm [看原始碼]

接著來看 bJqueryUI 選項, 這是用來設定是否要套用 jQuery UI 布景主題, 預設為 false (不套用). 如果我們有匯入 jQuery UI 函式庫, 那麼只要將此選項設為 true 即可套用所選之布景. 將範例 3-1 加上 bJqueryUI 選項即成下列範例 4 (要特別注意大小寫, 其中 J/Q/U/I 為大寫, 若寫成小寫將不會動) :

$(document).ready(function(){ 
   var opt={"oLanguage":{"sUrl":"dataTables.zh-tw.txt"},
                  "bJQueryUI":true
                  };
   $("#table1").dataTable(opt);
   });

測試範例 4 : http://tony1966.xyz/test/jquerytest/datatable_4.htm [看原始碼]

在範例 4 中, 將 bJQueryUI 設為 true 後, 因為我們載入的是 hot-sneaks 布景, 所以整個表格就套上了 hot-sneaks 的主題了, 這樣是不是比較有 fu 了呢? 其實 jQuery UI 提供了24 個佈景, 把範例 4 加上一個下拉式選單從微軟的 CDN 挑選佈景 css 檔, 改成 PHP 程式如範例 5, 看看哪一個佈景比較順眼 :

測試範例 5 : http://tony1966.xyz/test/jquerytest/datatable_5.php [看原始碼]

以上測試都以 DOM 元素中的表格為資料來源 (也就是 HTML 的 TABLE 啦), 其實, dataTables 可以從四種來源取得表格資料 :
  1. DOM
  2. 陣列
  3. Ajax JSON
  4. 後端伺服器處理分頁過濾與搜尋
現在就來看看如何利用陣列來提供表格內容. 以陣列作為資料來源時, 表格元素只要留下 table 即可, 不需要 thead 與 tbody, dataTables 物件會自動建立 :

<table id="table1"></table>

表格的欄位標題要使用選項 aoColumns 來指定, 從開頭的 "ao" 可知此為一物件陣列 (array of objects), 其屬性名稱 "sTitle" 用來設定欄位的標題名稱, 例如 "股票名稱" 等等.  而表格內容則要用選項 aaData 來設定, 從起首的 "aa" 可知此為二維陣列, 其中每一個元素都是一個一維陣列, 表示一筆或一列資料, 其順序與 aoColumns 中的 sTitle 屬性相對應 :

var opt={"oLanguage":{"sUrl":"dataTables.zh-tw.txt"},
              "bJQueryUI":true,
              "aoColumns":[{"sTitle":"股票名稱"},
                                   {"sTitle":"股票代號"},
                                   {"sTitle":"收盤價 (元)"},
                                   {"sTitle":"成交量 (張)"}],
              "aaData":[["台積電","2330",111.5,19268],
                             ["中華電","2412",95.1,7096],
                            ["中碳","1723",145.0,317],
                            ["創見","2451",104.0,459],
                            ["華擎","3515",104.0,95],
                            ["訊連","5203",98.5,326]]
             };
$("#table1").dataTable(opt);


測試範例 6 : http://tony1966.xyz/test/jquerytest/datatable_6.htm [看原始碼]

從範例 6 可知, 所呈現的表格與上面直接寫在 HTML 裡的效果完全一樣. 但是當表格很大時, 若表格內容直接寫在 HTML 中, 則瀏覽器要建立 DOM 樹會花比較多時間, 這時改用陣列可以加快網頁呈現之速度.

其實 aaData 選項除了用二維陣列以外, 像 aoColumns 那樣使用物件陣列也是可以的, 這時選項 aaData 裡的每一欄位資料都要加上欄位名稱作為屬性, 而且 aoColumns 選項也要多一個屬性 mData, 用來設定每一個欄位對應 aaData 中的哪一個屬性 :


var opt={"oLanguage":{"sUrl":"dataTables.zh-tw.txt"},
               "bJQueryUI":true,
               "aoColumns":[{"sTitle":"股票名稱","mData":"stock_name"},
                                      {"sTitle":"股票代號","mData":"stock_id"},
                                      {"sTitle":"收盤價 (元)","mData":"close"},
                                      {"sTitle":"成交量 (張)","mData":"volumn"}],
               "aaData":[{"stock_name":"台積電",
                                "stock_id":"2330",
                                "close":111.5,
                                "volumn":19268},
                               {"stock_name":"中華電",
                                "stock_id":"2412",
                                "close":95.1,
                                "volumn":7096},
                               {"stock_name":"中碳",
                                "stock_id":"1723",
                                "close":145.0,
                                "volumn":317},
                               {"stock_name":"創見",
"stock_id":"2451",
                             "close":104.0,
                                "volumn":459},
                               {"stock_name":"華擎",
                                "stock_id":"3515",
                                "close":104.0,
                                "volumn":95},
                               {"stock_name":"訊連",
                                "stock_id":"5203",
                                "close":98.5,
                                "volumn":326}]
                 };
$("#table1").dataTable(opt);


測試範例 7 : http://tony1966.xyz/test/jquerytest/datatable_7.htm [看原始碼]

我們把範例 6 稍做修改成範例 7, 可見結果完全一樣, 但我覺得範例 7 比較囉唆, 還是範例 6 較精簡. 所以忘了範例 7 吧, 此處純粹只是測試一下而已, 沒有人願意捨簡就繁啊!

接下來, 項莊舞劍意在沛公, 重點來了, 我們來看第三種資料來源, 也就是透過 Ajax 由遠端資料庫提供表格內容. 這就要用到選項 sAjaxSource 了, 此選項是一個 URL 字串, 指向遠端伺服器的一個能產生 JSON 內容的程式. 這個程式要能輸出一個含有 aaData 單一屬性的物件, 其值為一個表示表格內容的二維陣列, 例如我們要從遠端伺服器的 stocks_list 資料表取出全部台股上市公司股票名稱與代號, 其 JSON 格式如下 :

{"aaData":[["\u5bcc\u90a6","0015"],["\u53f0\u706350","0050"],...,["\u65fa\u65fa\u4fdd","2816"]]}

此 JSON 只有 aaData 一個屬性, 其值為二維陣列, 每一個元素代表表格的一列資料, 其第一欄位為股票名稱, 第二欄位為股票代號. 此資料表 stocks_list 的製作方法詳見 "jQuery UI 的自動完成器 autocomplete 測試" 中的範例 4 說明, 此處不再贅述. 產生此 JSON 輸出的 PHP 程式 get_stocks_list_aa.php 如下 :


<?php
header('Content-Type: text/html;charset=UTF-8');
$host="mysql.1freehosting.com"; //MySQL 主機位址
$username="tony1966_test"; //MySQL 使用者名稱
$password="123456"; //MySQL 使用者密碼
$database="testdb"; //資料庫名稱
$conn=mysql_connect($host, $username, $password); 
mysql_query("SET NAMES 'utf8'"); //設定查詢所用之字元集為 utf-8
mysql_select_db($database, $conn); //開啟資料庫
$SQL="SELECT * FROM `stocks_list`"; 
$result=mysql_query($SQL, $conn); //執行 SQL 指令
$stock=array(); //儲存表格內容之二維陣列
for ($i=0; $i<mysql_numrows($result); $i++) { //走訪紀錄集 (列)
     $row=mysql_fetch_array($result); //取得列陣列
     $stock_name=$row["stock_name"];
     $stock_id=$row["stock_id"];
     $stock[$i]=array($stock_name, $stock_id); //存入二維陣列
     } //end of for
$arr["aaData"]=$stock; //表格內容存入關聯式陣列echo json_encode($arr);  //將陣列轉成 JSON 資料格式傳回
?>

上述程式中, 我們從 stocks_list 資料表取得全部股票之名稱與代號後, 先存入二維陣列 $stock 中, 最後將其放入一個一維之關聯式陣列, 索引為 "aaData" (所以 $arr 陣列是三維), 這樣子在將其傳入 json_encode() 後, 就會被轉成具有單一屬性 aaData 的 JSON 物件了. 

然後回到網頁部分, 同樣地, 我們只需要一個空的 TABLE 元素即可, 不需要 THEAD 與 TBODY 元素, dataTables 物件會自動產生. jQuery 程式設定如下 :


var opt={"oLanguage":{"sUrl":"dataTables.zh-tw.txt"},
              "bJQueryUI":true,
              "bProcessing":true,
              "aoColumns":[{"sTitle":"股票名稱"},{"sTitle":"股票代號"}],
              "sAjaxSource":"http://tony1966.xyz/test/jquerytest/get_stocks_list_aa.php"
              };
$("#table1").dataTable(opt);

在上面程式中, 選項 aoColumns 用來設定表格標題, 而選項 sAjaxSource 則用來設定提供表格內容的遠端伺服器程式的 URL, 這樣 dataTables 物件會自動發出 Ajax 要求向遠端伺服器擷取表格內容 (預設是用 GET 方法, 可以用 sServerMethod 選項設為 "POST" 即可). 然後預期伺服器回應一個含有 "aaData" 屬性之 JSON 資, 注意 aoColumns 中的標題順序必須與 JSON 內容的欄位相對應. 為什麼要用 "aaData" 呢? 這其實是 dataTables 套件的預設值, 這可以利用 sAjaxDataProp 選項去更改, 例如 "sAjaxDataProp":"data" 表示伺服器傳回的 JSON 資料必須以 "data" 作為屬性名稱, 這樣上面的後端程式 get_stocks_list_5.php 最後一行就要配合改為 $arr["data"]=$stock 才行. 
但我覺得不要無事生事, 一切用預設值最省時省事. 
另外有沒有注意到, 這裡還多了一個選項 bProcessing (預設為 false), 設為 true 的話, 當表格資料很多, 需要一些時間處理 (例如排序) 時, 表格上會顯示 "處理中 ..." 
完整範例請看範例 8.

測試範例 8 : http://tony1966.xyz/test/jquerytest/datatable_8.htm [看原始碼]

當然我們也可以自行用 jQuery 的公用函式 $.ajax 來取得表格內容, 這樣就不能用 sAjaxSource 選項了, 而要改用 aaData 選項. 顧名思義, aaData 需要一個二維陣列當作其值, 所以上述的 get_stocks_list_aa.php 不能用, 因為那是三維陣列, 必須做一些修改為 get_stocks_list_6.php 如下 :

<?php
header('Content-Type: text/html;charset=UTF-8');
$host="mysql.1freehosting.com"; //MySQL 主機位址
$username="tony1966_test"; //MySQL 使用者名稱
$password="123456"; //MySQL 使用者密碼
$database="testdb"; //資料庫名稱
$conn=mysql_connect($host, $username, $password); //建立連線
mysql_query("SET NAMES 'utf8'"); //設定查詢所用之字元集為 utf-8
mysql_select_db($database, $conn); //開啟資料庫
$SQL="SELECT * FROM `stocks_list`"; 
$result=mysql_query($SQL, $conn); //執行 SQL 指令
$stock=array(); 
for ($i=0; $i<mysql_numrows($result); $i++) { //走訪紀錄集 (列)
     $row=mysql_fetch_array($result); //取得列陣列
     $stock_name=$row["stock_name"];
     $stock_id=$row["stock_id"];
     $stock[$i]=array($stock_name, $stock_id); //存入二維陣列
     } //end of for
echo json_encode($stock);  //將陣列轉成 JSON 資料格式傳回
?>

只要把二維陣列 $stock 直接轉成 JSON 格式傳回即可. 而 jQuery 程式部分則修改為 Ajax, 我們把遠端傳回的 JSON 資料 (參數 resultData) 設定給 aaData 選項就可以了 (當然也不要忘了 aoColumns 選項所負責的表格標題) :


$.ajax({
  type: "POST",
  url: "http://tony1966.xyz/test/jquerytest/get_stocks_list_6.php",
  data: "",
  dataType: "json",
  success: function(resultData) {
      var opt={"oLanguage":{"sUrl":"dataTables.zh-tw.txt"},
            "bJQueryUI":true,
            "aoColumns":[{"sTitle":"股票名稱"},{"sTitle":"股票代號"}],
            "aaData": resultData
            };         
       $("#table1").dataTable(opt);
       }
 });

完整測試範例請看範例 9:

測試範例 9 : http://tony1966.xyz/test/jquerytest/datatable_9.htm [看原始碼]

測試完第三種資料來源 (從後端伺服器取得表格內容之 JSON 資料) 後, 接著來看看第四種資料來源. 
第四種方式其實與第三種一樣都是從後端伺服器取得表格內容, 但差別是第四種方式把分頁, 過濾與搜尋功能都交由後端程式處理, dataTables 只是一個事件模組與呈現模組. 這種方式要將  bServerSide 選項設為 true, 同時如方式三那樣設定 sAjaxSource 選項為後端伺服器程式之 URL. 但本文不會對第四種方式進行測試, 因為那太複雜啦, 有違我們使用套件的初衷-使網頁應用簡化, 可以交給 dataTables 的事為何還要自己做? (果然, 科技始終來自於惰性啊!).

接下來我們要測試跟分頁功能有關的選項 (因為表格內容夠多了, 有頁可分了). 分頁相關選項有四個 : 
  1. iDisplayLength (數字, 預設 10)
  2. bLengthChange (true/false, 預設 true)
  3. aLengthMenu (1/2 維陣列, 預設 [10, 25, 50, 100])
  4. sPaginationType ("two_button"/"full_numbers", 預設 "two_button")
  5. bPaginate (true/false, 預設 true)
首先, iDisplayLength 這選項用來設定每一頁顯示幾列資料, 預設是 10 列, 所以 100 筆資料就會被分成 10 頁. 而 bLengthChange 選項用來控制是否要在表格左上角顯示一個可以更改每頁顯示列數的下拉式選單, 其選項值有 10, 25, 50, 與 100. 當使用者選擇時, 其值就會覆蓋 iDisplayLength  選項所設之預設值 10. 如果我們想要固定顯示每頁 80 列, 那麼就要先將 bLengthChange 設為 false 關掉左上角的下拉式選單, 然後將 iDisplayLength 設為 80 即可, 如下列範例 10 所示 (由範例 8 修改而來) :

var opt={"oLanguage":{"sUrl":"dataTables.zh-tw.txt"},
               "bJQueryUI":true,
               "bLengthChange":false,
               "iDisplayLength": 80,
               "aoColumns":[{"sTitle":"股票名稱"},{"sTitle":"股票代號"}],
               "sAjaxSource":"http://tony1966.xyz/test/jquerytest/get_stocks_list_aa.php"
               };
$("#table1").dataTable(opt);

測試範例 10-1 : http://tony1966.xyz/test/jquerytest/datatable_10_1.htm [看原始碼]

當 bLengthChange 選項設為 true, 如果對左上角的 10, 25,50,100 預設選單不滿意的話, 可以用 aLengthMenu 選項來更改. 此選項可以是一維陣列, 例如預設的 [10, 25,50,100], 這時選單的 option 元素內容與值 (value) 是一樣的. 如果是二維陣列的話, 例如下面這樣 :

"aLengthMenu":[[5, 30, 60, -1], [5, 30, 60, "All"]]

其中前面元素是 option 的值 (value, -1 表示全部), 後面是顯示 (option 的內容). 請看測試範例 10-2 :

測試範例 10-2 : http://tony1966.xyz/test/jquerytest/datatable_10_2.htm [看原始碼]

而 sPaginationType 選項是指右下角的分頁控制按鈕類型, 預設是 "two_button", 亦即只有上一頁與下一頁兩個按鈕, 上面所有範例皆是如此. 如果設為 "full_numbers", 則除了上頁, 下頁外, 還有首頁, 尾頁, 以及目前頁數前後 2 頁的按鈕, 如下列範例 11 :

var opt={"oLanguage":{"sUrl":"dataTables.zh-tw.txt"},
               "bJQueryUI":false,
               "sPaginationType":"full_numbers",
               "aoColumns":[{"sTitle":"股票名稱"},{"sTitle":"股票代號"}],
               "sAjaxSource":"http://tony1966.xyz/test/jquerytest/get_stocks_list_aa.php"
               };
$("#table1").dataTable(opt);

測試範例 11 : http://tony1966.xyz/test/jquerytest/datatable_11.htm [看原始碼]

有沒有注意到範例 11 中我們將 bJQueryUI 選項關閉了 (不套用 jQuery UI 佈景)? 這似乎是 dataTables 的一個小臭蟲, 如果套用主題佈景的話, "full_numbers" 的分頁按鈕會擠在一團, 如下列範例 12 所示 :

測試範例 12 : http://tony1966.xyz/test/jquerytest/datatable_12.htm [看原始碼]

接著來看 bPaginate 選項, 這是用來打開或關閉分頁功能的, 如果設為 false, 則其他分頁相關選項都將失去作用, 例如範例 12 所示 :

var opt={"oLanguage":{"sUrl":"dataTables.zh-tw.txt"},
               "bJQueryUI":true,
               "bPaginate":false,
               "bLengthChange":true,
               "sPaginationType":"full_numbers",
               "aoColumns":[{"sTitle":"股票名稱"},{"sTitle":"股票代號"}],
               "sAjaxSource":"http://tony1966.xyz/test/jquerytest/get_stocks_list_aa.php"
               };
$("#table1").dataTable(opt);

測試範例 13 : http://tony1966.xyz/test/jquerytest/datatable_13.htm [看原始碼]

可見只要 bPaginate 設為 false, 則左上角的每頁列數選單, 以及右下角的分頁切換按鈕全都不見了, 全部資料以一頁呈現. 

接下來看看跟卷軸有關的三個選項 :
  1. sScrollY (字串, 垂直卷軸, 預設值 "")
  2. bScrollCollapse (true/false, 卷軸高度設定瓦解, 預設 false)
  3. sScrollX (字串, 水平卷軸, 預設值 "")
如果不想分頁, 但表格資料又有點大, 把網頁撐得很長, 能不能用卷軸來控制呢? 可以的, 這要用到 sScrollY 選項 (字串), 它是用來控制垂直卷軸的 (但要配合將 bPaginate 設為 false 才行), 預設值為空字串 (無垂直卷軸), 其值可以為 CSS 的任何長度表示, 例如 "sScrollY":"400px" 表示, 表格內容部分將被放到一個 400px 的 viewport 容器裡, 只要資料高度超過 400px, 右方便會出現垂直卷軸. 我們將範例 13 稍微修改為範例 14-1 如下 :


var opt={"oLanguage":{"sUrl":"dataTables.zh-tw.txt"},
              "bJQueryUI":true,
              "bPaginate":false,
              "sScrollY":"400px",
              "aoColumns":[{"sTitle":"股票名稱"},{"sTitle":"股票代號"}],
              "sAjaxSource":"http://tony1966.xyz/test/jquerytest/get_stocks_list_aa.php"
              };
$("#table1").dataTable(opt);

測試範例 14-1 : http://tony1966.xyz/test/jquerytest/datatable_14_1.htm [看原始碼]

看到了嗎? 由於我們的上市股票超過 800 支, 在取消分頁功能時網頁會很長, 但若設定 sScrollY 選項, 就可以將網頁限縮在一頁的高度, 然後用卷軸來移動視野. 這比瀏覽器本身卷軸好的地方是, 表頭欄位名稱會一直都在那兒不動, 不像上面範例 13, 往下拉時表頭欄位名稱就看不到啦! 

另外, 跟垂直捲軸很有關係的便是 bScrollCollapse 選項, 當設定垂直捲軸高度時, 若使用搜尋功能找出的資料高度小於卷軸高度時, 這是資料底端與表格底端就會一片空白, 看來很突兀, 如果把 bScrollCollapse 設為 true, 就能瓦解卷軸高度設定, 使得表格底部與資料底部相接, 當然如果搜尋出來的資料高度大於卷軸高度, 仍然被捲軸高度所限制. 例如下列範例 14-2 所示 :

var opt={"oLanguage":{"sUrl":"dataTables.zh-tw.txt"},
              "bJQueryUI":true,
              "bPaginate":false,
              "sScrollY":"400px",
              "bScrollCollapse":true,
              "aoColumns":[{"sTitle":"股票名稱"},{"sTitle":"股票代號"}],
              "sAjaxSource":"http://tony1966.xyz/test/jquerytest/get_stocks_list_aa.php"
              };
$("#table1").dataTable(opt);


測試範例 14-2 : http://tony1966.xyz/test/jquerytest/datatable_14_2.htm [看原始碼]

水平卷軸的設定也是類似, 不過它跟資料筆數無關, 也跟分頁功能無關, 而是欄位太多, 或欄位內資料太長, 以至於表格寬度向右撐破頁面, 這樣瀏覽器下頭就會出現水平卷軸. 利用 sScrollX 選項就能把表格內容放入指定寬度的 viewport 容器中, 由 dataTables 物件自己產生水平卷軸.

由於我們的 stocks_list 資料表只有兩個欄位, 所以將範例 1 稍作修改成範例 15. 為了產生水平卷軸效果, 我們在 table 元素外面再包一個 div 元素, 並將其寬度限制為 300px :


<div style="width:300px;">
  <table id="table1">
    <thead>
      <tr>
        <th>股票名稱</th>
        <th>股票代號</th>
        <th>收盤價 (元)</th>
        <th>成交量 (張)</th>
      </tr>
    </thead>
  <tbody>
    <tr>
      <td>台積電</td>
      <td>2330</td>
      <td>111.5</td>
      <td>19268</td>
    </tr>
  ...
  ...
  </table>
</div>

這樣整個表格就被 div 限制在 300px 的寬度, 然後設定 sScrollX 選項為 400px 如下 :

$(document).ready(function(){
  var opt={"oLanguage":{"sUrl":"dataTables.zh-tw.txt"},
           "bJQueryUI":true,
           "sScrollX":"400px"
           };
  $("#table1").dataTable(opt);
  });

完整程式碼請看範例 15 :

測試範例 15 : http://tony1966.xyz/test/jquerytest/datatable_15.htm [看原始碼]

範例 15 顯示, 由於我們只給予表格 300px 寬度空間 (利用 div 元素來限制), 但 sScrollX 卻要求 400px 的寬度來放表格內容, 撐破了水平寬度限制, 因此 dataTables 物件就產生水平卷軸來移動視野.

接下來看看跟欄位有關的四個選項 :
  1. sTitle (字串)
  2. sWidth (字串, "20%"/"20px")
  3. sType (字串, "string"/"numeric"/"date"/"html", 預設 "string")
  4. sClass (字串, 樣式類別的名稱, 預設 "")
其實這些是 aoColumns 選項中的子選項, 前面我們已經用過 sTitle 選項, 這是用來設定欄位標題的. 而 sWidth 則是用來設定欄位寬度的, 可用百分比如 "20%", 或長度如 "100px". 而 sType 則是用來設定欄位排序時比對用的資料類型, 有四種 : "string"/"numeric"/"date"/"html", 預設是 "string", 把資料當作字串來排序 (即使資料全部是數字). 選項 sClass 則是用來設定欄位的 CSS 樣式類別, 我們可以自行在網頁中定義一個樣式類別, 然後用 sClass 指定某欄位套用此樣式類別. 下列範例 16 中, 我們增加一個編輯欄位放在第一欄 (寬度 20px), 此欄位內放一個編輯筆的 gif 圖檔, 我們此欄位內容居中對齊, 且文字不會折回 (nowrap), 這可以用一個自訂樣式類別 editClass 來控制 :

.editClass {text-align:center;white-space:nowrap;}

而第二欄位 (股票名稱) 寬度設為 20%, 以字串排序, 第三欄 (股票代號) 為 80%, 以數值排序 :

var opt={"oLanguage":{"sUrl":"dataTables.zh-tw.txt"},
               "bJQueryUI":true,
               "aoColumns":[{"sTitle":"編輯","sWidth":"20px","sClass":"editClass"},
                                        {"sTitle":"股票名稱","sWidth":"20%","sType":"string"},
                                        {"sTitle":"股票代號","sWidth":"80%","sType":"numeric"}],
               "sAjaxSource":"http://tony1966.xyz/test/jquerytest/get_stocks_list_5.php"
               };
$("#table1").dataTable(opt);

當然後端程式也要配合修改, 增加輸出一個編輯欄位, 我們修改上面範例 10~14 所用的 get_stocks_list_aa.php, 加上編輯欄位為 get_stocks_list_5.php 如下 :



$stock=array(); 
for ($i=0; $i<mysql_numrows($result); $i++) { //走訪紀錄集 (列)
     $row=mysql_fetch_array($result); //取得列陣列
     $stock_name=$row["stock_name"];
     $stock_id=$row["stock_id"];
     $edit="<img src='edit.gif' style='border-width:0px;width:15px;height:15px;'>";
     $stock[$i]=array($edit, $stock_name, $stock_id);
     } //end of for
$arr["aaData"]=$stock;
echo json_encode($arr);  //將陣列轉成 JSON 資料格式傳回



測試範例 16 : http://tony1966.xyz/test/jquerytest/datatable_16.htm [看原始碼]

從範例 16 可知, 透過 sClass 的樣式設定, 把圖檔在 td 元素裡置中對齊了, 而 sType 的設定使第三欄股票代號排序完全以數值大小為依據, 與範例 13 的以字串排序不同, 例如範例 13 中, 006203 是排在 0061 後面 (當作字串逐字元排序), 而在範例 16 中, 006203 排在 6201 後面 (當作數值排序).

以上都只談到選項的用法, 還沒介紹方法. 現在來看看 dataTables 套件最常用的方法 (API) : fnGetData(). 此處我們要用一個常見的應用來介紹此方法, 就是當我們點一下表格中的某列, 可以連結到某一個網頁, 這在以前的做法通常是在某一列的某欄資料加上超連結, 或是用 script 更改 window.location.href 屬性值. 下列範例 17 我們就來為上面範例 14 的股票列表添加一個功能, 當使用者點選某一列時, 就開啟一個新畫面, 顯示 Yahoo 股市網站中該股的當日行情.  Yahoo 股市當日行情的網址格式如下 (以中華電為例) :

http://tw.stock.yahoo.com/q/q?s=2412

所以, 我們只要取得股票代號就可以製作該股票的 URL 了. 但要如何取得使用者點選的那一列的股票代號呢? 這要從 dataTables 物件著手. 上面的所有範例中, 我們都呼叫了表格元素的 jQuery 物件的 dataTable() 方法, 這方法其實是有傳回值的, 它會傳回 dataTables 物件 :

var oTable=$("#table1").dataTable();  //傳回 dataTables 物件

利用此 dataTables 物件就可以知道使用者點了哪一列資料, 然後讀取該列第二欄位就是股票代號了. dataTables 物件有一個 $ 方法, 可用來執行 jQuery 的選擇器功能, 可以選取表格的 tbody 內容 , 例如列 (tr) 或 元素 (td), 然後我們就可以為選取的列加上 click 動作, 再用 fnGetData() 這個方法來取得列的內容. fnGetData() 方法可以傳入 tr 或 td 節點元素, 若傳入 td 節點, 則傳回該 td 的內容; 若傳入 tr 節點, 則傳回一陣列, 其元素就是該 tr 列中的所有 td 內容. 此處我們傳入 this 表示 tr 節點, 這樣就會顯示該列所有 td 內容, 例如 "台積電,2330,111.5,19268". 
我們將範例 1 修改為範例 17 如下 :


$(document).ready(function(){ 
   var opt={"oLanguage":{"sUrl":"dataTables.zh-tw.txt"},
                 "bJQueryUI":true
                 };
var oTable=$("#table1").dataTable(opt);
oTable.$('tr').click(function() {
    var row=oTable.fnGetData(this); //取得 tr 內容, 傳回陣列
    alert(row); //顯示陣列內容
    alert(row[1]); //顯示
    });
});

測試範例 17 : http://tony1966.xyz/test/jquerytest/datatable_17.htm [看原始碼]

範例 17 顯示, 點選任一列就會顯示該列所有 td 的內容以及第二個 td (股票代號) 內容. 所以我們就可以利用 row[1] 來製作超連結了, 如範例 18 所示 :


$(document).ready(function(){ 
   var opt={"oLanguage":{"sUrl":"dataTables.zh-tw.txt"},
                 "bJQueryUI":true
                 };
var oTable=$("#table1").dataTable(opt);
oTable.$('tr').click(function() {
    var row=oTable.fnGetData(this); //取得 tr 內容, 傳回陣列
    var url="http://tw.stock.yahoo.com/q/q?s=" + row[1]; //Yahoo 股市當日行情 url
    window.open(url); //開啟新視窗
    });
});

測試範例 18 : http://tony1966.xyz/test/jquerytest/datatable_18.htm [看原始碼]

範例 18 中, 點任何一列就會開啟 Yahoo 股市網站上該股的當日行情. 一切似乎都很美好不是嗎? 
但是有沒有注意到, 範例 17 與 18 都是以網頁中的表格內容為資料來源, 如果改從遠端伺服器取得表格資料, 上述用 $("tr").click 綁定 click 事件的作法就破功了, 點任何一列都不會動, 我們把範例 14 稍作修改為下列範例 19  :


var opt={"oLanguage":{"sUrl":"dataTables.zh-tw.txt"},
              "bJQueryUI":true,
              "bPaginate":false,
              "sScrollY":"400px",
              "aoColumns":[{"sTitle":"股票名稱"},{"sTitle":"股票代號"}],
              "sAjaxSource":"http://tony1966.xyz/test/jquerytest/get_stocks_list_aa.php"
              };

var oTable=$("#table1").dataTable(opt);
oTable.$('tr').click(function() {
    var row=oTable.fnGetData(this); //取得 tr 內容, 傳回陣列
    var url="http://tw.stock.yahoo.com/q/q?s=" + row[1]; //Yahoo 股市當日行情 url
    window.open(url); //開啟新視窗
    });

測試範例 19 : http://tony1966.xyz/test/jquerytest/datatable_19.htm [看原始碼]

沒錯吧! 範例 19 顯示, 當表格資料來源是遠端伺服器提供的 JSON 資料時, 用 $("tr").click 綁定 click 事件是沒作用的. 根據 dataTables 套件作者 Allan 的回覆是, 要用 on 方法綁定事件 (jQuery 1.7 開始) 才行, 詳情請參考 dataTables 論壇上的 "Use fnGetData w/Ajax data source" 這篇文章. 所以把範例 19 改為如下的範例 20 :


var opt={"oLanguage":{"sUrl":"dataTables.zh-tw.txt"},
              "bJQueryUI":true,
              "bPaginate":false,
              "sScrollY":"400px",
              "aoColumns":[{"sTitle":"股票名稱"},{"sTitle":"股票代號"}],
              "sAjaxSource":"http://tony1966.xyz/test/jquerytest/get_stocks_list_aa.php"
              };
var oTable=$("#table1").dataTable(opt);
oTable.on('click','tr',function() {
    var row=oTable.fnGetData(this); //取得 tr 內容, 傳回陣列
    var url="http://tw.stock.yahoo.com/q/q?s=" + row[1]; //Yahoo 股市當日行情 url
    window.open(url); //開啟新視窗
    });

測試範例 20 : http://tony1966.xyz/test/jquerytest/datatable_20.htm [看原始碼]

範例 20 顯示這招奏效了.  關於 jQuery 的 on() 方法, 可參考這篇 "jQuery 1.7筆記".

最後, 我們以範例 21 將以上測試作個總結, 以範例 20 的開啟 Yahoo 今日行情網頁為基礎, 結合範例 16 具有編輯欄位的表格, 並希望給這些編輯圖樣加上超連結, 點擊後會帶出 stock_id 與 stock_name 兩個變數給另一個編輯網頁 edit_stocks_list.php 如下  :


<form id="edit_form">
  <p>股票代號 : <input type="text" id="stock_id" value="<?php echo $stock_id ?>"></p>
  <p>股票名稱 : <input type="text" id="stock_name" value="<?php echo $stock_name ?>"></p>
  <p><input type="button" id="submit" value="確定"></p>
</form>
<script language="JavaScript">
  $(document).ready(function(){ 
    $("#submit").click(function() {
      alert("更新!");
      });
    });
  </script>

同時把範例 16 中的後端程式 get_stocks_list_5.php 修改為  get_stocks_list_7.php, 給圖檔添加超連結 :


for ($i=0; $i<mysql_numrows($result); $i++) { //走訪紀錄集 (列)
     $row=mysql_fetch_array($result); //取得列陣列
     $stock_name=$row["stock_name"];
     $stock_id=$row["stock_id"];
     $edit="<a href='edit_stocks_list.php?stock_id=".$stock_id."&stock_name=".
               $stock_name."' target='_blank'><img src='edit.gif' ".
               "style='border-width:0px;width:15px;height:15px;'></a>";
     $stock[$i]=array($edit, $stock_name, $stock_id);
     } //end of for
$arr["aaData"]=$stock;
echo json_encode($arr);  //將陣列轉成 JSON 資料格式傳回

而 jQuery 程式部分則必須修改, 在範例 20 中我們是給整列 tr 綁定 click 事件, 但是因為現在第一欄放了一個有超連結的編輯圖檔, 這樣編輯超連結就會被 tr 的 click 事件覆蓋過去, 亦即點擊編輯圖檔時不會開啟編輯網頁了, 而是開啟 Yahoo 每日行情網頁. 解決此問題的方法有二, 那就是也在後端程式中對 stock_id 與 stock_name 欄位加上超連結, 第二個方法是修改功能, 就是不要給 tr 綁定 click 事件, 改成給 td 綁定 click 事件, 而且只有點擊 stock_id 欄位時才開啟 Yahoo 每日行情網頁, 具體作法是判斷 fnGetData() 方法讀取 td 的內容, 如果是數字 (即 stock_id), 就開啟 Yahoo 每日行情網頁, 若不是數字 (stock_name 與編輯欄位) 就不動作 :


    $(document).ready(function(){ 
      var opt={"oLanguage":{"sUrl":"dataTables.zh-tw.txt"},
               "bJQueryUI":true,
               "bPaginate":false,
               "sScrollY":"400px",
               "aoColumns":[{"sTitle":"編輯","sWidth":"20px","sClass":"editClass"},
                                      {"sTitle":"股票名稱"},
                                      {"sTitle":"股票代號"}],
               "sAjaxSource":"http://tony1966.xyz/test/jquerytest/get_stocks_list_7.php"
               };
      oTable.on('click','td',function() {
          var cell=oTable.fnGetData(this); //取得 td 內容
          if (!isNaN(cell)) { //點擊股票代號欄位
              var url="http://tw.stock.yahoo.com/q/q?s=" + cell;
              window.open(url);
             }
         });
      });

完整程式碼如範例 21 :

測試範例 21 : http://tony1966.xyz/test/jquerytest/datatable_21.htm [看原始碼]

dataTables 套件中還有許多選項, 但我想上面所測試的應該是專案開發中最常用到的了, 其他的請參考官網的 Usage 與 API 項目下的說明, 下列連結是 dataTables 套件所有選項與方法的列表 :
http://datatables.net/ref

~全劇終

** 編後語 :

在範例 11 中提到一個 dataTables 的 bug, 若分頁時同時套用 jQuery UI, 則右下角的分頁按鈕會擠成一團 (no padding), 根本無法正常選擇分頁. 本來要寫信給 Allan, 但昨日在論壇找到解藥了, 原來套用 jQuery UI 時比較合用的 css 樣式表不是本文開頭所用的 media\css 下的 jquery.dataTables.css, 而是同目錄下的 jquery.dataTables_themeroller.css, 原文詳見 :

http://datatables.net/forums/discussion/8898/full-number-pagination-jquery-ui/p1

所以上面匯入函式庫的部分要改為如下 :


匯入函式庫

<link href="jquery/themes/hot-sneaks/jquery-ui.css" rel="stylesheet">
<link href="jquery/jquery.dataTables_themeroller.css" rel="stylesheet">
<script type="text/javascript" src="jquery/jquery.min.js"></script>
<script type="text/javascript" src="jquery/jquery-ui.min.js"></script>
<script type="text/javascript" src="jquery/jquery.dataTables.min.js"></script>


而從微軟 CDN 匯入的話, 也要改成 :


從微軟 CDN 匯入函式庫

<link href="http://ajax.aspnetcdn.com/ajax/jquery.ui/1.10.2/themes/hot-sneaks/jquery-ui.css" rel="stylesheet">
<link href="http://ajax.aspnetcdn.com/ajax/jquery.dataTables/1.9.4/css/jquery.dataTables_themeroller.css" rel="stylesheet">
<script type="text/javascript" src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-2.0.0.min.js"></script>
<script type="text/javascript" src="http://ajax.aspnetcdn.com/ajax/jquery.ui/1.10.2/jquery-ui.min.js"></script>
<script type="text/javascript" src="http://ajax.aspnetcdn.com/ajax/jquery.dataTables/1.9.4/jquery.dataTables.min.js"></script>



上面套用 jquery.dataTables.css 的測試就不再修改了 (做個對照), 這裡將範例 11 做些修改, 把 bJGueryUI 選項打開, 改為匯入 jquery.dataTables_themeroller.css 樣式表, 同時加上主題布景選擇器, 如下列測試範例 11-1  :


var opt={"oLanguage":{"sUrl":"dataTables.zh-tw.txt"},
               "bJQueryUI":true,
               "sPaginationType":"full_numbers",
               "aoColumns":[{"sTitle":"股票名稱"},{"sTitle":"股票代號"}],
               "sAjaxSource":"http://tony1966.xyz/test/jquerytest/get_stocks_list_aa.php"
               };
$("#table1").dataTable(opt);


測試範例 11-1 : http://tony1966.xyz/test/jquerytest/datatable_11_1.php [看原始碼]

呵呵, 經過這番更改後, 果然變成美美的分頁按鈕了. 除了 dark-hive 與 black-tie 主題太暗, 看不清楚分頁按鈕外, 其他都還不錯.
那如果取消分頁, 改用垂直卷軸, 套用 jquery.dataTables_themeroller.css 樣式表效果如何? 我們修改範例 14-2, 只是改了其中 css 匯入部分如範例 14-3 :

測試範例 14-3 : http://tony1966.xyz/test/jquerytest/datatable_14_3.htm [看原始碼]

感覺有比較好一點, 至少欄位標題沒那麼厚, 上下框框高度加大, 還不錯啦.


2020-07-24 補充 :

jQuery UI dataTables 外掛早已經有了新版, 參見 :

# 發現新的 jQuery UI Datatable

用法與上面舊版的有些不同, 參考 :

https://datatables.net/

17 則留言 :

motestu23912 提到...

想請問,如果我在搜尋欄進行搜尋以後
想留下搜尋欄位中的"資料"
ex:搜尋:籃球。
當我返回這頁面,如何讓搜尋欄位中保留"籃球"
請問這有辦法做到嗎??

謝謝^^

小狐狸事務所 提到...

您好,
DataTable 的搜尋欄位中輸入的字串本來就是一直保留最近一次輸入的值, 即使換下一頁都不會消失喲.

Unknown 提到...

想請教您的是,我現在資料庫的資料有五千多筆,所以頁面跑出來會發生嚴重的lag,我看到你提到資料的來源有Server Side,是不是只有用這個方式才能解決大量資料的問題呢?還是您有更好的方法呢?

小狐狸事務所 提到...

您好, 第三種與第四種取得資料的方法都是來自伺服端後台, 只是第四種方式把分頁, 過濾與搜尋功能都交由後端程式處理而已, 因此與資料筆數無關. 瀏覽器一次呈現太多資料當然會 lag, 但 jQuery Data Table 用分頁方式呈現應該不會這樣. 我個人抓資料的 log 紀錄達萬筆, 只有在載入時較慢, 跳頁顯示時就不會慢, 因為已經載入記憶體了.

YC 提到...

您的文章太受用了. 感謝您的分享!

匿名 提到...

想請問一下 ScrollX這個部分 為什麼我只要用了ScrollX之後 thead跟tboby的欄位就會沒辦法match在一起
tbody的資料很長

匿名 提到...

發現是因為TABLE外層的DIV預先設置了一個DISPLAY:NONE屬性 因為有這種要求
拿掉DISPLAY之後就能正常MATCH到

匿名 提到...

您好:
請教一下
如何在search框架裡面
加入select的功能?

就是在前端看的時候
搜尋框裡面會出現下拉選單
然後裡面的欄位可以篩選對應的資料

謝謝

eworldtw 提到...

很棒的專業介紹,
謝謝您的教學!

孫朝裕(小毛) 提到...

已經載入後 做手動點排序了 能再次更換Data內容嗎? 因為如果重新整理 就整個排序跑掉了 有點類似說 股票價位一直在走 總要更新內容

匿名 提到...

萬分感謝,您的教學使我收穫良多

kaka 提到...

你好

請教一下,當我進入頁面後已經從資料庫載入資料上來
而我想要每幾秒鐘去檢查是否有新的資料,如果有的話在載上來(不做reflush)
但如果重覆呼叫dataTable會發生錯誤,有什麼方式可以避免這個錯誤

謝謝

小狐狸事務所 提到...

Sorry, 我好久沒用 DataTables 了, 似乎給他忘光光哩! 要定期檢查若不使用 refresh, 那可以用 js 的 setInterval() 去呼叫 ajax.reload() 試試看, 參考 :

https://datatables.net/reference/api/ajax.reload()

匿名 提到...

您好,
[看原始碼] 點不到原始碼的資料喔 ?

小狐狸事務所 提到...

嗨, 因為搬移伺服器網址改變緣故, 我找時間修改一下, 謝謝您

匿名 提到...

謝謝版主分享 , 已經可以下載了 !

小狐狸事務所 提到...

歡迎多交流!