2013年7月15日 星期一

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

上一篇「ASP.NET MVC + jQuery Easy UI Tree 無限階層的樹狀選單」介紹了在 View Page 裡使用 ul li 清單項目顯示節點資料,然後套用 jQuery EasyUI Tree 以顯示樹狀選單,除了這個方式以外也可以讀取後端產生的 JSON 資料來產生樹狀選單。

讀取 JSON 資料來產生樹狀選單的方式有兩種,一種是先在後端把所有資料做好階層整理後再轉為 jQuery EasyUI Tree 可接受的 JSON 格式資料,而另一種則是非同步的方式,先顯示第一層的節點資料,如果有第二層資料,則透過 AJAX 的方式向後端取得,這兩種方式都將在這篇文章裡向大家說明。

 


方法一、載入全部節點的 JSON 資料

資料結構並沒有任何變化,但因為 JSON 階層資料必須在後端先做好整理,有關遞迴處理的部份也是在後端處理,jQuery EasyUI Tree 的 JSON 格式如下:

[
  {
    "id": "6d9c5dac-149e-4032-bf9e-92f126905144",
    "text": "Root",
    "children": [
      {
        "id": "21e7c543-c997-4eb3-ac23-5b9c4a7f9362",
        "text": "Level_1"
      },
      {
        "id": "14ab95ad-e854-4303-a76e-72e9372b80e8",
        "text": "Level_2",
        "children": [
          {
            "id": "37072189-49eb-4c4c-b609-b7d6d8e3254e",
            "text": "Level_6",
            "children": [
              {
                "id": "5cd028cc-c671-4c5d-afb7-5967ec8d7639",
                "text": "_Level_7"
              }
            ]
          }
        ]
      },
      {
        "id": "ae9c17ac-5132-4230-b417-faf55ad615cf",
        "text": "Level_3",
        "children": [
          {
            "id": "d096d40f-131d-4082-b062-a1b5552c81e0",
            "text": "Level_4",
            "children": [
              {
                "id": "99249963-1430-41f5-b66c-253a02d6cb41",
                "text": "Level_5"
              }
            ]
          }
        ]
      }
    ]
  }
]

節點資料本身的欄位為 id 與 text,如果該節點還有下層節點的話就是放在 children 欄位裡,可將上面的 JSON 資料複製起來然後貼到以前有介紹過的 Online JSON Viewer 來觀察,

Online JSON Viewer:http://jsonviewer.stack.hu/

image

在 Online JSON Viewer 裡可以清楚觀察 JSON 資料的階層,我們要在 Server 端來產生這樣的 JSON 資料內容。

 

EasyUITreeHelper

在前一篇文章已經建立的 EasyUITreeHelper.cs 裡增加以下 Method 程式內容,程式的內容應該不需要解釋太多,先找出 RootNode,然後再用遞迴的方式往下找出各階層的節點資料,

/// <summary>
/// Gets all json.
/// </summary>
/// <returns></returns>
/// <exception cref="System.NotImplementedException"></exception>
public string GetTreeJson()
{
    List<JObject> jObjects = new List<JObject>();
 
    var allNodes = this.db.TreeNode.Where(x => x.IsEnable == true).ToList();
    if (allNodes.Any())
    {
        var rootNode = this.GetRootNode();
        JObject root = new JObject
        {
            {"id", rootNode.ID.ToString()}, 
            {"text", rootNode.Name}
        };
        root.Add("children", this.GetChildArray(rootNode, allNodes));
        jObjects.Add(root);
    }
    return JsonConvert.SerializeObject(jObjects);
}
 
/// <summary>
/// Gets the child array.
/// </summary>
/// <param name="parentNode">The parent node.</param>
/// <param name="nodes">The nodes.</param>
/// <returns></returns>
private JArray GetChildArray(TreeNode parentNode, IEnumerable<TreeNode> nodes)
{
    JArray childArray = new JArray();
 
    foreach (var node in nodes.Where(x => x.ParentID == parentNode.ID))
    {
        JObject subObject = new JObject
        {
            {"id", node.ID.ToString()}, 
            {"text", node.Name}
        };
 
        if (nodes.Where(y => y.ParentID == node.ID).Any())
        {
            subObject.Add("children", this.GetChildArray(node, nodes));
        }
        childArray.Add(subObject);
    }
 
    return childArray;
}

 

Controller

在 TreeViewController 裡增加以下兩個 Action 方法,一個是對應 View Page,另一個則是讓前端藉由 AJAX 取得 JSON 資料,

public ActionResult TreeLoadJson()
{
    ViewBag.HasRootNode = this._helper.GetRootNode() != null ? "true" : "false";
    return View();
}
 
[HttpPost]
public ActionResult GetTreeNodeJSON()
{
    string result = this._helper.GetTreeJson();
    return Content(result, "application/json");
}

 

View

TreeLoadJson.cshtml 的內容,在 Javascript 的程式裡,要讓指定的 ul 清單標籤轉換為樹狀選單,則是使用了 jQuery EasyUI Tree 的設定方法,讓 tree 載入指定 URL 位置所產生的 JSON 資料,

@{
    ViewBag.Title = "Tree, Load JSON";
    Layout = "~/Views/Shared/_TreeGridLayout.cshtml";
}
 
<h2>ASP.NET MVC + jQuery EasyUI - Tree, Load JSON</h2>
<hr />
<div>
    <ul id="tt" class="easyui-tree"></ul>
</div>
<input type="hidden" id="HasRootNode" name="HasRootNode" value="@ViewBag.HasRootNode" />
 
@section scripts
{
    <script type="text/javascript">
        $(document).ready(function () {
 
            var hasData = $.trim($('#HasRootNode').val()) === 'true';
            if (hasData) {
                $('#tt').tree({
                    url: '@Url.Action("GetTreeNodeJSON", "TreeView")'
                });
            }
        });
    </script>
}

 

執行結果

image

HTML 原始碼

<!DOCTYPE html>
 
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Tree, Load JSON</title>
    <script src="/Scripts/modernizr-2.6.2.js"></script>
 
    <link href="/Content/easyui/themes/default/easyui.css" rel="stylesheet" />
    <link href="/Content/easyui/themes/icon.css" rel="stylesheet" />
</head>
<body>
    <div>
        
 
<h2>ASP.NET MVC + jQuery EasyUI - Tree, Load JSON</h2>
<hr />
<div>
    <ul id="tt" class="easyui-tree"></ul>
</div>
<input type="hidden" id="HasRootNode" name="HasRootNode" value="true" />
 
 
    </div>
    <script src="/Scripts/jquery-1.8.2.js"></script>
    <script src="/Content/easyui/jquery.easyui.min.js"></script>    
    <script type="text/javascript">
        $(document).ready(function () {
 
            var hasData = $.trim($('#HasRootNode').val()) === 'true';
            if (hasData) {
                $('#tt').tree({
                    url: '/TreeView/GetTreeNodeJSON'
                });
            }
        });
    </script>
 
</body>
</html>

從 FireBug 觀察 HTML 內容,可以發現到動態載入 JSON 所產生的樹狀選單也是使用 ul 清單來顯示,

image

 


方法二、Async Tree 非同步載入

上面的範例是前端透過 AJAX 從後端載入全部的階層資料,這種一次載入全部資料的作法比較適合資料不多的情況,如果這種階層資料相當多的情況,一次載入全部資料的作法就比較不適合,雖然可以另外使用 Cache 的方式讓資料於第一次讀取後就置入 Cache 裡,讓第二次之後的讀取可以加快速度,但第一次的讀取還是會很慢,所以我們可以用非同步的方式來處理,先在頁面上顯示第一層與地第二層的節點資料,第三層以後的資料再向後端讀取,因為每次讀取資料都是有限的,所以就不會讓讀取時間過長,以下就來看看要怎麼做。

使用 jQuery Easy UI Tree 的 Async Tree,有個很重要的地方要注意,那就是產生的 JSON 裡一定要有「id」這個屬性,此外就是頁面上的設定方式也略有不同。

 

Controller

因為一開始是載入指定的階層,看是要載入根節點還是根節點與第一層節點,必須要有個起始資料,而我這邊是先顯示根節點,而前端向後端取得資料是依據上一層節點的 id 資料,例如 RootNode 底下有三個節點,要取得 RootNode 下的節點資料,則要將 RootNode 的 id 給 POST 到後端,然後取得下一層的節點資料,

public ActionResult AsyncTree()
{
    ViewBag.HasRootNode = this._helper.GetRootNode() != null;
    return View();
}
 
[HttpPost]
public ActionResult GetData(Guid? id)
{
    string result = this._helper.GetNodes(id);
    return Content(result, "application/json");
}

 

EasyUITreeHelper

因為取得資料的不同,所以在 EasyUITreeHelper 再加入以下的 Method 程式內容,

/// <summary>
/// Gets the nodes.
/// </summary>
/// <param name="parentID">The parent ID.</param>
/// <returns></returns>
public string GetNodes(Guid? parentID)
{
    List<JObject> jObjects = new List<JObject>();
 
    if (!parentID.HasValue)
    {
        var rootNode = this.GetRootNode();
        JObject node = new JObject
        {
            {"id", rootNode.ID.ToString()}, 
            {"text", rootNode.Name},
            {"state", this.db.TreeNode.Any(x => x.ParentID == rootNode.ID) 
                ? "closed" 
                : "open" }
        };
        jObjects.Add(node);
    }
    else
    {
        var nodes = this.db.TreeNode
            .Where(x => x.ParentID == parentID && x.IsEnable == true)
            .ToList();
 
        if (nodes.Any())
        {
            foreach (var item in nodes)
            {
                JObject node = new JObject
                {
                    {"id", item.ID.ToString()}, 
                    {"text", item.Name},
                    {"state", this.db.TreeNode.Any(x => x.ParentID == item.ID) 
                        ? "closed" 
                        : "open" }
                };
                jObjects.Add(node);
            }
        }
    }
    return JsonConvert.SerializeObject(jObjects);
}

而在 GetNode Method 裡,每個節點資料的屬性除了 id 與 text 之外,另外增加了「state」屬性,在 Async Tree 模式下,state 屬性表示該節點是否有下層節點資料,如果有則屬性資料為 closed,如果沒有則屬性資料為 oped。

 

View

View 的話就簡單多了,因為也不需要另外寫 Javascript 程式,因為不需要另外去處理取得節點資料的方法,只要把提供階層節點資料的後端 Action 的 URL 位置給指定 ul 標籤上就可以,如下,

@{
    ViewBag.Title = "AsyncTree";
    Layout = "~/Views/Shared/_TreeGridLayout.cshtml";
}
 
<h2>ASP.NET MVC + jQuery EasyUI - Tree, Async Load</h2>
<hr />
<div>
    @if ((bool)ViewBag.HasRootNode)
    {
        <ul id="tt" class="easyui-tree" url='@Url.Action("GetData", "TreeView")'></ul>    
    }
</div>

 

執行結果

第一次載入,載入根節點

image

image

第二次載入,載入第一層節點資料

image

image

第三次載入,載入 Level_2 下的節點資料

image

image

第四次載入,載入 Level_6 下的節點資料

image

image

最後載入全部節點

image

image

 

2013-07-15 更新

補上會動的操作示意圖

20130715_AsyncTree

 


至於 jQuery EasyUI Tree 還有什麼其他功能,就讓有興趣的朋友自己去玩囉!

下此換另外一個前端的 Tree 套件來玩玩。

 

以上

沒有留言:

張貼留言

提醒

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