2011年9月21日 星期三

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


上一篇資料分頁的文章「ASP.NET MVC - 資料分頁(2) 自訂分頁功能列 MvcSimplePager」最後有說到,

當表單查詢遇到資料分頁時,用原本的資料分頁方式會出現問題,

因為資料分頁於跳頁時是利用HyperLink將頁數資料以Get傳回後端處理,查詢條件就無法一併送到後端去處理,

為了達到這個需求,所以就把原本的MvcSimplePostPager做了一些修改,

讓前端的分頁功能列可以將查詢表單上的條件一起傳回後端,送回前端的資料一樣是維持查詢條件,

所以這篇文章就是說明如何製作一個使用「Post」方式的資料分頁方式。


因為上篇文章所做出來的分頁功能叫做「MvcSimplePager」,

這次主要是要做一個用Post方式將查詢條件與頁數送回後端的分頁功能,所以就叫做「MvcSimplePostPager」。

另外這個MvcSimplePostPager除了ASP.NET MVC的程式需要撰寫外,

在前端部分也會需要用到jQuery,以達到將查詢條件、頁數使用Post送到後端的操作。

 

一、PagerHelper.cs

在原本已經建立SimplePager()方法的PagerHelper.cs裡面在去增加兩個方法,方法名稱都是 MvcSimplePostPager,

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

第二個則是主要實作的方法,可以指定是否顯示頁數連結的bool參數。

MvcSimplePostPager的方法參數比MvcSimplePager多了一個 currentPage,

這是因為原本MvcSimplePager是以HyperLink方式將頁數以URI傳送回後端,

可以使用「html.ViewContext.HttpContext.Request.QueryString」去取得URI裡面要指定跳頁的頁數資料,

而使用Post的話,則需要在前端將要跳頁的頁數給放到表單中的一個Hidden Field中,讓這個參數傳回到後端,

所以後端的MvcSimplePostPager方法要多個參數來接前端所傳過來的頁數。

  1: #region MvcSimplePostPager
  2: 
  3: /// <summary>
  4: /// Pagers the specified HTML using POST.
  5: /// </summary>
  6: /// <param name="html">The HTML.</param>
  7: /// <param name="pageSize">Size of the page.</param>
  8: /// <param name="totalCount">The total count.</param>
  9: /// <returns></returns>
 10: public static string MvcSimplePostPager(this HtmlHelper html, int currentPage, int pageSize, int totalCount)
 11: {
 12:   return MvcSimplePostPager(html, currentPage, pageSize, totalCount, false);
 13: }
 14: 
 15: /// <summary>
 16: /// Pagers the specified HTML using POST.
 17: /// </summary>
 18: /// <param name="html">The HTML.</param>
 19: /// <param name="currentPage">The current page.</param>
 20: /// <param name="pageSize">Size of the page.</param>
 21: /// <param name="totalCount">The total count.</param>
 22: /// <param name="showNumberLink">if set to <c>true</c> [show number link].</param>
 23: /// <returns></returns>
 24: public static string MvcSimplePostPager(this HtmlHelper html, int currentPage, int pageSize, int totalCount, bool showNumberLink)
 25: {
 26:   if (totalCount == 0)
 27:   {
 28:     return string.Empty;
 29:   }
 30:   else
 31:   {
 32:     currentPage++;
 33: 
 34:     //總頁數
 35:     var totalPages = Math.Max((totalCount + pageSize - 1) / pageSize, 1);
 36: 
 37:     var _renderPager = new StringBuilder();
 38: 
 39:     if (currentPage <= 0)
 40:     {
 41:       currentPage = 1;
 42:     }
 43: 
 44:     string emptyAtagFormat = "<a href=\"#\" style=\"cursor:pointer;\">{0}</a>";
 45: 
 46:     if (totalPages == 1)
 47:     {
 48:       _renderPager.AppendFormat(emptyAtagFormat, "第一頁");
 49:       _renderPager.AppendFormat(emptyAtagFormat, "上一頁");
 50:       _renderPager.AppendFormat(emptyAtagFormat, "下一頁");
 51:       _renderPager.AppendFormat(emptyAtagFormat, "最後一頁");
 52:     }
 53:     else if (totalPages > 1)
 54:     {
 55:       #region 處理首頁連接
 56: 
 57:       if (currentPage != 1)
 58:       {
 59:         _renderPager.Append("<a class=\"PostPager first-page\">第一頁</a>");
 60:       }
 61:       else
 62:       {
 63:         _renderPager.AppendFormat(emptyAtagFormat, "第一頁");
 64:       } 
 65:       #endregion
 66: 
 67:       #region 處理上一頁的連接
 68:       
 69:       if (currentPage > 1)
 70:       {
 71:         _renderPager.AppendFormat("<a class=\"PostPager previous-page\" value=\"{0}\">上一頁</a>", currentPage - 1);
 72:       }
 73:       else
 74:       {
 75:         _renderPager.AppendFormat(emptyAtagFormat, "上一頁");
 76:       } 
 77:       #endregion
 78: 
 79:       #region 顯示頁數連結
 80:       if (showNumberLink)
 81:       {
 82:         var pageCount = (int)Math.Ceiling(totalCount / (double)pageSize);
 83:         const int nrOfPagesToDisplay = 10;
 84: 
 85:         var start = 1;
 86:         var end = pageCount;
 87: 
 88:         if (pageCount > nrOfPagesToDisplay)
 89:         {
 90:           var middle = (int)Math.Ceiling(nrOfPagesToDisplay / 2d) - 1;
 91:           var below = (currentPage - middle);
 92:           var above = (currentPage + middle);
 93: 
 94:           if (below < 4)
 95:           {
 96:             above = nrOfPagesToDisplay;
 97:             below = 1;
 98:           }
 99:           else if (above > (pageCount - 4))
100:           {
101:             above = pageCount;
102:             below = (pageCount - nrOfPagesToDisplay);
103:           }
104: 
105:           start = below;
106:           end = above;
107:         }
108: 
109:         if (start > 3)
110:         {
111:           _renderPager.AppendFormat("<a class=\"PostPager number-page\" value=\"{0}\">{0}</a>", "1");
112:           _renderPager.AppendFormat("<a class=\"PostPager number-page\" value=\"{0}\">{0}</a>", "2");
113:           _renderPager.Append("...");
114:         }
115: 
116:         for (var i = start; i <= end; i++)
117:         {
118:           if (i == currentPage || (currentPage <= 0 && i == 0))
119:           {
120:             _renderPager.AppendFormat("<span class=\"current\">{0}</span>", i);
121:           }
122:           else
123:           {
124:             _renderPager.AppendFormat("<a class=\"PostPager number-page\" value=\"{0}\">{0}</a>", i.ToString());
125:           }
126:         }
127:         if (end < (pageCount - 3))
128:         {
129:           _renderPager.AppendFormat("...");
130:           _renderPager.AppendFormat("<a class=\"PostPager number-page\" value=\"{0}\">{0}</a>", (pageCount - 1).ToString());
131:           _renderPager.AppendFormat("<a class=\"PostPager number-page\" value=\"{0}\">{0}</a>", pageCount.ToString());
132:         }
133:       }
134:       #endregion
135: 
136:       #region 處理下一頁的連結
137:       
138:       if (currentPage < totalPages)
139:       {
140:         _renderPager.AppendFormat("<a class=\"PostPager next-page\" value=\"{0}\">下一頁</a>", currentPage + 1);
141:       }
142:       else
143:       {
144:         _renderPager.AppendFormat(emptyAtagFormat, "下一頁");
145:       }
146:       
147:       #endregion
148: 
149:       #region 最後一頁
150:       if (currentPage != totalPages)
151:       {
152:         _renderPager.AppendFormat("<a class=\"PostPager last-page\" value=\"{0}\">最後一頁</a>", totalPages);
153:       }
154:       else
155:       {
156:         _renderPager.AppendFormat(emptyAtagFormat, "最後一頁");
157:       } 
158:       #endregion
159:     }
160: 
161:     // 目前頁數/總頁數
162:     _renderPager.AppendFormat("  第 {0} 頁  /  共 {1} 頁  共 {2} 筆", currentPage, totalPages, totalCount);
163: 
164:     return _renderPager.ToString();
165:   }
166: }
167: 
168: #endregion

在上面的程式中,雖然每個操作跳頁的element還是一樣使用HyperLink,但仔細看看,每個HyperLink並沒有指定href,

而且每個HyperLink都有指定class「PostPager」以及幾個不同的class

「first-page」「previous-page」「next-page」「last-page」「number-page」

這幾個class是為了要給前端的jQuery操作所使用的,稍後在說明前端網頁的處理時會再仔細的解釋。

 

二、Controller/Action

在Controller的Action部分就會有所不同了,首先一開始的頁面是還沒有任何查詢條件,所以也沒有資料可以顯示,

如下圖:

image

先直接看程式的部份:

  1: #region PageMethod3
  2: /// <summary>
  3: /// Pages the method3.
  4: /// </summary>
  5: /// <param name="page">The page.</param>
  6: /// <returns></returns>
  7: public ActionResult PageMethod3(int? page)
  8: {
  9:   return PageMethod3(page, new FormCollection());
 10: }
 11: 
 12: /// <summary>
 13: /// Pages the method3.
 14: /// </summary>
 15: /// <param name="page">The page.</param>
 16: /// <param name="formCollection">The form collection.</param>
 17: /// <returns></returns>
 18: [HttpPost]
 19: public ActionResult PageMethod3(int? page, FormCollection formCollection)
 20: {
 21:   ViewData["CategoryDDL"] = categoryService.GenerateCategoryDDL(formCollection["CategoryDDL"] ?? string.Empty);
 22: 
 23:   if (formCollection.AllKeys.Length.Equals(0))
 24:   {
 25:     return View();
 26:   }
 27:   else
 28:   {
 29:     int check = 0;
 30:     if (!int.TryParse(formCollection["CategoryDDL"], out check))
 31:     {
 32:       return View();
 33:     }
 34:     else
 35:     {
 36:       int categoryId = int.Parse(formCollection["CategoryDDL"]);
 37:       var result = this.productService.GetCollectionBy(categoryId);
 38: 
 39:       int currentPageIndex = page.HasValue ? page.Value - 1 : 0;
 40:       ViewData["CurrentPage"] = currentPageIndex;
 41:       return View(result.ToPagedList(currentPageIndex, 5));
 42:     }
 43:   }
 44: } 
 45: #endregion

建立兩個同名方法,第一個方法是一開始進入頁面所執行的Action,而第二個方法則是實作的部份,主要的運作都是在第二個方法中,

一開始進入頁面是進入到第一個Action,因為沒有任何查詢條件,所以直接return第二個Action的內容,而FormCollection則是建立一個instance傳入,

第二個Action一開始就是先處理查詢表單所要顯示的Category下拉選單,當FormCollection裡面有包含Category下拉選單的選取值時,

將FormCollection[“CategoryDDL”]的值傳給產生下拉選單Html Tag的程式,如此一來產生的下拉選單就可以保留選取狀態,

再來就是當FormCollection裡面有包含前端送出的CurrentPage資料時,再加上FormCollection[“CategoryDDL”]資料,這樣就可以取得分頁資料。

另外我們還必須將CurrentPage資料放到ViewData中,讓前端頁面使用,產生分頁功能列時才能夠知道目前的顯示頁數。

這邊處補充一下有關產生下拉選單的程式部分,

  1: /// <summary>
  2: /// Generates the category DDL.
  3: /// </summary>
  4: /// <param name="selectedValue">The selected value.</param>
  5: /// <returns></returns>
  6: public string GenerateCategoryDDL(string selectedValue)
  7: {
  8:   string tagIdName = "CategoryDDL";
  9: 
 10:   Dictionary<string, string> optionData = new Dictionary<string, string>();
 11: 
 12:   var query = this.GetCollection();
 13:   foreach (var item in query)
 14:   {
 15:     if (optionData.Where(x => x.Key.Equals(item.ToString())).Count().Equals(0))
 16:     {
 17:       optionData.Add(item.ToString(), item.CategoryID.ToString());
 18:     }
 19:   }
 20: 
 21:   string _html = DropDownListHelper.GetDropdownList(tagIdName, tagIdName, optionData, null, selectedValue, true, null);
 22:   return _html;
 23: }
 24: 

 

三、前端頁面

查詢表單部分:

<div id="formArea" style="width: 90%; margin-left:auto; margin-right:auto;">
  <% using(Html.BeginForm("PageMethod3", "Home", FormMethod.Post, new { id = "formPager" })) { %>
  Category:<%= ViewData["CategoryDDL"] %>
  <input type="button" id="ButtonSearch" value="List" />
  <% } %>
</div>

要注意到表單的傳送方法,一定要使用Post,並且一定要給表單一個Element ID,這樣接下來的前端Script才能藉由ID取得表單。

 

前端頁面放置分頁功能列的部份就有了一些改變,先來看程式的內容:

<div id="pager" class="pager">
<% 
  if (Model != null) 
  { 
    int currentPage = ViewData["CurrentPage"] == null ? 0 : int.Parse(ViewData["CurrentPage"].ToString());
    Response.Write(Html.MvcSimplePostPager(currentPage, Model.PageSize, Model.TotalItemCount, true)); 
  } 
%>
</div>

這邊要去判斷Model是否還有資料,沒有資料就不需要顯示分頁功能列,而有資料時才顯示,

在Controller的Action中有將CurrentPage資料給放到ViewData裡,而MvcSimplePostPager方法就會使用到這個CurrentPage資料。

 

四、前端Script

<script language="javascript" type="text/javascript">
<!--
  $(document).ready(function ()
  {
    $('#table1 tr:odd').css('background-color', '#F5FBFC');
    PostPager();
  });
  $('#ButtonSearch').click(function ()
  {
    var categoryId = $.trim($('#CategoryDDL option:selected').val());
    if (categoryId.length == 0)
    {
      alert('Please select Category.');
      return false;
    }
    else
    {
      $('#formPager').submit();
    }
  });
  //================== Post方式做換頁 =========================
  //將Pager上面的連結項目的行為分離出來
  //使用class方式做行為判別與動作執行
  function PostPager()
  {
    $('.PostPager').each(function (i, item)
    {
      $(item).attr('href', '#');
    });
    $('.PostPager.first-page').click(function () { GoToPage(1); });
    $('.PostPager.previous-page,.next-page,.last-page,.number-page').click(function ()
    {
      GoToPage($(this).attr('value'));
    });
  }
  //預設的Form表單名稱為 formPager
  function GoToPage(page)
  {
    var targetFormID = '#formPager';
    if ($(targetFormID).size() > 0)
    {
      $('<input>').attr({ type: 'hidden', id: 'page', name: 'page', value: page }).appendTo($(targetFormID));
      $(targetFormID).submit();
    }
  }
  //================== Post方式做換頁 =========================
-->
</script>

在document於載入完成後就執行 PostPager(),這個function就是綁定分頁功能列上面每個連結的click事件,

當click事件觸發時,就將連結element裡的value放到一個動態產生的hidden field中,這個hidden field再附加到查詢表單裡,最後再submit查詢表單,

如此一來當表單送出時就同時把分頁的頁數以post傳送到後端去了。

 

來看看執行的結果:

查詢表單於選擇下拉表單送出後所顯示的畫面

image

點擊分頁功能列的「2」或是「下一頁」送出後的畫面,Category的下拉選單仍然保持剛才的選取項目,

下方的表格也確實顯示分頁資料,而網址列部分也沒有出現page的參數。

image

最後一頁

image

 

好的,最後完成了以Post方式來操作的分頁功能,並且在傳送分頁後仍然可以保持Post送出前的查詢條件狀態,

有些人應該會覺得這樣的分頁好像還少了些什麼?!

應該說還少了AJAX的操作,因為ASP.NET MVC的一大特色就是前端比ASP.NET WebForm更容易與jQuery結合,

所以下一篇就來介紹如何用MvcSimplePager以及ASP.NET MVC的Html.RenderPartial()來達成AJAX分頁的功能操作。

 

以上

1 則留言:

提醒

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