[ASP.net MVC 4] 自己打造分頁Pager範例程式碼 (數字版,Ajax)
前言
用ASP.net MVC開發網站真的要寫很多javascript
這時就很感謝之前自己待過Java Team,有寫過JSP的經驗,運用到ASP.net MVC這邊XD
看了一下網路上關於資料分頁這塊,很多人都分享現成的分頁元件解決方案,自己手寫打造的範例程式碼還滿少見
用現成元件好處是功能馬上做好
缺點是若要套美工人員設計的版,比較沒有彈性
而且萬一被客戶發現:咦?這個分頁樣式好像跟當初確認的版型不一樣…,就GG了,呵呵
為了避免有網友重蹈我的覆徹,把分頁底層的程式碼公開,數字Ajax版
下拉選單版(頁數都塞進下拉選單裡),等有空再補完,不過若會數字版的原理,下拉選單版應該不是問題
我覺得比較困難的是計算切換數字群組部份,有比此篇文章更好公式的話,麻煩請提出,先謝囉~
說明都在註解裡
實作
Model資料夾
準備Html Table要呈現的資料類別
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace MvcApplicationPager.Models
{
public class ProductViewModel
{
/// <summary>
/// 編號
/// </summary>
public int No { get; set; }
/// <summary>
/// 名稱
/// </summary>
public string ProductName { get; set; }
/// <summary>
/// 是否上下架
/// </summary>
public bool IsOpen { get; set; }
}
}
Global.asax.cs塞假資料
using MvcApplicationPager.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Routing;
namespace MvcApplicationPager
{
// Note: For instructions on enabling IIS6 or IIS7 classic mode,
// visit http://go.microsoft.com/?LinkId=9394801
public class MvcApplication : System.Web.HttpApplication
{
//假裝這是DB撈出來的資料
public static List<ProductViewModel> dbList = new List<ProductViewModel>();
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
#region 塞假資料
Random rand = new Random(Guid.NewGuid().GetHashCode());
for (int i = 1; i <= 104; i++)
{
bool IsOpen = true;//Default
int number = rand.Next(1, 3);//1~2
if (number == 2)
{
IsOpen = false;
}
dbList.Add(new ProductViewModel()
{
No = i,
ProductName = Guid.NewGuid().ToString().Substring(0, 5).ToUpper(),
IsOpen = IsOpen
});
}
#endregion
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
}
}
}
HomeController.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
//剛剛宣告的
using MvcApplicationPager.Models;
using System.Text;
namespace MvcApplicationPager.Controllers
{
public class HomeController : Controller
{
[HttpGet]
public ActionResult Index()
{
//上下架的下拉選單的資料清單
ViewData["IsOpenItems"] = new List<SelectListItem>()
{
new SelectListItem(){ Text="請選擇",Value="-1",Selected=true},
new SelectListItem(){ Text="上架",Value="true" },
new SelectListItem(){ Text="下架",Value="false" },
};
IEnumerable<ProductViewModel> result = this.QueryData(10, 0, new FormCollection());
return View(result);
}
/// <summary>
/// 查詢資料
/// </summary>
/// <returns></returns>
public IEnumerable<ProductViewModel> QueryData(int iPageSize, int iCurrentPageIndex, FormCollection form)
{
var result = from r in MvcApplication.dbList
select r;
if (!string.IsNullOrEmpty(form["sProductName"]))
{//有輸入名稱
result = from r in result
where r.ProductName.Contains(form["sProductName"])
select r;
}
//有輸入上下架
if (form["sIsOpen"] != null && form["sIsOpen"] != "-1")
{
result = from r in result
where r.IsOpen == Convert.ToBoolean(form["sIsOpen"])
select r;
}
//部份檢視要顯示TempData["pager"]
TempData["pager"] = this.pageString(result,iPageSize,iCurrentPageIndex);
//抓DB少量的資料
result = result.Skip(iCurrentPageIndex * iPageSize).Take(iPageSize);
return result;
}
/// <summary>
/// 產生pager字串(這個請再自行抽到其他Utility之類的類別裡)
/// </summary>
/// <param name="result">集合物件</param>
/// <param name="iPageSize">每頁顯示幾筆</param>
/// <param name="iCurrentPageIndex">目前的索引頁</param>
/// <returns></returns>
public string pageString(IEnumerable<object> result,int iPageSize,int iCurrentPageIndex)
{
#region Pager處理(文章重點在這!)
//總筆數
int iRowTotal = result.Count();
//總頁數
int iPageTotal = (iRowTotal / iPageSize);
if (iRowTotal % iPageSize > 0)
{
iPageTotal++;
}
//目前頁數
int iCurrentPage = iCurrentPageIndex + 1;
//防呆
if (iCurrentPage > iPageTotal)
{
iCurrentPage = iPageTotal;
iCurrentPageIndex = iCurrentPage - 1;
}
if (iCurrentPage < 0)
{
iCurrentPage = 1;
iCurrentPageIndex = 0;
}
//頁數字按鈕每5個一組呈現(這邊可自行更改)
int pNumber = 5;
//頁數字按鈕共被分為幾分
int ppSize = iPageTotal / pNumber;
if (iPageTotal % pNumber > 0)
{
ppSize++;
}
//目前在哪一份(從1算起),為了顯示「...」
int currentPPSize = (iCurrentPageIndex / pNumber) + 1;
//要回傳的字串
StringBuilder sb = new StringBuilder();
//第二頁以後才有上一頁
string strPreJS = ((iCurrentPage > 1) ? "javascript:search('" + (iCurrentPage - 1) + "');" : "#");
sb.Append("<a href=\"" + strPreJS + "\">← Prev</a>");
sb.Append("<a href=\"javascript:search(1);\">First</a>");
//這公式算了我好久...Orz
int pStart = ((iCurrentPageIndex / pNumber) * pNumber + 1);
int pEnd = pStart + (pNumber - 1);
if (pEnd > iPageTotal)
{
pEnd = iPageTotal;
}
if (currentPPSize > 1)
{
sb.Append("<a href=\"javascript:search('" + (pStart - 1) + "')\">...</a>");
}
//產生頁數按鈕(從1算起)
for (int i = pStart; i <= pEnd; i++)
{
//複製範例時候,class="highColor"可以刪除
sb.Append("<a class='"+(i==iCurrentPage?"highColor":"")+"' href=\"javascript:search('" + i + "');\">" + i + "</a>");
}
if ((currentPPSize < ppSize))
{
sb.Append("<a href=\"javascript:search('" + (pEnd + 1) + "');\">...</a>");
}
//目前頁小於最後一頁,就有下一頁按鈕
string strNextJS = (iCurrentPage < iPageTotal ? "javascript:search('" + (iCurrentPage + 1) + "');" : "#");
sb.Append("<a href=\"javascript:search(" + iPageTotal + ");\">Last</a>");
sb.Append("<a href=\"" + strNextJS + "\">Next → </a>");
#endregion
return sb.ToString();
}
/// <summary>
/// Ajax查詢資料
/// </summary>
/// <param name="iPageSize"></param>
/// <param name="iCurrentPageIndex"></param>
/// <returns></returns>
[HttpPost]
public ActionResult ActionQueryData(FormCollection form)
{
//這裡的int iPageSize, int iCurrentPageIndex參數,前端使用者可能會用F12開發者工具亂改成字串
//怎麼防禦就請自由料理了...
int iPageSize = Convert.ToInt32(form["hPageSize"]);
int iCurrentPageIndex = Convert.ToInt32(form["hCurrentPageIndex"]);
IEnumerable<ProductViewModel> result = this.QueryData(iPageSize, iCurrentPageIndex, form);
return View("HtmlTable", result);
}
}
}
View的Index.cshtml
讀取中效果有使用到jQuery Block UI
官網:http://www.malsup.com/jquery/block/
@model IEnumerable<MvcApplicationPager.Models.ProductViewModel>
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Index</title>
<script type="text/javascript" src="@Url.Content("~/Scripts/jquery-2.0.0.min.js")"></script>
<script type="text/javascript" src="http://malsup.github.com/jquery.blockUI.js"></script>
<script type="text/javascript">
//清除畫面上的hidden
function clearHidden() {
$("input[name='hPageSize']").val("");
$("input[name='hCurrentPageIndex']").val("");
}
//Ajax查詢
function search(pageNumber) {
//要block哪個元素這邊自由料理
$("form[name='myForm']").block({
message: '<h1>讀取中...</h1>',
css: { border: '3px solid #a00' }
});
//每頁顯示幾筆
var pageSize = $("#selectPageSize").val();
$("input[name='hPageSize']").val(pageSize);
//目前頁索引
$("input[name='hCurrentPageIndex']").val((pageNumber - 1));
var myForm = $("form[name='myForm']").serialize();
$.ajax({
url: "@Url.Action("ActionQueryData", "Home")",
type: "post",
async: true,
data: myForm,
success: function (result) {
//先清除上一次的結果
$("#divHtmlTable").empty();
//更新Html Table
$("#divHtmlTable").html(result);
//hidden都清掉,避免影響下一次的查詢結果
clearHidden();
//解除block UI
$("form[name='myForm']").unblock();
},
error: function (xhr) {
//hidden都清掉,避免影響下一次的查詢結果
clearHidden();
//顯示錯誤訊息
alert(xhr.responseText);
}
});
}
</script>
</head>
<body>
@using (Html.BeginForm("", "", FormMethod.Post, new { name="myForm"}))
{
@Html.Hidden("hPageSize")
@Html.Hidden("hCurrentPageIndex")
@*查詢輸入條件,要放在更新區塊外面*@
<div>
名稱:@Html.TextBox("sProductName")<br />
上下架:@Html.DropDownList("sIsOpen",(IEnumerable<SelectListItem>)ViewData["IsOpenItems"] )<br />
每頁顯示幾筆:@Html.DropDownList("selectPageSize", new List<SelectListItem>() {
new SelectListItem(){ Text="10",Value="10",Selected=true},
new SelectListItem(){ Text="30",Value="30"},
new SelectListItem(){ Text="50",Value="50"},
}, new { onchange="search(1);"})
@*按下搜尋後,到搜尋結果第1頁*@
<input type="button" value="搜尋" onclick="search(1);" /><br />
</div>
@*要更新的Html Table和Pager
為了Ajax的callback function方便更新畫面上的Table,把Table和Pager包成部份檢視*@
<div id="divHtmlTable">
@Html.Partial("HtmlTable",Model)
</div>
}
</body>
</html>
部份檢視的HtmlTable.cshtml
@model IEnumerable<MvcApplicationPager.Models.ProductViewModel>
@*複製範例時候,這個css可以刪除*@
<style type="text/css">
/*讓pager各數字間有空隔*/
#divPager a {
margin:2px;
}
/*目前頁數字的背景顏色*/
.highColor
{
background-color:red;
}
</style>
<table cellspacing="0" cellpadding="0" border="1" >
<thead>
<tr >
<th>編號</th>
<th>名稱</th>
<th>上下架</th>
</tr>
</thead>
<tbody >
@foreach (var item in Model)
{
<tr>
<td>@item.No</td>
<td>@item.ProductName</td>
<td>@item.IsOpen</td>
</tr>
}
</tbody>
</table><br />
@*分頁*@
<div id="divPager">
@Html.Raw(TempData["pager"])
</div>
執行畫面:
畫面弄得很陽春,這樣才好套美工人員的設計版型
結語:
大概這樣,剩下參數的驗證檢查就再自行補完吧
本文範例檔下載,放到MSDN期滿
2013.5.10追記下拉選單版:[ASP.net MVC 4] 自己打造分頁Pager範例程式碼 (下拉選單版,表單Post方式)
2013.6.6追記
原本Index.cshtml裡,部份檢視使用@Html.Partial()
如要使用@Html.Action()也是可以,修改Index.cshtml和HomeController.cs,而部份檢視HtmlTable.cshtml維持不變
※修改的部份已加註/**/符號
@*/*先移除ViewModel宣告*/*@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Index</title>
<script type="text/javascript" src="@Url.Content("~/Scripts/jquery-2.0.0.min.js")"></script>
<script type="text/javascript" src="http://malsup.github.com/jquery.blockUI.js"></script>
<script type="text/javascript">
function clearHidden() {
$("input[name='hPageSize']").val("");
$("input[name='hCurrentPageIndex']").val("");
}
function search(pageNumber) {
$("form[name='myForm']").block({
message: '<h1>讀取中...</h1>',
css: { border: '3px solid #a00' }
});
var pageSize = $("#selectPageSize").val();
$("input[name='hPageSize']").val(pageSize);
$("input[name='hCurrentPageIndex']").val((pageNumber - 1));
var myForm = $("form[name='myForm']").serialize();
$.ajax({
url: "@Url.Action("ActionQueryData", "Home")",
type: "post",
async: true,
data: myForm,
success: function (result) {
$("#divHtmlTable").empty();
$("#divHtmlTable").html(result);
clearHidden();
$("form[name='myForm']").unblock();
},
error: function (xhr) {
clearHidden();
alert(xhr.responseText);
}
});
}
</script>
</head>
<body>
@using (Html.BeginForm("", "", FormMethod.Post, new { name="myForm"}))
{
@Html.Hidden("hPageSize")
@Html.Hidden("hCurrentPageIndex")
<div>
名稱:@Html.TextBox("sProductName")<br />
上下架:@Html.DropDownList("sIsOpen",(IEnumerable<SelectListItem>)ViewData["IsOpenItems"] )<br />
每頁顯示幾筆:@Html.DropDownList("selectPageSize", new List<SelectListItem>() {
new SelectListItem(){ Text="10",Value="10",Selected=true},
new SelectListItem(){ Text="30",Value="30"},
new SelectListItem(){ Text="50",Value="50"},
}, new { onchange="search(1);"})
<input type="button" value="搜尋" onclick="search(1);" /><br />
</div>
<div id="divHtmlTable">
<!--/**/-->
@Html.Action("FirstAction","Home")
</div>
}
</body>
</html>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using MvcApplicationPager.Models;
using System.Text;
namespace MvcApplicationPager.Controllers
{
public class HomeController : Controller
{
/**/
[HttpGet]
public ActionResult Index()
{
ViewData["IsOpenItems"] = new List<SelectListItem>()
{
new SelectListItem(){ Text="請選擇",Value="-1",Selected=true},
new SelectListItem(){ Text="上架",Value="true" },
new SelectListItem(){ Text="下架",Value="false" },
};
return View();
}
/**/
//網頁一開始載入時,Partial View要呼叫的Action
public ActionResult FirstAction()
{
int iPageSize = 10;
int iPageCurrentIndex = 0;
IEnumerable<ProductViewModel> result = this.QueryData(iPageSize, iPageCurrentIndex, new FormCollection());
return View("HtmlTable", result);
}
//以下內容和之前的一樣
/// <summary>
/// 查詢資料
/// </summary>
/// <returns></returns>
public IEnumerable<ProductViewModel> QueryData(int iPageSize, int iCurrentPageIndex, FormCollection form)
{
var result = from r in MvcApplication.dbList
select r;
if (!string.IsNullOrEmpty(form["sProductName"]))
{//有輸入名稱
result = from r in result
where r.ProductName.Contains(form["sProductName"])
select r;
}
//有輸入上下架
if (form["sIsOpen"] != null && form["sIsOpen"] != "-1")
{
result = from r in result
where r.IsOpen == Convert.ToBoolean(form["sIsOpen"])
select r;
}
//部份檢視要顯示TempData["pager"]
TempData["pager"] = this.pageString(result,iPageSize,iCurrentPageIndex);
//抓DB少量的資料
result = result.Skip(iCurrentPageIndex * iPageSize).Take(iPageSize);
return result;
}
/// <summary>
/// 產生pager字串(這個請再自行抽到其他Utility之類的類別裡)
/// </summary>
/// <param name="result">集合物件</param>
/// <param name="iPageSize">每頁顯示幾筆</param>
/// <param name="iCurrentPageIndex">目前的索引頁</param>
/// <returns></returns>
public string pageString(IEnumerable<object> result,int iPageSize,int iCurrentPageIndex)
{
#region Pager處理(文章重點在這!)
//總筆數
int iRowTotal = result.Count();
//總頁數
int iPageTotal = (iRowTotal / iPageSize);
if (iRowTotal % iPageSize > 0)
{
iPageTotal++;
}
//目前頁數
int iCurrentPage = iCurrentPageIndex + 1;
//防呆
if (iCurrentPage > iPageTotal)
{
iCurrentPage = iPageTotal;
iCurrentPageIndex = iCurrentPage - 1;
}
if (iCurrentPage < 0)
{
iCurrentPage = 1;
iCurrentPageIndex = 0;
}
//頁數字按鈕每5個一組呈現(這邊可自行更改)
int pNumber = 5;
//頁數字按鈕共被分為幾分
int ppSize = iPageTotal / pNumber;
if (iPageTotal % pNumber > 0)
{
ppSize++;
}
//目前在哪一份(從1算起),為了顯示「...」
int currentPPSize = (iCurrentPageIndex / pNumber) + 1;
//要回傳的字串
StringBuilder sb = new StringBuilder();
//第二頁以後才有上一頁
string strPreJS = ((iCurrentPage > 1) ? "javascript:search('" + (iCurrentPage - 1) + "');" : "#");
sb.Append("<a href=\"" + strPreJS + "\">← Prev</a>");
sb.Append("<a href=\"javascript:search(1);\">First</a>");
//這公式算了我好久...Orz
int pStart = ((iCurrentPageIndex / pNumber) * pNumber + 1);
int pEnd = pStart + (pNumber - 1);
if (pEnd > iPageTotal)
{
pEnd = iPageTotal;
}
if (currentPPSize > 1)
{
sb.Append("<a href=\"javascript:search('" + (pStart - 1) + "')\">...</a>");
}
//產生頁數按鈕(從1算起)
for (int i = pStart; i <= pEnd; i++)
{
//複製範例時候,class="highColor"可以刪除
sb.Append("<a class='"+(i==iCurrentPage?"highColor":"")+"' href=\"javascript:search('" + i + "');\">" + i + "</a>");
}
if ((currentPPSize < ppSize))
{
sb.Append("<a href=\"javascript:search('" + (pEnd + 1) + "');\">...</a>");
}
//目前頁小於最後一頁,就有下一頁按鈕
string strNextJS = (iCurrentPage < iPageTotal ? "javascript:search('" + (iCurrentPage + 1) + "');" : "#");
sb.Append("<a href=\"javascript:search(" + iPageTotal + ");\">Last</a>");
sb.Append("<a href=\"" + strNextJS + "\">Next → </a>");
#endregion
return sb.ToString();
}
/// <summary>
/// Ajax查詢資料
/// </summary>
/// <param name="iPageSize"></param>
/// <param name="iCurrentPageIndex"></param>
/// <returns></returns>
[HttpPost]
public ActionResult ActionQueryData(FormCollection form)
{
//這裡的int iPageSize, int iCurrentPageIndex參數,前端使用者可能會用F12開發者工具亂改成字串
//怎麼防禦就請自由料理了...
int iPageSize = Convert.ToInt32(form["hPageSize"]);
int iCurrentPageIndex = Convert.ToInt32(form["hCurrentPageIndex"]);
IEnumerable<ProductViewModel> result = this.QueryData(iPageSize, iCurrentPageIndex, form);
return View("HtmlTable", result);
}
}
}
2013.8.29 追記
現在手邊專案很多都是用bootstrap樣式做的網站
所以補上個人用的bootstrap分頁樣式範例檔
產生分頁數字的function
/// <summary>
/// 取得分頁pager字串
/// </summary>
/// <param name="RowTotal">未分頁過的資料總筆數</param>
/// <param name="iPageSize">每頁顯示幾筆</param>
/// <param name="iCurrentPageIndex">目前的頁索引</param>
/// <returns></returns>
public static string GetPagerNumericString(int RowTotal, int iPageSize, int iCurrentPageIndex)
{
#region Pager處理
//總筆數
int iRowTotal = RowTotal;
//總頁數
int iPageTotal = (iRowTotal / iPageSize);
if (iRowTotal % iPageSize > 0)
{
iPageTotal++;
}
//目前頁數
int iCurrentPage = iCurrentPageIndex + 1;
//防呆
if (iCurrentPage > iPageTotal)
{
iCurrentPage = iPageTotal;
iCurrentPageIndex = iCurrentPage - 1;
}
if (iCurrentPage < 0)
{
iCurrentPage = 1;
iCurrentPageIndex = 0;
}
int pNumber = 5;//頁數字按鈕每5個一組呈現
//頁數字按鈕共被分為幾分
int ppSize = iPageTotal / pNumber;
if (iPageTotal % pNumber > 0)
{
ppSize++;
}
//目前在哪一份(從1算起),為了顯示「...」
int currentPPSize = (iCurrentPageIndex / pNumber) + 1;
StringBuilder sb = new StringBuilder();
sb.Append(@"<ul>");
//第二頁以後才有上一頁
string strPreJS = ((iCurrentPage > 1) ? "javascript:search('" + (iCurrentPage - 1) + "');" : "#");
sb.Append("<li class='prev " + (strPreJS == "#" ? "disabled" : "") + "'><a href=\"" + strPreJS + "\">← Prev</a></li>");
sb.Append("<li ><a href=\"javascript:search(1);\">First</a></li>");
int pStart = ((iCurrentPageIndex / pNumber) * pNumber + 1);
int pEnd = pStart + (pNumber - 1);
if (pEnd > iPageTotal)
{
pEnd = iPageTotal;
}
if (currentPPSize > 1)
{
sb.Append("<li class=''><a href=\"javascript:search('" + (pStart - 1) + "')\">...</a></li>");
}
//產生頁數按鈕(從1算起)
for (int i = pStart; i <= pEnd; i++)
{
sb.Append(@"<li class='" + (i == (iCurrentPageIndex + 1) ? "active" : "") + "'><a href=\"javascript:search('" + i + "');\">" + i + "</a></li>");
}
if ((currentPPSize < ppSize))
{
sb.Append("<li class=''><a href=\"javascript:search('" + (pEnd + 1) + "');\">...</a></li>");
}
//目前頁小於最後一頁,就有下一頁按鈕
string strNextJS = (iCurrentPage < iPageTotal ? "javascript:search('" + (iCurrentPage + 1) + "');" : "#");
sb.Append("<li ><a href=\"javascript:search(" + iPageTotal + ");\">Last</a></li>");
sb.Append("<li class='next " + (strNextJS == "#" ? "disabled" : "") + "'><a href=\"" + strNextJS + "\">Next → </a></li>");
sb.Append("</ul>");
#endregion
return sb.ToString();
}
執行成果大概會是這樣↓
記得畫面上有引用bootstrap相關.css就行了