Grid (그리드)
Va.Grid — 데이터 그리드 컴포넌트
대량 데이터를 테이블 형태로 표시·편집·조회하는 핵심 그리드 컴포넌트입니다. 가상 스크롤, 정렬, 필터링, 그룹핑, 고정 컬럼, 셀 인라인 편집(FlashTag), 셀 내 상시 컴포넌트(Tag), 엑셀/CSV 다운로드/업로드, 다단 헤더, 요약 행, 다중 선택 등 실무에서 필요한 그리드 기능을 총망라합니다.
클래스 정보
Va.Grid extends Va.Component
tagName: 'grid'
isContainer: false
파일: va_grid.js
기본 사용법
{
ref: 'refGrid',
tagName: 'grid',
style: { flex: 1, minHeight: '400px', width: '100%' },
columns: [
{ key: 'itemId', title: '품목ID', width: 100, align: 'center' },
{ key: 'itemName', title: '품목명', fillRatio: 1 },
{ key: 'price', title: '가격', width: 120, align: 'right' }
],
onRowClick: 'onRowClick'
}
데이터 세팅 (mounted 후):
this.getRef('refGrid').setData([
{ itemId: '001', itemName: '노트북', price: 1500000 },
{ itemId: '002', itemName: '마우스', price: 25000 }
]);
핸들러는 반드시 문자열 참조 (JSON 원칙)
VanillaFront는 화면 정의를 JSON으로 유지하는 철학입니다. 모든 이벤트/렌더 핸들러는 문자열로 View 메서드 이름을 지정:
{ onRowClick: 'onRowClick', onEdit: 'onEditCell', onRender: 'onColorRender' }
인라인 function 사용 금지 — JSON 직렬화/에디터/저장 불가.
컬럼 정의 (columns)
각 컬럼 객체의 속성:
속성타입설명
| key | String | 데이터 필드명 (필수) |
| title | String | 헤더 텍스트 (필수) |
| width | Number | 고정 폭 (px) |
| fillRatio | Number | 가변 폭 비율 — 남는 공간 배분 |
| minWidth | Number | 최소 폭 (fillRatio와 조합) |
| align | String | 'left', 'center', 'right' |
| type | String | 'date', 'number', 'checkbox', 'rowNumber' |
| dateFormat | String | 예: 'mdy' (type: date일 때) |
| dateSeperator | String | 예: '/', '-' |
| sortable | Boolean | 헤더 클릭 정렬 |
| hidden | Boolean | 숨김 |
| fixed | Boolean | 좌측 고정 컬럼 (가로 스크롤에도 유지) |
| summaryType | String | 요약 계산 ('sum', 'avg', 'count' 등) |
| flashTag | Object | 셀 인라인 편집 컴포넌트 설정 |
| tag | Object | 셀 안 상시 표시 컴포넌트 (버튼 등) |
| onRender | String | 커스텀 셀 렌더 메서드 이름 |
| onEdit | String | 셀 편집 종료 콜백 |
주요 그리드 속성
속성타입기본값설명
| columns | Array | — | 컬럼 정의 (필수) |
| data | Array | [] | 초기 데이터 |
| size | String | 'medium' | xsmall, small, medium, large, xlarge |
| rowHeight | Number | 35 | 행 높이 (size로 자동 조정) |
| headerHeight | Number | 34 | 헤더 높이 |
| clickToSelect | Boolean | true | 클릭 시 자동 선택 |
| selectType | String | 'singleSelect' | 'singleSelect', 'multiSelect' |
| sortable | Boolean | — | 전체 컬럼 정렬 활성 |
| stripe | Boolean | — | 홀짝 행 배경 구분 |
| columnBorder | Boolean | true | 셀 구분선 |
| autoCommit | Boolean | false | 편집 즉시 커밋 |
| enableCopy | Boolean | false | Ctrl+C 셀 복사 |
| groupColumns | Array | — | 그룹핑 설정 |
| groupSummary | Boolean | — | 그룹별 요약 행 자동 |
| excelColumns | Array | — | 엑셀 다운로드 컬럼 정의 |
| csvColumns | Array | — | CSV 다운로드 컬럼 정의 |
주요 이벤트
이벤트콜백발생 시점
| onRowClick | 'onRowClick' | 행 클릭 |
| onRowDblClick | 'onRowDblClick' | 행 더블클릭 |
| onEdit | 'onEditCell' | 셀 편집 완료 |
주요 메서드
데이터 조작:
- setData(list) — 전체 교체
- getData() — 현재 데이터
- addData(row) — 마지막에 추가
- insertData(row, target) — target 위치에 삽입 (target: 선택 데이터 or 인덱스)
- modifyData(row) — 행 갱신
- removeData(row) — 행 삭제
선택:
- getSelectedData() — 선택 데이터 (single: 객체, multi: 배열)
- getSelectedIndex() — 선택 인덱스
다운로드/업로드:
- downloadExcel(filename) — 엑셀 파일
- downloadCsv() — CSV 파일
- uploadCsv(content) — CSV 문자열 로드
상태:
- isEditing (속성) — 편집 중이면 true
커스텀 셀 렌더 — onRender
핸들러는 View 메서드 이름 문자열로 지정.
검증된 시그니처 (DemoGridColumnType.js 기준):
onColorRender(grid, key, value, groupData, column, rowIndex, colIndex, row, col, cell, component)
인자의미
| grid | 그리드 컴포넌트 |
| key | 컬럼 key |
| value | 셀 값 |
| groupData | 행 데이터 (그룹 컨텍스트 포함) |
| column | 컬럼 정의 객체 |
| rowIndex, colIndex | 인덱스 |
| row | 행 DOM |
| col | 셀 DOM (스타일 직접 수정) |
| cell | 셀 wrapper DOM |
| component | 관련 컴포넌트 |
config:
columns: [
{ key: 'color', title: '색상', width: 100, onRender: 'onColorRender' },
{ key: 'amt', title: '금액', width: 100, align: 'right', onRender: 'onAmtRender' }
]
View 클래스:
// 색상 값을 셀 배경색으로
onColorRender(grid, key, value, groupData, column, rowIndex, colIndex, row, col, cell, component) {
col.style.backgroundColor = value;
}
// 금액 값에 따라 행 색상 변경
onAmtRender(grid, key, value, groupData, column, rowIndex, colIndex, row, col, cell, component) {
if (value > 500) {
row.style.backgroundColor = 'lightgreen';
row.style.color = 'red';
} else if (value < 0) {
row.style.backgroundColor = 'pink';
}
}
// 셀 padding 조정
onRenderItem(grid, key, value, groupData, column, rowIndex, colIndex, row, col, cell, component) {
cell.style.paddingLeft = '30px';
}
셀 인라인 편집 — flashTag
FlashTag는 셀 클릭 시 셀 위에 뜨는 편집 컴포넌트. 편집 후 다른 곳 클릭 시 값이 자동 반영.
지원 컴포넌트 (DemoGridFlashTag.js 기준):
- input — 텍스트 입력
- combobox — 드롭다운 선택
- datePicker — 날짜
- checkbox — 체크박스 (valueType으로 저장 형식 지정)
- slider — 슬라이더
기본 사용
columns: [
// 텍스트 입력
{
key: 'itemName', title: '품목명', width: 200,
flashTag: {
tagName: 'input',
style: { width: '100%' }
}
},
// 콤보박스 (View의 DataManager 참조)
{
key: 'itemId', title: '품목코드', width: 200,
flashTag: {
tagName: 'combobox',
key: 'itemId',
display: 'itemName',
mountDataManager: 'itemDataManager',
style: { width: '100%' }
},
onEdit: 'onEditItemId'
},
// 슬라이더
{
key: 'price', title: '가격', width: 200, align: 'center',
flashTag: {
tagName: 'slider',
min: 0, max: 100, step: 1,
style: { width: '100%' }
}
},
// 체크박스 (Y/N 값)
{
key: 'useYn', title: '사용여부', width: 100, align: 'center',
flashTag: {
tagName: 'checkbox',
valueType: 'YN'
}
},
// 날짜 선택
{
key: 'createDate', title: '등록일', width: 120,
flashTag: {
tagName: 'datePicker',
style: { width: '100%' }
}
}
]
FlashTag onEdit 콜백 확장
콤보 선택 시 다른 컬럼 값도 함께 세팅:
onEditItemId(grid, key, value, data, column, rowIndex, colIndex, row, col, cell, component) {
// component는 flashTag의 combobox 인스턴스
data['itemName'] = component.getDisplay();
this.getRef('refGrid').modifyData(data);
}
FlashTag에서 DataManager 연동
mounted() {
// View에 DataManager 준비
this.itemDataManager = new Va.DataManager();
this.itemDataManager.set([
{ itemId: '001', itemName: '품목1' },
{ itemId: '002', itemName: '품목2' }
]);
}
// columns 안에서 mountDataManager로 참조
{
key: 'itemId',
flashTag: {
tagName: 'combobox',
key: 'itemId',
display: 'itemName',
mountDataManager: 'itemDataManager'
}
}
셀 내 상시 컴포넌트 — tag
셀 안에 항상 표시되는 컴포넌트 (버튼 등):
{
key: 'reg', title: '등록', width: 100, align: 'center',
tag: {
tagName: 'button',
text: '버튼',
appearance: 'primary',
onClick: 'onButtonClick',
style: { width: '100%' }
}
}
핸들러:
onButtonClick(btn) {
// btn.getGrid()로 그리드 접근
let grid = btn.getGrid();
console.log(grid);
}
그룹핑 — groupColumns
특정 컬럼 값 기준으로 행 그룹화 (DemoGridGrouping.js 기준):
{
tagName: 'grid',
groupColumns: [
{ key: 'itemName', summary: false },
{ key: 'color', summary: false }
],
columns: [
{ key: 'itemName', title: '품명', width: 100 },
{ type: 'rowNumber', key: 'rowNumber', title: '#', width: 80, align: 'center' },
{ key: 'color', title: '색상', width: 100, align: 'center' },
{ key: 'amt', title: '금액', width: 200, align: 'right' },
{ key: 'createDate', title: '등록일', type: 'date', width: 140, align: 'center' }
],
onRowClick: 'onRowClick'
}
동일 itemName끼리 묶이고, 그 안에서 다시 color별로 묶임.
고정 컬럼 — fixed
columns: [
{ key: 'itemId', title: '품목ID', width: 100, fixed: true },
{ key: 'itemName', title: '품목명', width: 100, fixed: true },
{ key: 'price', title: '가격', width: 200 }, // 스크롤 대상
{ key: 'stock', title: '재고', width: 200 }
]
앞쪽 두 컬럼이 가로 스크롤해도 좌측 고정.
정렬 — Sorting
columns: [
{ key: 'itemId', title: 'ID', width: 100, sortable: true },
{ key: 'price', title: '가격', width: 120, sortable: true }
]
// 또는 전체 활성
{ tagName: 'grid', sortable: true, columns: [...] }
데이터 CRUD — 추가/삽입/변경/삭제
DemoGridFlashTag.js 검증 예제:
// 마지막에 추가
onAdd() {
this.getRef('refGrid').addData({
itemGroupId: '999', itemGroupName: '추가그룹',
itemId: '999', itemName: '추가품목'
});
}
// 선택 위치에 삽입
onInsert() {
let selectedData = this.getRef('refGrid').getSelectedData();
if (selectedData == null) {
new Va.Alert({ title: '확인', message: '먼저 행을 선택하십시오' }).show(this.getView());
return;
}
this.getRef('refGrid').insertData({
itemGroupId: '999', itemGroupName: '추가그룹',
itemId: '999', itemName: '추가품목'
}, selectedData);
}
// 선택 행 변경
onModify() {
let selectedData = this.getRef('refGrid').getSelectedData();
if (selectedData == null) {
alert('데이터를 선택해 주십시오.');
return;
}
selectedData.itemGroupName = '변경함';
this.getRef('refGrid').modifyData(selectedData);
}
// 선택 행 삭제
onRemove() {
let selectedData = this.getRef('refGrid').getSelectedData();
if (selectedData == null) {
alert('데이터를 선택해 주십시오.');
return;
}
this.getRef('refGrid').removeData(selectedData);
}
// 전체 데이터 조회 (편집 중 확인)
onGetAllData() {
if (this.getRef('refGrid').isEditing == true) {
new Va.Alert({
title: '확인',
message: '아직 편집중입니다. 편집을 종료하고 다시 시도하십시오'
}).show(this);
return;
}
console.log(this.getRef('refGrid').getData());
}
// 선택 데이터 조회
onGetSelectedData() {
console.log(this.getRef('refGrid').getSelectedData());
}
엑셀/CSV 다운로드·업로드
DemoGridExcelDownload.js 검증 예제:
{
tagName: 'grid',
excelColumns: [
{ name: 'itemId', text: '품목ID', type: 'text', width: '100px', align: 'center' },
{ name: 'itemName', text: '품목명', type: 'text', width: '150px' },
{ name: 'price', text: '가격', type: 'number', width: '100px', align: 'right' }
],
csvColumns: [
{ name: 'itemId', text: '품목ID' },
{ name: 'itemName', text: '품목명' },
{ name: 'price', text: '가격' }
]
}
핸들러:
// 엑셀 다운로드
onExcelDownload() {
this.getRef('refGrid').downloadExcel('엑셀 다운로드 테스트');
}
// CSV 다운로드
onCsvDownload() {
this.getRef('refGrid').downloadCsv();
}
// CSV 업로드 (파일 컴포넌트에서)
onCsvUpload(comp) {
let file = comp.fieldEl.files[0];
let reader = new FileReader();
reader.view = this;
reader.onload = function(e) {
this.view.getRef('refGrid').uploadCsv(e.target.result);
};
reader.readAsText(file);
}
셀 복사 — Ctrl+C
{ tagName: 'grid', enableCopy: true }
활성 시 선택 셀에서 Ctrl+C로 복사 (탭 구분자, 엑셀에 붙여넣기 가능).
다중 선택 + Shift
{
tagName: 'grid',
selectType: 'multiSelect',
columns: [
{ key: 'chk', title: '', width: 40, type: 'checkbox' },
// ...
]
}
조작:
- 일반 클릭: 해당 행만 선택
- Ctrl + 클릭: 개별 토글 (추가/해제)
- Shift + 클릭: 마지막 선택 위치부터 클릭 위치까지 범위 선택
- 드래그: 드래그 범위 선택
선택 데이터 조회:
onClickDelete() {
let selected = this.getRef('refGrid').getSelectedData(); // 배열
if (!selected || selected.length === 0) {
alert('선택된 항목이 없습니다.');
return;
}
selected.forEach(item => this.getRef('refGrid').removeData(item));
}
종합 예시 — CRUD + FlashTag + 다운로드
config() {
return {
tagName: 'page',
tags: [{
tagName: 'panel',
style: { flex: 1 },
tags: [{
tagName: 'actionBar',
layout: 'ds-flex fd-row ai-center gap-s',
tags: [
{ tagName: 'button', text: '추가', onClick: 'onAdd' },
{ tagName: 'button', text: '삽입', onClick: 'onInsert' },
{ tagName: 'button', text: '변경', onClick: 'onModify' },
{ tagName: 'button', text: '삭제', appearance: 'danger', onClick: 'onRemove' },
{ tagName: 'div', style: { flex: 1 } },
{ tagName: 'button', text: '엑셀', icon: 'ico_download', onClick: 'onExcelDownload' },
{ tagName: 'button', text: 'CSV', onClick: 'onCsvDownload' }
]
}, {
ref: 'refGrid',
tagName: 'grid',
style: { flex: 1, minHeight: '400px', width: '100%' },
autoCommit: false,
enableCopy: true,
selectType: 'multiSelect',
sortable: true,
stripe: true,
excelColumns: [
{ name: 'itemId', text: '품목ID', width: '100px' },
{ name: 'itemName', text: '품목명', width: '150px' },
{ name: 'price', text: '가격', type: 'number', width: '100px' }
],
columns: [
{ key: 'chk', title: '', width: 40, type: 'checkbox', fixed: true },
{ key: 'itemId', title: '품목ID', width: 100, fixed: true, sortable: true },
{
key: 'itemName', title: '품목명', width: 200, sortable: true,
flashTag: { tagName: 'input', style: { width: '100%' } }
},
{
key: 'itemGroupId', title: '그룹', width: 150,
flashTag: {
tagName: 'combobox',
key: 'itemGroupId',
display: 'itemGroupName',
mountDataManager: 'itemGroupDataManager'
}
},
{
key: 'price', title: '가격', width: 120, align: 'right',
flashTag: {
tagName: 'slider',
min: 0, max: 1000000, step: 1000,
style: { width: '100%' }
}
},
{
key: 'useYn', title: '사용', width: 80, align: 'center',
flashTag: { tagName: 'checkbox', valueType: 'YN' }
},
{
key: 'color', title: '색상', width: 100,
onRender: 'onColorRender'
},
{
key: 'action', title: '액션', width: 100, align: 'center',
tag: {
tagName: 'button',
text: '상세',
appearance: 'primary',
onClick: 'onClickDetail'
}
}
],
onRowClick: 'onRowClick',
onEdit: 'onEditCell'
}, {
ref: 'pagination',
tagName: 'pagination',
refGrid: 'refGrid',
service: 'getItemList'
}]
}]
};
}
// 색상 셀 배경 렌더
onColorRender(grid, key, value, groupData, column, rowIndex, colIndex, row, col, cell, component) {
col.style.backgroundColor = value;
}
주의사항
- 최상위에 명시적 크기 필요 — style: { flex: 1, minHeight: '...' } 없으면 안 그려짐
- columns 배열은 필수 — 없으면 초기화 실패
- 핸들러는 문자열 참조만 — 인라인 function은 JSON 원칙 위반
- onRender 시그니처 11개 인자 — DemoGridColumnType 기준
- 가상 스크롤 — 화면에 보이는 행만 DOM 렌더, 대량 데이터도 성능 유지
- fillRatio 사용 시 — 하나라도 있으면 flexGrid 모드
- autoCommit: false 기본 — commit() 명시 or autoCommit: true로 즉시 반영
- isEditing 확인 — getData 등 조회 전 편집 중인지 반드시 확인
- FlashTag의 mountDataManager는 View에 미리 준비 — mounted에서 DataManager 세팅
- selectType 'multiSelect' 시 getSelectedData는 배열, 'singleSelect'는 객체
- 엑셀/CSV 컬럼 정의는 별도 — 그리드 columns와 다른 구성 가능
대안 비교
상황추천
| 데이터 목록/편집 그리드 | Va.Grid |
| 트리 구조 그리드 | Va.TreeGrid |
| 동적 컬럼/피벗 | Va.DynamicGrid / Va.PivotGrid |
| 정적 격자 레이아웃 | Va.GridDiv |
| 단순 리스트 | Va.Div + 반복 |
참고
- API 문서 페이지: https://vanillafront.com/docs.html?theme=light#main#apigrid
- 연관: Va.Pagination(짝을 이루는 페이지네이션), Va.TreeGrid(트리 확장판)