본문으로 건너뛰기

Tree 컨트롤

Tree는 상품 분류, 조직도, 폴더, 지역처럼 계층이 있는 데이터를 표시합니다. 작고 변경이 적은 트리는 페이지 구조에서 직접 관리할 수 있고, 업무 시스템이 소유한 데이터는 API에서 루트와 자식 노드를 불러올 수 있습니다. 검색, 노드별 작업, 드래그 정렬도 함께 지원합니다.

1. 구성 방식 선택​

상황권장 구성
페이지 작성자가 관리하는 소규모 고정 목록정적 Tree Nodes
업무 시스템이 관리하는 계층 데이터Tree Data Source
추가, 편집, 삭제, 컨텍스트 명령동적 소스 + Node Actions
형제 순서 변경 또는 가지 이동동적 소스 + onDrop

1.1 Tree: 정적 상품 분류​

정적 예제는 Set Page Context로 [[treeNode.name]]을 selectedCategory에 저장한 다음 ProTable의 reloadTable을 호출합니다. ProTable Query Context에서는 category 요청 파라미터를 이 값에 연결합니다.

1.2 Tree02: 동적 로딩과 계층 편집​

Tree02는 페이지를 열 때 루트 노드를 불러오고, 디렉터리를 펼칠 때 parentId로 바로 아래 자식만 요청합니다. 서버 검색, 상세 보기, 추가, 편집, 삭제, 드래그 저장도 보여 줍니다. 루트 응답, 지연 로딩, 검색, 클릭 연동, 노드 작업, 드래그 순서로 설정하면 오류 원인을 쉽게 구분할 수 있습니다.

2. Tree 추가​

  1. Page Designer에서 대상 페이지를 열고 Screen을 선택합니다.
  2. Navigation Tree 자식 영역에 Tree를 추가합니다.
  3. 정적 데이터는 Tree Nodes, API 데이터는 Data Source 아래에 Tree Data Source를 추가합니다.
  4. ProTable, Form 등 본문 컨트롤은 Panels에 배치합니다.

Navigation Tree는 왼쪽 탐색 영역을 만들고 Panels와 Grids는 본문 영역을 만듭니다. 같은 구조를 Grid로 다시 만들 필요가 없습니다.

3. Tree 기본 설정​

속성용도
Title트리 위에 표시할 선택적 제목
Width데스크톱 너비. 좁은 화면에서는 한 행 전체로 전환
Show Search검색 입력란 표시
Checkable체크박스 표시
Default Expand All처음 표시할 때 이미 로드된 노드 펼치기
Node Draggable드래그를 허용하고 onDrop 실행

4. 정적 노드​

Tree Nodes에 TreeNode를 추가하고 Title, Value, Disabled, Selectable, Checkable, Is Leaf, Icon, Extra Info를 설정합니다. Value는 전체 Tree에서 고유하고 안정적이어야 합니다. 이후 상호작용은 이 값으로 노드를 식별합니다.

5. 동적 데이터 소스​

Data Source 아래에 TreeDataSource를 추가하고 먼저 루트 요청을 설정합니다.

설정Tree02 예제
Root Data URL/spark/api/sample/navigation/tree/nodes
Request MethodGET
Response List Pathdata.nodes
Title Fieldlabel
Value Fieldid
Children Fieldchildren
Leaf Fieldleaf
Extra Fieldcount

5.1 Test로 요청 확인​

Test를 클릭합니다. Test, 디자인 캔버스, 실행 페이지는 동일한 Tree 요청 구성 및 응답 파싱 로직을 사용합니다. 따라서 결과를 통해 URL, 백엔드 데이터, Response List Path, 필드 매핑 중 어디에 문제가 있는지 직접 판단할 수 있습니다.

플랫폼은 다음 헤더를 자동으로 전송합니다.

X-Spark-Page-Environment: DEV
X-Spark-Page-Render-Mode: DESIGN_PREVIEW

Data Source에 이 헤더를 직접 추가하지 마십시오. 전체 규약은 페이지 요청 컨텍스트와 백엔드 디버깅을 참고하십시오.

백엔드는 공유 계약으로 헤더를 해석하고 열거형 값으로 개발 미리보기를 판단해야 합니다.

SparkPageRequestContext context = SparkPageRequestHeaders.resolve(
environment, renderMode);
boolean developmentPreview = context != null
&& context.getEnvironment() == SparkPageEnvironment.DEV
&& context.getRenderMode() == SparkPageRenderMode.DESIGN_PREVIEW;

문자열, Map, 별도 쿼리 파라미터로 같은 판단을 중복 구현하지 마십시오. 미리보기 데이터에는 운영 비밀, 개인정보, 민감한 업무 데이터를 포함하면 안 됩니다.

5.2 전체 로딩과 지연 로딩​

작은 트리는 children에 전체 계층을 반환할 수 있습니다. 큰 트리는 Child Nodes URL과 Parent Parameter를 설정합니다. 리프가 아닌 노드를 펼치면 현재 Value가 parentId 같은 파라미터로 전달됩니다. 자식을 아직 불러오지 않았더라도 디렉터리는 leaf: false를 반환해야 합니다.

6. 검색​

정적 데이터나 이미 전체 로드된 데이터에는 로컬 검색을 사용할 수 있습니다. 지연 로딩에서는 Search URL과 검색 파라미터를 설정하여 아직 로드하지 않은 가지도 서버에서 찾습니다. 검색 결과에는 일치 노드의 상위 경로도 함께 반환해야 합니다.

7. 현재 노드와 이벤트 컨텍스트​

이벤트주요 데이터
onClicktreeNode, selectedTreeNode
onChecktreeCheck.checkedKeys, treeCheck.checkedNodes
onExpandtreeExpand.expandedKeys, treeExpand.node
onLoadtreeLoad.node, treeLoad.children
onDroptreeDrop.dragNode, treeDrop.dropNode, treeDrop.position

자주 쓰는 경로는 treeNode.value, treeNode.title, treeNode.isLeaf, treeNode.data.category입니다. Node Action은 현재 노드를 갱신하지만 Tree의 onClick을 함께 실행하지 않습니다.

8. 노드 작업 구성​

Node Actions 아래에 TreeNodeAction을 추가하고 제목, 공통 Icon 선택기, 트리거, 의미 색상, 표시 조건, 비활성 조건을 설정합니다. More 버튼과 데스크톱 오른쪽 클릭 메뉴는 같은 작업 목록을 사용합니다. 터치 기기에서는 길게 누르기 대신 More를 사용합니다.

PUT /spark/api/sample/navigation/tree/nodes/[[treeNode.value]]
POST /spark/api/sample/navigation/tree/nodes
DELETE /spark/api/sample/navigation/tree/nodes/[[treeNode.value]]

Interaction은 Tree가 아니라 Tree Node Action 자체에 설정하고 요청 성공 후에만 reloadTree를 호출합니다. 작업을 숨기는 것은 보안 경계가 아니므로 백엔드도 권한과 삭제 규칙을 검증해야 합니다.

9. 드래그 결과 저장​

Node Draggable을 켜면 화면에서 이동한 뒤 onDrop이 실행됩니다. treeDrop.position은 BEFORE, INSIDE, AFTER 중 하나입니다. 결과를 저장하려면 Send Request를 추가합니다.

POST /spark/api/sample/navigation/tree/reorder

dragId = [[treeDrop.dragNode.key]], dropId = [[treeDrop.dropNode.key]], position = [[treeDrop.position]]을 전송합니다. 요청이 실패하거나 중단되면 Tree는 이전 구조로 되돌립니다. 서버에서는 노드, 부모, 순환 참조, 권한, 동시 변경을 확인해야 합니다.

10. 노드 선택으로 본문 새로 고침​

정적 Tree의 onClick은 먼저 [[treeNode.name]]을 selectedCategory에 기록하고 ProTable의 reloadTable을 호출합니다. Tree02는 [[treeNode.value]]을 selectedNodeId, [[treeNode.data.category]]를 selectedCategory에 기록한 뒤 새로 고칩니다. 순서를 바꾸면 이전 선택 값으로 조회됩니다.

11. 컴포넌트 함수​

Function동작
reloadTree루트를 다시 요청하고 여전히 유효한 선택 유지
expandAll현재 로드된 모든 디렉터리 펼치기
collapseAll모든 디렉터리 접기

expandAll은 로드되지 않은 모든 가지를 자동 요청하지 않습니다.

12. Java 백엔드 예제​

Tree02 계약은 Java 17과 Spring Boot 4를 기준으로 합니다. 노드 DTO에는 최소한 id, label, parentId, leaf, children이 필요합니다. 쓰기 요청은 parentId, label, category, 순서 변경 요청은 dragId, dropId, position을 사용합니다.

용도메서드와 경로
루트, 자식, 검색GET /spark/api/sample/navigation/tree/nodes
자식 추가POST /spark/api/sample/navigation/tree/nodes
노드 편집PUT /spark/api/sample/navigation/tree/nodes/{nodeId}
노드 삭제DELETE /spark/api/sample/navigation/tree/nodes/{nodeId}
드래그 저장POST /spark/api/sample/navigation/tree/reorder

조회 서비스는 표준 컨텍스트에 따라 재현 가능한 미리보기 데이터 또는 대상 환경 Repository를 선택할 수 있습니다.

Map<String, NodeRecord> source = isDevelopmentPreview(context)
? designPreviewNodes : nodes;
String keyword = StringUtils.trimToNull(query);
if (keyword != null) {
return children(source, null).stream()
.map(root -> filterTree(source, root, keyword))
.filter(Objects::nonNull)
.toList();
}
return children(source, StringUtils.trimToNull(parentId)).stream()
.map(node -> toDto(source, node))
.toList();

추가, 편집, 삭제, 드래그 요청은 노드, 부모, 순환 참조, 권한을 서버에서 다시 검증해야 합니다. 사용자에게 보이는 오류는 LatticeRuntimeException(Message.code(...))와 서버 i18n 리소스를 사용하고 Service에서 문자열을 조합하지 마십시오. 부모와 형제 순서는 같은 트랜잭션에서 갱신합니다.

오른쪽 ProTable은 일반 조회 요청에 nodeId를 추가할 수 있습니다. 분류 노드는 category를 사용하고 업무 노드는 백엔드가 nodeId를 적절한 필터로 변환합니다.

13. 검증과 문제 해결​

데이터 로딩, 선택 연동, 노드 작업, 드래그 저장 순서로 검증하십시오. 먼저 Test로 루트와 필드 매핑을 확인하고, 펼치기와 검색, 여러 노드 선택, 쓰기 작업으로 진행합니다.

증상먼저 확인할 항목
Test 실패URL, 메서드, 연결, 인증, CORS
HTTP 200이지만 노드가 0개Response List Path가 배열을 가리키는지
개수는 보이지만 제목이 없음Title Field, Value Field, 실제 JSON 이름
디렉터리를 펼칠 수 없음Leaf Field가 잘못 true를 반환하는지
노드를 바꿔도 같은 목록Page Context 실행 순서와 Query Context 경로
새로 고치면 순서가 복원됨onDrop 파라미터와 백엔드 영속화