2013年7月14日 星期日

ASP.NET MVC + jQuery Easy UI Tree 無限階層的樹狀選單

在網站裡要能夠清楚顯示一個有階層項目的資料,能夠清楚顯示資料的功能有很多,但樹狀選單似乎都是最多人的選擇,而要顯示無限階層的樹狀資料,應該會有很多人就會卡住。

初學者在剛開始做這種階層資料的時候,通常都會一層一層的包含下去,但是要讓他們做無限階層,除非他們知道要如何使用遞迴的方式,以及在 SQL Server 裡用 CTE 的方式取得所有的階層資料,在 We b Forms 的專案裡還可以使用現有的 Server Control 然後套上資料就可以解決(當然還是需要花點功夫與時間去做處理),而在 ASP.NET MVC 所開發的網站並沒有現成的控制項可以使用,絕大部分的開發者都去找現成的前端套件來使用。

這篇文章就簡單地來介紹在 ASP.NET MVC 網站裡使用 jQuery EasyUI Tree 來完成無限階層樹狀選單的功能。

 


jQuery EasyUI - Tree

Demo:http://www.jeasyui.com/demo/main/index.php?plugin=Tree

Documentation:http://www.jeasyui.com/documentation/tree.php

使用 jQuery EasyUI Tree 顯示樹狀階層資料的方式有很多種,可以先在頁面上使用 ul li 的清單項目顯示資料,然後再套用 jQuery EasyUI Tree class 就可以轉換為樹狀選單,另一種是在頁面上指定一個 ul 標籤並套用 class,從後端取得 JSON 資料後讓指定的 ul 標籤轉換為樹狀選單。

這一篇要使用最簡單的方式,採用「Create Tree from markup」這個範例的方式,先在頁面上以 ul li 清單項目顯示階層資料,然後再套用 jQuery Easyui Tree。

 

一、資料結構

以下是「TreeNode」這個 Table 的結構,

image

其中 ParentID 是關鍵,因為需要有階層關連,所以一定要有這個欄位,最上面根節點的 ParentID 為 null,此外 ID 與 ParentID 的關聯也要建立起來,

image

SNAGHTMLb100d14

 

二、Model

這裡我不會使用資料庫裡先取得階層資料的方式,而是直接使用程式的方式來做處理。我們在資料庫裡已經建立好 TreeNode 的關聯,所以在 Entity Framework 裡加入 TreeNode 時,就會把關連給加進來,

image

 

三、Controller

接著就是建立 TreeViewController 以及 Index Action 方法,

image

因為我們要在 View 上面先建立好 ul 清單項目,要把所有的 TreeNode 給帶到 View 裡,另外在建立 ul 清單的時候要先從根節點開始建立,所以我在後端 Index Action 方法裡,就先把根節點給找出來,所以我另外建立了 TreeViewModel 類別,這個類別就只有兩個屬性,分別為 RootNode 以及 TreeNodes,

image

而在 Index Action 方法裡要取得 ViewModel 所要的資料是套過另外建立的 EasyUITreeHelper,一般來說向這種取得資料的方法通常都會是在 Service 或是 Repository 裡,

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using MvcApplication1.Models;
    
public class EasyUITreeHelper
{
    private TestDBEntities db = new TestDBEntities();
 
    /// <summary>
    /// Gets the root node.
    /// </summary>
    /// <returns></returns>
    /// <exception cref="System.NotImplementedException"></exception>
    public TreeNode GetRootNode()
    {
        if (this.db.TreeNode.Any())
        {
            return this.db.TreeNode.FirstOrDefault(x => !x.ParentID.HasValue);
        }
        return null;
    }
 
    /// <summary>
    /// Gets the nodes.
    /// </summary>
    /// <param name="isReadAll">if set to <c>true</c> [is read all].</param>
    /// <returns></returns>
    /// <exception cref="System.NotImplementedException"></exception>
    public IQueryable<TreeNode> GetNodes(bool isReadAll = false)
    {
        var query = this.db.TreeNode.AsQueryable();
        if (!isReadAll)
        {
            return query.Where(x => x.IsEnable == true);
        }
        return query;
    }
}

 

四、View

在 Index.cshtml 裡並沒有太過於複雜的程式,不過要特別說明的是 Line 9 ~23 的 Razor Helper 程式碼,

   1: @using MvcApplication1.Models;
   2: @model MvcApplication1.Models.ViewModels.TreeViewModel
   3:  
   4: @{
   5:     ViewBag.Title = "Index";
   6:     Layout = "~/Views/Shared/_TreeGridLayout.cshtml";
   7: }
   8:  
   9: @helper GenerateTree(IEnumerable<TreeNode> nodes, Guid nodeID)
  10: {
  11:     if (nodes.Any(x => x.ParentID == nodeID))
  12:     {
  13:         <ul>
  14:         @foreach (var item in nodes.Where(x => x.ParentID == nodeID).OrderBy(x => x.Sort))
  15:         {
  16:             <li>
  17:                 <span>@item.Name</span>
  18:                 @GenerateTree(nodes, item.ID)
  19:             </li>
  20:         }
  21:         </ul>
  22:     }
  23: }
  24:  
  25: <h2>ASP.NET MVC + jQuery EasyUI - Tree</h2>
  26: <hr />
  27: <div id="TreeView">
  28:     @if (Model.RootNode != null && Model.TreeNodes.Count > 0)
  29:     {
  30:         <ul id="tt" class="easyui-tree">
  31:             <li>
  32:                 <span>@Model.RootNode.Name</span>
  33:                 @GenerateTree(Model.TreeNodes, Model.RootNode.ID)
  34:             </li>
  35:         </ul>
  36:     }
  37: </div>

Line 9 ~ 23 是使用了 Razor Syntax 的特性,可以將頁面中需要以程式來處理顯示的部份給提取出來,以 @helper 的方式建立可重複使用的 Method,因為我們要顯示無限階層的資料,每個階層所顯示的結構都是一樣使用 ul li 的清單,Line 9 ~ 23 是一個遞迴處理的程式,當處理完成目前階層的項目後,在 Line 18 就會處理下一個階層的內容,如此一直遞迴下去,直到沒有下一層的資料為止。

另外在 Line 30 ~ 35 這邊,要先顯示根節點的資料,然後以根節點為起始,在 Line 33 使用 GenerateTree() 的 Razor Helper 處理根節點以下的階層資料。

而將 ul 清單項目轉換為 jQuery EasyUI Tree,則是在 ul 標籤裡去加入「easyui-tree」的 class,然後記得在 View 或是外層的 _Layout.cshtml 加入 jquery 以及 jquery.easyui.js 還有 jQuery EasyUI 相關的 css 檔案,

<!DOCTYPE html>
 
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>@ViewBag.Title</title>
    @Scripts.Render("~/bundles/modernizr")
    <link href="~/Content/easyui/themes/default/easyui.css" rel="stylesheet" />
    <link href="~/Content/easyui/themes/icon.css" rel="stylesheet" />
</head>
<body>
    <div>
        @RenderBody()
    </div>
    @Scripts.Render("~/bundles/jquery")
    <script src="~/Content/easyui/jquery.easyui.min.js"></script>
    @RenderSection("scripts", required: false)
</body>
</html>

 

五、執行

我已經在資料庫的 TreeNode Table 建立了多筆資料,

image

以下為網頁執行結果,

image

網頁原始檔(Tree 的部份),

image

修改節點內容,讓 Level_6 節點移到 Level_2 階層下,

image

透過 Firefox 的 FireBug 套件觀察 HTML,

image

 


由上面的內容可以看到,並沒有什麼太複雜的程式內容,唯一比較複雜一點的就是 View 裡面的 Razor Helper,在 Razor Helper 的 Method 裡用了遞迴,但也只有簡單的處理方式而已。

以往開發 ASP.NET Web Forms 的時候,如果沒有物件觀念,取得資料就是要透過 SQL Statement 的方式,雖然在資料庫處理會比較快(因為耗費的是 SQL Server 的資源),但這樣的作法就是必須要去知道如何使用 SQL Statement 取得階層資料(例如使用 CTE),一旦開發人員從 ASP.NET Web Forms 轉換到 ASP.NET MVC 時,遇到這種要顯示無限階層的資料時就會卡住了。

我是覺得像這樣的需求,在程式裡面解決會比較適當,可以因應前端需求來做調整,如果前端不希望使用 Razor Helper 的方式,那麼我們就可以改用後端產生 JSON 方式,讓前端取得 JSON 格式的階層資料後再產生 Tree。

前端相關 Tree 的套件有很多種,每一種套件所使用的格式也不盡相同,實在是很難從 SQL Statement 取得資料後再轉換為適當的內容給前端使用,所以只要你在系統中所建立的物件類別有做好定義以及關連,然後運用遞迴處理的方式就可以簡單完成這種無限階層的樹狀選單功能。

下一篇來說明後端產生 JSON 資料給前端套用的 jQuery EasyUI Tree 操作。

 

以上

沒有留言:

張貼留言

提醒

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