티스토리 뷰

개발환경 : nodejs / mariaDB / JS / jqgrid 

jqgrid : 그리드 조회, 수정, 추가, 삭제 전부 구현하기(1) - 그리드 조회
jqgrid : 그리드 조회, 수정, 추가, 삭제 전부 구현하기(2) - 그리드 수정

조회 / 수정을 했으니 추가를 구현 해 보자 !

처음에 그리드 조회 화면에서 추가버튼을 누르면 아래에 새로운 row 가 생기고 inline 형식으로 입력 후 저장버튼을 누르면 데이터가 저장되는 형식으로 만들려고 했으나 현재 셀 수정시 remote 형식을 사용하고 있기 때문에 row 추가할때 한셀 한셀이 입력되게 되는 문제가 있어서 추가할때 inline으로 바꿔주고 또 수정할땐 remote로 바꿔주고 할 수 없어서 추가 버튼 누르면 모달팝업이 떠서 입력 후 저장하는 방식으로 구현하기로 하였다. 

일단 먼저 html 부분부터 수정하자. 첫번째 포스팅에서 html에 추가, 삭제 버튼을 만들어 줬는데 추가 버튼을 클릭했을 때 모달팝업이 뜰 수 있도록 이벤트를 걸어주자

    <div class="main-panel">
        <div class="content-wrapper">
           
            <div class="row col-12 d-flex align-items-center justify-content-end">
              <button class="btn onclick="javascript:gridFunc.addRow();">추가</button>
              <button class="btn">삭제</button>
            </div>
            
            <div class="row ">
              <div class="col-12 grid-margin">
                <div class="card">
                  <div class="card-body">
                    
                    <div class="table-responsive">
                        <table id="dataGrid"></table>
                    </div>
           
                  </div>
                </div>
              </div>
            </div>

        </div>
    </div>

 

그리고 스크립트를 작성하자 !
버튼을 클릭하면 gridFunc.addRow 함수를 타게 되는데  editGridRow 메소드를 이용해서 모달 팝업을 띄우게 된다.

$("#dataGrid").jqGrid('editGridRow', "new", options );

여기에서 new 는 새로운 데이터를 생성한다는 의미이고 options에 이제 띄워질 모달팝업의 속성들과 이벤트를 넣어줄 수 있다.
그래서 options를 작성해보자

const gridFunc = {
  addRow : function(){
    const options = {
        modal : true,
        width : 400,
        url : '/data/menumast/createGridData',
        mtype : 'POST',
        addCaption : "MENUMAST 추가",
        closeAfterAdd : true,
        recreateForm : true, //대화상자가 활성화될 때 마다 form이 다시 생성된다 
        reloadAfterSubmit:true //서버 ajax후 그리드 데이터 다시 로드 
    }
    $("#dataGrid").jqGrid('editGridRow', "new", options );
  }
}

modal : true 로 해야 모달이 뜬다
url : 모달 팝업에서 데이터 입력 후 전송 눌르면 전송되는 url 
closeAfterAdd : true Add 후 모달이 자동으로 닫힌다
recreateForm : true 모달이 생성될 때 마다 form이 새로고침된다.(안그러면 데이터 남아있음)
reloadAfterSubmit : true 추가 후 reload 자동으로 해줌

이게 모달팝업이 띄워졌을때 기본적으로 설정해준 속성들이다. 
이제 모달팝업이 떴을 때 입력가능한 데이터들은 기본적으로 그리드에 띄운 colName들인데. 이걸 colModel 속성에서 정의해 줘야 한다. 

예를 들면 처음에 colModel 을 선언할 때에 아래와 같이 한다고 하면 여기 속성에다가  editable : true 를 넣어주면 팝업 띄웠을 때 수정 가능하도록 나오게 된다. 그런데 이 속성을 처음부터 주면 문제가 생기는데 (나의 경우에) editable : true를 주면 모달팝업 뿐 아니라 그리드 내에서 클릭시 바로 수정 가능한 Input 박스를 띄워주게 된다. 

var gridColModel = [ 
    	  {name:'chkbox', index:'chkbox', align:'center', width:'3%', edittype:'checkbox', editoptions:{value:'Y:N'}, formatter:'checkbox', formatoptions: {disabled: false}}
          ]

그리드 수정시에 봤듯 editable 속성을 처음에 주지 않고 있다가. 더블클릭시 클릭한 colName의 속성을 editable : true 로 바꿔준것을 기억할 수 있을 것이다. 

그래서 현재 editable 속성이 없기 때문에 모달 팝업을 띄우면 입력 요소들이 나오지 않는다.
Form 데이터들을 초기화 하기 전에 호출되는 beforeInitData 메소드를 사용해서 editable : true 속성을 주자.

beforeInitData : function(formid){ //form 데이터를 초기화 하기 전에 호출 
            $("#dataGrid").setColProp("group_code", {editable : true});
}

이런식으로 입력값을 받아야 하는 모든 컬럼에 대하여 setColProp를 해주어야 하는데
나의 경우에는 7개의 컬럼 모두의 값을 받아와야 하고 또 모달이 닫힌 후에는 editable 속성을 false로 또 바꿔줘야 하는데 그때마다 7개의 컬럼을 모두 바꾸는게 비효율적이라고 생각해서 함수를 따로 빼서 boolean 값을 전달하게 하였다.

function setColData(param){
    const gridColData = ["group_code", "prog_id", "depth_lv", "prog_name", "prog_url", "order_no", "view_yn"];
    for(var idx in gridColData){
        $("#dataGrid").setColProp(gridColData[idx], {editable : param});
    }
}

const gridFunc = {
  addRow : function(){
    const options = {
        modal : true,
        width : 400,
        url : '/data/menumast/createGridData',
        mtype : 'POST',
        addCaption : "MENUMAST 추가",
        closeAfterAdd : true,
        recreateForm : true, //대화상자가 활성화될 때 마다 form이 다시 생성된다 
        reloadAfterSubmit:true, //서버 ajax후 그리드 데이터 다시 로드 
        beforeInitData : function(formid){ //form 데이터를 초기화 하기 전에 호출 
            setColData(true);
        }
    }
    $("#dataGrid").jqGrid('editGridRow', "new", options );
  }
}

setColData(true) 를 주어 컬럼들에 editable : true 속성을 주면 팝업이 생성되면서 입력할 수 있는 컬럼들이 보이게 된다.

 

그럼 이제 submit 전에 입력 값을 validation 할 수 있는 beforeSubmit과
완료 후 성공/실패를 반환하는 afterComplete, 취소버튼을 눌렀을 때 닫히는 메소드인 onClose를 구현하자

beforeSubmit : function(postdata, formid){
            /* 입력 값 validation*/
            if(postdata.group_code.length>2){
                return [false, "그룹코드의 입력값은 두자리 입니다."];
            }
            if(postdata.prog_id.length>10){
                return [false, "프로그램 아이디는 10자리를 넘을 수 없습니다."];
            }
            if(postdata.prog_name.length>50){
                return [false, "프로그램명은 50자리를 넘을 수 없습니다."]; 
            }
            if(postdata.prog_url.length>50){
                return [false, "프로그램 URL은 50자리를 넘을 수 없습니다."]; 
            }

            /*group_code === prog_id (_)이전 값 */
            const progid = postdata.prog_id.split('_'); 
            console.log(progid);
            if(postdata.group_code!==progid[0]){
                return [false, "그룹코드와 프로그램 ID의 코드는 동일해야 합니다."]; 
            }

            const rowData = $("#dataGrid").getRowData();
            /*prog_id 중복확인 */
            for(const idx in rowData){
                if(rowData[idx].prog_id===postdata.prog_id){
                    return [false, "프로그램 ID는 중복값을 가질 수 없습니다."];
                }
            }
            
            // const progIdArr = [];
            // for(const idx in rowData){
            //     progIdArr.push(rowData[idx].prog_id);
            // }
            // let progIdCnt = progIdArr.reduce((cnt, element)=>cnt +(postdata.prog_id=== element),0);
            // if(progIdCnt>=1){
            //     return [false, "프로그램 ID는 중복값을 가질 수 없습니다."]; 
            // }

            /*depth_lv===1일때 존재하는 상위메뉴 체크 */
            if(postdata.depth_lv==='1'){
                // const depthLvArr = [];
                for(const key in rowData){
                    if(rowData[key].group_code===postdata.group_code && rowData[key].depth_lv===postdata.depth_lv){
                        return [false, "상위 메뉴가 이미 존재합니다. 메뉴레벨을 변경 해 주세요."]; 
                    }
                }
            }

            return [true, ''];
        },
        afterComplete : function(response, postdata, formid){ 
            setColData(false);
            if(response.responseText==='success'){
              alert("등록되었습니다 !");
            }else{
              alert("에러 발생 !\nerrMsg : "+response.responseText);
            }
        },
        onClose : function(){
            setColData(false);
        }

 

beforeSubmit : submit 전에 input 값들의 validation 체크를 할 수 있다. 기본적인 속성에서 validation 체크를 해주는데 colModel 부분에 editrules를 추가를 해 주면 된다. required: true (필수 입력값) number:true (숫자만 입력 가능) 등등 속성이 많다..

    var gridColModel = [ 
    	  {name:'chkbox', index:'chkbox', align:'center', width:'3%', edittype:'checkbox', editoptions:{value:'Y:N'}, formatter:'checkbox', formatoptions: {disabled: false}},
    	  {name:'group_code', index:'group_code', align:'center', width:'7%',edittype:'text', editrules:{required:true}},
       	  {name:'prog_id', index:'prog_id', align:'center', width:'10%', edittype:'text', editrules:{required:true}, key:true},
          {name:'depth_lv', index:'depth_lv', align:'center', width:'5%',  edittype:'text', editrules:{required:true, number:true, integer:true, maxValue:255}},
          {name:'prog_name', index:'prog_name', align:'left', width:'25%',  edittype:'text', editrules:{required:true}},
          {name:'prog_url', index:'prog_url', align:'left', width:'30%', edittype:'text'},
          {name:'order_no', index:'order_no', align:'center', width:'5%', edittype:'text', editrules:{required:true, number:true, integer:true, maxValue:65535}},
          {name:'view_yn', index:'view_yn', align:'center', width:'5%',  edittype:'select', editoptions:{value:{"Y":"Y","N":"N"}}, formatter:'select', editrules:{required:true},}
        ];

그리고 내가 직접 설정해서 validation 체크할 항목을 구현하면 된다. 
중간에 주석 처리한 부분은 처음에 중복확인 하는 코드를 reduce ? 까지 써가면서 복잡스럽게도 짜놓은 코드다.. 쉽게 생각하자
리턴값을 [true , "" ] 이렇게 주어야하는데 validation에 걸리면 모달 창에 에러메시지가 나온다

 

afterComplete는 서버에 데이터가 전송되어서 DB작업을 마치고 리턴해주는 값을 받아올 수 있다. 전송버튼을 누르면 자동으로 모달팝업창이 닫히게 설정 해 주었기 때문에 그리드가 reload 되고 성공/실패시에 리턴되는 값에 따라 결과값을 화면에 표시해준다.
그리고 setColData(false)를 호출해서 editable:true 설정을 다시 false로 바꿔주어 그리드 화면에서 수정 input이 뜨지 않게 해줘야 한다!

onClose는 모달팝업에서 취소버튼을 눌렀을 때 발생하는 이벤트이다. 클릭시 전송 완료때와 동일하게 setColData(false)를 호출해서 editable:true 설정을 다시 false로 바꿔주어야 한다.

 

더보기

수정부분 전체 코드

function setColData(param){
    const gridColData = ["group_code", "prog_id", "depth_lv", "prog_name", "prog_url", "order_no", "view_yn"];
    for(var idx in gridColData){
        $("#dataGrid").setColProp(gridColData[idx], {editable : param});
    }
}

const gridFunc = {
  addRow : function(){
    const options = {
        modal : true,
        width : 400,
        url : '/data/menumast/createGridData',
        mtype : 'POST',
        addCaption : "MENUMAST 추가",
        closeAfterAdd : true,
        recreateForm : true, //대화상자가 활성화될 때 마다 form이 다시 생성된다 
        reloadAfterSubmit:true, //서버 ajax후 그리드 데이터 다시 로드 
        beforeInitData : function(formid){ //form 데이터를 초기화 하기 전에 호출 
            setColData(true);
        },
        beforeSubmit : function(postdata, formid){
            /* 입력 값 validation*/
            if(postdata.group_code.length>2){
                return [false, "그룹코드의 입력값은 두자리 입니다."];
            }
            if(postdata.prog_id.length>10){
                return [false, "프로그램 아이디는 10자리를 넘을 수 없습니다."];
            }
            if(postdata.prog_name.length>50){
                return [false, "프로그램명은 50자리를 넘을 수 없습니다."]; 
            }
            if(postdata.prog_url.length>50){
                return [false, "프로그램 URL은 50자리를 넘을 수 없습니다."]; 
            }
            
            /*group_code === prog_id (_)이전 값 */
            const progid = postdata.prog_id.split('_'); 
            console.log(progid);
            if(postdata.group_code!==progid[0]){
                return [false, "그룹코드와 프로그램 ID의 코드는 동일해야 합니다."]; 
            }

            const rowData = $("#dataGrid").getRowData();
            /*prog_id 중복확인 */
            for(const idx in rowData){
                if(rowData[idx].prog_id===postdata.prog_id){
                    return [false, "프로그램 ID는 중복값을 가질 수 없습니다."];
                }
            }

            /*depth_lv===1일때 존재하는 상위메뉴 체크 */
            if(postdata.depth_lv==='1'){
                // const depthLvArr = [];
                for(const key in rowData){
                    if(rowData[key].group_code===postdata.group_code && rowData[key].depth_lv===postdata.depth_lv){
                        return [false, "상위 메뉴가 이미 존재합니다. 메뉴레벨을 변경 해 주세요."]; 
                    }
                }
            }

            return [true, ''];
        },
        afterComplete : function(response, postdata, formid){ 
            setColData(false);
            if(response.responseText==='success'){
              alert("등록되었습니다 !");
            }else{
              alert("에러 발생 !\nerrMsg : "+response.responseText);
            }
        },
        onClose : function(){
            setColData(false);
        }
    }
    $("#dataGrid").jqGrid('editGridRow', "new", options );
  }
}

 

그럼 이제 화면쪽은 구현이 완료되었고 라우터에서 DB 처리를 해주자

router.post('/data/menumast/createGridData', (req, res) => {
    const body = req.body;
    let sql="";

    sql = "INSERT INTO MENUMAST (sys_gbn, group_code, prog_id, depth_lv, prog_name, prog_url, order_no, view_yn, cre_date)";
    sql += "values ('USEPAY', ? , ? , ? , ? , ? , ? , ? , now())";
    const param = [body.group_code, body.prog_id, body.depth_lv, body.prog_name, body.prog_url, body.order_no, body.view_yn];
 
    console.log(sql);
    
    db_pool.getConnection(async function (err, conn) {
        if (err) { //connction 오류 
            if (conn)
                conn.release();
            return;
        }

        await conn.beginTransaction();
        await conn.query(sql, param, function(err, result){
            if(err){ //쿼리 에러 
                console.log("쿼리 에러");
                conn.rollback();
                console.log(err);
                res.send(err.sqlMessage);
            }else{ //에러 안남 
                console.log("에러 안남");
                res.send('success');
                conn.commit();
            }
        });
        conn.release(); //connection pool을 반납 
    });
});

 

추가 완 !

 

 

 

* jqGrid 관련해서 구글을 미친듯이 뒤졌는데 그중에서도 참고한 포스팅들 목록
http://1004lucifer.blogspot.com/2019/05/jqgrid-cell.html (이분게 찐임..다 나와있으나 나같은 초보자는 이해하기 쉽지 않았음)
https://withsoju.tistory.com/703
https://okky.kr/article/307721
https://backback.tistory.com/92
https://isprogramming.tistory.com/13
https://blog.naver.com/PostView.naver?blogId=aufcl4858&logNo=220988211038&parentCategoryNo=&categoryNo=40&viewDate=&isShowPopularPosts=true&from=search
https://monkeybusiness.tistory.com/25
https://pjsprogram.tistory.com/30
https://hianna.tistory.com/488
https://jjanggun.tistory.com/466

댓글