2011年9月18日 星期日

ASP.NET MVC - 資料分頁(2) 自訂分頁功能列 MvcSimplePager


在「ASP.NET MVC - 資料分頁(1) 使用MvcPaging」這篇文章中已經有介紹如何使用MvcPaging來做資料分頁,

而MvcPaging所提供的Html.Pager()所產生的分頁功能列是比較陽春的,

就是有各個頁數的連結以及上一頁、下一頁的連結,但有時候客戶那邊會希望分頁的功能列可以有些變化,

在不去修改MvcPaging原始碼的情況下,我們可以在IPagedList<T>的基礎上去自己設計一個分頁功能頁。

以下說明如何自訂分頁功能列(姑且命名為:MvcSimplePager)


2011-09-20 更新:

因為原來取名的SimplePager,已經有個jQuery Plugin也是同樣這個名字,所以就改為「MvcSimplePager」。

jQuery Plugin:「SimplePager - an easy jquery paging plugin

Demo:http://geckohub.com/tests/simplepager/demo/2/

有興趣的人再去看看囉!



先來看看我們所做的MvcSimplePager呈顯的樣子。

image

上圖可以看到,將原本的用< , > 所代表的上頁、下頁給拿掉,取而代之的是第一頁、上一頁、下一頁、最後一頁,

並且在最後面顯示 當前頁、全部頁數、全部資料比數,而中間的頁數連結也希望可以使用參數的方式選擇是否出現。

下圖則是選擇不出現頁數連結的樣子。

image

 

一、建立PagerHelper.cs

建立一個靜態類別 PagerHelper,並且在類別中建立兩個靜態方法 MvcSimplePager,

第一個MvcSimplePager方法是預設不顯示頁數連結,

第二個則是主要的實作,另外提供是否顯示頁數連結的bool參數。

  1: #region MvcSimplePager
  2: 
  3: /// <summary>
  4: /// 分頁Pager顯示
  5: /// </summary>
  6: /// <param name="html"></param>
  7: /// <param name="pageSize">每頁顯示</param>
  8: /// <param name="totalCount">總數據量</param>
  9: /// <returns></returns>
 10: public static string MvcSimplePager(this HtmlHelper html, int pageSize, int totalCount)
 11: {
 12:   return MvcSimplePager(html, pageSize, totalCount, false);
 13: }
 14: 
 15: /// <summary>
 16: /// 分頁Pager顯示
 17: /// </summary>
 18: /// <param name="html">The HTML.</param>
 19: /// <param name="pageSize">每頁顯示</param>
 20: /// <param name="totalCount">總數據量</param>
 21: /// <param name="showNumberLink">是否顯示數字連結.</param>
 22: /// <returns></returns>
 23: public static string MvcSimplePager(this HtmlHelper html, int pageSize, int totalCount, bool showNumberLink)
 24: {
 25:   var queryString = html.ViewContext.HttpContext.Request.QueryString;
 26: 
 27:   //當前頁
 28:   int currentPage = 1;
 29: 
 30:   //總頁數
 31:   var totalPages = Math.Max((totalCount + pageSize - 1) / pageSize, 1);
 32: 
 33:   //從ViewContext.RouteData.Values取得目前的RouteValueDictionary
 34:   var routeValueDict = new System.Web.Routing.RouteValueDictionary(html.ViewContext.RouteData.Values);
 35: 
 36:   var _renderPager = new StringBuilder();
 37: 
 38:   if (!string.IsNullOrEmpty(queryString[currentPageStr]))
 39:   {
 40:     //與相應的QueryString綁定
 41:     foreach (string key in queryString.Keys)
 42:     {
 43:       if (queryString[key] != null && !string.IsNullOrEmpty(key))
 44:       {
 45:         routeValueDict[key] = queryString[key];
 46:       }
 47:     }
 48:     int.TryParse(queryString[currentPageStr], out currentPage);
 49:   }
 50:   else
 51:   {
 52:     //取得 ~/Page/{page number} 的頁號參數
 53:     if (routeValueDict.ContainsKey(currentPageStr))
 54:     {
 55:       int.TryParse(routeValueDict[currentPageStr].ToString(), out currentPage);
 56:     }
 57:   }
 58: 
 59:   //保留查詢字元到下一頁
 60:   foreach (string key in queryString.Keys)
 61:   {
 62:     routeValueDict[key] = queryString[key];
 63:   }
 64: 
 65:   if (currentPage <= 0)
 66:   {
 67:     currentPage = 1;
 68:   }
 69: 
 70:   string emptyAtagFormat = "<a href=\"#\" style=\"cursor:pointer;\">{0}</a>";
 71: 
 72:   if (totalPages == 1)
 73:   {
 74:     _renderPager.AppendFormat(emptyAtagFormat, "第一頁");
 75:     _renderPager.AppendFormat(emptyAtagFormat, "上一頁");
 76:     _renderPager.AppendFormat(emptyAtagFormat, "下一頁");
 77:     _renderPager.AppendFormat(emptyAtagFormat, "最後一頁");
 78:   }
 79:   else if (totalPages > 1)
 80:   {
 81:     if (currentPage != 1)
 82:     {
 83:       //處理首頁連接
 84:       routeValueDict[currentPageStr] = 1;
 85:       _renderPager.Append(html.RouteLink("第一頁", routeValueDict));
 86:     }
 87:     else
 88:     {
 89:       _renderPager.AppendFormat(emptyAtagFormat, "第一頁");
 90:     }
 91: 
 92:     if (currentPage > 1)
 93:     {
 94:       //處理上一頁的連接
 95:       routeValueDict[currentPageStr] = currentPage - 1;
 96:       _renderPager.Append(html.RouteLink("上一頁", routeValueDict));
 97:     }
 98:     else
 99:     {
100:       _renderPager.AppendFormat(emptyAtagFormat, "上一頁");
101:     }
102: 
103:     #region 顯示頁數連結
104:     if (showNumberLink)
105:     {
106:       var pageCount = (int)Math.Ceiling(totalCount / (double)pageSize);
107:       const int nrOfPagesToDisplay = 10;
108: 
109:       var start = 1;
110:       var end = pageCount;
111: 
112:       if (pageCount > nrOfPagesToDisplay)
113:       {
114:         var middle = (int)Math.Ceiling(nrOfPagesToDisplay / 2d) - 1;
115:         var below = (currentPage - middle);
116:         var above = (currentPage + middle);
117: 
118:         if (below < 4)
119:         {
120:           above = nrOfPagesToDisplay;
121:           below = 1;
122:         }
123:         else if (above > (pageCount - 4))
124:         {
125:           above = pageCount;
126:           below = (pageCount - nrOfPagesToDisplay);
127:         }
128: 
129:         start = below;
130:         end = above;
131:       }
132: 
133:       if (start > 3)
134:       {
135:         routeValueDict[currentPageStr] = "1";
136:         _renderPager.Append(html.RouteLink("1", routeValueDict));
137: 
138:         routeValueDict[currentPageStr] = "2";
139:         _renderPager.Append(html.RouteLink("2", routeValueDict));
140:         
141:         _renderPager.Append("...");
142:       }
143: 
144:       for (var i = start; i <= end; i++)
145:       {
146:         if (i == currentPage || (currentPage <= 0 && i == 0))
147:         {
148:           _renderPager.AppendFormat("<span class=\"current\">{0}</span>", i);
149:         }
150:         else
151:         {
152:           routeValueDict[currentPageStr] = i.ToString();
153:           _renderPager.Append(html.RouteLink(i.ToString(), routeValueDict));
154:         }
155:       }
156:       if (end < (pageCount - 3))
157:       {
158:         _renderPager.Append("...");
159: 
160:         routeValueDict[currentPageStr] = (pageCount - 1).ToString();
161:         _renderPager.Append(html.RouteLink((pageCount - 1).ToString(), routeValueDict));
162: 
163:         routeValueDict[currentPageStr] = pageCount.ToString();
164:         _renderPager.Append(html.RouteLink(pageCount.ToString(), routeValueDict));
165:       }
166:     }
167:     #endregion
168: 
169:     if (currentPage < totalPages)
170:     {
171:       //處理下一頁的連結
172:       routeValueDict[currentPageStr] = currentPage + 1;
173:       _renderPager.Append(html.RouteLink("下一頁", routeValueDict));
174:     }
175:     else
176:     {
177:       _renderPager.AppendFormat(emptyAtagFormat, "下一頁");
178:     }
179: 
180:     if (currentPage != totalPages)
181:     {
182:       routeValueDict[currentPageStr] = totalPages;
183:       _renderPager.Append(html.RouteLink("最後一頁", routeValueDict));
184:     }
185:     else
186:     {
187:       _renderPager.AppendFormat(emptyAtagFormat, "最後一頁");
188:     }
189:   }
190: 
191:   // 目前頁數/總頁數
192:   _renderPager.AppendFormat("  第 {0} 頁  /  共 {1} 頁  共 {2} 筆", currentPage, totalPages, totalCount);
193: 
194:   return _renderPager.ToString();
195: }
196: 
197: #endregion

在上面的程式碼中,比較重要的地方在於RouteValueDictionary的使用

第38行,建立一個routeValueDict物件,從ViewContent.RouteData.Value取得RouteValueDictionary的內容,

var routeValueDict = new System.Web.Routing.RouteValueDictionary(html.ViewContext.RouteData.Values);

取出RouteValueDictionary則是為了要產生在分頁功能列中每個連結裡的HyperLink

然後在每個要產生有效連結的地方,還要去設定routeValueDict[“page”] ( routeValueDict[currentPageStr] )

這是要設定每個連結的HyperLink中的page參數,設定正確的頁數,

而設定連結的HyperLink則是使用了 Html LinkExtensions 的 RouteLink()方法

MSDN - Html LinkExtensions.RouteLink() 方法

MSDN - LinkExtensions.RouteLink 方法 (HtmlHelper, String, RouteValueDictionary)

 

而位在Line:104的地方,就是判斷 showNumberLink 參數已決定是否顯示頁數連結,

而這中間產生頁數連結的程式部分則是直接取用MvcPaging的Pager()裡面的程式,

而有修改的地方就是產生HtperLink的部份。

 

好的,MvcSimplePager()方法已經準備就緒,接下來就是直接使用。

 

二、Controller, Action

在Controller的Action內容部分是不會有所變化

#region PageMethod2
/// <summary>
/// Pages the method2.
/// </summary>
/// <param name="page">The page.</param>
/// <returns></returns>
public ActionResult PageMethod2(int? page)
{
  int currentPageIndex = page.HasValue ? page.Value - 1 : 0;
  var result = this.productService.GetCollection();
  return View(result.ToPagedList(currentPageIndex, 5));
}
#endregion

 

三、ViewPage

ViewPage的部份則是延續「ASP.NET MVC - 資料分頁(1) 使用MvcPaging」中對於ViewPage的設定,

而MvcSimplePager的使用上有以下幾種方式

1.這是預設不會顯示頁數連結

<div class="pager">
  <%= Html.MvcSimplePager(Model.PageSize, Model.TotalItemCount)%>
</div>

2.這是另一種不會顯示頁數連結的使用方式

<div class="pager">
  <%= Html.MvcSimplePager(Model.PageSize, Model.TotalItemCount, false)%>
</div>

3.顯示頁數連結

<div class="pager">
  <%= Html.MvcSimplePager(Model.PageSize, Model.TotalItemCount, true)%>
</div>

可以看到,在Controller/Action中沒有需要改變的地方,而在ViewPage裡需要變動的地方也相當少,

與原來MvcPaging Pager()做個比較:

<div class="pager">
  <%= Html.Pager(Model.PageSize, Model.PageNumber, Model.TotalItemCount) %>
</div>

頂多就只是方法名稱不一樣,且又多了另一種顯示方式的選擇。

 

最後再來看看顯示的畫面:

第一頁

image

中間頁數顯示

image

最後一頁

image

 

這裡說明了如何以MvcPaging為基礎,讓我們自訂頁面上的分頁功能列,

所以客戶那邊對於分頁功能列想要什麼樣的變化,對我們來說就不是一件困難的事啦!

 

下篇「資料分頁-使用MvcPaging」的文章,再來討論如何在有查詢表單下依然可以使用資料分頁的作法,

因為現在介紹的兩篇資料分頁的文章,最後的結果都是使用HyperLink的方式將頁數資料傳回後端,

再把指定頁數的分頁傳回到前端,因為是用HyperLink的方式,所以表單上的查詢條件就無法保存,

甚至傳回前端的分頁資料也不是符合查詢條件的資料了。

 

以上。

3 則留言:

  1. 大大你好~我又來打擾了

    請問大大做的這篇也可以把它應用成Ajax的方式嗎?

    回覆刪除
    回覆
    1. 如果你有看過這部落格的「資料分頁」系列文章,你將會有發現的...
      「資料分頁」系列的文章有兩個範例文章下載,
      請將程式下載後再依照文章內容來看程式,這樣才會有所收穫。

      另外一個建議,學習一項技術或是技巧,不要太追求片面的內容,
      真的建議你要有系統的做學習,而不是跳著看然後只挑想要的來用,

      如果你有認真找資料的話,
      「ASP.NET MVC - 資料分頁(4) MvcSimplePostPager + AJAX」
      這一篇就是你所要的!!!

      刪除
  2. 回覆大大
    其實您這篇ASP.NET MVC 資料分頁 MVCPaging 2.0 應用 Part.4:分頁進階處理
    才是我想要的資料
    只是我想一步步作
    1.由最單純的分頁加上Ajax
    2.加上條件search部份資料加上Ajax
    3.由以上的加上排序作Ajax

    感謝大大的建議~^^

    回覆刪除

提醒

千萬不要使用 Google Talk (Hangouts) 或 Facebook 及時通訊與我聯繫、提問,因為會掉訊息甚至我是過了好幾天之後才發現到你曾經傳給我訊息過,請多多使用「詢問與建議」(在左邊,就在左邊),另外比較深入的問題討論,或是有牽涉到你實作程式碼的內容,不適合在留言板裡留言討論,請務必使用「詢問與建議」功能(可以夾帶檔案),謝謝。