발행일

jsTree 기본 사용법 — 플러그인·API 정리와 실무에서 걸린 지점

jsTree 기본 사용법

jsTree는 트리 구조를 웹에서 표현하는 jQuery 기반 플러그인입니다. 폴더 구조, 조직도, 카테고리 계층을 보여줄 때 씁니다. 표준데이터 관리 시스템의 트리 화면(메타 클래스·OID·인스턴스 트리)에 붙이면서 정리한 레퍼런스이고, 뒤에 실무에서 걸린 지점을 붙였습니다.

플러그인 한눈에 보기

플러그인설명
checkbox노드에 체크박스 표시
contextmenu우클릭 메뉴
dnd드래그 앤 드롭
search노드 검색
sort자동 정렬
state열림/선택 상태 기억 (localStorage)
types노드 타입별 아이콘 지정
unique형제 간 중복 이름 방지
wholerow행 전체 클릭·강조

기본 초기화와 플러그인 예제

// checkbox — 체크된 항목 가져오기
$('#tree').jstree({
  plugins: ['checkbox'],
  core: {
    data: [
      { text: '문서', children: [{ text: '계약서.docx' }, { text: '견적서.xlsx' }] },
      { text: '사진', children: [{ text: '여행사진.jpg' }] },
    ],
  },
});
const checked = $('#tree').jstree('get_checked', true);
// search — 입력과 연결
$('#tree-search').on('keyup', function () {
  $('#tree').jstree('search', $(this).val());
});
// contextmenu — 항목을 함수로 구성
$('#tree').jstree({
  plugins: ['contextmenu'],
  contextmenu: {
    items: (node) => ({
      create: { label: '노드 추가', action: () => {/* ... */} },
      delete: { label: '노드 삭제', action: () => $('#tree').jstree('delete_node', node) },
    }),
  },
});
// dnd — check_callback 없이는 이동이 전부 거부된다 (아래 실무 절 참고)
$('#tree').jstree({
  plugins: ['dnd'],
  core: { check_callback: true, data: [/* ... */] },
});
// types — 노드 타입별 아이콘
$('#tree').jstree({
  plugins: ['types'],
  types: {
    default: { icon: 'fa fa-folder' },
    file: { icon: 'fa fa-file' },
  },
  core: { data: [{ text: '문서', type: 'default', children: [{ text: '이력서.docx', type: 'file' }] }] },
});
// AJAX — 노드를 펼칠 때마다 서버에서 자식 로드
$('#tree').jstree({
  core: {
    data: {
      url: '/get/nodes/',
      data: (node) => ({ id: node.id }),  // 루트는 id가 '#' — 서버가 이 값을 알아야 한다
    },
  },
});

주요 API

const tree = $('#myTree').jstree(true);  // 인스턴스 얻기
함수설명
open_node(node) / close_node(node)노드 열기/닫기
create_node(parent, data)노드 생성
rename_node(node, name)이름 변경
delete_node(node)삭제
get_selected() / get_node(id)선택 목록 / 노드 객체
select_node(node) / deselect_all()선택 / 전체 해제

주요 이벤트

이벤트시점
ready.jstree초기화 완료
select_node.jstree / changed.jstree선택 / 선택 집합 변경
open_node.jstree / close_node.jstree펼침 / 접힘
rename_node.jstree / move_node.jstree이름 변경 / 이동(dnd 포함)
실무 요구사항조합
트리 보여주기 + 아이콘기본 + types
선택·검색·우클릭checkbox / search / contextmenu
순서 변경·상태 기억·중복 방지dnd / state / unique

실무에서 걸린 지점

1. check_callback의 진짜 의미 — 예제마다 true로 적혀 있는 이유

dnd 예제마다 core.check_callback: true가 들어 있는데, 처음엔 왜 필요한지 몰랐습니다. jsTree는 트리를 바꾸는 모든 조작(create/rename/delete/move)을 기본적으로 거부합니다. true는 "전부 허용"이라는 뜻이고요. 문제는 실무에서 true로 두면 어떤 노드든 어디로든 이동된다는 것 — 루트를 리프 밑으로 끌어넣는 것도 허용됩니다. 실전에서는 함수를 줘야 합니다:

check_callback: (operation, node, parent) => {
  if (operation === 'move_node') return parent.id !== '#';  // 루트 승격 금지 등 규칙
  return true;
},

예제의 true는 "데모가 되게 하는 값"이지 "실무 기본값"이 아니었습니다.

2. 루트의 id는 '#'이다

AJAX 로딩에서 data: (node) => ({ id: node.id })로 노드 id를 서버에 보내는데, 최초 로드 때 오는 id는 '#' 입니다. 서버가 이걸 모르면 루트 요청이 "id가 #인 노드"를 찾다 실패해요. 서버 쪽에 id == '#' → 최상위 목록 반환 분기가 반드시 필요합니다. 문서를 안 읽고 붙이면 첫 요청부터 걸리는 지점입니다.

3. 기본 contextmenu는 버리고 모달로 갔다

우클릭 메뉴로 노드 추가/수정을 붙여 보면, 기본 contextmenu의 인라인 rename(더블클릭 편집)만으로는 필드가 여러 개인 폼(이름 + 설명 + 타입 등)을 받을 수 없습니다. 결국 contextmenu는 "메뉴를 여는 트리거"로만 쓰고, 실제 입력은 커스텀 모달로 뺐어요. 그 과정은 별도 글로 정리했습니다.

4. state 플러그인은 데이터가 바뀌면 거짓말을 한다

열림/선택 상태를 localStorage에 저장해 주는 건 편한데, 트리 데이터가 서버에서 바뀌면 저장된 상태가 이미 없는 노드를 가리킬 수 있습니다. 관리 화면처럼 노드가 추가·삭제되는 트리에서는 상태 복원이 어긋난 것처럼 보이는 원인이 됐고, 데이터 갱신 시점에 state를 초기화하는 처리가 같이 필요했습니다.

5. 이 글의 search 예제는 그대로 쓰면 안 된다

위 예제는 keyup마다 search()를 호출합니다. 노드 수백 개까지는 티가 안 나는데, 검색은 전체 노드를 순회하므로 트리가 커지면 타이핑마다 전체 탐색이 됩니다. 디바운스를 끼우는 게 맞고, 매칭 노드만 보이게 하려면 search.show_only_matches: true 옵션도 함께 켜야 기대한 화면이 나옵니다. 예제를 정리할 때는 몰랐고, 실무 트리에 붙이면서 배웠습니다.


한계

jQuery 의존은 그대로 남습니다 — 이미 jQuery를 쓰는 서버 렌더링 프로젝트라 문제가 없었지만, React/Vue 프로젝트라면 시작부터 다른 선택지를 보는 게 맞습니다. 수천 노드 이상에서의 렌더 성능은 검증하지 않았습니다(실무 화면은 수백 노드 수준). 라이브러리 선택 배경과 1년 운영 후의 결말은 JSTree & VTree 사전 조사에 있습니다.