[ASP.NET MVC][jQuery] 純手工打造屬於自己的 jQuery GridView (含完整程式碼下載)


相信寫過 ASP.NET MVC 的人都知道,在 ASP.NET MVC 中已經沒有像 ASP.NET Web Form 那樣有許多內建控制項了,而一般在開發專案上可能會滿常碰到一個需求:當我點了 TD 之後那格會變成 TextBox 離開後就會儲存或者是 Table 的 TH 可以改變大小、可以改變順序...等等的功能,當然從 jQuery 出來之後網路已經有許多好用的套件了,像是 jqGrid、DataTables...等,但往往這些套件提供的功能又不是全部都是我們需要的,所以這篇就要教大家如何實作一個簡單的 GridView 可以讓使用者編輯,並且提供了的分頁功能 ~

前言

相信寫過 ASP.NET MVC 的人都知道,在 ASP.NET MVC 中已經沒有像 ASP.NET Web Form 那樣有許多內建控制項了,而一般在開發專案上可能會滿常碰到一個需求:當我點了 TD 之後那格會變成 TextBox 離開後就會儲存或者是 Table 的  TH 可以改變大小、可以改變順序...等等的功能,當然從 jQuery 出來之後網路已經有許多好用的套件了,像是 jqGrid、DataTables...等,但往往這些套件提供的功能又不是全部都是我們需要的,所以這篇就要教大家如何實作一個簡單的 GridView 可以讓使用者編輯,並且提供了的分頁功能 ~

範例需求

這篇主要說明方向會著重在前端 jQuery 的設計,後端的部份我們會搭配到 ASP.NET MVC Web API 並且利用 OData 的概念來做分頁技巧,所以後端 MVC 的概念就不會贅述太多,畢竟那只是我們撈資料的輔助而已,來!我們總要有個方向,走!我們邁向 jQuery 的美妙世界吧 ~ 

小提醒:在 ASP.NET MVC 中不應該直接寫死網址,故此範例為求易讀所以直接寫死

前置作業

1.首先我們先建立一個 ASP.NET MVC 4 的專案,預設範本的話選擇基本就OK了。

2.建立兩個 Controller ,一個為空白 MVC 控制器名為 HomeController,另外一個為空白 API 控制器名為 Product 控制器。

3.接下來我們先透過 Nuget 來安裝最新的 jQuery UI 套件,Nuget 會替我們安裝該有的 CSS 和 JS 檔案。

4.接著我們打開在 App_Start 裡的 BundleConfig.cs ,並將原本的 

 bundles.Add(new StyleBundle("~/Content/css").Include( "~/Content/site.css"));

修改成

 bundles.Add(new StyleBundle("~/Content/css").Include(
                "~/Content/site.css", 
                "~/Content/themes/base/jquery-ui.css"
                ));

這樣等下的 jQuery UI 套件才套的到 CSS ,當然還是可以在頁面上加入外部連結,透過 Bundle 的設定可以將同一個群組的 CSS 合併成同一個檔案

5.接著再到預設的 Layout 裡面將 jQuery UI 的套件引用進來,而預設的 Layout 在 /Views/Shared/_Layout.cshtml ,我們修改後為:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>@ViewBag.Title</title>
    @Styles.Render("~/Content/css")
    @RenderSection("styles", required: false)
    @Scripts.Render("~/bundles/modernizr")
</head>
<body>
    @RenderBody()
    @Scripts.Render("~/bundles/jquery")
    @Scripts.Render("~/bundles/jqueryui")
    @RenderSection("scripts", required: false)
</body>
</html>

在這邊我們也在 Layout 裡挖了兩個洞,用來稍後在 View 裡面來填入 CSS 和 Javascript 的區塊:

 @RenderSection("styles", required: false)

 @RenderSection("scripts", required: false)

6.接下來我們在 HomeController 新增 Index 的 Action

        public ActionResult Index()
        {
            ViewBag.Count = db.Products.Count(); // 計算分頁用
            return View();
        }

並且建立 View,如下:

7.接著我們修改我們需要的 View ,一個 Table 然後就,然後加上我們的分頁按鈕,所以透過 for 迴圈來產生 <a>,並且在每個 TH 中都加入 data-name 的屬性來做識別,嗯...說實在還滿簡單的,其他的東西我們都要利用 jQuery 來產出來,這樣才符合我們的純手工打造嘛XD,程式碼如下:

@{
    ViewBag.Title = "純手工打造 jQuery Grid";
    int Count = ViewBag.Count / 10;
}
@section styles
{

}
<div id="main">
    <table id="GridTable" tabindex="0" cellspacing="0" cellpadding="0" border="0">
        <thead>
            <tr>
                <th data-name="ProductID">產品代號</th>
                <th data-name="ProductName">產品名稱</th>
                <th data-name="QuantityPerUnit">產品規格</th>
                <th data-name="UnitPrice">產品價格</th>
            </tr>
        </thead>
        <tbody id="GridContent">
        </tbody>
    </table>
   @for (var i = 0; i <= Count; i++)
    {
        <a class="pageList" href="#" data-page="@(i + 1)">@(i + 1)</a>
    }
</div>
@section scripts{

}

而大家有沒有發現,這邊如果對應到我們剛剛 Layout 畫面挖的兩個洞,剛好在 View 裡面我們把洞給填上去,至於填什麼資料 ?當然是 CSS 和 Javascript 啦,這也是 ASP.NET MVC 4 的特性之一。

8.前置作業做的差不多了,接著我們先將資料庫匯入專案內,而小弟這邊用的資料庫是用微軟提供的 NorthWind 當範例,如果有需要的朋友們也可以到微軟官方下載 Northwind and pubs Sample Databases :

9.再來我們在到 Model 中新增一個類別為 ProductDTO.cs ,而一般實務上很少會返回所有的資料表欄位給使用者看,所以我們新增一個類別來做轉換:

    public class ProductDTO
    {
        public int ProductID { get; set; }
        public string ProductName { get; set; }
        public string QuantityPerUnit { get; set; }
        public decimal? UnitPrice { get; set; }
    }

10.接著我們繼續完成我們 Web API 的 Function ,首先我們先引用

using MvcGridView.Models;

然後再裡面新增一個 Function 名為 GetProducts,這邊我也透過剛剛的那個 ProductDTO 來做欄位轉換的動作,這樣就可以確保返回頁面給使用者的欄位都是我們要的。

    public class ProdcutController : ApiController
    {
        NorthwindEntities db = new NorthwindEntities();

       [Queryable]
        public IQueryable GetProduct()
        {
            return db.Products.Select(p => new ProductDTO
            {
                ProductID = p.ProductID,
                ProductName = p.ProductName,
                QuantityPerUnit = p.QuantityPerUnit,
                UnitPrice = p.UnitPrice
            });
        }

    }

另外這邊需要注意的是,在 GetProduct 中加上了 [Queryable] 的屬性,加上後我們才能使用 OData,不過過如果你的 VS 2012 是 Update 2 以下的話,可能還是要先透過 NuGet 來安裝 Odata 的套件,而究竟什麼是 OData ?OData 全名為 Open Data Protocal  在官方網站可以看到 It’s an open web protocol for querying and updating data. 屬於一個開放的網路協定,負責用來作資料的查詢以及更新,簡單來說他能透過前端網址傳送過來的參數直接幫我們來對資料庫進行過濾的動作,根本非常強大,稍後我們的分頁也會透過 OData 來處理,如果有興趣的朋友也可以到 OData 的官方網站看看 ~ 

做到這邊我們的前置作業也做的差不多了,接下來就是進入我們核心的 jQuery 部分了,在開始之前我們先看看現在我們的網站長什麼樣子:


嗯 ~ 老實說非常醜,不過如果一開始就很漂亮的話那我們大概也不需要美編人員了(勿),嗯... 這不是重點 ~休息一下喘口氣,再接再厲!!!


化腐朽為神奇的 jQuery & CSS

1.我們先完成分頁的部份,而這邊會用到剛剛前面提到的 OData ,透過網址傳遞參數的方式直接來為我們做資料的查詢,EX:localhost/api/GetProducts?$skip=10&$top=10 ,$skip 代表會跳過10筆資料,然後 $top 會抓前10筆資料,這樣就可以輕鬆的做到分頁的概念 ~

        $(function () {
            PagerView(0); // 頁面載入時先Load第一筆資料
            $('a.pageList').click(function () {
                var page = $(this).data("page") - 1 * 1;
                PagerView(page);
            });
        });

        function PagerView(page) {
            $.getJSON("/api/Product?$skip=" + page * 10 + "&$top=10", function (data) {
                var arr = [];
                $("#GridTable th").each(function () {
                    arr.push($(this).data("name"));
                });
                var tr = "";
                for (var i = 0; i <= data.length - 1; i++) {
                    tr += "<tr>" +
                    "<td>" + data[i][arr[0]] + "</td>" +
                    "<td>" + data[i][arr[1]] + "</td>" +
                    "<td>" + data[i][arr[2]] + "</td>" +
                    "<td>" + data[i][arr[3]] + "</td></tr>";
                }
                $("#GridContent").html(tr);
            });
        }

使用 $.getJSON 來呼叫我們後端的 Web API,而為回傳的資料為 JSON Object ,並透過 for 迴圈來串成字串,最後再塞到我們的 tbody 裡面,而做到這邊我們總算能看到資料了(感動)。

2.既然是 GridView 那一定缺少不了編輯 TD 的功能,所以我們在 document.ready 事件最下面加上 :

            $(document).delegate("#GridContent td", "click", function () {
                var $this = $(this);
                // 假如有input則返回
                if ($this.has("input#edit_input").length > 0)
                    return false;
                else
                    $("#edit_input").closest("td").text($("#edit_input").val());  // 將input的值填入td

                var text = $this.text(); // 取得目前 td 的值
                $this.html("<input type='text' id='edit_input' value="+text+"/>");
                $this.find("#edit_input").val(text); // 將原本td的值給input
                return true;
            });

這個方法不難,每次使用者點了 TD 之後會先取得舊的 input 並透過 .closest("td") 找出其 td 並填入 input 的值,接下來在使用 .text() 的方法取得當下 td 的值,在利用 .html 將 input 加入當下的 td 中。

3.再來就是我們的 jQuery UI 派上用場的地方了,還記得前面說過我們的 GridView 可以讓使用者改變 TH 大小的以及改變 TH 順序的功能嘛 ,所以我們會用到 jQuery UI 的 Resizable 和 Sortable 這兩個 plug-in 了,如果對這兩個方法不熟的朋友可以先到 jQuery UI 官網看看官網的範例,jQuery-UI-ResizablejQuery-UI-Sortable,這邊我們先看看如何實作 TH 改變大小的功能:

我們先在最上方 CSS 區塊加上簡單的 CSS:

@section styles
{
    <style>
        .ui-resizable-helper {
            border: 2px dotted #00F;
        }

        table tr th {
            width: 150px;
            border: 1px solid #000;
            padding: 5px;
            height: 22px!important;
            overflow: hidden;
            text-overflow: clip;
            white-space: nowrap;
        }

        table tr td {
            height: 22px;
            border: 1px solid #000;
            padding: 0 2px 0 2px;
            overflow: hidden;
            white-space: pre;
            text-overflow: clip;
        }

        #edit_input {
            height: 22px;
        }
    </style>
}

接著完成我們 jQuery 的部份,一樣加在 document.ready 的最下面。

            var $this = $("#GridTable");
            var width = $this.width(); // 用來存放Table的寬度
            $this.css("table-layout", "fixed");

            $this.find("th").resizable({
                minWidth: 0, //最小寬度
                helper: "ui-resizable-helper", // 
                handles: "e", 
                stop: function (event, ui) {
                    //停止後重新記算table的寬度
                    width = width - ui.originalSize.width + ui.size.width;
                    $this.width(width);
                }
            });

首先第一段第一段宣告了 width 當成全域變數,目的就是用來存放目前 Table 的寬度,接著我們對每個 th 透過 each 都加上 resizable() 方法 ,而 stop 的方法代表當拖拉結束時要做的事情,我們重新計算 width 的大小,並且填入 table 內,另外 helper 的屬性加上了 ui-resizable-helper , resizable 會幫我們在拖拉時產生一個 class="ui-resizable-helper" 的 div,在搭配 CSS 加上border的屬性,就會有類似在拖拉時有輔助格線的感覺了。

4. 最後,就是我們的 TH 改變順序的功能了,利用 sortable 的方法來做也非常簡單:

             var old_index = 0;
             $this.sortable({
                items: "th", // 建立排序的項目
                cursor: "move", // 滑鼠的游標圖案
                placeholder: "ui-state-highlight",
                axis: "x", // 只允許橫向移動
                start: function (event, ui) {
                    old_index = ui.item.index();
                },
                update: function (event, ui) {
                    if (old_index < ui.item.index()) {
                        $this.find("tr").each(function () {
                            $(this).find("td").eq(old_index).insertAfter($(this).find("td").eq(ui.item.index()));
                        });
                    }
                    else {
                        $this.find("tr").each(function () {
                            $(this).find("td").eq(old_index).insertBefore($(this).find("td").eq(ui.item.index()));
                        });
                    }
                }
            }).disableSelection();

完成後的 GridView 為這樣:

程式碼下載(注意若要開啟專案必須有 ASP.NET MVC 4 的套件)

MvcGridView.rar

總結

寫這篇文章從範例到撰寫就花了小弟快三天的時間,而我們的 GridView 搭配 jQuery UI 來做真的非常方便,小弟個人滿偏好 jQuery UI 這個 Plug-in 的,像是 DatePicker 、Dialog、Spinner 都是網頁上滿常看到的應用,而有下載 Code 回去研究的朋友也可以發現,TH、和 TD 其實也透過 CSS 做到如果內容超過寬度會自動隱藏而不會換行,這個個人覺得也算是一種需求所以就在 CSS 上加上去了,而我們的 GridView 雖然很陽春,但是如果經過美編人員幫我們美化一下還是可以上線的,不過當然還是得依需求來決定要自己 Code 還是用現成的套件來完成。 

新手發文,如有錯誤煩請告知,感謝。
如果喜歡我的文章請按推薦,有任何問題歡迎下面留言~~~

 

 

簽名:

學習這條路很廣,喜歡什麼技術不重要,重要的是你肯花時間去學習