TreeView
이 컨트롤은 무엇인가요?
TreeView는 부모-자식 관계를 가진 계층형(트리) 데이터를 화면에 펼쳐 보여주는 컨트롤입니다.
내부적으로는 FancyTree 라이브러리를 감싸서 syn.uicontrols.$tree 싱글턴 객체로 제공합니다.
조직도, 메뉴 트리, 폴더 구조처럼 "상위 항목 아래 하위 항목이 계속 붙는" 형태의 데이터를 다룰 때 사용합니다. 평평한(flat) 배열 데이터를 넣으면 컨트롤이 알아서 parentID 기준으로 계층 구조를 만들어 그려주고, 반대로 화면에 그려진 트리를 다시 평평한 배열로 꺼내올 수도 있습니다.
언제 사용하나요?
- 조직도/부서 목록, 메뉴/프로그램 목록처럼 계층 구조를 가진 데이터를 트리 형태로 보여줘야 할 때
- 트리 노드를 검색어로 필터링하거나(
filter확장), 체크박스로 다중 선택해서 값을 가져와야 할 때 - 노드를 우클릭했을 때 "추가/이름변경/삭제" 같은 메뉴를 띄워야 할 때 — 이 경우 TreeView 단독으로는 우클릭 메뉴를 그리지 못하므로, ContextMenu 컨트롤과 함께 사용합니다.
target에 트리 컨테이너 셀렉터(#트리ID)를,delegate에 노드 타이틀 셀렉터(span.fancytree-title)를 지정하는 방식입니다.
빠른 시작
<syn_tree id="tvlTreeView" syn-options="{
itemID: 'key',
parentItemID: 'parentID',
childrenID: 'children',
reduceMap: { key: 'ProgramID', title: 'ProgramName', parentID: 'ParentID', folder: 'FolderYN', icon: false }
}" syn-events="['click', 'dblclick']"></syn_tree>
'use strict';
let $samplePage = {
event: {
tvlTreeView_dblclick(evt, data) {
// data.node.data 에 원본 행 데이터가 그대로 들어 있습니다.
syn.$l.eventLog('tvlTreeView_dblclick', data.node.data.ProgramName);
}
}
}
값을 채워 넣을 때는 setValue에 평평한(flat) 배열을 그대로 넘기면 됩니다. 각 항목은 reduceMap에 지정한 컬럼명(ProgramID, ProgramName, ParentID, FolderYN)을 가지고 있어야 합니다.
syn.uicontrols.$tree.setValue('tvlTreeView', [
{ ProgramID: 'ROOT', ProgramName: '전체', ParentID: null, FolderYN: 'Y' },
{ ProgramID: 'A01', ProgramName: '인사팀', ParentID: 'ROOT', FolderYN: 'Y' },
{ ProgramID: 'A0101', ProgramName: '홍길동', ParentID: 'A01', FolderYN: 'N' }
]);
핵심 포인트:
- 마크업은
<syn_tree>커스텀 태그로 작성하고,syn-options에itemID/parentItemID/childrenID/reduceMap등을 JSON으로 넣습니다. - 페이지에
<script src="/js/syn.loader.js"></script>한 줄만 있으면 FancyTree를 포함한 나머지 CSS/JS는 자동으로 로드됩니다. 별도의pageLoadFiles설정이 필요 없습니다. getValue/setValue는 항상 "평평한 배열"을 주고받습니다. 트리 안에서만 계층(중첩) 구조로 다뤄지고, 컨트롤 밖으로 나올 때는 평평한 형태로 변환됩니다.checkbox: true로 설정하면 체크박스를 통한 다중 선택이 가능해집니다.
예제 실행하기
example/ 폴더의 각 HTML 파일을 handstack의 wwwroot 정적 서버(예: rdy 프로젝트) 경로 아래에 두고 브라우저로 열면 바로 동작을 확인할 수 있습니다.
basic.html: 기본 트리 표시 +getValue/setValue/clear/getControl기능 확인checkbox.html:checkbox: true로 다중 선택하고 선택된 노드 목록을 가져오는 예제contextmenu.html: ContextMenu와 결합해서 노드 우클릭 메뉴를 띄우는 예제filter.html:filter확장으로 입력한 텍스트에 맞는 노드만 찾아 보여주는 예제
각 예제는 화면 하단 로그 영역(syn.$l.eventLog 출력)에서 이벤트 발생 순서와 전달값을 확인할 수 있습니다.
더 알아보기
- API 상세는 같은 폴더의
API.md를 참고하세요. - 실제 소스:
wwwroot/uicontrols/TreeView/TreeView.js,TreeView.css - 기존 샘플:
wwwroot/sample/uicontrol/treeview.html - 실사용 예: qcn.groupware
modules/easywork/wwwroot/easywork/view/HDS/EWP/ATM020.html(tplDepartment다이얼로그의 소속조직 트리) — 검색창(txtTreeNodeFilter)으로 필터링하고, 더블클릭으로 노드를 선택하는 실제 화면 흐름을 참고할 수 있습니다. - ContextMenu와 함께 쓰는 방 법:
../ContextMenu/README.md,../ContextMenu/API.md - FancyTree 원본 문서: https://github.com/mar10/fancytree/wiki
실전 예제 페이지
/uicontrols/TreeView/example/ 경로의 예제를 아래 iframe에서 바로 확인할 수 있습니다.
basic.html
checkbox.html
contextmenu.html
filter.html
소스와 로드 파일
| 항목 | 내용 |
|---|---|
| 컨트롤 | TreeView |
| 전역 별칭 | syn.uicontrols.$tree |
| 버전 | v2025.3.1 |
| 소스 | uicontrols/TreeView/TreeView.js |
| 스타일 | uicontrols/TreeView/TreeView.css |
API 참조
싱글턴 객체: syn.uicontrols.$tree
소스 파일: wwwroot/uicontrols/TreeView/TreeView.js, wwwroot/uicontrols/TreeView/TreeView.css
내부 라이브러리: FancyTree (jQuery 플러그인)
마크업
<syn_tree id="tvlTreeView" syn-options="{
checkbox: false,
itemID: 'key',
parentItemID: 'parentID',
childrenID: 'children',
reduceMap: { key: 'ProgramID', title: 'ProgramName', parentID: 'ParentID', folder: 'FolderYN', icon: false },
height: '334px',
extensions: ['filter']
}" syn-events="['dblclick', 'select']"></syn_tree>
id는 페이지 내에서 유일해야 하며,syn.uicontrols.$tree의 각종 메서드에서 이id(elID)를 사용합니다.controlLoad실행 시 원래 태그는{elID}_hidden으로 이름이 바뀌며display:none처리되고, 같은 위치에 실제 FancyTree가 그려질<div id="{elID}">를 감싼 래퍼(div.tree-container)가 새로 추가됩니다.width/height는 마크업 요소의 인라인style.width/style.height가 지정되어 있으면 그 값이syn-options보다 우선합니다.
Options (defaultSetting)
| 속성 | 타입 | 기본값 | 설명 |
|---|---|---|---|
width | string | '100%' | 트리 컨테이너 너비 |
height | string | '300px' | 트리 컨테이너 높이(넘치면 스크롤) |
itemID | string | 'id' | 평평한(flat) 데이터에서 각 행의 고유 키가 되는 컬럼명. getValue/setValue가 트리 ↔ flat 배열 변환 시 사용합니다. |
parentItemID | string | 'parentID' | flat 데이터에서 부모 키 컬럼명 |
childrenID | string | 'children' | 중첩(nested) 구조로 변환할 때 자식 배열을 담을 속성명 |
reduceMap | object | 아래 "reduceMap" 참고 | 트리 내부 필드(key/title/parentID/folder/icon)와 실제 데이터 컬럼명을 매핑 |
toggleEffect | boolean | false | 노드 확장/축소 시 애니메이션 효과 여부 (FancyTree toggleEffect 옵션) |
checkbox | boolean | false | 노드마다 체크박스를 표시할지 여부. true로 하면 체크박스 클릭으로 다중 선택이 가능해집니다. |
extensions | array | ['persist', 'filter'] | 사용할 FancyTree 확장 목 록. (확장 전체 목록) |
persist | object | false | 아래 "persist" 참고 | 세션 저장(확장/포커스/선택 상태 유지) 확장 옵션. 주의: controlLoad가 실행될 때 내부에서 무조건 false로 재설정되므로, syn-options에서 직접 persist: {...} 객체를 명시해야만 실제로 동작합니다. 기본값만 믿고 켜두면 동작하지 않습니다. |
multi | object | false | 객체 설정 | 다중 선택 확장 옵션. persist와 동일한 이유로 controlLoad 시점에 무조건 false로 재설정됩니다. 다중 선택을 쓰려면 extensions에 'multi'를 추가하고 syn-options에서 multi: {...} 를 직접 지정하세요. |
filter | object | 아래 "filter" 참고 | 필터 확장(extensions에 'filter'가 있을 때) 옵션 |
source | array | [] | FancyTree 초기 데이터. 보통 직접 지정하지 않고 setValue가 내부적으로 채웁니다. |
dataType | string | 'string' | 다른 컨트롤과 형식을 맞추기 위한 공통 속성 |
belongID / getter / setter / controlText / validators / transactConfig / triggerConfig | - | null/false | syn.uicontrols 공통 옵션(값 바인딩·유효성검사·트랜잭션 연동용) |
reduceMap
getValue/setValue가 트리 노드(key/title/parentID/folder/icon)와 원본 데이터 컬럼을 서로 바꿔치기할 때 쓰는 매핑 테이블입니다.
| 속성 | 기본값 | 설명 |
|---|---|---|
key | 'key' | 노드 고유 키에 대응하는 실제 데이터 컬럼명 |
title | 'title' | 노드에 표시할 텍스트에 대응하는 컬럼명 |
parentID | 'parentID' | 부모 노드 키에 대응하는 컬럼명 |
folder | 'folder' | 폴더(하위 노드를 가질 수 있는 노드) 여부 컬럼명. 값은 $string.toBoolean()으로 boolean 변환됩니다. |
icon | false | 노드 아이콘. false면 아이콘을 표시하지 않습니다. |
persist
| 속성 | 기본값 | 설명 |
|---|---|---|
expandLazy | false | 지연 로딩(lazy) 노드까지 확장 상태를 복원할지 여부 |
expandOpts.noAnimation | false | 복원 시 애니메이션 생략 여부 |
expandOpts.noEvents | false | 복원 시 이벤트 발생 생략 여부 |
overrideSource | true | 저장된 상태로 source를 덮어쓸지 여부 |
store | 'session' | 저장소 종류('session' = sessionStorage, 'local' = localStorage) |
types | 'active expanded focus selected' | 저장할 상태 종류(공백 구분 문자열) |
multi
| 속성 | 기본값 | 설명 |
|---|---|---|
mode | 'sameParent' | 다중 선택 시 같은 부모를 가진 노드끼리만 선택 가능하도록 제한 |
filter
| 속성 | 기본값 | 설명 |
|---|---|---|
counter | false | 필터링된 자식 노드 개수를 배지로 표시할지 여부 |
mode | 'hide' | 필터에 걸리지 않는 노드 처리 방식('hide' = 숨김, 'dimm' = 흐리게 표시) |
메서드
syn.uicontrols.$tree.<메서드명>(...) 형태로 호출합니다.
| 메서드 | 설명 |
|---|---|
getControl(elID) | 등록된 컨 트롤 정보(객체 설정)를 반환. tree가 실제 FancyTree 인스턴스입니다. |
getValue(elID, meta) | 트리 전체를 reduceMap 기준의 평평한(flat) 배열로 변환해서 반환합니다. |
setValue(elID, value, meta) | reduceMap 컬럼명을 가진 평평한(flat) 배열을 받아 중첩 구조로 변환한 뒤 트리를 다시 그립니다(tree.reload). |
clear(elID, isControlLoad) | 트리 내용을 비웁니다(빈 배열로 reload). |
getActiveNode(elID) | 현재 활성(activate)된 노드를 반환합니다. |
toogleEnabled(elID) | 트리 활성/비활성 상태를 토글하고, 토글 후 활성화 여부(boolean)를 반환합니다. (메서드명 오타이지만 실제 API명이 toogleEnabled입니다) |
getRootNodeID(elID) | 루트 노드의 키를 반환합니다(내부적으로 FancyTree가 붙이는 'root_' 접두어는 제거해서 반환). |
activateKey(elID, key) | 지정한 key를 가진 노드를 활성화(선택 포커스 이동)합니다. |
expendLevel(elID, level) | 지정한 깊이(level, 기본 1)보다 얕은 노드까지 모두 펼칩니다. (메서드명 오타이지만 실제 API명이 expendLevel입니다) |
collapseLevel(elID, level) | 지정한 깊이(level, 기본 1)보다 얕은 노드까지 모두 접습니다. |
expandAll(elID) | 모든 노드를 펼칩니다. |
collapseAll(elID) | 모든 노드를 접습니다. |
getSelectedNodes(elID) | (체크박스/다중 선택 시) 선택된 노드 배열을 반환합니다. |
filterNodes(elID, filter) | 텍스트(또는 필터 함수)와 일치하는 노드만 남기고 나머지는 filter.mode에 따라 숨기거나 흐리게 표시합니다. 일치 노드까지 자동으로 펼칩니다(autoExpand: true). extensions에 'filter'가 있어야 동작합니다. |
filterBranches(elID, filter) | filterNodes와 유사하지만, 일치하는 노드가 속한 가지(branch) 전체를 남깁니다(leavesOnly: true). |
clearFilter(elID) | 적용된 필터를 해제합니다. |
setNodeSelectedAll(elID, node) | 주어진 node의 현재 선택 상태를 그 하위 노드 전체에 그대로 적용합니다. |
selectedAll(elID, isSelected) | 트리의 모든 노드를 한 번에 선택(true)/선택 해제(false)합니다. |
setLocale(elID, translations, control, options) | 다국어 텍스트 적용 훅(TreeView는 별도 구현 없음, 빈 함수) |
controlLoad(elID, setting)은 컨트롤이 마크업을 실제 FancyTree로 초기화하는 내부 진입점으로, 프레임워크가 자동으로 호출합니다. 직접 호출할 일은 거의 없습니다.
이벤트 (syn-events)
syn-events 배열에 이름을 넣으면 페이지 스크립트의 event 객체에서 {elID}_{이벤트명} 함수를 자동으로 연결해 줍니다. (핸들러 함수 시그니처는 FancyTree 콜백 그대로 (evt, data)이며, 대부분의 경우 data.node로 대상 노드에 접근합니다.)
중요: 아래 eventHooks 목록에 있는 이름만 실제로 동작합니다. 목록에 없는 이름(예: 'select')을 syn-events에 적어도 에러는 나지 않지만 아무 핸들러도 연결되지 않습니다. 체크박스로 선택 상태가 바뀌는 것을 감지하려면 select가 아니라 click(체크박스 클릭 시) 이벤트에서 data.node.isSelected()를 직접 확인하는 방식을 사용하세요.
| 이벤트명 | 발생 시점 |
|---|---|
blurTree | 트리 전체가 포커스를 잃을 때 |
focusTree | 트리 전체가 포커스를 얻을 때 |
activate | 노드가 활성화(선택)되었을 때 |
beforeActivate | 노드가 활성화되기 직전(취소 가능) |
beforeExpand | 노드가 펼쳐지기 직전(취소 가능) |
beforeSelect | 노드의 체크박스 선택 상태가 바뀌기 직전(취소 가능) |
blur | 개별 노드가 포커스를 잃을 때 |
click | 노드를 클릭했을 때 |
collapse | 노드가 접힐 때 |
createNode | 노드 DOM 요소가 생성되었을 때(노드별 커스텀 렌더링에 활용) |
dblclick | 노드를 더블클릭했을 때 |
expand | 노드가 펼쳐질 때 |
focus | 개별 노드가 포커스를 얻을 때 |
keydown | 트리에 포커스가 있는 상태에서 키를 눌렀을 때 |
keypress | 트리에 포커스가 있는 상태에서 키를 입력했을 때 |
핸들러 등록 예:
let $samplePage = {
event: {
tvlTreeView_click(evt, data) {
syn.$l.eventLog('tvlTreeView_click', data.node.title);
},
tvlTreeView_dblclick(evt, data) {
syn.$l.eventLog('tvlTreeView_dblclick', data.node.data);
},
tvlTreeView_activate(evt, data) {
syn.$l.eventLog('tvlTreeView_activate', data.node.key);
}
}
}
참고
-
내부적으로 jQuery의
.fancytree(setting)호출을 그대로 감싼 구조이므로, 옵션/콜백의 더 자세한 원본 설명은 FancyTree 문서를 참고하세요: https://github.com/mar10/fancytree/wiki -
노드를 우클릭했을 때 메뉴를 띄우려면 TreeView 자체가 아니라 ContextMenu를 함께 사용합니다.
target에 트리 컨테이너 셀렉터(#트리ID)를,delegate에'span.fancytree-title'을 지정하면 노드 타이틀을 우클릭했을 때 메뉴가 뜹니다.<syn_tree id="tvlTreeView" syn-options="{...}" syn-events="['click']"></syn_tree>
<syn_contextmenu id="ctxTreeItem" syn-options="{
target: '#tvlTreeView', delegate: 'span.fancytree-title', menu: [...]
}" syn-events="['beforeOpen', 'select']"></syn_contextmenu>이때
ctxTreeItem_beforeOpen(evt, ui)에서$.ui.fancytree.getNode(ui.target)(또는syn.uicontrols.$tree.getControl(elID).tree.getActiveNode())로 우클릭된 노드를 얻어 메뉴 항목을 동적으로 켜고 끌 수 있습니다. -
getValue/setValue는 항상 flat 배열 기준입니다. 트리 내부 계층 변환은syn.$l.flat2Nested/syn.$l.nested2Flat유틸리티가 담당합니다. -
체크박스 다중 선택 결과를 가져오려면
getSelectedNodes(elID)를 사용하세요(전체 트리 데이터를 가져오는getValue와는 다릅니다).