들어가며

이 강좌에서는 TreeDataProvider의 Data 구조에 대해 알아보도록 하겠습니다.

이론

TreeView의 데이터 제공자인 TreeDataProvider의 데이터셋은 Array, Json, Xml, Csv등 다양한 소스로 부터 구성될 수 있습니다.
TreeDataProvider에 추가된 데이터행들은 부모/자식 관계를 갖는 구조로 재구성되며 원본 데이터셋 내에서의 위치 정보는 유지하지 않습니다.
하지만, 원본의 각 행이 TreeDataProvider의 데이터행으로 추가될 때 rowId 라는 유일한 키값이 행별로 지정되고, 이 후 Javascript 등에서는 이 값으로 TreeDataProvider의 데이터행을 추적할 수 있습니다.

그러면 TreeDataProvider에 부모/자식 관계를 갖는 구조로 재구성되기 위해 참조되는 정보를 설정하는 방법에 대해 알아야 할 필요가 있습니다.

이번 강좌에서는 Array형식의 Data를 구성하는 방법을 설명하겠습니다.
Tree의 DataField목록에 일반 Grid에는 없는 DataField를 추가 해야 합니다. 이 추가된 필드는 Tree를 구성하게될 정보를 갖고있는 DataField로 사용됩니다.

Grid의 Fields

provider.setFields([{
	fieldName : "field1"
}, {
	fieldName : "field2"
}]);

Tree의 Fields

provider.setFields([{
	fieldName : "tree"
}, {
	fieldName : "field1"
}, {
	fieldName : "field2"
}]);

Tree Field의 데이터 구성은 자신의 위에있는 row의 값과 비교하여 부모/자식 관계를 갖게 됩니다. 첫번째 row의 Tree Field값이 1이면 자신 위의 상위노드가 없기 때문에 그리드 화면에는 보이지 않지만 최상위 루트로 지정되어있는 row의 하위에 위치하게 됩니다.

TreeDataProvider의 내부 구조를 보면 다음과 같이 구성됩니다.

tree = [["1"]]

Hidden Root
	┗ Root 1

Tree의 구성은 자신 윗행의 값과 비교하여 구성된다고 하였는데 그 방법은 자신의 값에 윗행의 값이 포함되는지 비교합니다. 만약 데이터가 1,1,2로 설정하면 맨앞의 1은 루트가되고 다음1은 루트 1의 하위 노드 그리고 2는 자신위의 1과 비교하게 됩니다.
2와 1은 다른 값이기 때문에 1의 루트노드까지 찾아 자신의 값과 비교 후 자신에 포함하는 루트를 못찾으면 자신을 루트노드로 설정합니다.

tree = [["1"],["1"],["2"]]

Hidden Root
	┗ Root "1"
		┗ child "1"
	┗ Root "2"

물론 트리의 실제 데이타가 중복되면 잘못된 데이터이기 때문에 올바른 데이터의 구조로 표현하려면 [“1”,”11”,”2”]가 되겠습니다.

tree = [["1"],["11"],["2"]]

Hidden Root
	┗ Root "1"
		┗ child "11"
	┗ Root "2"

그러면 좀더 깊은 노드의 트리를 구성해 보겠습니다.
트리그리드를 다음과 같이 보여줘야 한다면

Hidden Root
	┗ "국내"
		┗ "컴퓨터"
			┗ "인텔"
			┗ "키보드"
			┗ "팩스"
		┗ "서적"
			┗ "미움"
			┗ "기탄"

데이터의 구성은 1(국내),11(컴퓨터),111(인텔),112(키보드),113(팩스),12(서적),121(미움),122(기탄) 으로 표현할 수 있습니다.

tree = [["1"],["11"],["111"],["112"],["113"],["12"],["121"],["122"]]

Hidden Root
	┗ 1 "국내"
		┗ 11 "컴퓨터"
			┗ 111 "인텔"
			┗ 112 "키보드"
			┗ 113 "팩스"
		┗ 12 "서적"
			┗ 121 "미움"
			┗ 122 "기탄"

이제 앞의 데이터를 좀더 직관적으로 보기 쉽게 마침표.로 구분지어 표현해 보겠습니다.
맆노드의 경우 루트의 정보까지 모두 가지고 있으면 한눈에 데이터의 구조를 파악할 수 있기 때문에 나중에 데이터의 생성에 혼돈을 방지 할 수 있습니다.
마침표.는 데이터의 가독성을 위한 표현이지 트리를 구성하기위한 구분값은 아닙니다.
인텔의 경우 루트 국내 1, 컴퓨터 11의 값을 추가하여 1.11.111 표현합니다.
전체 데이터를 위와 같이 적용하면 다음과 같습니다.

tree = [
	["1"]
	,["1.11"]
	,["1.11.111"]
	,["1.11.112"]
	,["1.11.113"]
	,["1.12"]
	,["1.12.121"]
	,["1.12.122"]
]

Hidden Root
	┗ 1 "국내"
		┗ 1.11 "컴퓨터"
			┗ 1.11.111 "인텔"
			┗ 1.11.112 "키보드"
			┗ 1.11.113 "팩스"
		┗ 1.12 "서적"
			┗ 1.12.121 "미움"
			┗ 1.12.122 "기탄"

지금 데이터를 보면 중복되는 값이 많아 길어 졌습니다. 좀더 간략화 하면

Hidden Root
	┗ 1 "국내"
		┗ 1.1 "컴퓨터"
			┗ 1.1.1 "인텔"
			┗ 1.1.2 "키보드"
			┗ 1.1.3 "팩스"
		┗ 1.2 "서적"
			┗ 1.2.1 "미움"
			┗ 1.2.2 "기탄"

위와 같이 정리 할 수 있습니다.

그럼 여기서 만약 키보드의 값이 3으로 변경된다면 트리의 구조는 어떻게 될지 예상해 보십시오.

Hidden Root
	┗ 1 "국내"
		┗ 1.1 "컴퓨터"
			┗ 1.1.1 "인텔"
			┗ 3 "키보드"
			┗ 1.1.3 "팩스"
		┗ 1.2 "서적"
			┗ 1.2.1 "미움"
			┗ 1.2.2 "기탄"

결과는 키보드은 윗행의 인텔과 값을 비교하여 키보드인텔의 값이 포함되지 않게되어 인텔부모 컴퓨터와 값을 비교합니다. 역시 키보드의 값에 컴퓨터의 값은 포함되지 않고 컴퓨터의 부모 국내와 값비교를 하지만 역시 키보드의 값에 국내값은 포함되지 않습니다.

그럼 키보드는 Root값으로 지정되고 그 다음 팩스키보드와 비교하여 팩스도 Root가 됩니다. 서적역시 Root가 되고 미움,기탄서적의 자식노드로 설정됩니다.

Hidden Root
	┗ 1 "국내"
		┗ 1.1 "컴퓨터"
			┗ 1.1.1 "인텔"
	┗ 3 "키보드"
	┗ 1.1.3 "팩스"
	┗ 1.2 "서적"
		┗ 1.2.1 "미움"
		┗ 1.2.2 "기탄"

위와 같이 정렬되지 않은 데이터를 로드해야 할 경우 그리드 자체에 데이터를 정렬 후 로드할 수 있는 기능이 있습니다.
Array형식의 데이터를 로드하는 함수인 LocalTreeDataProvider.setRows의 파라메터에 needSorting을 true로 지정하면 Tree Field를 정렬한 다음 트리를 구성하게 됩니다.

	dataProvider.setRows(rows, "tree", true);

그리고 지금은 데이터의 수가 적어 각 노드를 가르키는 자릿수를 한자리로 지정하였는데 데이터의 양이 많을 경우 명확한 노드의 지정을 위해 예상되는 데이터의 자릿수를 맞춰주는게 좋습니다.

3뎁스의 데이터 수가 1000개가 된다면 인텔의 경우 “1.1.0001”로 지정합니다.
만일 자릿수를 미리 지정하지 않고 데이터를 정렬하면 다음과 같은 상황이 생길 수 있습니다.

데이터가 1씩 증가된다고 하였을때 자릿수가 증가되었을 경우 1, 1.1,..., 1.9, 1.10 

["1", "1.1", "1.9", "1.10"].sort() //정렬하면.

output : ["1", "1.1", "1.10", "1.9"] 로 정렬됩니다.

의도와 다르게 1.10은 1.1의 자식 노드로 지정됩니다.

1
┗ 1.1
   ┗ 1.10
┗ 1.9

["1", "1.01", "1.09", "1.10"].sort() //자릿수를 맞춰줘야

output : ["1", "1.01", "1.09", "1.10"] 의도한데로 1의 자식노드로 동일한 레벨에 위치하게 됩니다. 

1
┗ 1.01
┗ 1.09
┗ 1.10

실습

이제 실제 데이터를 트리로 구성해보도록 하겠습니다.

id parent product price
1 0 국내 null
2 1 컴퓨터 null
3 2 인텔 SSD 520 120GB 63900
4 2 LED 게이밍키보드 29700
5 2 팩스무선레이저복합기 119000
6 1 서적 null
7 6 미움받을 용기 13410
8 6 기탄 세계명작동화50권 69000
9 0 해외 null
10 9 가전 null
11 10 32G Amazon phone 1301
12 10 TDK- Wireless Speaker 49.99
13 10 대륙의 실수 QCY QY5s 18.59
14 9 TV/영상 null
15 14 LG 55LF6000 498
16 14 Samsung Smart LED FHD 60” 897.99

위와 같은 데이터가 있을 경우 직접 하드코딩으로 데이터를 작성해 보면 다음과 같습니다.
(트리로 생성 가능한 데이터를 트리구조로 자동 생성하는 로직은 다음 강좌에 소개도록 하겠습니다.)

var rows = [
	["ko","국내"]
	,["ko.co", "컴퓨터"]
	,["ko.co.001", "인텔 SSD 520 120GB", 63900]
	,["ko.co.002", "LED 게이밍키보드", 29700]
	,["ko.co.003", "팩스무선레이저복합기", 119000]
	,["ko.bo", "서적"]
	,["ko.bo.001", "미움받을 용기", 13410]
	,["ko.bo.002", "기탄 세계명작동화50권", 69000]
	,["ov", "해외"]
	,["ov.ha", "가전"]
	,["ov.ha.001", "32G Amazon phone", 130]
	,["ov.ha.002", "TDK- Wireless Speaker", 49.99]
	,["ov.ha.003", "대륙의 실수 QCY QY5s", 18.59]
	,["ov.tv", "TV/영상"]
	,["ov.tv.001", "LG 55LF6000", 498]
	,["ov.tv.002", "Samsung Smart LED FHD 60\"", 897.99]
]

Array형식의 데이터는 DataField의 구성순으로 설정되기 때문에 트리의 노드정보를 가지고 있는 배열의 첫번째값과 연결되는 DataField를 임의의 명칭 tree로 생성합니다.

dataProvider.setFields([{
	fieldName: "tree"
}, {
	fieldName: "product"
}, {
	fieldName: "price"
}]);

또는 각 Array에 값이 String외에 Object형태로도 데이터를 불러올수 있습니다.
이때는 DataField의 구성순과 무관하게 각 DataField의 fieldName과 Object의 name이 매칭되어 데이터가 로드됩니다.

var rows = [
	{tree : "ko", product : "국내"}
	,{tree : "ko.co", product :  "컴퓨터"}
	,{tree : "ko.co.001", product :  "인텔 SSD 520 120GB", price :  63900 }
	,{tree : "ko.co.002", product :  "LED 게이밍키보드", price :  29700 }
	,{tree : "ko.co.003", product :  "팩스무선레이저복합기", price :  119000 }
	,{tree : "ko.bo", product :  "서적"}
	,{tree : "ko.bo.001", product :  "미움받을 용기", price :  13410 }
	,{tree : "ko.bo.002", product :  "기탄 세계명작동화50권", price :  69000 }
	,{tree : "ov", product :  "해외"}
	,{tree : "ov.ha", product :  "가전"}
	,{tree : "ov.ha.001", product :  "32G Amazon phone", price :  130 }
	,{tree : "ov.ha.002", product :  "TDK- Wireless Speaker", price :  49.99 }
	,{tree : "ov.ha.003", product :  "대륙의 실수 QCY QY5s", price :  18.59 }
	,{tree : "ov.tv", product :  "TV/영상"}
	,{tree : "ov.tv.001", product :  "LG 55LF6000", price :  498 }
	,{tree : "ov.tv.002", product :  "Samsung Smart LED FHD 60\"", price :  897.99 }
];

setRows함수로 생성한 데이터를 불러옵니다. 이때 임의로 생성한 트리의 노드정보를 가지고있는 fieldName tree를 두번째 파라메터로 설정합니다.

dataProvider.setRows(rows, "tree");

아래 코드는 각 버튼의 클릭 이벤트 입니다.

$("#btnDoNotSort").click(function(){
	dataProvider.setRows(rows, "tree", false);
});

$("#btnItSort").click(function(){
	dataProvider.setRows(rows, "tree", true);
});

실행화면

  1. 버튼을 눌러 데이터를 로드한 다음 국내노드를 열어 컴퓨터와 서적의 정렬 순서를 확인하세요.

  2. 버튼을 눌러 데이터를 로드한 다음 국내노드를 열어 컴퓨터와 서적의 정렬 순서를 확인하세요.

전체 소스코드

SCRIPT
<link rel="stylesheet" href="/css/bootstrap.css">
<script type="text/javascript" src="/script/jquery-1.112.min.js"></script>
<script type="text/javascript" src="/script/bootstrap.min.js"></script>
<!--realgrid-->
<script type="text/javascript" src="/script/realgridjs-lic.js"></script>
<script type="text/javascript" src="/script/realgridjs_eval.1.0.14.min.js"></script>
<script type="text/javascript" src="/script/realgridjs-api.1.0.14.js"></script>

<script>
  var treeView;
  var dataProvider;
  var rows = [
    ["ko","국내"]
    ,["ko.co", "컴퓨터"]
    ,["ko.co.001", "인텔 SSD 520 120GB", 63900]
    ,["ko.co.002", "LED 게이밍키보드", 29700]
    ,["ko.co.003", "팩스무선레이저복합기", 119000]
    ,["ko.bo", "서적"]
    ,["ko.bo.001", "미움받을 용기", 13410]
    ,["ko.bo.002", "기탄 세계명작동화50권", 69000]
    ,["ov", "해외"]
    ,["ov.ha", "가전"]
    ,["ov.ha.001", "32G Amazon phone", 130]
    ,["ov.ha.002", "TDK- Wireless Speaker", 49.99]
    ,["ov.ha.003", "대륙의 실수 QCY QY5s", 18.59]
    ,["ov.tv", "TV/영상"]
    ,["ov.tv.001", "LG 55LF6000", 498]
    ,["ov.tv.002", "Samsung Smart LED FHD 60\"", 897.99]
];

  $(document).ready( function() {

    RealGridJS.setTrace(false);
    RealGridJS.setRootContext("/script");
    
    dataProvider = new RealGridJS.LocalTreeDataProvider();
    treeView = new RealGridJS.TreeView("realgrid");
    treeView.setDataSource(dataProvider);

    dataProvider.setFields([{
      fieldName: "tree"
    }, {
      fieldName: "product"
    }, {
      fieldName: "price"
    }]);

    treeView.setColumns([{
      name: "product",
      fieldName: "product",
      width: 200
    }, {
      name: "price",
      fieldName: "price"
    }]);

    $("#btnDoNotSort").click(function(){
        dataProvider.setRows(rows, "tree", false);
    });
    
    $("#btnItSort").click(function(){
        dataProvider.setRows(rows, "tree", true);
    });
    //dataProvider.setRows(rows, "tree");
  })
</script>
HTML
1. <input type="button" class="btn btn-primary btn-xs" id="btnDoNotSort" value="데이터를 정렬하지 않고 로드" /> 버튼을 눌러 데이터를 로드한 다음 `국내`노드를 열어 컴퓨터와 서적의 정렬 순서를 확인하세요.

2. <input type="button" class="btn btn-primary btn-xs" id="btnItSort" value="데이터를 정렬하고 로드" /> 버튼을 눌러 데이터를 로드한 다음 `국내`노드를 열어 컴퓨터와 서적의 정렬 순서를 확인하세요.

<div id="realgrid" style="width: 100%; height: 200px;"></div>

관련 데모 페이지


API 참조