2020. 2. 17. 15:30

부서 구조나 파일 구조 같은 것들을 표시할 때 많이 사용하는 것이 트리구조입니다.

윈도우의 파일 탐색기를 보면 트리구조로 되어 있습니다.

 

 

웹에서 이런 UI를 구현하는 라이브러리는 엄청 많습니다.

자기에게 맞는 라이브러리를 찾아서 사용하면 됩니다.

 

이 포스팅에서는 'jsTree'를 'ASP.NET Core'와 같이 사용하여 봅시다.

(참고 : jsTree - 공식 사이트 )

 

 

1. 프로젝트 준비

프로젝트는 'ASP.NET Core 2.2', 'WebAPI' 생성합니다.

 

아랫글을 참고해 'index.html'을 시작 페이지로 설정해 줍니다.

(참고 : [ASP.NET Core] 빈 프로젝트 세팅 (1) - 'index.html'을 시작페이지로 설정하기)

 

 

'index.html'를 아래와 같이 작성합니다.

<!doctype html>

<html class="no-js" lang="en">
<head>
    <meta charset="utf-8">
    <link rel="shortcut icon" href="/favicon.ico">
</head>

<body>
    <h2>jsTree 테스트</h2>
    <br />
    <button onclick="Test01();">기본 테스트</button>
    <button onclick="Test02();">아작스 테스트</button>
    <button onclick="Test03();">새로고침</button>
    <br />
    <br />
    <div id="jstree">

    </div>
</body>
</html>

 

'wwwroot'에 'Library'폴더를 만들고 'jsTree'라이브러리를 다운받아 넣어줍니다.

 

헤더에 'jquery'와 'jsTree'를 선언해줍니다.

<link rel="stylesheet" href="/Library/jsTree/3.3.9/themes/default/style.min.css" />

<!-- jquery 3.4.1 -->
<script src="https://code.jquery.com/jquery-3.4.1.js" integrity="sha256-WpOohJOqMqqyKL9FccASB9O0KwACQJpFTUBLTYOVvVU=" crossorigin="anonymous"></script>
<!-- jsTree -->
<script src="/Library/jsTree/3.3.9/jstree.min.js"></script>

 

 

2. 기본 테스트

로컬에 있는 파일을 읽어 들여 트리를 그려봅시다.

 

 

'jsTree'가 사용하는 데이터 구조는 아래와 같습니다.

{
  id          : "string" // 구분을 위한 고유키
  text        : "string" // 노드에 표시될 텍스트
  icon        : "string" // 노드에 사용할 아이콘
  state       : {
    opened    : boolean  // 열려있는지 여부
    disabled  : boolean  // 선택가능 여부
    selected  : boolean  // 선택 여부
  },
  children    : []  // 하위 리스트
 // 미리 바인딩하지 않을때는 bool 타입으로 사용한다.
 // 하위가 있으면 true 없으면 false
}

 

위 데이터 구조를 테스트 데이터를 만들겠습니다.

'wwwroot'에 'TestData'폴더를 만들고 아래 파일들을 만들어 넣습니다.

 

'root.json'파일을 만들고 아래와 같이 넣습니다.

[
  {
    "id": 1,
    "text": "Root node",
    "children": true
  },
  {
    "id": 2,
    "text": "Root node2",
    "children": false
  }
]

 

'children.json'파일을 만들고 아래와 같이 넣습니다.

[
  {
    "id": 3,
    "text": "Child node 3",
    "children": true
  },
  {
    "id": 4,
    "text": "Child node 4"
  }
]

 

'children2.json'파일을 만들고 아래와 같이 넣습니다.

[
  {
    "id": 5,
    "text": "Child node 5"
  },
  {
    "id": 6,
    "text": "Child node 6"
  }
]

 

 

'jsTree'를 사용하려면 'jsTree'를 적용할 개체를 선택하고 '.jstree()'로 생성하여 기능을 세팅합니다.

 

자바스크립트 영역에 아래 코드를 넣어줍니다.

/** 로컬 json 파일을 읽어 사용해 테스트 */
function Test01()
{
    $("#jstree").empty().jstree ("destroy");

    //jsTree
    $("#jstree").jstree({
        "core": {
            "data": {
                "url": function (node)
                {
                    var sUri = "";

                    switch (node.id)
                    {
                        case "#":
                            sUri = "/TestData/root.json";
                            break;
                        case "1":
                            sUri = "/TestData/children.json";
                            break;
                        case "3":
                            sUri = "/TestData/children2.json";
                            break;

                    }

                    return sUri;
                },
                "data": function (node)
                {
                    return { "id": node.id };
                }
            }
        }
    });


    //노드 클릭 이벤트
    $("#jstree").on("changed.jstree", function (e, data)
    {
        console.log(data.selected);
    });
}

 

'core'에 'data'를 넣어 줘야 합니다.

 

'url'에 직접 url을 넣어도 되지만 함수를 넣어 url를 리턴시켜도 됩니다.

이때 리턴되는 url은 파라메타가 없는 상태인 것이 좋습니다.

 

'data'는 위에서 만든 url에 파라메타를 붙이는 역할을 합니다.

 

 

실행해봅시다.

 

트리를 클릭하면 실시간으로 '.json'파일을 읽어 하위 트리를 바인딩하게 됩니다.

 

 

3. Web API를 이용하여 바인딩

위에서 json으로 만든 데이터를 모델로 만들고 리턴해주는 api를 만들어 주면 됩니다.

 

 

3-1. 모델 만들기

모델은 'jsTree' 데이터 구조를 그대로 따라가야 합니다.

 

'JsTreeItemModel.cs'를 생성하고 아래 코드를 넣어줍니다.

public class JsTreeItemModel
{
    public string id { get; set; }
    //public string parent { get; set; }

    /// <summary>
    /// 노드에 표시될 텍스트
    /// </summary>
    public string text { get; set; }
    /// <summary>
    /// 표시될 아이콘
    /// </summary>
    public string icon { get; set; }

    /// <summary>
    /// 상태값
    /// </summary>
    public JsTreeItemStateModel state { get; set; }

    /// <summary>
    /// 자식 노드 정보.
    /// 미리 바인딩 할때는 'JsTreeItemModel'을 배열로 넣는다.
    /// 미리 바인딩 하지 않을때는 bool 값을 넣어준다.
    /// </summary>
    public object children { get; set; }
}

/// <summary>
/// jsTree노드의 상태
/// </summary>
public class JsTreeItemStateModel
{
    /// <summary>
    /// 열려있는지 여부
    /// </summary>
    public bool opened { get; set; }
    /// <summary>
    /// 비활성화 여부
    /// </summary>
    public bool disabled { get; set; }
    /// <summary>
    /// 선택되어있는지 여부
    /// </summary>
    public bool selected { get; set; }
}

 

 

3-2. 컨트롤러 만들기

 

테스트는 DB에 접속하거나 리스트를 검색하여 데이터를 줄게 아니기 때문에 들어온 'id'에 맞는 고정된 데이터를 리턴하도록 만듭니다.

 

'JsTreeController.cs'를 생성하고 아래 코드를 넣어 줍니다.

[Route("api/[controller]")]
[ApiController]
public class JsTreeController : ControllerBase
{
    [HttpGet]
    [Route("ListGet")]
    public ActionResult<JsTreeItemModel[]> ListGet(int nId)
    {
        List<JsTreeItemModel> listChildren = new List<JsTreeItemModel>();

        switch(nId)
        {
            case 0:
                listChildren.Add(new JsTreeItemModel { id = "1", text = "ajax 1", children = true });
                listChildren.Add(new JsTreeItemModel { id = "2", text = "ajax 2", children = false });
                break;

            case 1:
                listChildren.Add(new JsTreeItemModel { id = "3", text = "ajax 3", children = false });
                listChildren.Add(new JsTreeItemModel { id = "4", text = "ajax 4", children = true });
                listChildren.Add(new JsTreeItemModel { id = "5", text = "ajax 5", children = false });
                break;

            case 4:
                listChildren.Add(new JsTreeItemModel { id = "6", text = "ajax 6", children = false });
                listChildren.Add(new JsTreeItemModel { id = "7", text = "ajax 7", children = false });
                break;
        }

        return listChildren.ToArray();
    }
}

 

 

3-3. API 호출하기

'url'은 '/api/JsTree/ListGet'로 고정합니다.

(만약을 위해 함수형을 그대로 뒀습니다.)

 

'data'는 선택한 로드의 'id'를 'nId'로 리턴하면 됩니다.

루트의 경우 'id'가 '#'으로 들어오므로 '0'으로 바꿔 호출합니다.

/** api 콜을 활용한 바인딩 */
function Test02()
{
    $("#jstree").empty().jstree ("destroy");

    //jsTree
    $("#jstree").jstree({
        "core": {
            "data": {
                "url": function (node)
                {
                    var sUri = "/api/JsTree/ListGet";

                    return sUri;
                },
                "data": function (node)
                {
                    var sId = 0;

                    if (node.id === "#")
                    {
                        sId += 0;
                    }
                    else
                    {
                        sId += node.id;
                    }

                    return { "nId": sId };
                }
            }
        }
    });


    //노드 클릭 이벤트
    $("#jstree").on("changed.jstree", function (e, data)
    {
        console.log(data.selected);
    });
}

 

 

3-4. 테스트

결과는 첫 번째 예제와 같습니다.

 

 

중단점을 찍어보면 확실하게 확인이 됩니다.

 

 

4. 자주 쓰는 기능

자주 쓰는 기능들입니다.

 

객체 제거

 

객체를 깨끗하게 제거하려면 'destroy'명령을 사용해야 합니다.

(참고 : jsTree API - destroy )

//초기화
jstree ("destroy");

예> $("#jstree").empty().jstree ("destroy");

 

 

트리 전체 새로 고침

트리를 새로 고침합니다.

(참고 : jsTree API - refresh )

//새로고침
jstree ("refresh");

예> $("#jstree").jstree("refresh");

 

 

특정 노드 새로 고침

특정 노드를 새로 고치려면

'get_node'를 사용하여 새로 고침할 노드를 찾고

'refresh_node'로 노드를 새로 고침 해줍니다.

(참고 : jsTree API - get_node, refresh_node)

 

jstree(true).get_node([선택할 노드의 id]);
.jstree(true).refresh_node([새로 고침할 노드의 ]);

예>
//갱신할 노드 선택
var node = $("#jstree").jstree(true).get_node(1);
//선택된 노드를 갱신한다.
$("#jstree").jstree(true).refresh_node(node);

 

 

ajax 결과 커스텀

리턴 받은 아작스 결과를 커스텀 하여 jsTree로 넘기는 방법입니다.

 

개발자의 말에 따르면 'success'는 지원 예정조차 없다고 합니다.

대신 'dataFilter'를 사용하여 'success'로 넘기라고 합니다.

 

이건 둘 다 'jquery'사용법이 똑같습니다.

/** api 콜을 활용하여 직접 바인딩 */
function Test05()
{
    $("#jstree").empty().jstree("destroy");

    //jsTree
    $("#jstree").jstree({
        "core": {
            "data": {
                "url": function (node)
                {
                    var sUri = "/api/JsTree/DataGet";

                    return sUri;
                },
                "data": function (node)
                {
                    var sId = 0;

                    if (node.id === "#")
                    {
                        sId += 0;
                    }
                    else
                    {
                        sId += node.id;
                    }

                    return { "nId": sId };
                },

                "dataFilter": function(data, type)
                {
                    //데이터 분리
                    var jsonData = jQuery.parseJSON(data);
                    //jsTree 트리로 전달할 데이터 다시 만들기
                    var sData = JSON.stringify(jsonData.data);
                    return sData;
                }
            }
        }
    });


    //노드 클릭 이벤트
    $("#jstree").on("changed.jstree", function (e, data)
    {
        console.log(data.selected);
    });
}

 

'dataFilter'에 오는 데이터는 문자열 상태기 때문에 일단 파싱을 해주고

리턴할때는 다시 문자열로 바꿔야 합니다.

 

 

마무리

완성된 샘플 : Github dang-gun - HtmlJavascriptSamples/JsTree_Test/

 

 

업데이트가 끊긴 지 오래된 라이브러리지만.....

원하는 기능은 깔끔하게 잘 사용할 수 있기 때문에 신경 쓰지 않고 쓰고 있습니다 ㅎㅎㅎㅎ

 

다른 라이브러리를 소게할 기회가 되면 소개하도록 하죠 ㅎㅎㅎㅎ