상세 컨텐츠

본문 제목

ag-grid 간단 사용법(사용법 쪼메 바뀜!)

자바스크립트

by e7e 2023. 6. 29. 21:14

본문

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

 

먼저 아래 샘플로 아두 쉽겡 시작해 보장.

agGrid_start.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Ag-Grid 시작 예제</title>
    <script src="https://cdn.jsdelivr.net/npm/ag-grid-community/dist/ag-grid-community.min.js"></script>
</head>

<body>
    <h1>지금은 뉴진스 시대</h1>
    <!-- 테마는  -->
    <div id="myGrid" style="height: 250px; width:600px" class="ag-theme-quartz-dark"></div>
    <script>
        // 샘플 ROW 데이타 정의, DB Select의 1줄 Row들의 모임 list가 될거시당
        const rowData = [
            { name: "민지", birth: "2004-05-07", nationality: "대한민국" },
            { name: "하니", birth: "2004-10-06", nationality: "호주|베트남" },
            { name: "다니엘", birth: "2005-04-11", nationality: "호주|한국" },
            { name: "해린", birth: "2006-05-15", nationality: "대한민국" },
            { name: "혜인", birth: "2008-04-21", nationality: "대한민국" }
        ];

        // 통합 설정 객체, 아주 많은 속성들이 제공됨(일단 몇개만)
        const gridOptions = {
            rowData: rowData,
            columnDefs: [                            // 컬럼 정의
                { field: "name", headerName: "이름" },
                { field: "birth", headerName: "생일" },
                { field: "nationality", headerName: "국적" }
            ],
            autoSizeStrategy: {                    // 자동사이즈정책
                type: 'fitGridWidth',              // 그리드넓이기준으로
                defaultMinWidth: 150               // 컬럼 최소사이즈
            },
            rowHeight: 45                          // row 높이지정
        };

        const gridDiv = document.querySelector('#myGrid');
        //  new agGrid.Grid(gridDiv, gridOptions);  // deprecated
        const gridApi = agGrid.createGrid(gridDiv, gridOptions);
    </script>
</body>

</html>

 

 

실행하면 아래처럼 보일 것이다. 컬럼(메이커,모델,가격 암거나)을 마우스로 잡아서 끌어보장.

[느낌과 기대가 같이 올 것이당.]

 

컬럼(Column) 속성에는 아래와 같은 것들을 쓸 수 있는데(여기선 columnDefs에 기술가능)

sortable, filter, resizable, editable, checkboxSelection,minWidth,flex 등등 문서 참조 필요

 columnDefs 설정 부분을 아래처럼 변경해서 결과를 확인하여 느낌 받도록 하장

columnDefs: [                            // 컬럼 정의
    { field: "name", headerName: "이름" ,sortable:true,filter:true,resizable:true},
    { field: "birth", headerName: "생일",sortable:true,filter:true,resizable:true },
    { field: "nationality", headerName: "국적",sortable:true,filter:true,resizable:true }
],

 실행결과를 확인해 보면 , 3개 컬럼 모두 정렬,검색,사이즈 조정 기능이 추가되었당(누네 보인당).

문제는 지금은 컬럼이 3개라서 그냥 각 컬럼에 썼지만, 억지로 컬럼이 100개 라면,

일일이 쓰는 것이 옴청 귀찮을 것이다. 그래서 나왔당.  일일이 컬럼에 기술하지 않고

아래처럼 gridOptions 안에 defaultColDef로 정의 하면 해당 속성은

모든 컬럼의 기본 속성으로 적용된당. 공통되지 않은 속성만 해당 컬럼에 별도 정의하면 된당.

const gridOptions = {
  columnDefs: columnDefs,
  rowData: rowData,
  defaultColDef: {
    sortable:true,
    filter:true,
    resizable:true,
    minWidth:100
  }
};

요정도로 만족하긴 힘들당. 리스트라면 당연 페이지 기능이 필요하당.

페이지 기능을 확인하기 위해서 Ajax로 불러 올 더미 데이타를 아래 처럼 정의하고 같은 폴더에 저장하장

dummyData.json

[
    {"name": "태한경", "alias": "숨겨진 고민", "strong": "다 이해됨", "weak": "근거가 없음"},
    {"name": "공석규", "alias": "보면 다 이해", "strong": "담배피며 여유찾기", "weak": "찾을 여유가 없음"},
    {"name": "노해건", "alias": "수학의 달인", "strong": "수학잘함", "weak": "산수못함"},
    {"name": "뢰슬우", "alias": "허리 사선", "strong": "눈에 안띔", "weak": "존재가 안보임"},
    {"name": "태래은", "alias": "일단 직진", "strong": "의외로 귀여움", "weak": "외모는 전혀 안 귀여움"},
    {"name": "옥화정", "alias": "경로당 느낌?", "strong": "나이로 대우받기", "weak": "신체나이는?"},
    {"name": "도중희", "alias": "외모는 진지", "strong": "눈빛은 남자", "weak": "말투는 여성여성"},
    {"name": "구산아", "alias": "알람 그게 뭐얌", "strong": "계속 잠", "weak": "깨워도 잠"},
    {"name": "좌하주", "alias": "부끄부끄 초딩", "strong": "챙겨주고 싶음", "weak": "맛집을 모름"},
    {"name": "나승회", "alias": "나름욜씨미 ", "strong": "친한척하기", "weak": "안 친하고 픔"},
    {"name": "은진온", "alias": "멋짱이", "strong": "파란차색깔", "weak": "본인은 안 푸름"},
    {"name": "창승해", "alias": "깨달은자", "strong": "남의 실수 잘 찾기", "weak": "본인 실수는 못 찾음"},
    {"name": "곡루이스", "alias": "배탈", "strong": "넘 열심", "weak": "취미 없음"},
    {"name": "상현덕", "alias": "오마이걸", "strong": "유아 좋아함", "weak": "본인 나이는 어쩔"},
    {"name": "교근찬", "alias": "술쎄보임", "strong": "좋은마우스", "weak": "힘없음"},
    {"name": "연건한", "alias": "그저 웃지요", "strong": "답변은 웃음으로", "weak": "입이 할일이 없음"},
    {"name": "홍길동", "alias": "전공자", "strong": "서류상 전공자", "weak": "서류빼면 비전공자"},
    {"name": "김방구", "alias": "엄마", "strong": "노력 산더미", "weak": "결과는 티끌"},
    {"name": "김채현", "alias": "비밀의 숲", "strong": "잘 감춤", "weak": "보여주면서 감춤"}
]

위 데이타를 가져다 쓰는 aggridTest.html을 아래처럼 맹글장

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Ag-Grid Basic Example</title>
    <style>
        #wrapper {
            width: 650px;
            height: 500px;
            margin: 50px auto;
            text-align: center;
        }

        #myGrid {
            height: 100%;
        }

        /* 헤더 텍스트 가운데 정렬 위함 */
        .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>
</head>

<body>
    <div id="wrapper">
        <h1>AG-Grid 꽤 잘 만들었어용</h1>
        <div id="myGrid" class="ag-theme-quartz-dark">
        </div>
        <button onclick="fExp()">내보내깅</button>
    </div>
    <script>
        const columnDefs = [
            { field: "name", headerName: "이름", sortable: true },
            { field: "alias", headerName: "별명", sortable: true, cellStyle: { textAlign: "left" } },
            { field: "strong", headerName: "장점", cellStyle: { textAlign: "left" } },
            { field: "weak", headerName: "단점", cellStyle: { textAlign: "left" } },
            { field: "birth", headerName: "생일", cellRenderer: MyComp }
        ];

        // 일단 빈 데이터 설정(초기값), 설정 안하면 주구창창 로딩중 메세지
        const rowData = [];

        // 설정 옵션: 중요, 위에 정의한 것들이 여기서 통합됨에 주목
        const gridOptions = {
            columnDefs: columnDefs,
            rowData: rowData,
            defaultColDef: {
                flex: 1,       // 자동으로 같은 사이즈롱
                filter: true,
                resizable: true,
                minWidth: 100,
                headerClass: "centered"

            },
            // 페이지 설정
            pagination: true,
            //paginationAutoPageSize:true,  // 요게 열려있으면 아래껀 무시당함!
            paginationPageSizeSelector: [5, 10, 20],  // 원하는 페이지 수 나열
            paginationPageSize: 10,    // 위에 꺼 중 하나를 선택
            onCellClicked: params => {
                console.log('cell was clicked', params);
                //alert("클릭하신 값은: " + params.value); //요게 있음 뭔가? 방해됨
            }
        };

        function getData() {
            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);
                    for (let i = 0; i < rslt.length; i++) {
                        rslt[i].birth = ranDate();
                    }

                    //gridOptions.api.setRowData(rslt);  // deprecated

                    gridApi.setGridOption('rowData', rslt);  //아래 라인과 동일한 의미
                    //gridApi.updateGridOptions({ rowData: rslt });
                }
            }
            xhr.send();
        }

        // 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('#myGrid');
            // new agGrid.Grid(gridDiv, gridOptions);  // deprecated
            gridApi = agGrid.createGrid(gridDiv, gridOptions);
            getData();  // 데이터 불러오깅
        });


        function MyComp() {
            return this;
        }

        // AG-GRID 문서에 나오는 커스텀 Cell Renderer
        MyComp.prototype.init = function (params) {
            this.eGui = document.createElement('div');
            this.eGui.innerHTML = `
            <input type="date" value="" class="myCal" style="border:0px;width:0px"> 
            <span class="mySpan">${params.value}</span> 
        `;

            this.myCal = this.eGui.querySelector('.myCal')
            this.mySpan = this.eGui.querySelector('.mySpan')
            this.mySpan.onclick = () => {
                this.myCal.showPicker();
            }
            this.myCal.onchange = () => {
                console.log("check", this.myCal.value);
                this.mySpan.innerHTML = this.myCal.value;
            }
        }
        MyComp.prototype.getGui = function () {
            console.log("check1");
            return this.eGui;
        }

        MyComp.prototype.refresh = function () {
            console.log("check2");
            return true;

        }

        MyComp.prototype.destroy = function () {
            console.log("check3");
        }

        function ranDate() {
            let now = new Date();
            now.setDate(now.getDate() - Math.floor(Math.random() * 90));

            let rYear = now.getFullYear();

            let rMonth = now.getMonth() + 1;
            if (rMonth < 10) rMonth = "0" + rMonth;

            let rDate = now.getDate();
            if (rDate < 10) rDate = "0" + rDate;

            return `${rYear}-${rMonth}-${rDate}`;
        }
    </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);

 

 

 

그랬다 뭔가 나사가 하나 빠진 듯  결론이 왜 그렇게 되었는지?

자연스럽지 못하고, 덜컹거리며 억지 결론에 억지로 종점을 찍는다.

난 아직도 완벽히는 이해 못한다. 난 왜 그때 사오정이 되었을까?

무얼 놓친 걸까? 운명인건가?  

마음 맞는 좋은 친구가 될 수 있었는데...  친구에게 친구가...

 

 

https://www.youtube.com/watch?v=g0qGCnXotlY 

 

관련글 더보기