nv-grid
ag-grid-vue3를 기반으로 만든 Vue 3 컴포넌트로, 고급 그리드 기능과 사용자 컬럼 설정 기능을 통합하여 재사용 가능하게 만든 커스텀 그리드입니다.
props
| 이름 | 타입 | 기본값 | 설명 |
|---|---|---|---|
grid-class | String | ag-grid에 적용할 CSS 클래스 | |
grid-style | String | 'width: 100%; height:98%;' | ag-grid의 style 속성 |
pagination | Object | 페이징 처리 및 정렬용 객체 (nv-pagination 의 ref) | |
row-height | String | Number | 35 | ag-grid row 높이 |
keep-column-state | Boolean | false | 컬럼 상태(너비, 순서, 숨김)를 localStorage 에 저장/복원할지 여부 |
enable-cell-text-selection | Boolean | true | 셀 텍스트 드래그 선택 가능 여부 |
row-data | any | null | 그리드에 표시할 데이터 |
grid-options | Object | ag-grid의 native 옵션 객체 | |
grid-id | String | 'default' | 컬럼 상태 저장 식별자. 아래 주의 참고 |
mobile-actions | String | 'hide' | 좁은 화면에서 pinned="right" 컬럼 처리. 'hide' | 'keep'. 아래 참고 |
...ag-grid-props | 다양 | 선언되지 않은 속성은 $attrs 로 ag-grid 에 그대로 전달된다 |
grid-id 는 한 route 에 그리드가 둘 이상일 때만 지정한다
컬럼 상태 저장소는 이미 usePageConfig() 가 route 이름으로 네임스페이스하므로, 화면에 그리드가 하나면 기본값('default') 그대로 두면 된다.
단 모달 안의 그리드는 자기를 띄운 화면과 route 를 공유하므로 반드시 다른 grid-id 를 줘야 한다. (예: grid-id="user-search")
Emits
| 이름 | 설명 |
|---|---|
nv-grid-ready | 그리드 초기화 완료 시 호출. 인자로 params 객체 전달 |
nv-sorted | 컬럼 정렬 변경 시 호출. 현재 columnState 전달 |
nv-selection-changed | row 선택 상태 변경 시 호출 (사용자가 row 선택 시 등) |
nv-row-drag-end | row drag & drop 종료 시 호출. 전체 row data 배열 전달 |
nv-row-clicked | row 클릭 시 호출. (rowData, cellEvent) 전달 |
Expose (ref 로 접근)
| 이름 | 설명 |
|---|---|
api | ag-grid GridApi. 예: gridRef.value.api.getSelectedRows() |
openConfig() | 그리드 설정 모달 열기 (nv-list-body 의 톱니 버튼이 호출) |
loadingOverlay() | 로딩 오버레이 표시 |
noRowsOverlay() | "데이터 없음" 오버레이 표시 |
clearOverlay() | 오버레이 감추기 |
actionsCollapsible | 지금 화면 폭에서 Action 컬럼이 접히는 상태인지 |
actionsVisible | Action 컬럼을 펼쳐 놓았는지 |
toggleActions() | Action 컬럼 접기/펴기. 보통은 그리드가 그리는 손잡이로 충분하고, 화면이 자체 버튼을 둘 때만 쓴다 |
오버레이 3종은 nv-pagination 의 :grid prop 계약이다. <nv-pagination :grid="$refs.myGrid" /> 로 넘기면 조회 시작/종료에 맞춰 자동으로 호출된다.
정렬은 nv-pagination 과 연동된다
:pagination 으로 nv-pagination 의 ref 를 넘기면, 헤더 정렬이 서버 재조회로 이어진다.
사용자가 헤더 클릭
→ nv-grid 가 `{sortKey}:{asc|desc}` 문자열을 만든다
→ nv-pagination.applySort(sortBy) 호출
→ 쿼리의 sortBy 갱신(setQuerySort) + 재조회그리드는 query 를 직접 건드리지 않고 페이지네이션의 applySort() 만 호출한다. 정렬을 페이지네이션 내부 상태가 아니라 query 에 두는 이유는 excel-download 가 같은 query 를 서버로 보내기 때문 — 목록과 엑셀의 정렬을 맞추려면 쿼리에 있어야 한다.
nv-pagination 이 추가로 노출하는 것:
| 이름 | 설명 |
|---|---|
applySort(sortBy?, reloadNow = true) | 정렬을 쿼리에 반영하고(선택적으로) 재조회 |
currentSortBy() | 현재 쿼리에 걸린 정렬 문자열 |
초기 로드에서 목록 API 가 두 번 호출되지 않는 이유
gridReady 안에서 컬럼 상태를 복원하거나 기본 정렬을 적용하면 ag-grid 가 sortChanged 를 쏜다. 이때 재조회까지 하면 화면이 하는 첫 조회와 겹치므로, 초기화 구간에서는 applySort(sortBy, false) 로 쿼리만 갱신하고 재조회는 건너뛴다.
좁은 화면에서의 Action 컬럼
pinned="right" 로 고정한 Action 컬럼은 모바일에서 화면 폭의 상당 부분을 계속 차지해 좌우 스크롤을 방해합니다. 그래서 nv-grid 는 $q.screen.lt.md(< 1024px) 에서 Action 컬럼을 접고, 그리드 오른쪽 위에 손잡이 하나를 띄웁니다.
접힘: │ 보드 코드 │ 보드 명 │ … [‹] ← 손잡이 하나 (30px, 헤더 높이)
펼침: │ 보드 코드 │ … │ Action │ [›] ← Action 이 우측 고정으로 돌아온다- 상세는 행 클릭으로 엽니다 (위 Row Click 참고)
- 그 외 기능 버튼이 필요하면 손잡이를 누릅니다
- 펼치면 Action 컬럼이 우측 고정 상태로 돌아오므로 가로 스크롤 없이 바로 누를 수 있습니다
- 데스크톱 폭으로 돌아가면 손잡이가 사라지고 원래대로 돌아갑니다
keep-column-state로 저장한 컬럼 폭·순서에는 영향을 주지 않습니다
화면 단위로 끄려면 mobile-actions="keep" 을 줍니다.
<nv-grid mobile-actions="keep" ...> <!-- 모바일에서도 데스크톱과 동일하게 -->컬럼 선언 방식
nv-grid 는 기본 슬롯의 nv-grid-col VNode 를 직접 읽어 columnDefs 를 만든다 (use-grid-columns.ts). nv-grid-col 은 렌더되지 않는 순수 선언이다.
이 덕분에 컬럼 순서가 템플릿 순서로 보장되고, v-if / v-for 로 만든 동적 컬럼과 반응형 속성이 그대로 반영된다. 자세한 내용은 nv-grid-col 참고.
기본
<nv-grid ref="Grid"
:rowData="list"
:gridOptions="grid.option"
:keepColumnState="true"
:pagination="pagination">
<nv-grid-col headerName="번호" field="positionIdx" :width="50" :cellStyle="grid.style.end"></nv-grid-col>
<nv-grid-col headerName="제목" field="title" :width="150" :flex="1"></nv-grid-col>
</nv-grid>Row Click
고객은 우측 끝의 버튼보다 행을 그냥 눌러 상세를 여는 쪽을 선호합니다. @nv-row-clicked 를 달면 행 전체가 클릭 대상이 되고, 커서도 포인터로 바뀝니다.
행 클릭으로 치지 않는 경우
다음은 nv-grid 가 알아서 걸러냅니다. 화면에서 @click.stop 을 붙일 필요가 없습니다.
| 상황 | 비고 |
|---|---|
| 셀 안의 버튼·링크·입력요소 클릭 | 커스텀 요소는 .no-row-click 클래스를 주면 같이 제외됩니다 |
pinned="right" 컬럼 | Action 컬럼 관례라 자동 제외됩니다 |
| 행 드래그 핸들 | 드래그가 클릭으로 새지 않습니다 |
| 셀 텍스트를 드래그해 선택 | enable-cell-text-selection 을 쓸 때의 오작동을 막습니다 |
no-row-click 을 선언한 컬럼 | 아래 참고 |
@cell-click 을 선언한 컬럼 | 행 클릭 대신 그 핸들러가 실행됩니다 |
Action 컬럼과 행 클릭을 같이 써도 안전하다
@nv-row-clicked 와 Action 컬럼의 detail-button 을 함께 둬도 상세가 두 번 열리지 않습니다.
컬럼마다 다른 동작 주기
게시판 목록의 "회원아이디" 처럼 특정 컬럼만 다른 팝업을 열어야 할 때는 @cell-click 을 씁니다.
<nv-grid @nv-row-clicked="openDetail">
<nv-grid-col header-name="제목" field="title" />
<!-- 이 컬럼을 누르면 관리자 상세가 열린다 (행 클릭은 발생하지 않는다) -->
<nv-grid-col header-name="등록자" field="createdBy"
@cell-click="(row) => openAdmin(row.createdBy)">
<template #render="{params}">{{ params.data.createdUser.loginId }}</template>
</nv-grid-col>
<!-- 아무 일도 하지 않는다 -->
<nv-grid-col header-name="비고" field="memo" no-row-click />
</nv-grid>
<script setup lang="ts">
const openDetail = (data) => { /* 보드 상세 모달 */ };
const openAdmin = (adminId) => { /* 관리자 상세 모달 */ };
</script>@cell-click 컬럼에는 nv-cell-clickable 클래스가 자동으로 붙어 포인터 커서와 링크 색이 적용됩니다. 셀 안에 <a href> 를 직접 넣으면 위 표의 "셀 안의 링크" 규칙에 걸려 @cell-click 이 발동하지 않으므로 넣지 마세요.
실제 적용 예는 src/views/board/board-list.vue 의 등록자·수정자 컬럼을 참고하세요.
기본 사용
@nv-row-clicked을 이용하여 row 클릭시 데이터를 가져올 수 있습니다.
<nv-grid ref="UserGrid"
:row-data="list"
:grid-options="grid.option"
keep-column-state
@nv-row-clicked="rowClicked"
>...</nv-grid>
<script setup lang="ts">
const rowClicked = (data) => {
alert(JSON.stringify(data));
}
</script>row click으로 detail 모달 열기
<nv-grid ref="BoardGrid"
:row-data="list"
:grid-options="grid.option"
keep-column-state
:pagination="pagination"
@nv-row-clicked="openDetail"
>...</nv-grid>
<script setup lang="ts">
const openDetail = (data) => {
const {open,} = useModal({
component: CommonDetailModal,
attrs: {
label: '보드',
id: data.id,
modalId: 'board-detail',
comp: detailComponent,
callback: retrieve
}
});
open();
}
</script>Checkbox Select
gridRef.value.api.getSelectedRows()를 사용하여 선택한 row의 데이터를 가져올 수 있습니다.
<nv-grid ref="gridRef"
:row-data="list"
:grid-options="grid.option"
keep-column-state
@nv-row-clicked="rowClicked"
>
<nv-grid-col :width="50"
:header-checkbox-selection="true"
:header-checkbox-selection-filtered-only="true"
:checkbox-selection="true"/>
...
</nv-grid>
<script setup lang="ts">
const gridRef = useTemplateRef('gridRef');
const selectRowData = () => {
const selected = gridRef.value.api.getSelectedRows();
alert(JSON.stringify(selected));
}
</script>여러개의 grid를 한 페이지에서 사용할 때
grid-options는 서로 다른 객체를 사용해야 합니다.
<nv-grid ref="multiGird1"
:row-data="list"
:grid-options="gridOptions3"
keep-column-state
>
...
</nv-grid>
<nv-grid ref="multiGird2"
:row-data="list2"
:grid-options="gridOptions4"
keep-column-state
>
...
</nv-grid>
<script setup lang="ts">
const gridOptions3 = JSON.parse(JSON.stringify(grid.option));
const gridOptions4 = JSON.parse(JSON.stringify(grid.option));
</script>WARNING
하나의 gridOptions 객체를 여러 그리드에 공유할 경우, 다음과 같은 문제가 발생할 수 있습니다.
columnApi,api,columnState등이 겹쳐서 충돌- 그리드 상태(정렬, 필터, 컬럼 너비 등)가 서로 영향을 줌
- 일부 이벤트가 한 그리드에서 발생해도 다른 그리드로 전파