Skip to content

nv-grid

ag-grid-vue3를 기반으로 만든 Vue 3 컴포넌트로, 고급 그리드 기능과 사용자 컬럼 설정 기능을 통합하여 재사용 가능하게 만든 커스텀 그리드입니다.

props

이름타입기본값설명
grid-classStringag-grid에 적용할 CSS 클래스
grid-styleString'width: 100%; height:98%;'ag-grid의 style 속성
paginationObject페이징 처리 및 정렬용 객체 (nv-pagination 의 ref)
row-heightString | Number35ag-grid row 높이
keep-column-stateBooleanfalse컬럼 상태(너비, 순서, 숨김)를 localStorage 에 저장/복원할지 여부
enable-cell-text-selectionBooleantrue셀 텍스트 드래그 선택 가능 여부
row-dataanynull그리드에 표시할 데이터
grid-optionsObjectag-grid의 native 옵션 객체
grid-idString'default'컬럼 상태 저장 식별자. 아래 주의 참고
mobile-actionsString'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-changedrow 선택 상태 변경 시 호출 (사용자가 row 선택 시 등)
nv-row-drag-endrow drag & drop 종료 시 호출. 전체 row data 배열 전달
nv-row-clickedrow 클릭 시 호출. (rowData, cellEvent) 전달

Expose (ref 로 접근)

이름설명
apiag-grid GridApi. 예: gridRef.value.api.getSelectedRows()
openConfig()그리드 설정 모달 열기 (nv-list-body 의 톱니 버튼이 호출)
loadingOverlay()로딩 오버레이 표시
noRowsOverlay()"데이터 없음" 오버레이 표시
clearOverlay()오버레이 감추기
actionsCollapsible지금 화면 폭에서 Action 컬럼이 접히는 상태인지
actionsVisibleAction 컬럼을 펼쳐 놓았는지
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" 을 줍니다.

html
<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 참고.

기본

html
  <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 을 씁니다.

vue
<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 클릭시 데이터를 가져올 수 있습니다.
vue
<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 모달 열기

vue
<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의 데이터를 가져올 수 있습니다.
vue
<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는 서로 다른 객체를 사용해야 합니다.
vue
<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 등이 겹쳐서 충돌
  • 그리드 상태(정렬, 필터, 컬럼 너비 등)가 서로 영향을 줌
  • 일부 이벤트가 한 그리드에서 발생해도 다른 그리드로 전파