2012年3月12日 星期一

ASP.NET MVC 3 分頁 - 使用jQuery.Templates + MvcSimplePostPager


上一篇文章「ASP.NET MVC 3 分頁 - 使用jQuery.Templates + jQuery Pagination Plugin」裡,

我們使用了「jQuery Pagination Plugin」這個jQuery Plugin來做為前端頁面Pager的解決方案,

然而這個 Pager 所提供的UI介面是有些陽春,

image

假如想要增加一些資料在上面讓使用者可以得到更多資訊的話,就要去更改「jquery.pagination.js」程式內容,

但其實在之前介紹「資料分頁」的時候,我們就已經建立了 MvcSimplePager 與 MvcSimplePostPager ,

這兩種Pager就已經提供了相當充足的分頁資訊,

接下來就會介紹如何以前端的jQuery Ajax的方式與jQuery Templates來完成分頁的需求功能。

 



進入主題前,先回顧一下之前的MvcSimplePager與MvcSimplePostPager,

 

MvcSimplePager

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

這個Pager是單純使用Http Get的方式將分頁的Index傳送到後端後再去產生Pager以及顯示分頁的資料內容,

比較適合不需要做分類選擇而且不必帶其他參數的分頁資料顯示。

image

 

MvcSimplePostPager

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

為了要保持表單上的分類選擇狀態以及顯示該分類的分頁資料,所以原本Http Get方式的Pager就無法沿用,

所以就以原本MvcSimplePager的Html.Helper架構再設計一個可供使用Http Post方式的Pager,

前端再搭配jQuery的事件來完成在使用Form的情境下的狀態保持及資料分頁的功能。

image

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

MvcSimplePostPager搭配使用jQuery Ajax來完成需求功能,Pager以及分頁資料是後端輸出的部分檢視,

所以每一次的分頁除了分類的下拉選單不會重新Render外,其他的地方都會重新Render,

而這些變化都是以AJAX來完成,所以不會有畫面Reload的情況發生。

image

 

而這一次我們要做的分頁功能,每一個分類的分頁資料都還是維持使用jQuery.Templates方式來顯示分頁資料,

Pager的部份則會使用MvcSimplePostPager,因為MvcSimplePostPager上面的每個A元素都沒有另外指定事件,

所以當動態輸出到前端頁面時,就可以動態的去綁定前端處理事件,

不過這一次的作法會有所不同,之前所假設的情境是資料來源是同一個網站,而這一次我們必須假設資料來源有可能是其他的網站,

廢話不多說,就直接進入程式實作說明。

 


Controller

Action「PageMethod2」:此次分頁功能的主要進入頁面。

public ActionResult PageMethod2()
{
    ViewData["CategoryDDL"] = categoryService.GenerateCategoryDDL(null);
    return View();
}

 

Action「PagerContent」:接收前端所傳來的pageIndex, pageSize, totalItemCount參數資料,以PartialView回傳Pager內容。

public ActionResult PagerContent(int? page, int? pageSize, int? totalItemCount)
{
    int currentPageIndex = page.HasValue ? page.Value - 1 : 0;
    ViewData["CurrentPage"] = currentPageIndex;
 
    int page_size = pageSize.HasValue ? pageSize.Value : 10;
    ViewData["PageSize"] = page_size;
 
    int totalCount = totalItemCount.HasValue ? totalItemCount.Value : 0;
    ViewData["TotalItemCount"] = totalCount;
 
    return PartialView();
}

 

Action「ProductCount」:取得分類產品的總數。

[HttpPost]
public JsonResult ProductCount(string categoryId)
{
    Dictionary<string, object> jo = new Dictionary<string, object>();
 
    if (string.IsNullOrWhiteSpace(categoryId))
    {
        jo.Add("Msg", "Please Select a Category");
        return this.Json(jo);
    }
 
    int id = 0;
    bool parseResult = int.TryParse(categoryId, out id);
    if (!parseResult)
    {
        jo.Add("Msg", "Wrong Category ID");
        return this.Json(jo);
    }
 
    var result = this.productService.GetCollectionBy(id);
    jo.Add("count", result.Count());
    return this.Json(jo);
}

 

Action「PageContent」:取得指定分類以及指定分頁的產品資料,回傳JSON格式的資料。

[HttpPost]
public JsonResult PageContent(int? page, string categoryId)
{
    Dictionary<string, string> jo = new Dictionary<string, string>();
 
    if (string.IsNullOrWhiteSpace(categoryId))
    {
        jo.Add("Msg", "Please Select a Category");
        return this.Json(jo);
    }
 
    int id = 0;
    bool parseResult = int.TryParse(categoryId, out id);
 
    if (!parseResult)
    {
        jo.Add("Msg", "Wrong Category ID");
        return this.Json(jo);
    }
 
    var result = this.productService.GetCollectionBy(id);
 
    int currentPageIndex = page.HasValue ? page.Value - 1 : 0;
    ViewData["CurrentPage"] = currentPageIndex;
    var dataPaged = result.ToPagedList(currentPageIndex, 5);
 
    List<Dictionary<string, object>> ja = new List<Dictionary<string, object>>();
 
    foreach (var item in dataPaged)
    {
        Dictionary<string, object> product = new Dictionary<string, object>();
 
        product.Add("ProductID", item.ProductID);
        product.Add("ProductName", item.ProductName);
        product.Add("SupplierID", item.SupplierID);
        product.Add("CategoryID", item.CategoryID);
        product.Add("QuantityPerUnit", item.QuantityPerUnit);
        product.Add("UnitPrice", String.Format("{0:F}", item.UnitPrice));
        product.Add("UnitsInStock", item.UnitsInStock);
        product.Add("UnitsOnOrder", item.UnitsOnOrder);
        product.Add("ReorderLevel", item.ReorderLevel);
        product.Add("Discontinued", item.Discontinued);
 
        ja.Add(product);
    }
 
    return this.Json(ja);
}  

 

View

PagerContent.cshtml:產出Pager的內容,這邊沒有任何的Model資料。

@using MVC3_Page.Helpers;
 
<div id="pager" class="pager">
    @if (ViewData["CurrentPage"] != null && ViewData["PageSize"] != null && ViewData["TotalItemCount"] != null)
    { 
        int currentPage = int.Parse(ViewData["CurrentPage"].ToString());
        int pageSize = int.Parse(ViewData["PageSize"].ToString());
        int totalItemCount = int.Parse(ViewData["TotalItemCount"].ToString());
        @Html.Raw(Html.MvcSimplePostPager(currentPage, pageSize, totalItemCount, true)); 
    } 
</div>

 

PageMehtod2.cshtml:其中ID是PagerWrapperTop的DIV,為放置Pager的容器。

@{
    ViewBag.Title = "PageMethod2";
    Layout = "~/Views/Shared/_Layout.cshtml";
}
 
<div id="TableContent" class="div_default">
 
    <h2>PageMethod2</h2>
 
    <div id="formArea" style="width: 90%; margin-left: auto; margin-right: auto;">
        Category:@Html.Raw(ViewData["CategoryDDL"].ToString())
        <input type="button" id="ButtonSearch" value="List" />
    </div>
    <hr />
    <p></p>
    <div id="Information" style="display: none; width: 90%; margin-left: auto; margin-right: auto; background-color: #fffeee;">
        Category:<span id="CategoryName" style="font-weight: bold;"></span>
        The number of products:<span id="ProductNumber" style="font-weight: bold;"></span>
        <p></p>
    </div>
    
    <div id="PagerWrapperTop" class="PagerWrapper" style="text-align:left; : 90%; margin-left: auto; margin-right: auto;">
    </div>
    <br style="clear:both;" />
 
    <table id="table1" class="grid" style="width: 90%; margin-left:auto; margin-right:auto; background-color: #fffeee;">
        <thead>
            <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>
        </thead>
        <tbody>
        </tbody>
    </table>
    <p></p>
</div>

 

PageMethod2.cshtml 分頁資料的Templates內容:

<script id="TemplateProduct" type="text/x-jquery-tmpl">
    <tr id="tr_${ProductID}">
        <td>
            ${ProductID}
        </td>
        <td>
            ${ProductName}
        </td>
        <td>
            ${SupplierID}
        </td>
        <td>
            ${CategoryID}
        </td>
        <td>
            ${QuantityPerUnit}
        </td>
        <td>
            ${UnitPrice}
        </td>
        <td>
            ${UnitsInStock}
        </td>
        <td>
            ${UnitsOnOrder}
        </td>
        <td>
            ${ReorderLevel}
        </td>
        <td>
            ${Discontinued}
        </td>
    </tr>
</script>

 

PageMehtod2.cshtml JavaScript

開頭:

var pageSize = 5;
var totalCount = 0;
var selected_Category_ID = '';
var selected_Category_Text = '';
 
$(document).ready(function ()
{
    $('#CategoryDDL option:eq(0)').attr('selected', true);
    $('#ButtonSearch').click(function () { ButtonSearchEventHandler() });
});

ButtonSearchEventHandler:

取得分類的產品總數以及該分類的第一頁分頁資料內容。

function ButtonSearchEventHandler()
{
    this.selected_Category_ID = $.trim($('#CategoryDDL option:selected').val());
    this.selected_Category_Text = $.trim($('#CategoryDDL option:selected').text());
 
    if (selected_Category_ID.length == 0)
    {
        alert('Please select a Category.');
        return false;
    }
    else
    {
        //取得分類的產品總數
        CategoryProductCount(selected_Category_ID);
 
        //取得產品分頁內容
        GetCategoryProducts();
    }
}

CategoryProductCount(取得分類的產品總數):

function CategoryProductCount(categoryId)
{
    var count = 0;
 
    if (categoryId.length == 0)
    {
        return count;
    }
    else
    {
        $.ajax({
            url: '@Url.Action("ProductCount", "Home")',
            data: { categoryId: categoryId },
            type: 'post',
            async: false,
            cache: false,
            dataType: 'json',
            success: function (data)
            {
                if (!data.Msg)
                {
                    count = parseInt(data.count, 10);
                    totalCount = count;
                }
            }
        });
    }
    return false;
}

GetCategoryProducts(取得產品的分頁資料內容):

先取得分頁的資料並顯示於頁面上之後,再去處理Pager的顯示內容以及Javascript事件綁定。

function GetCategoryProducts(pageIndex)
{
    if (selected_Category_ID.length == 0 || selected_Category_Text.length == 0)
    {
        alert('Please select a Category.');
        return false;
    }
    else
    {
        if (pageIndex == undefined || pageIndex.length == 0)
        {
            pageIndex = 1;
        }
 
        $.ajax({
            url: '@Url.Action("PageContent", "Home")',
            data: { page: pageIndex, categoryId: parseInt(selected_Category_ID, 10) },
            type: 'post',
            async: false,
            cache: false,
            dataType: 'json',
            success: function (data)
            {
                $('#table1 tbody').empty();
                $('#CategoryName').empty();
                $('#ProductNumber').empty();
 
                if (data.Msg)
                {
                    $('#Information').hide();
                    alert(data.Msg);
                }
                else
                {
                    $.each(data, function (i)
                    {
                        var item =
                        {
                            ProductID: data[i]["ProductID"],
                            ProductName: data[i]["ProProductNameductID"],
                            SupplierID: data[i]["SupplierID"],
                            CategoryID: data[i]["CategoryID"],
                            QuantityPerUnit: data[i]["QuantityPerUnit"],
                            UnitPrice: data[i]["UnitPrice"],
                            UnitsInStock: data[i]["UnitsInStock"],
                            UnitsOnOrder: data[i]["UnitsOnOrder"],
                            ReorderLevel: data[i]["ReorderLevel"],
                            Discontinued: data[i]["Discontinued"]
                        };
 
                        $('#table1 tbody').append($('#TemplateProduct').tmpl(item));
                    });
 
                    $('#table1 tbody tr:odd').css('background-color', '#F5FBFC')
 
                    $('#ProductNumber').html(totalCount);
                    $('#CategoryName').html(selected_Category_Text);
                    $('#Information').show();
 
                    //PageBar
                    PagerContent_Init(pageIndex)
                }
                return false;
            }
        });
    }
}

 

PagerContent_Init(顯示Pager內容):

向後端的Action「PagerContent」取得PagerContent.cshtml的部分檢視內容。

function PagerContent_Init(pageIndex)
{
    $.ajax(
    {
        url: '@Url.Action("PagerContent", "Home")',
        data: { page: pageIndex, pageSize: pageSize, totalItemCount: totalCount },
        type: 'post',
        async: false,
        cache: false,
        dataType: 'html',
        success: function (data)
        {
            $('#PagerWrapperTop').html(data);
            Pager_Setting();
        }
    });
}

 

Pager_Setting(Pager內容的事件綁定):

//將Pager上面的連結項目的行為分離出來
//使用class方式做行為判別與動作執行
function Pager_Setting()
{
    $('.PostPager').each(function (i, item)
    {
        $(item).css('cursor', 'pointer');
    });
    $('.PostPager.first-page').click(function () { GoToPage(1, selected_Category_ID); });
    $('.PostPager.previous-page,.next-page,.last-page,.number-page').click(function ()
    {
        GoToPage($(this).attr('value'), selected_Category_ID);
    });
}
 
function GoToPage(pageIndex, selectedCategoryID)
{
    if (selectedCategoryID.length == 0 || selectedCategoryID == 0)
    {
        alert('Please select a Category.');
        return false;
    }
    else
    {
        if ($.trim($('#CategoryDDL option:selected').val()) != selectedCategoryID.toString())
        {
            $('#CategoryDDL option[value=' + selectedCategoryID + ']').attr('selected', true);
        }
 
        GetCategoryProducts(pageIndex);
    }
}

 

最後完成的分頁功能:

ASP.NET MVC 3 + jQuery Templates + MvcSimplePostPager

 


其實有關於資料分頁的處理有相當多種,必須依照你的需求來決定什麼樣的方式比較適合專案,

之前我做某一個MVC專案時,因為分頁的功能比較晚開發,所以一開始只是以MvcPaging的基本樣式來完成分頁,

一開始這種單純以Http Get(以連結的方式使用網址加上參數)的分頁還可以應付基本的分頁需求,

但是當系統功能加上表單也有了分類資料等類似的輸入條件時,就必須要想辦法讓頁面可以有分頁功能又要可以保持表單狀態,

一開始還會使用Session的方式來保持頁面表單的條件狀態,雖然可以達到同樣的結果,但是此舉卻遭到專案另一個團隊成員的「嫌棄」,

說什麼用Session很LOW、很笨、很耗記憶體之類的,於是請他就此需要來開發一套符合他要求的分頁套件,

我想大家都應該知道,當專案的團隊成員有這麼一個要挑剔又愛抱怨什麼功能不足的人,這個人十之八九會是個「嘴砲王」!

只會在那邊出張嘴,真正叫他做點事來看看卻什麼也拿不出來,唉~遇到這樣的團隊成員時,就是顯出其他團隊成員的真正價值的時候,

在當時就依據MvcPaging的原形而衍生出可以與Form表單整合的分頁,不過那時候的PostPager是在有限時間下倉促完成的,

以致於之後會再另外設計為「MvcSimplePager」以及「MvcSimplePostPager」,

之前與朋友聚會的時候,聽到在資料來源是外部網站時也可以利用jQuery.Templates來做分頁,所以就發展出jQuery.Templates相關的三篇文章,

之後還會就jQuery.Templates來說明以往在專案開發時的其他應用,如果各位不嫌棄的話就小小期待一下吧!

 

以上

沒有留言:

張貼留言

提醒

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