https://www.ag-grid.com/javascript-data-grid/getting-started/
웹페이지를 만들다 보면 엑셀 모양의 리스트 페이지를 많이 만들게 되고 당연히 리스트를 만들면
페이지 나누기와 검색기능이 들어가야 하고 업무용 이라면 사용자는 원하는 부분을 파일로 받는 것을
넘어서 엑셀 시트 처럼 컬럼순서도 자유롭게 바꾸고,보고 싶은 컬럼과 보고 싶지 않은 컬럼 까지 자유롭게
선택할 수 있는 기능을 요구할거시당.
어쩌면 이미 엑셀등에 익숙한 사용자 입장에선 당연한 요구 지만 개발 입장에선 조금 난감하당.
이 경우 서버단과 클라이언트단이 이 책임을 나누어 가지는 것은 좋은 디자인이 아니고,
화면제어가 많이 필요한 상황에서 서버단이 책임을 지는 것 역시 좋은 디자인이 아닐 거시당.
클라이언트 단에서 서버쪽에서 데이타를 받아서 책임을 지는 것이 서버부하도 줄일 수 있고,
책임 소재도 한쪽에 있어(Single Responsibility) 좋은 디자인이지 않을까앙?.
문제는 이 모든 기능을 가진 웹 컴포넌트를 개발하자니 시간과 인력등 비용이 많이 소비 될 거시당.
이럴 때 사용하는 것이 Grid 컴포넌트라 불리는 놈이당.
jQGrid를 포함해 이런저런 많은 유/무료 그리드(Grid) 플러인이 있는데, 현재까지 찾아본 것 중에서는
ag-grid 플러그인이 개인적으로 기능 적으로나, 사용 편의성, 사용법 매뉴얼등 모두 가장 좋은
거스로 판단되어 여기서 억지로 소개.(실제 라이선스를 구매해 사용하는 좋은 회사도 꽤 됨!)
단지 일부 기능(Group, Pivot등)이 엔터프라이즈 라이센스로 유료라는 점이 맘속에
도둑놈 심보가 있는지 괜스레 아쉽고도 넘나 아쉽지만 무료 기능만으로도 훌륭하더이다.
만약 Enterprise 최신 버젼을 테스트해 보고 싶다면 아래 URL 사용(서비스를 위해선 라이센스 필요)
https://cdn.jsdelivr.net/npm/ag-grid-enterprise/dist/ag-grid-enterprise.min.js
먼저 요즘 뜨겁게 Hot한 CSS 게임 체인져 tailwindcss를 일부러 넣은
아래 샘플로 아두 아두 쉽겡 시작해 보장.
index.css 는 그냥 재미 참고용 (홈페이지에 있는 헤더 애니메이션 포함)
@keyframes animatedTextGradient { to { background-position: -200% center; } } .ag-header-cell-text { background: linear-gradient( 90deg, red, orange, yellow, green, blue, indigo, violet, red ); font-weight: 600; font-size: 20px; animation: animatedTextGradient 2.5s linear infinite; background-clip: text; -webkit-text-fill-color: transparent; background-size: 200% auto; } /* ag-grid 페이지 판넬 부분 스타일링 */ .ag-paging-panel { background-color: blueviolet; } .ag-picker-field-display { color: yellow; } .ag-paging-row-summary-panel { color: skyblue; } .ag-paging-page-summary-panel { color: greenyellow; }
agGrid를 쓰면 기본적으로 무엇이 좋아지는지 간략하게 보장.
agGrid_start.html
<!DOCTYPE html> <html lang="ko"> <head> <meta charset="UTF-8" /> <title>E7E Ag-Grid 시작 예제</title> <!-- 하는 김에 괘니 tailwindcss도 적용해 보깅 --> <script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script> <style type="text/tailwindcss"> .e7e { @apply text-center text-3xl my-1 pb-4 rounded bg-blue-500 px-4 py-2 font-bold text-white hover:bg-blue-600; } </style> <!-- tailwindcss 적용 끝, production mode에선 사용하지 말것--> <script src="https://cdn.jsdelivr.net/npm/ag-grid-community/dist/ag-grid-community.min.js"></script> <link rel="stylesheet" href="index.css" /> </head> <body> <h1 class="e7e">대덕 에스파 출동</h1> <!-- 테마는 quartz, balham, material,alpine 있음 --> <div id="e7eGrid" class="ag-theme-quartz-dark w-full h-100"></div> <script type="module"> // 샘플 ROW 데이타 정의, DB Select의 1줄 Row들의 모임 list가 될거시당 const rowData = [ { name: "경미닝", birth: "2000-03-03", nationality: "한국", special: "기획", }, { name: "선주닝", birth: "1999-09-09", nationality: "한국", special: "비동기", }, { name: "카리나", birth: "2000-04-11", nationality: "한국", special: "리액트", }, { name: "지젤", birth: "2000-10-30", nationality: "일본", special: "JSP", }, { name: "윈터", birth: "2001-01-01", nationality: "한국", special: "바닐라JS", }, { name: "닝닝", birth: "2002-10-23", nationality: "중국", special: "마이바티스", }, ]; // 통합 설정 객체, 아주 많은 속성들이 제공됨(일단 몇개만) const gridOptions = { theme: "legacy", rowData: rowData, columnDefs: [ // 컬럼 정의 { field: "name", headerName: "이름" }, { field: "birth", headerName: "생일" }, { field: "nationality", headerName: "국적" }, { field: "special", headerName: "담당" }, ], autoSizeStrategy: { // 자동사이즈정책 type: "fitGridWidth", // 그리드넓이기준으로 defaultMinWidth: 150, // 컬럼 최소사이즈 }, rowHeight: 45, // row 높이지정 }; const gridDiv = document.querySelector("#e7eGrid"); const gridApi = agGrid.createGrid(gridDiv, gridOptions); </script> </body> </html>
실행하면 아래처럼 보일 것이다. 컬럼(이름,생일,국적 담당)을 마우스로 잡아서 끌어보장.
[느낌과 기대가 같이 올 것이당.]
컬럼(Column) 속성에는 아래와 같은 것들을 쓸 수 있는데(여기선 columnDefs에 기술가능)
sortable, filter, resizable, editable, checkboxSelection,minWidth,flex 등등 문서 참조 필요
columnDefs 설정 부분을 아래처럼 변경해서 결과를 확인하여 느낌 받도록 하장.
[sortable 과 resizable은 디폴트 기능이당]
columnDefs: [ // 컬럼 정의 { field: "name", headerName: "이름" ,filter:true,floatingFilter: true, editable:true}, { field: "birth", headerName: "생일",filter:true }, { field: "nationality", headerName: "국적",filter:true } ],
실행결과를 확인해 보면 , 3개 컬럼 모두 정렬,검색,사이즈 조정 기능이 추가되었당(누네 보인당).
문제는 지금은 컬럼이 3개라서 그냥 각 컬럼에 썼지만, 억지로 컬럼이 100개 라면,
일일이 쓰는 것이 옴청 귀찮을 것이다. 그래서 나왔당. 일일이 컬럼에 기술하지 않고
아래처럼 gridOptions 안에 defaultColDef로 정의 하면 해당 속성은
모든 컬럼의 기본 속성으로 적용된당. 공통되지 않은 속성만 해당 컬럼에 별도 정의하면 된당.
const gridOptions = { columnDefs: columnDefs, rowData: rowData, defaultColDef: { filter:true, filterFloating:true, editable:true, minWidth:100 } };
요정도로 만족하긴 힘들당. 리스트라면 당연 페이지 기능이 필요하당.
페이지 기능을 확인하기 위해서 Ajax로 불러 올 더미 데이타를 아래 처럼 정의하고 같은 폴더에 저장하장
dummyData.json
[ { "name": "경미닝", "birth": "2000-03-03", "nationality": "한국", "special": "기획", "isMZ": true }, { "name": "선주닝", "birth": "1999-09-09", "nationality": "한국", "special": "비동기", "isMZ": true }, { "name": "카리나", "birth": "2000-04-11", "nationality": "한국", "special": "리액트", "isMZ": true }, { "name": "지젤", "birth": "2000-10-30", "nationality": "일본", "special": "JSP", "isMZ": true }, { "name": "윈터", "birth": "2001-01-01", "nationality": "한국", "special": "바닐라JS", "isMZ": true }, { "name": "닝닝", "birth": "2002-10-23", "nationality": "중국", "special": "마이바티스", "isMZ": true }, { "name": "누구닝", "birth": "1976-07-06", "nationality": "러시아", "special": "파이터", "isMZ": false }, { "name": "람보닝", "birth": "1982-08-02", "nationality": "미국", "special": "사격", "isMZ": false }, { "name": "슈퍼맨", "birth": "1978-07-08", "nationality": "호주", "special": "망토펴기", "isMZ": false }, { "name": "머하닝", "birth": "1988-08-08", "nationality": "중국", "special": "어리둥절", "isMZ": false }, { "name": "추우닝", "birth": "1999-09-09", "nationality": "한국", "special": "댄스폭격", "isMZ": true }, { "name": "그러닝", "birth": "2000-01-01", "nationality": "한국", "special": "오호공감", "isMZ": true }, { "name": "아프닝", "birth": "2002-09-03", "nationality": "중동", "special": "꽤병", "isMZ": true }, { "name": "들었닝", "birth": "2004-04-04", "nationality": "대만", "special": "엿듣기", "isMZ": true }, { "name": "달리닝", "birth": "1989-02-11", "nationality": "몽골", "special": "스피드", "isMZ": false }, { "name": "언제닝", "birth": "1977-07-07", "nationality": "필리핀", "special": "역사", "isMZ": false }, { "name": "믿겠닝", "birth": "2010-10-10", "nationality": "한국", "special": "거짓말", "isMZ": true }, { "name": "오해닝", "birth": "2011-11-21", "nationality": "한국", "special": "착각", "isMZ": true }, { "name": "버리닝", "birth": "1991-01-09", "nationality": "일본", "special": "분리수거", "isMZ": true }, { "name": "다우닝", "birth": "2005-05-09", "nationality": "한국", "special": "향기롭깅", "isMZ": true }, { "name": "두리번", "birth": "2004-05-07", "nationality": "한국", "special": "경계하기", "isMZ": true }, { "name": "느꼈닝", "birth": "2017-10-17", "nationality": "베트남", "special": "눈치보기", "isMZ": true }, { "name": "얼마닝", "birth": "2002-09-08", "nationality": "중국", "special": "흥정", "isMZ": true }, { "name": "취했닝", "birth": "1972-08-10", "nationality": "과테말라", "special": "과태료", "isMZ": false }, { "name": "가스닝", "birth": "1993-08-03", "nationality": "러시아", "special": "가스제거", "isMZ": true }, { "name": "바라닝", "birth": "1899-04-05", "nationality": "한국", "special": "기대하기", "isMZ": false }, { "name": "조으닝", "birth": "2004-04-01", "nationality": "한국", "special": "마구긍정", "isMZ": true }, { "name": "시르닝", "birth": "1961-02-02", "nationality": "한국", "special": "고집펴기", "isMZ": false }, { "name": "어디닝", "birth": "2001-03-09", "nationality": "홍콩", "special": "오리무중", "isMZ": true }, { "name": "끄치닝", "birth": "2004-08-08", "nationality": "슬로바키아", "special": "은폐엄폐", "isMZ": true } ]
위 데이터를 가져다 쓰는 agGrid_base.html을 아래처럼 맹글장.
[주석을 읽고, 그냥 가져다 쓰기 좋도록 만들었당. 꼭 읽어보장!]
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>Ag-Grid Basic Example</title> <script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script> <style type="text/tailwindcss"> .e7e { @apply text-center text-3xl my-1 pb-4 rounded bg-blue-500 px-4 py-2 font-bold text-white hover:bg-blue-600; } /* 헤더 텍스트 가운데 정렬 위함 */ .centered { .ag-header-cell-label { justify-content: center !important; } } </style> <script src="https://cdn.jsdelivr.net/npm/ag-grid-community/dist/ag-grid-community.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/1.8.4/axios.min.js"></script> <link rel="stylesheet" href="index.css" /> </head> <body> <h1 class="e7e">대덕 에스파 출동</h1> <!-- 테마는 quartz, balham, material,alpine 있음 --> <div id="e7eGrid" class="ag-theme-quartz-dark w-full h-100"></div> <button class="mt-3 rounded-[15%] border-2 bg-slate-700 text-white" onclick="fExp()" > 내보내깅 </button> <script type="module"> // locale 한국어 import import { AG_GRID_LOCALE_KR } from "https://cdn.jsdelivr.net/npm/@ag-grid-community/locale@33.2.1/+esm"; // 커스텀 CellRenderer 함수 만들기, 간단히 name과 isMz에 대해서만 function nameRenderer(objInfo) { //console.log("체킁:", objInfo); let color = objInfo.data.isMZ ? "yellow" : "#ED9247FF"; let weight = objInfo.data.isMZ ? "bolder" : "normal"; return ` <span style="color:${color};font-weight:${weight}" > <img style="display:inline-block" src="https://api.dicebear.com/9.x/big-smile/svg?seed=${objInfo.value}" width=25 height=25 alt="avatar" /> ${objInfo.value} </span>`; } function mzRenderer(objInfo) { return ` <input type=checkbox ${objInfo.value ? "checked" : ""} `; } // 컬럼 정의 const columnDefs = [ { field: "name", headerName: "이름", cellRenderer: nameRenderer, }, { field: "birth", headerName: "생일", valueFormatter: (objInfo) => { //console.log("체로롱:", typeof objInfo.value); if (typeof objInfo.value == "object") { return objInfo.value.toISOString().split("T")[0]; } return objInfo.value; }, cellEditor: "agDateCellEditor", }, { field: "nationality", headerName: "국적", cellEditor: "agSelectCellEditor", // 추가 cellEditorParams: { // 선택사항 내맘대로 추강 values: [ "한국", "미국", "중국", "일본", "슬로바키아", /* 생략 */ ], }, }, { field: "special", headerName: "담당", cellEditor: "agSelectCellEditor", // 추가 cellEditorParams: { // 선택사항 내맘대로 추강 values: [ "기획", "비동기", "리액트", "JSP", "바닐라JS", "마이바티스", "파이터", "사격", "망토펴기", "어리둥절", "댄스폭격", /* 생략 */ ], }, }, { field: "isMZ", headerName: "엠지?", valueFormatter: (objInfo) => { return objInfo.value; }, cellRenderer: mzRenderer, cellEditor: "agCheckboxCellEditor", }, ]; // 일단 빈 데이터 설정(초기값), 설정 안하면 주구창창 로딩중 메세지 const rowData = []; // 설정 옵션: 중요, 위에 정의한 것들이 여기서 통합됨에 주목 const gridOptions = { localeText: AG_GRID_LOCALE_KR, // 한국어로 theme: "legacy", // 요걸 주어야 div에 준 theme class명이 동작 columnDefs: columnDefs, rowData: rowData, defaultColDef: { flex: 1, // 자동으로 같은 사이즈롱 filter: true, floatingFilter: true, // 검색 입력창 별도 row editable: true, // 수정 가능 minWidth: 100, headerClass: "centered", }, rowHeight: 45, // row 높이지정 // 페이지 설정 pagination: true, //paginationAutoPageSize:true, // 요게 열려있으면 아래껀 무시당함! paginationPageSizeSelector: [5, 10, 20], // 원하는 페이지 수 나열 paginationPageSize: 10, // 위에 꺼 중 하나를 선택 onCellClicked: (params) => { console.log("cell was clicked", params); }, }; function getData() { // axios를 이용한 AJAX axios.get("dummyData.json").then((response) => { let rslt = response.data; console.log("rslt", rslt); gridApi.updateGridOptions({ rowData: rslt }); }); /* 바닐라 JS를 이용한 AJAX var xhr = new XMLHttpRequest(); xhr.open("get", "dummyData.json", true); xhr.onreadystatechange = function () { if (xhr.readyState == 4 && xhr.status == 200) { // 데이타 구조 억지 땜빵! let rslt = JSON.parse(xhr.responseText); console.log("체킁:", rslt); gridApi.setGridOption("rowData", rslt); //아래 라인과 동일한 의미 //gridApi.updateGridOptions({ rowData: rslt }); } }; xhr.send(); */ /* jQuery를 이용한 AJAX $.ajax({ method: "get", url: "dummyData.json", dataType: "json", success: (rslt) => { gridApi.updateGridOptions({ rowData: rslt }); }, }); */ /*fetch를 이용한 AJAX fetch("dummyData.json").then((response) => { response.json().then((rslt) => { gridApi.setGridOption("rowData", rslt); }); }); */ } // csv 내보내기, 별로 안 중요, 그냥 이대로 설정하고 값만 고치면 됨 function fExp() { var v_params = { suppressQuotes: "true", // none, true columnSeparator: " ", // default값이 ,(콤마) customHeader: "이름 축하요 국적 담당 엠지", // 헤더명 추가 출력 customFooter: "이거슨 푸터", // 데이타 아래에 footer추가 }; gridApi.exportDataAsCsv(v_params); } // 그리드 셋업 let gridApi; document.addEventListener("DOMContentLoaded", () => { const gridDiv = document.querySelector("#e7eGrid"); // new agGrid.Grid(gridDiv, gridOptions); // deprecated gridApi = agGrid.createGrid(gridDiv, gridOptions); getData(); // 데이터 불러오깅 // 컬럼 속성 동적으로 바꾸깅 const columnDefs = gridApi.getColumnDefs(); // 현재 컬럼정의 가져오깅(당근 배열임) for (let colDef of columnDefs) { if (colDef.headerName == "생일") { colDef.headerName = "축하요"; break; } } gridApi.setGridOption("columnDefs", columnDefs); }); </script> </body> </html>
실행결과는 아래와 같을 지어당.
페이지 설정부분과, 함수 getData안의 gridApi.setRowData에 주목하장.
gridApi는 agGrid.createGrid(..)에서 왔당. (최근 사용법이 바뀌어당!, 좋은 방향으로..)
script src의 community버젼의 js를 enterprise 버젼으로 바꾸고 마우스 오른쪽 버튼 눌러보깅!
AG-Grid는 그 명성에 맞게 문서 양이 꽤 방대하다, 사용패턴의 느낌이 아래 명령어에서 왔음 좋겠당.
// 생일 헤더명을 탄생일로 바꾸어 보깅 const columnDefs = gridApi.getColumnDefs(); // 현재 컬럼정의 가져오깅(당근 배열임) for (let colDef of columnDefs) { if (colDef.headerName == "생일") { colDef.headerName = "탄생일"; break; } } gridApi.setGridOption('columnDefs', columnDefs);
AG-Grid는 잘 설계 되었기 때문에 React에서 사용하는 것은 더 쉽당.
2025.03.12 - [React] - React(리액트) 티키타카 25 (AG-Grid 활용 1)
한번 해보는 것만으로 거저 많은 걸 얻을 수 있을거시당.
아!~~ 브라우져 console 로그에 보면 아래와 같은 메세지가 보일거당.
여기에서 쓴 tailwindcss 방식은 실제 서비스에 쓰지 말란 이야기당. 곧 공부용으로만 쓰장.
실제 서비스에 쓰기 위해선 설치하여 사용하는 방식을 사용해야만 한당.
그랬다 뭔가 나사가 하나 빠진 듯 결론이 왜 그렇게 되었는지?
자연스럽지 못하고, 덜컹거리며 억지 결론에 억지로 종점을 찍는다.
난 아직도 완벽히는 이해 못한다. 난 왜 그때 사오정이 되었을까?
무얼 놓친 걸까? 운명인건가?
내 건조한 삶의 외로움에 행님 이모티콘 하나로
보습 가득한 웃음의 비를 내렸던 금수저~~
마음 맞는 좋은 친구가 될 수 있었는데... 친구에게 친구가...
https://www.youtube.com/watch?v=g0qGCnXotlY
주민번호 생성기 (0) | 2025.03.24 |
---|---|
AJAX 파일(blob)다운로드 사용법 정리(axios,fetch,jquery, vanilla) (2) | 2025.03.20 |
노션에 등장 블록 에디터 (editor.js) (6) | 2025.03.12 |
네이버 오늘 날씨 한번 가져와 보깅깅 (2) | 2025.03.12 |
faker-js 이용 가짜(Dummy) 데이터 마궁 맹글깅(쪼메 바뀜) (0) | 2024.10.24 |