트리 플러그인
트리 · treePlugin
부모 키를 가진 평탄한 행 배열을 계층으로 표시합니다. 5단계 예제에서 연결선·초기 펼침·접기·말단 검색 시 조상 유지를 확인합니다.
설치 함수: treePlugin(config) · 해제 이름: tree
설정 속성 · 기본값
필수값은 해당 입력에서 반드시 제공하는 값입니다. 조건부 필수값은 설명에 적힌 기능을 사용할 때 필요합니다. 표시가 없는 옵션은 생략할 수 있으며 기본값을 사용합니다. 빈 문자열·누락과 0·false를 혼동하지 마세요.
| 설정 | 기본값 | 설명 |
|---|---|---|
field | 첫 컬럼 | 트리 들여쓰기와 펼침 버튼을 표시할 컬럼 field입니다. |
parentField | parentId | 각 행의 부모 rowKey가 들어 있는 데이터 필드입니다. 루트는 null 또는 부모 없음입니다. |
showLines | false | 들여쓰기 영역에 계층 연결선을 표시합니다. |
defaultExpanded | false | true는 이미 적재된 부모를 처음 펼치고 false는 접습니다. 이후 사용자가 접은 상태를 유지합니다. |
expanded | 생략 | 처음 펼칠 행 키 배열. 지정하면 defaultExpanded보다 우선합니다. []는 처음 모두 접힘입니다. |
hasChildrenField | hasChildren | 자식이 아직 적재되지 않았더라도 이 필드가 true이면 펼침 버튼을 표시합니다. |
loadChildren | 없음 | loadChildren(row,{signal})이 부모 키를 포함하는 자식 행 배열을 반환합니다. 처음 수동으로 펼칠 때 조회합니다. |
사용 순서
- 모든 행에 고유 rowKey를 넣고 부모 행 키를 parentField에 지정합니다.
- treePlugin(config) 또는 plugins:[treePlugin()]과 options.tree로 구성합니다. 같은 속성은 플러그인 config가 우선합니다.
- 초기 상태는 defaultExpanded 또는 expanded로 정합니다. lazy 행은 hasChildrenField와 loadChildren을 함께 구성합니다.
연결 API
| 호출 | 동작 |
|---|---|
grid.toggleTree(id) | 해당 행의 펼침 상태를 전환합니다. lazy 조회를 포함하며 Promise를 반환합니다. 필수값 |
grid.setTreeLines(boolean) | 연결선을 켜거나 끄고 다시 표시합니다. 필수값 |
설치 · 해제 · Vue 3 수명주기
생성 시 plugins에 등록하거나, 아직 설치하지 않은 인스턴스에서 grid.install을 호출합니다. 같은 name은 중복 설치할 수 없습니다. 설정에 쓰는 데이터·함수는 아래 전체 예제 소스를 참고하세요.
grid.install(treePlugin({ field: 'name', showLines: true, defaultExpanded: true }));
grid.uninstall('tree'); // 플러그인 해제
// Vue 3 컴포넌트가 종료될 때
onBeforeUnmount(() => grid.destroy());미완료 자식 조회를 취소하고 트리 변환·셀 렌더러를 제거합니다. role을 grid로 복원하고 설치 전의 toggleTree/setTreeLines를 복원합니다.
관련 이벤트
- render
- error
- datachange
이벤트 구독은 grid.on(name, (detail, grid) => {})를 사용합니다. 해제 함수 또는 grid.off로 구독을 정리하세요.
실행 예제 · 실제 원본
아래 소스는 examples/features/tree.js와 Vue 원본입니다. 해당 예제 경로를 기준으로 import하며, 행 데이터는 별도 파일을 함께 사용합니다.
JavaScript 예제 실행 ↗ · Vue 3 예제 실행 ↗ · 행 데이터 원본
JavaScript 전체 소스 · tree.js
import { ZixoGrid, treePlugin } from '../../src/index.js';
import { rows } from './tree.data.js';
import { createToolbar } from '../feature-controls.js';
const host = document.querySelector('#grid'),
toolbar = document.querySelector('#toolbar'),
report = document.querySelector('#report');
const { button, select } = createToolbar(toolbar, report);
const options = {
rowKey: 'id',
height: 360,
rows: rows,
columns: [{ field: 'name', title: '업무', width: 360 }],
plugins: [
treePlugin({ field: 'name', showLines: true, defaultExpanded: true }),
],
};
let grid = new ZixoGrid(host, options);
window.addEventListener('pagehide', () => grid.destroy(), { once: true });
grid.on(
'validationerror',
(detail) => (report.textContent = detail.error.message),
);
select('처음 표시', ['펼침', '접힘'], (value) => {
grid.destroy();
grid = new ZixoGrid(host, {
...options,
plugins: [
treePlugin({
field: 'name',
showLines: true,
defaultExpanded: value === '펼침',
}),
],
});
});
button('연결선 켜기', () => grid.setTreeLines(true));
button('연결선 끄기', () => grid.setTreeLines(false));
button('5단계 검색', () => grid.setSearch('5단계'));
button('검색 해제', () => grid.setSearch(''));
report.textContent =
'100행을 5단계 계층으로 구성합니다. defaultExpanded: true는 처음 펼침, false는 처음 접힘이며 기본값은 false입니다. 5단계 검색 시 1~4단계 조상을 유지합니다. parentId로 부모 행의 키를 참조합니다.';
Vue 3 전체 소스 · vue-tree.vue
<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue';
import { ZixoGrid, treePlugin } from '../../src/index.js';
import { rows } from './tree.data.js';
// Instance and DOM belong to this component, never to window or a global ID.
const gridHost = ref(null);
const message = ref('');
const controls = ref([]);
let grid;
let alive = true;
async function run(action, value) {
try {
const result = await action(value);
if (alive && result !== undefined)
message.value =
typeof result === 'string' ? result : JSON.stringify(result, null, 2);
} catch (error) {
if (alive) message.value = error.message;
}
}
const button = (label, action) =>
controls.value.push({ kind: 'button', label, action });
const select = (label, values, action) =>
controls.value.push({
kind: 'select',
label,
values,
value: values[0],
action,
});
onMounted(() => {
const options = {
rowKey: 'id',
height: 360,
rows: rows,
columns: [{ field: 'name', title: '업무', width: 360 }],
plugins: [
treePlugin({ field: 'name', showLines: true, defaultExpanded: true }),
],
};
grid = new ZixoGrid(gridHost.value, options);
grid.on(
'validationerror',
(detail) => (message.value = detail.error.message),
);
select('처음 표시', ['펼침', '접힘'], (value) => {
grid.destroy();
grid = new ZixoGrid(gridHost.value, {
...options,
plugins: [
treePlugin({
field: 'name',
showLines: true,
defaultExpanded: value === '펼침',
}),
],
});
});
button('연결선 켜기', () => grid.setTreeLines(true));
button('연결선 끄기', () => grid.setTreeLines(false));
button('5단계 검색', () => grid.setSearch('5단계'));
button('검색 해제', () => grid.setSearch(''));
message.value =
'100행을 5단계 계층으로 구성합니다. defaultExpanded: true는 처음 펼침, false는 처음 접힘이며 기본값은 false입니다. 5단계 검색 시 1~4단계 조상을 유지합니다. parentId로 부모 행의 키를 참조합니다.';
});
onBeforeUnmount(() => {
alive = false;
grid?.destroy(); // Cancels grid requests, removes events and releases plugins.
grid = undefined;
});
defineExpose({ getGrid: () => grid });
</script>
<template>
<main>
<h1>트리 · 연결선 · 조상 필터</h1>
<div class="toolbar">
<template v-for="(control, index) in controls" :key="index">
<button
v-if="control.kind === 'button'"
type="button"
@click="run(control.action)"
>
{{ control.label }}
</button>
<label v-else
>{{ control.label }}
<select
v-model="control.value"
:aria-label="control.label"
@change="run(control.action, control.value)"
>
<option v-for="value in control.values" :key="value" :value="value">
{{ value }}
</option>
</select>
</label>
</template>
</div>
<div ref="gridHost" class="vue-grid-host"></div>
<div class="notes result" aria-live="polite">{{ message }}</div>
</main>
</template>
제약 사항 · 주의할 동작
- tree와 grouping은 같은 인스턴스에 함께 설치할 수 없습니다.
- 미조회 lazy 자식은 defaultExpanded:true만으로 자동 조회하지 않습니다.
- 여러 lazy 요청이 공유 로딩·오류·재시도 상태를 사용하므로 동시 요청 상태 표시에 제한이 있습니다.
- 순환 부모 참조는 허용되지 않습니다. 검색에 맞는 행의 조상을 유지하되 사용자가 접은 상태는 유지합니다.