2011年9月21日 星期三

ASP.NET MVC - 資料分頁(4) MvcSimplePostPager + AJAX


之前幾篇的資料分頁文章裡面所說明的,無論是用HyperLink或是用Post方式,送出後再傳回前端燈一定會Reload,

有的專案會比較要求UI操作,會提出分頁不想要有整頁Reload的情況,而是要用AJAX的方式,只更新分頁資料部分,

在以往採用ASP.NET WebForm開發時,我想大部分的開發者一定會把這種要有AJAX效果的部份給塞到UpdatePanel,

但是在ASP.NET MVC就不需要用這樣的方式來達成AJAX(很多人會覺得不習慣沒有UpdatePanel),

但是ASP.NET MVC+jQuery的搭配下,可以讓我們以更加彈性的方式來完成AJAX分頁的效果。


前端ViewPage的HTML部分還是一樣延續上篇文章的內容「ASP.NET MVC - 資料分頁(3) 自訂分頁功能列 MvcSimplePostPager」,

為了達成AJAX分頁的功能,整個網頁不會因為分頁資料的改變而Reload,所以把資料會變動的部份給挖出來,

另外存在於一個ascx(View User Control)的檔案中,下圖中以紅線框起來的就是會變動的部分。

image

先還不動手做這一部分,先來處理Controller/Action的地方

下面是前篇文章的Controller Action的程式:

/// <summary>
/// Pages the method3.
/// </summary>
/// <param name="page">The page.</param>
/// <param name="formCollection">The form collection.</param>
/// <returns></returns>
[HttpPost]
public ActionResult PageMethod3(int? page, FormCollection formCollection)
{
  ViewData["CategoryDDL"] = categoryService.GenerateCategoryDDL(formCollection["CategoryDDL"] ?? string.Empty);
  if (formCollection.AllKeys.Length.Equals(0))
  {
    return View();
  }
  else
  {
    int check = 0;
    if (!int.TryParse(formCollection["CategoryDDL"], out check))
    {
      return View();
    }
    else
    {
      int categoryId = int.Parse(formCollection["CategoryDDL"]);
      var result = this.productService.GetCollectionBy(categoryId);
      int currentPageIndex = page.HasValue ? page.Value - 1 : 0;
      ViewData["CurrentPage"] = currentPageIndex;
      return View(result.ToPagedList(currentPageIndex, 5));
    }
  }
} 

因為是以Post的方式來處理資料分頁,所以無論下拉選單還是取出鄉符合的分頁資料都會在同一個Action去做處理,

但因為要達到AJAX的功能,所以上面的程式當中會有需要抽出另外處理的部份就是取出分頁資料的地方,

下面就是調整後的程式:

#region PageMethod4
public ActionResult PageMethod4()
{
  ViewData["CategoryDDL"] = categoryService.GenerateCategoryDDL(null);
  return View();
}
public ActionResult PageContent(int? page, string categoryId)
{
  if (string.IsNullOrWhiteSpace(categoryId))
  {
    return View();
  }
  else
  {
    int id = int.Parse(categoryId);
    var result = this.productService.GetCollectionBy(id);
    int currentPageIndex = page.HasValue ? page.Value - 1 : 0;
    ViewData["CurrentPage"] = currentPageIndex;
    var dataPaged = result.ToPagedList(currentPageIndex, 5);
    return View(dataPaged);
  }
} 
#endregion

進入網頁的入口Action:PageMethod4(),這個Action只需要處理顯示Category下拉選單,

而取得資料分頁的處理就抽出來放在另一個Action:PageContent()中,

而等一下要由這個Action去新增一個View User Contrrol,

image

在PageContent()的Action區塊中click滑鼠右鍵,顯示的ContextMenu裡去選擇並click「加入檢視」,

記得要勾選「建立部分檢視().ascx」

image

新建立的PageContent.ascx是個空空的檔案,解下來就逐步調整,

image

首先修改第一行裡面的Inherits以及Import Namespace

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<IPagedList<Product>>" %>
<%@ Import Namespace="MvcPaging"%>
<%@ Import Namespace="MVC_Page.Models" %>
<%@ Import Namespace="MVC_Page.Helpers" %>

再來就是從原本ViewPage的Html去把會變動的部份給搬到PageContent.ascx裡,

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<IPagedList<Product>>" %>
<%@ Import Namespace="MvcPaging"%>
<%@ Import Namespace="MVC_Page.Models" %>
<%@ Import Namespace="MVC_Page.Helpers" %>
<div id="PagerWrapperTop" class="PagerWrapper" style="text-align:left;">
  <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>
</div>
<table id="table1" class="grid" style="width: 90%; margin-left:auto; margin-right:auto; background-color: #fffee;">
  <tr>
    <th>
      ProductID
    </th>
    <th>
      ProductName
    </th>
    <th>
      SupplierID
    </th>
    <th>
      CategoryID
    </th>
    <th>
      QuantityPerUnit
    </th>
    <th>
      UnitPrice
    </th>
    <th>
      UnitsInStock
    </th>
    <th>
      UnitsOnOrder
    </th>
    <th>
      ReorderLevel
    </th>
    <th>
      Discontinued
    </th>
  </tr>
  <% 
  if (Model == null)
  {
    Response.Write("<tr><td colspan=\"10\" align=\"center\">No Data.</td></tr>");
  }
  else
  {
    foreach (var item in Model)
    { %>    
  <tr>
    <td>
      <%: item.ProductID%>
    </td>
    <td>
      <%: item.ProductName%>
    </td>
    <td>
      <%: item.SupplierID%>
    </td>
    <td>
      <%: item.CategoryID%>
    </td>
    <td>
      <%: item.QuantityPerUnit%>
    </td>
    <td>
      <%: String.Format("{0:F}", item.UnitPrice)%>
    </td>
    <td>
      <%: item.UnitsInStock%>
    </td>
    <td>
      <%: item.UnitsOnOrder%>
    </td>
    <td>
      <%: item.ReorderLevel%>
    </td>
    <td>
      <%: item.Discontinued%>
    </td>
  </tr>
    
  <% 
    }
  } 
  %>
</table>

到這裡我們已經完成了PageContent.ascx的準備工作,接下來就是PageMethod4.aspx的調整與jQuery程式的編寫。

 

來看看現在的PageMethod4.aspx內容

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/ViewMaster.Master" Inherits="System.Web.Mvc.ViewPage<dynamic>" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
  PageMethod4
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
  <div id="TableContent" class="div_default">
    <h2>MvcSimplePostPager - AJAX</h2>
    <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>
    <p></p>
    <div id="PageContent">
      <% Html.RenderAction("PageContent", "Home"); %>
    </div>
    <p></p>
  </div>
</asp:Content>

在中間可以看到有個地方使用Html.RenderAction()方法,

這裡原本放置的內容就是剛才我們所完成PageContent.ascx裡面的Html,

另外還需要注意的是第一行,在Inherits這裡就不需去指定Model類別了,甚至原本Import Namespace也拿掉了,

這是因為這些為了要配合資料分頁而修改的部份,並不是不需要了,而轉移到PageContent.ascx當中。

 

這邊簡單說明一下ASP.MET MVC兩個有關使用部分檢視的HtmlHepler方法:

Html.RenderAction()

Html.RenderPartial()

兩者的結果都是載入部分檢視到ViewPage中,但是差別在於:

RenderAction 是會再去執行後端Controller中相對應的Action,

RenderPartial 則是直接輸入指定的部份檢視內容。

延伸閱讀:星寂 - ASP.NET MVC 2.0 Html.RenderPartial & Html.RenderAction

 

最後就是jQuery的程式部分:

<script language="javascript" type="text/javascript">
<!--
  var SelectedCategoryID = 0;
  $(document).ready(function ()
  {
    PageContent_Init();
  });
  function PageContent_Init()
  {
    $('#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
    {
      SelectedCategoryID = parseInt(categoryId, 10);
      $.ajax({
        url: '<%= Url.Action("PageContent", "Home") %>',
        data: { categoryId: categoryId },
        type: 'post',
        async: false,
        cache: false,
        dataType: 'html',
        success: function (data)
        {
          $('#PageContent').html(data);
          PageContent_Init();
        }
      });
    }
  });
  //================== 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'));
    });
  }
  function GoToPage(page)
  {
    if (SelectedCategoryID.length == 0 || SelectedCategoryID == 0)
    {
      alert('Please select Category.');
      return false;
    }
    else
    {
      if ($.trim($('#CategoryDDL option:selected').val()) != SelectedCategoryID.toString())
      {
        $('#CategoryDDL option[value=' + SelectedCategoryID + ']').attr('selected', true);
      }
      
      $.ajax({
        url: '<%= Url.Action("PageContent", "Home") %>',
        data: { page: page, categoryId: SelectedCategoryID },
        type: 'post',
        async: false,
        cache: false,
        dataType: 'html',
        success: function (data)
        {
          $('#PageContent').html(data);
          PageContent_Init();
        }
      });
    }
  }
-->
</script>

程式中的ajax處理都是使用post方式將資料傳送到後端,這邊要另外提醒一下,

後端的Action:PageContent,這個方法不需要加上[HttpPost]的Attribute,不然是無法執行的。

另外就是 ajax 所指定的「url: '<%= Url.Action("PageContent", "Home") %>',」,就是直接對應到後端PageContent()。

因為PageContent()傳回前端的內容是部分檢視,為html文件,所以ajax 所接收的dataType為html,

前端接收到後端傳回的資料後,就將ID為PageContent的內容置換為後端傳回的資料,

最後的PageContent_Init(),則是對已經置入到ViewPage的Html去執行PostPager(),以綁定分頁功能列的事件。

 

最後來看看執行的畫面:

一開始的畫面

image

選擇Category下拉選單送出後

image

執行分頁(圖片看不出來是否有Reload,但是AJAX的處理下並不會整頁重整)

image

到這裡就完成了 MvcSimplePostPager + AJAX 的修改。

 

以上。

2 則留言:

  1. 大大你好~
    若我想單純用您的分頁(2)MvcSimplePager做出Ajax效果
    那有關Ajax的寫法只會用到以下這個嗎?
    $.ajax({
    url: '<%= Url.Action("PageContent", "Home") %>',
    data: { page: page },
    type: 'post',
    async: false,
    cache: false,
    dataType: 'html',
    success: function (data)
    {
    $('#PageContent').html(data);
    }
    那Page這個數值是從前端的哪裡傳給後端呀?

    還請前輩能指點一下小的

    回覆刪除
    回覆
    1. 你所貼出來的 jQuery 程式,依照這篇文章的程式內容來看,
      這一段程式應該是 function GoToPage(page){ ...... } 所擷取出來的,
      我這樣寫出來,你應該知道 page 是從那裡來的吧!

      刪除

提醒

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

最近的留言