본문 바로가기
[ 전자정부프레임워크 ]

eGov Simple HomePage 호환성 인증 샘플 적용하기

by 히앤님 2025. 2. 20.
반응형
SMALL

egov 4.2 버전

JDK 1.8

Tomcat 8.0

HSQL 사용


1. 프로젝트 생성

eGovFrame Template Project 생성

 

group_id는 동일하게 넣고

 

프로젝트가 만들어지면 Server를 연결합니다.

 

 

/sht_webapp 으로 실행됨.

 

그럼 이렇게 홈페이지 생성 완료.

 


2. 수정할 부분 확인

여기서 공지사항과 갤러리 부분만 시트로 변경해줄 것이다.

 

기본화면이고

 

등록버튼을 누르면

 

이렇게 등록화면으로 가서 게시글 저장 후

 

다시 조회화면으로 돌아온다.

 

 

기존 로직은 조회페이지 / 상세페이지 / 등록페이지 / 수정페이지 등등 나뉘어져 있으나,

우리는 단순 조회성 table만 제공하므로 페이지 이동은 빼고, 그냥 조회페이지에서 CRUD 전체를 진행하게 한다.

 

+++ 추가로 조회수는 사용하지 않음. 상세페이지를 들어가는게 아니라 count 불가함.

 


3. 제품 넣기

1) 제품 파일 넣기

프로젝트 구조는 아래처럼 되어있다.

 

ibsheet는 javascript 기반의 제품으로 js 부분에 제품을 넣어 사용하는 페이지에 import 해서 사용하면 된다.

js 폴더가 있으니 알기 쉽게 이쪽에 넣어주자.

붙여넣기

이동되면 이런경로로 되어있음.

 

 

2) 공지사항 페이지 확인

 

공지사항 페이지로 들어가서 주소를 보면 

 

http://localhost:8080/sht_webapp/cop/bbs/selectBoardList.do?bbsId=BBSMSTR_AAAAAAAAAAAA&menuNo=43

 

이런식으로 되어있는데, 

 

따라가보니 EgovBBSManageController.java 발견.

 

return 쪽을 보니

 

 

cop/bbs/EgovNoticeList 로 이동시킴을 알 수 있음.

이게 페이지 jsp 이다.

 

화면 여기있구요.

 

내용을 보니 알림마당 안에 있는 [공지사항], [사이트갤러리]가 이쪽을 바라보는 듯 하다.

 

일단 이 페이지에 시트를 띄워본다.

 

div 추가

<script type="text/javascript" src="<c:url value='/js/EgovBBSMng.js' />" ></script>


<%-- 시트 관련 js 추가 --%>
<link type="text/css" rel="stylesheet" href="<c:url value='/js/ibsheet8/css/default/main.css'/>"/>
<script type="text/javascript" src="<c:url value='/js/ibsheet8/ibsheet.js'/>"></script> <%-- 코어 파일 --%>
<script type="text/javascript" src="<c:url value='/js/ibsheet8/locale/ko.js'/>"></script> <%-- 메세지 파일 --%>
<script type="text/javascript" src="<c:url value='/js/ibsheet8/plugins/ibsheet-common.js'/>"></script> <%-- 시트 공통 설정 파일 --%>
<script type="text/javascript" src="<c:url value='/js/ibsheet8/plugins/ibsheet-dialog.js'/>"></script> <%-- 시트에서 띄우는 다이얼로그 관련 함수 파일 --%>
<script type="text/javascript" src="<c:url value='/js/ibsheet8/plugins/ibsheet-excel.js'/>"></script> <%-- 서버롤 호출하여 다운로드/업로드시 필요 파일 --%>
<script type="text/javascript" src="<c:url value='/js/ibsheet8/ibleaders.js'/>"></script> <%-- 라이선스  파일 --%>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>


...


    <!-- //검색 필드 박스 끝 -->
    <!-- 
    <div id="page_info"><div id="page_info_align"></div></div>                    
     -->

     <!-- ibsheet 표시 -->
    <div id="search_field">
        <div id="sheetDiv" style="width: 100%; height: 500px;"></div>
    </div>               



    <!-- table add start -->

 

 

js 부분은 아래와 같이 작성

더보기

 

<body onload="initSheet()"> 로 해서 페이지 로딩과 동시에 생성되도록 설정

<script type="text/javascript">

function initSheet(){
	var options = {
		Cfg: {
			SearchMode:2
		},
		
		Def: {
			Row: {
			}
		},
		LeftCols:[
			{Header: "번호", 		Type: "Int", 	Name: "SEQ", 		Width: 50,	 CanEdit:0,Visible:0},
		],
		Cols: [
			{Header: "제목",		Type: "Text",	Name: "nttSj",		Width: 200,		Align: "Left",RelWidth:1,},
			{Header: "작성자",		Type: "Text",	Name: "frstRegisterNm",	Width: 80,	Align: "Center", 	Visible:1, CanEdit:0},
			{Header: "작성일",		Type: "Date",	Name: "frstRegisterPnttm",	Width: 100,		Align: "Center", 
					Format:"yyyy-MM-dd",DataFormat:"yyyy-MM-dd",EditFormat:"yyyyMMdd", CanEdit:0
			},
			{Header: "조회수",		Type: "Int", 		Name: "inqireCo",	Width: 100,	Align: "Center",	Visible:1, CanEdit:0},		
		],
		Events: {}

	};
	
	//시트 초기화
	IBSheet.create({
		id: 'sheet', // 생성할 시트의 id
		el: 'sheetDiv', // 시트를 생성할 Dom 객체 및 id
		options: options, // 생성될 시트의 속성
	});

}


</script>

...

<body onload="initSheet()">

 

 

그럼 아래와 같이 표시된다.

 

 

여기까지 생성 완료


4. 조회

조회를 해보자.

 

EgovBBSManageController.java에서 공지사항 페이지로 오는 selectBoardList.do 쪽을 보면

 

 

Map<String, Object> map = bbsMngService.selectBoardArticles(boardVO, vo.getBbsAttrbCode());

 

bbsMngService는 게시판 관리 기능을 담당하는 서비스 클래스이다.

이 클래스는 게시글 목록을 조회하는 selectBoardArticles 메서드를 제공하며,

해당 메서드는 조회 결과를 Map<String, Object> 형태로 반환한다.

반환된 Map 객체에는 게시글 목록을 담은 resultList와 총 게시글 수를 나타내는 resultCnt가 포함된다.

 

이 데이터를 처리하기 위해 컨트롤러에서는 bbsMngService.selectBoardArticles 메서드를 호출하고,

결과로 얻어진 Map 객체에서 resultList와 resultCnt를 추출한다.

추출된 데이터를 모델에 담아 뷰로 전달하기 위해 model.addAttribute 메서드를 사용하여 각각 resultList와 resultCnt를 화면에 전달한다.

 

즉, 게시글 목록과 총 게시글 수를 화면에서 받을 수 있는 것.

 

 

페이지 네비게이션도 있는 화면이니 SearchMode:4를 사용해서 페이징 모드로 조회를 하자.

기존 로직은 냅두되, selectBoardArticles 로 데이터를 불러오는것만 동일하게 하기.

 

 

1) 컨트롤러에 조회로직 추가

▼ EgovBBSManageController.java 에 ibsheet 전용 paging 조회용 selectLargeSheetData 추가

더보기

 

페이징 조회를 하기 위해서는 ibpage에 담긴 현재페이지 수에 맞게 데이터를 보내줘야한다.

 

    /**
     * IBSheet8 paging 조회
     *
     * @param boardVO
     * @param model
     * @return
     * @throws Exception
     */
	@RequestMapping(value = "/cop/bbs/searchPaging.do")
	@ResponseBody
	public String selectLargeSheetData(@RequestParam Map<String,String> map,
							@ModelAttribute("searchVO") BoardVO searchVO,
							ModelMap model) throws Exception {

		//서버페이징 필수 조건
		int rowidx = Integer.parseInt((String)map.get("ibpage"));  //현재가 몇번째 페이지
		int onepagerow = Integer.parseInt((String)map.get("OnePageRow")); //한번에 몇건씩 가져올 것인지
		String searchCnd = (String)map.get("searchCnd"); //검색조건
		String searchWrd = (String)map.get("searchWrd"); //검색어
		
		/***********************  이 부분이 제일 중요 ***************************************/
		int startrow,endrow;
		startrow = rowidx==1?0:((rowidx-1) * onepagerow);
		endrow = startrow + (onepagerow-1);
		/***********************  이 부분이 제일 중요 ***************************************/

		//정보를 보낸다
		searchVO.setFirstIndex(startrow); //첫인덱스
		searchVO.setLastIndex(endrow); //마지막인덱스
		searchVO.setRecordCountPerPage(onepagerow); //페이지당 레코드 개수
		searchVO.setPageIndex(rowidx); //페이지인덱스
		searchVO.setSearchCnd(searchCnd); //조회조건
		searchVO.setSearchWrd(searchWrd); //검색어
		searchVO.setBbsId(searchVO.getBbsId()); // bbsId

		Map<String, Object> map2 = bbsMngService.selectBoardArticles(searchVO,null);
		int totCnt = Integer.parseInt((String)map2.get("resultCnt"));

	
		Map<String, Object> sheetData = new HashMap<>();
		sheetData.put("Total", totCnt); // 반드시 필요
		sheetData.put("data", map2.get("resultList"));
		System.out.println("데이터 조회 : "+map2.get("resultList"));
		
		ObjectMapper objectMapper = new ObjectMapper();
		objectMapper.getFactory().configure(JsonWriteFeature.ESCAPE_NON_ASCII.mappedFeature(), true); //한글설정
		String jsonString = objectMapper.writeValueAsString(sheetData);

		return jsonString;
	}

 

import com.fasterxml.jackson.core.json.JsonWriteFeature;
import com.fasterxml.jackson.databind.ObjectMapper;

 

 이것들이 추가되어야하는데 pom.xml 에서 jackson을 추가해줘야 함....

하는 김에 common-lang도 추가하자.

 

 

 

2) pom.xml 추가

Controller에 쓰는거 미리 추가하자.

		<!-- ibsheet용 jackson 추가 -->
		<dependency>
			<groupId>com.fasterxml.jackson.core</groupId>
			<artifactId>jackson-core</artifactId>
			<version>2.15.2</version>
		</dependency>
		<dependency>
			<groupId>com.fasterxml.jackson.core</groupId>
			<artifactId>jackson-databind</artifactId>
			<version>2.15.2</version>
		</dependency>
		<dependency>
			<groupId>com.fasterxml.jackson.core</groupId>
			<artifactId>jackson-annotations</artifactId>
			<version>2.15.2</version>
		</dependency>
		<!-- ibsheet용 commons-lang 추가 -->
		<dependency>
			<groupId>org.apache.commons</groupId>
			<artifactId>commons-lang3</artifactId>
			<version>3.12.0</version>
		</dependency>

 

pom.xml 에 위 구문 추가해주고 Maven > Update Project 하는거 잊지말기!

 

 

3) js에 조회로직 추가

 

		Events: {
			onRenderFirstFinish: function(param) {
				search();
			},
		}

	};
    
    ....
    
    
    //외부함수로 조회로직 추가
    function search(){
	// 폼 요소 선택
	const form = document.forms["frm"];
	// FormData 생성 후 URLSearchParams로 변환
	const SearchParam = new URLSearchParams(new FormData(form)).toString();

	
	sheet.doSearchPaging({
		url: "searchPaging.do",
		param:SearchParam,
		cPage: 1,
		sync: 0,
		method:"POST",
		pageLengthParam:"OnePageRow",
		callback:function (rtn) {
		    var rtnData = JSON.parse(rtn.data); 

		   
		  }
	});
}

 

기존 조회로직에 맞게 Form 요소를 전체 던져주자.

기본적으로 내장 DB인 HSQL로 돌고있으니 데이터가 없음.

등록버튼 눌러서 게시글 하나 추가 후 페이지 새로고침 해보면 시트에도 잘 조회됨을 볼 수 있다.

조회데이터 : [egovframework.let.cop.bbs.service.BoardVO@3adbf1bd[bbsAttrbCode=,bbsNm=,bbsTyCode=,fileAtchPosblAt=,firstIndex=1,frstRegisterNm=관리자,isExpired=N,lastIndex=1,lastUpdusrNm=,pageIndex=1,pageSize=10,pageUnit=10,parntsReplyLc=,parntsSortOrdr=,plusCount=false,posblAtchFileNumber=0,recordCountPerPage=10,replyPosblAt=,rowNo=0,searchBgnDe=,searchCnd=,searchEndDe=,searchUseYn=,searchWrd=,sortOrdr=0,subPageIndex=,atchFileId=,bbsId=BBSMSTR_AAAAAAAAAAAA,frstRegisterId=USRCNFRM_00000000000,frstRegisterPnttm=2025-2-18,inqireCo=0,lastUpdusrId=,lastUpdusrPnttm=,ntceBgnde=10000101            ,ntceBgndeView=,ntceEndde=99991231            ,ntceEnddeView=,ntcrId=,ntcrNm=,nttCn=,nttId=1,nttNo=0,nttSj=제목입니다,parnts=0,password=,replyAt=N,replyLc=0,sortOrdr=0,useAt=Y]]

서버 콘솔에도 sysout로 데이터를 확인할 수 있음.

 

여기까지 기본조회 완료.

 

[페이지 디자인 및 구현 방법]

시트는 조회성으로 따로 페이지 이동해서 저장하는 등의 기능을 구현하기엔 번거롭다.

따라서 최대한 기존 게시판과 동일하게 만들기 위해 EditDialog를 사용해서 게시글 추가 페이지처럼 사용한다.
목록 상에서는 제목만 보이지만 실제로는 내용 및 첨부파일까지 가져온다.
클릭 이벤트에서 EditDialog를 띄우고 내용을 추가할 수 있으며, 첨부파일도 등록할 수 있다.
Insert, Update 페이지를 각각 만드는 것이 아닌, [저장]버튼 하나로 게시글을 한번에 저장한다.
단, 게시글은 1개씩만 수정될 수 있도록 한다.

 

 

 


추가적으로 날짜형식을 맞춰준다던지, 작성자를 가져온다던지, 게시글 추가, 저장 버튼을 만든다던지 등

jsp 수정이 필요하다.

 

아래 전체 소스 붙여넣기

 

▼ 아래에 EgovNoticeList.jsp 전체 소스

더보기

시트 표시를 위한 js 설정 + 버튼 모양을 위한 .styled-button 스타일 추가 + 기존테이블 주석처리

 

<%--
  Class Name : EgovNoticeList.jsp
  Description : 게시물 목록화면
  Modification Information
 
      수정일         수정자                   수정내용
    -------    --------    ---------------------------
     2009.03.19   이삼섭          최초 생성
     2011.08.31   JJY       경량환경 버전 생성
 
    author   : 공통서비스 개발팀 이삼섭
    since    : 2009.03.19  
--%>
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<%@ page import="egovframework.com.cmm.service.EgovProperties" %>    
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="ui" uri="http://egovframework.gov/ctl/ui"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<c:set var="ImgUrl" value="/images/egovframework/cop/bbs/"/>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" >
<meta http-equiv="content-language" content="ko">
<link href="<c:url value='/'/>css/common.css" rel="stylesheet" type="text/css" >
<c:if test="${anonymous == 'true'}"><c:set var="prefix" value="/anonymous"/></c:if>
<script type="text/javascript" src="<c:url value='/js/EgovBBSMng.js' />" ></script>


<%-- 시트 관련 js --%>
<link type="text/css" rel="stylesheet" href="<c:url value='/js/ibsheet8/css/default/main.css'/>"/>
<script type="text/javascript" src="<c:url value='/js/ibsheet8/ibsheet.js'/>"></script> <%-- 코어 파일 --%>
<script type="text/javascript" src="<c:url value='/js/ibsheet8/locale/ko.js'/>"></script> <%-- 메세지 파일 --%>
<script type="text/javascript" src="<c:url value='/js/ibsheet8/plugins/ibsheet-common.js'/>"></script> <%-- 시트 공통 설정 파일 --%>
<script type="text/javascript" src="<c:url value='/js/ibsheet8/plugins/ibsheet-dialog.js'/>"></script> <%-- 시트에서 띄우는 다이얼로그 관련 함수 파일 --%>
<script type="text/javascript" src="<c:url value='/js/ibsheet8/plugins/ibsheet-excel.js'/>"></script> <%-- 서버롤 호출하여 다운로드/업로드시 필요 파일 --%>
<script type="text/javascript" src="<c:url value='/js/ibsheet8/ibleaders.js'/>"></script> <%-- 라이선스  파일 --%>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>



<c:choose>
<c:when test="${preview == 'true'}">
<script type="text/javascript">
<!--
    function press(event) {
    }

    function fn_egov_addNotice() {
    }
    
    function fn_egov_select_noticeList(pageNo) {
    }
    
    function fn_egov_inqire_notice(nttId, bbsId) {      
    }
//-->
</script>
</c:when>
<c:otherwise>
<script type="text/javascript">
<!--
    function press(event) {
        if (event.keyCode==13) {
            fn_egov_select_noticeList('1');
        }
    }

    function fn_egov_addNotice() {
        document.frm.action = "<c:url value='/cop/bbs${prefix}/addBoardArticle.do'/>";
        document.frm.submit();
    }
    
    function fn_egov_select_noticeList(pageNo) {
        document.frm.pageIndex.value = pageNo;
        document.frm.action = "<c:url value='/cop/bbs${prefix}/selectBoardList.do'/>";
        document.frm.submit();  
    }
    
    function fn_egov_inqire_notice(nttId, bbsId) {
        document.subForm.nttId.value = nttId;
        document.subForm.bbsId.value = bbsId;
        document.subForm.action = "<c:url value='/cop/bbs${prefix}/selectBoardArticle.do'/>";
        document.subForm.submit();          
    }
//-->

/*********
 시트 초기화
 **********/
function initSheet(){
	var options = {
		Cfg: {
			SearchMode:4,
			ColorState: 7, // 편집할 수 없는 셀의 배경색을 표현하지 않음
			InfoRowConfig: {
				Visible: true,
				Space: "Bottom",
				"Layout": ['Paging2',"<button onclick='addArticle()' class='styled-button'>게시글 추가</button> " + 
				 	"<button onclick='saveArticle()' class='styled-button''>저장</button>"],
			},
			Export:{
				FilePath:'../../files/'
			},
			
			PageLength: 5,  // 한페이지당 표시할 게시글 수
		},
		
		Def: {
			Row: {
				CanFormula: true,
				CalcOrder: "sStatus,isLblistFile"
				
			}
		},
		LeftCols:[
			{Header: "번호", 		Type: "Int", 	Name: "SEQ", 				Width: 50,	 	CanEdit:0,	Visible:0},
			{Header: "번호",		Type: "Int",	Name: "nttIdx",				Width: 50,	 	CanEdit:0, 	Align: "Center", Visible:1},
			{Header: "상태",						Name: "sStatus",			Width: 60,   	Extend: IB_Preset.STATUS, CanEdit:0},
		],
		Cols: [
			{Header: "제목",		Type: "Text",	Name: "nttSj",				Width: 200,		Align: "Left",	RelWidth:1},
			{Header: "첨부파일",	Type: "File",	Name: "lblistFile",			Width: 100,		Align: "Center"},
			{Header: "내용",		Type: "Lines",	Name: "nttCn",				Width: 100,		Align: "Left",	Visible:0},
			{Header: "작성자",		Type: "Text",	Name: "frstRegisterNm",		Width: 80,		Align: "Center", 	Visible:1, DefaultValue:getUsername(), CanEdit:0},
			{Header: "작성일",		Type: "Date",	Name: "frstRegisterPnttm",	Width: 100,		Align: "Center", 
					Format:"yyyy-MM-dd",DataFormat:"yyyy-MM-dd",EditFormat:"yyyyMMdd", DefaultValue:today(), CanEdit:0
			},
		//	{Header: "조회수",		Type: "Int", 		Name: "inqireCo",	Width: 100,	Align: "Center",	Visible:0, CanEdit:0}, //조회수 사용 안함
			{Header: "첨부파일 포함유무",	Type: "Bool",	Name: "isLblistFile",	Width: 100,		Align: "Center", Visible:0,
				Formula:function(fr){
					if(fr.Row['lblistFile'] == ''){
						return false;
					}else{
						return true;
					}
				}
			},
			{Header: "게시물 삭제",						Name: "sDelete",	Width: 60,	Extend: IB_Preset.DelCheck, Visible:0},
			{Header: "파일 id",		Type: "Text",	Name: "atchFileId",	Width: 100,						Visible:0},
			{"Header": "파일명", 		Type: "Text",	Name: "streFileNm",	Width: 100,	Align: "Center", 	Visible: 0},
			
				
		],
		Events: {
			onRenderFirstFinish: function(param) {
				//데이터 조회
				search();
			},
			
			onBeforeDataLoad:function(evtParam){
				// 조회 결과 데이터
				var DATA = evtParam.data;
				// 조회된 데이터 일부를 수정한다.
			    for(var i = 0; i < DATA.length; i++){
			        var row = DATA[i];
			        //frstRegisterPnttm 값이 비어있지 않는 경우, 날짜가 한자리수 일 때 0을 붙여준다.
			        if(!isNull(row["frstRegisterPnttm"])){
			            row["frstRegisterPnttm"] = formatDate(row["frstRegisterPnttm"]);
			        }

					//nttId 값을 넣어준다.
			        row["nttIdx"] =row["nttId"];
			        
					//삭제된 행의 데이터를 지워준다.
			    	if( row["nttSj"] == "이 글은 작성자에 의해서 삭제되었습니다." ){
			    	   row["Del"] = 1;
			    	   row["nttSjTextStyle"] = 9;
			    	   row["nttSjCanEdit"] = 0;

					   //컬럼 비우기
			    	   row["lblistFileDisabled"] = 1;
			    	   row["frstRegisterNm"] = '';
			    	   row["frstRegisterPnttm"] = '';
			    	   row["atchFileId"] = '';
			    	   row["streFileNm"] = '';
			    	   row["nttCn"] = '';
			    	   
			      	}
			       
				   //파일명을 첨부파일 컬럼에 넣는다.
		            if(row["streFileNm"]) row["lblistFile"] = row["streFileNm"];
			        
			    }
			},
			onSetFile: function (evtParam) {
				if(evtParam.files[0]){
				    console.log("첨부파일명 : ", evtParam.files[0].name);
					//파일명을 저장하는 더미에 이름을 추가한다.
					evtParam.row["streFileNm"] = evtParam.files[0].name;
				}
			},
			onAfterClick: function(param) {
				//게시글은 무조건 한개만 수정되도록 한다.
				
				// 상태값 확인
				var isModified = sheet.getRowsByStatus("Added,Changed,Deleted");
				
				if(!isNull(param.row.Del)){
					return;
				}
				
    		    if(param.col == "nttSj" && isModified.length < 1 ){ //변경된게 없음
    		     	sheet.showEditDialog({row:param.row,excludeHideCol:1});
    		    }else if(param.col == "nttSj" && isModified.length == 1 && param.row.sStatus != "R"){ //변경된게 있고 그걸 클릭한 경우
      		      	sheet.showEditDialog({row:param.row,excludeHideCol:1});
    		    }else if((param.col == "nttSj" || param.col == "lblistFile") && isModified.length == 1 && param.row.sStatus == "R"){ //변경된게 있고 신규를 클릭한 경우
					sheet.setAttribute(param.row, "lblistFile", "Disabled", 1);
    		        sheet.showMessageTime({message: "<span style='color:black'>게시글은 한개씩 편집이 가능합니다.</span>",time: 1000});

    		    }
			},
			onDblClick:function(evtParam){
    		    if(evtParam.col == "nttSj"){
					return true;//더블클릭을 막음
    		    }
			},
	   		 onAfterSave : function(evtParam) {
	   			 //저장 후 메시지 출력
				 sheet.showMessageTime({
				        message: "<span style='color:black'>저장되었습니다.</span>",
				        func:function(args){
				        	search(); //재조회
				        }
				 })
			 }
		}
	};
	
	//시트 초기화
	IBSheet.create({
		id: 'sheet', // 생성할 시트의 id
		el: 'sheetDiv', // 시트를 생성할 Dom 객체 및 id
		options: options, // 생성될 시트의 속성
	});

}


/**************
 편집 다이얼로그 수정
 **************/
IBSheet.onBeforeCreate = function (obj) {
	//파일 타입 컬럼은 EditDialog에 뜨지 않게 한다.(EditDialog에서 파일첨부 불가)
	if(obj.id == "editSheet_sheet"){
		var editDialogCols = obj.options["Body"][0];
		for(i=0;i<editDialogCols.length;i++){ 
			if(editDialogCols[i]["TargetType"] == "File"){
				editDialogCols[i]["Visible"] = 0;
				
			}else if(editDialogCols[i]["TargetType"] == "Lines" 
					|| editDialogCols[i]["TargetType"] == "Bool"){
				editDialogCols[i]["Visible"] = 1;
			}
				
		}
	}
    return obj;
}



/*****
 조회
 *****/
function search(){
	// 폼 요소 선택
	const form = document.forms["frm"];
	// FormData 생성 후 URLSearchParams로 변환
	const SearchParam = new URLSearchParams(new FormData(form)).toString();

	
	sheet.doSearchPaging({
		url: "searchPaging.do",
		param:SearchParam,
		cPage: 1,
		sync: 0,
		method:"POST",
		pageLengthParam:"OnePageRow",
		callback:function (rtn) {
		    var rtnData = JSON.parse(rtn.data); 

		   
		  }
	});
}


/********************
 한자릿수 날짜 앞에 0을 넣는다.
 ********************/
function formatDate(dateString) {
    // 문자열을 "-"를 기준으로 분리
    let parts = dateString.split("-");

    // parts[1]이 한 자리 숫자인 경우 (월)
    if (parts[1].length === 1) {
        parts[1] = "0" + parts[1];
    }

    // parts[2]이 한 자리 숫자인 경우 (일)
    if (parts[2].length === 1) {
        parts[2] = "0" + parts[2];
    }

    // "YYYY-MM-DD" 형식으로 다시 합치기
    return parts.join("-");
}


/*************
 공백 여부 판단한다.
 *************/
function isNull(value) {
	
	if(value == null || value == undefined || value == ""){
		return true;	
	}else{
		return false;
	}
	
}

/**************
 오늘 날짜를 가져온다.
 **************/
function today(){
	return IBSheet.dateToString(new Date(),'yyyy-MM-dd');
}


/********************
 상단에서 접속한 아이디를가져온다.
 ********************/
function getUsername() {
    const loginElement = document.getElementById("header_loginname");
    const fullName = loginElement.textContent;
    // "님"을 제외한 아이디 부분만 추출
    const username = fullName.trim().replace("님", "");
    
    return username;
}


/*********
 게시글 추가
 **********/
function addArticle(){

	//로그인한 아이디가 없을 경우 alert 발생
	if(isNull(getUsername())){
		alert("로그인 후 등록 가능합니다.");
		return;
	}

	
	//행추가 시 EditDialog 열림
	if(!sheet.hasChangedData()){
		sheet.addRow({"next":sheet.getFirstRow(),init:{nttIdx : sheet.getTotalRowCount()+1}} );
		sheet.showEditDialog({row:sheet.getFocusedRow(),excludeHideCol:1});
	}else{

        sheet.showMessageTime({message: "<span style='color:black'>게시글은 한개씩 등록이 가능합니다.</span>",time: 1000});
	}
}


/**********
 상태값 저장
 **********/
function saveArticle(){
	//로그인한 아이디가 없을 경우 alert 발생
	if(isNull(getUsername())){
		alert("로그인 후 등록 가능합니다.");
		return;
	}
	
	popup(); //alert발생
}

/******************
 상태값에 따른 alert 발생
 ******************/
function popup() {
	
	//상태값에 따라 Text 변경
	var article = sheet.getSaveJson().data;
	var statusNm = "";
	var statusBtn = "저장";

	if(article.length == 0){
        sheet.showMessageTime({message: "<span style='color:black'>변경사항이 없습니다.</span>",time: 1000});	
        return;
	}
	
	if(article[0].sStatus == "I"){
		statusNm = "추가";
	}else if(article[0].sStatus == "U"){
		statusNm = "수정";
	}else if(article[0].sStatus == "D"){
		statusNm = "삭제";
		statusBtn = "삭제";
	}
	
	
	//팝업 띄우기
	if(article.length > 1){ //편집 게시글 한개이상
        sheet.showMessageTime({message: "<span style='color:black'>게시글은 한개씩 편집이 가능합니다.</span>",time: 1000});
	
	}else if(article.length == 1){ //게시글을 한개씩만 저장한다.

		sheet.showMessageTime({
			message: "<span style='color:black'>해당 게시글을 " + statusNm + "하시겠습니까?</span>",
			time: 0,
			buttons: [statusBtn, "취소"],
			func: function (args) {
				//저장 시
				if (args==1) {
					
					var formData = sheet.getSaveJson({saveMode:2, formData: true}); //시트의 데이터와 file를 추출


					// 'frm' 폼 데이터 추가
					const form = document.forms["frm"];
					for (var k = 0; k < form.elements.length; k++) {
						var frmInput = form.elements[k];
						if (frmInput.name) {
							formData.append(frmInput.name, frmInput.value);
						}
					}

					
					//데이터 저장
					$.ajax({
						url: "/sht_webapp/cop/bbs/sheetfileSave.do",
							data: formData,
							method: "POST",
							enctype: 'multipart/form-data',
							contentType: false,
							processData: false,
							cache: false,
							success:function(data){
								var result = data.IO.Result;
								var fileData = data.IO.data;
								//결과를 시트에 반영한다.
								sheet.applySaveResult(result, null, null, fileData);
								
							},
							error: function(jqXHR, textStatus, errorThrown) {
								// 오류 발생 시 처리할 코드
								console.error("Error: " + textStatus, errorThrown);
							}

					});
				} else if (args==2) {
					//취소에 대한 처리
				}
			}
		});
	}
}

</script>
</c:otherwise>
</c:choose>
<title><c:out value="${brdMstrVO.bbsNm}"/> 목록</title>

<style type="text/css">
    h1 {font-size:12px;}
    caption {visibility:hidden; font-size:0; height:0; margin:0; padding:0; line-height:0;}
    
    /* ibsheet InfoRow의 버튼 디자인 */
	.styled-button{
		background-color: #3d5575; /* 버튼 배경색 */
		color: white; /* 텍스트 색상 */
		border: none; /* 테두리 제거 */
		padding: 6px 15px; /* 내부 여백 */
		font-size: 12px; /* 글씨 크기 */
		font-weight: bold; /* 글씨 굵게 */
		border-radius: 5px; /* 둥근 모서리 */
		cursor: pointer; /* 마우스 포인터 */
		transition: background-color 0.3s ease, transform 0.2s ease; /* 호버 효과 애니메이션 */
		float: right; /* 오른쪽 정렬 */
		margin-left: 10px; /* 버튼 간격 */
	}
</style>


</head>
<body onload="initSheet()">
<noscript class="noScriptTitle">자바스크립트를 지원하지 않는 브라우저에서는 일부 기능을 사용하실 수 없습니다.</noscript>
<!-- 전체 레이어 시작 -->
<div id="wrap">
    <!-- header 시작 -->
    <div id="header_mainsize"><jsp:include page="/WEB-INF/jsp/main/inc/EgovIncHeader.jsp"/></div>
    <div id="topnavi"><jsp:include page="/WEB-INF/jsp/main/inc/EgovIncTopnav.jsp"/></div>        
    <!-- //header 끝 --> 
    <!-- container 시작 -->
    <div id="container">
        <!-- 좌측메뉴 시작 -->
        <div id="leftmenu"><jsp:include page="/WEB-INF/jsp/main/inc/EgovIncLeftmenu.jsp"/></div>
        <!-- //좌측메뉴 끝 -->
            <!-- 현재위치 네비게이션 시작 -->
            <div id="content">
                <div id="cur_loc">
                    <div id="cur_loc_align">
                        <ul>
                            <li>HOME</li>
                            <li>&gt;</li>
                            <li>알림마당</li>
                            <li>&gt;</li>
                            <li><strong>${brdMstrVO.bbsNm}</strong></li>
                        </ul>
                    </div>
                </div>
                <!-- 검색 필드 박스 시작 -->
                <div id="search_field">
                    <div id="search_field_loc"><h2><strong><c:out value='${brdMstrVO.bbsNm}'/></strong></h2></div>
					<form name="frm" action ="<c:url value='/cop/bbs${prefix}/selectBoardList.do'/>" method="post">
						<input type="hidden" name="bbsId" value="<c:out value='${boardVO.bbsId}'/>" />
						<input type="hidden" name="nttId"  value="0" />
						<input type="hidden" name="bbsTyCode" value="<c:out value='${brdMstrVO.bbsTyCode}'/>" />
						<input type="hidden" name="bbsAttrbCode" value="<c:out value='${brdMstrVO.bbsAttrbCode}'/>" />
						<input type="hidden" name="authFlag" value="<c:out value='${brdMstrVO.authFlag}'/>" />
						<input name="pageIndex" type="hidden" value="<c:out value='${searchVO.pageIndex}'/>"/>
						<input name="ntcrId" type="hidden" value=""/>
						<input name="ntceBgnde" type="hidden" value=""/>
						<input name="ntceEndde" type="hidden" value=""/>
                        <input type="submit" value="실행" onclick="fn_egov_select_noticeList('1'); return false;" id="invisible" class="invisible" />
                        
                        <fieldset><legend>조건정보 영역</legend>
                        <div class="sf_start">
                            <ul id="search_first_ul">
                                <li>
								    <select name="searchCnd" class="select" title="검색조건 선택">
								           <option value="0" <c:if test="${searchVO.searchCnd == '0'}">selected="selected"</c:if> >제목</option>
								           <option value="1" <c:if test="${searchVO.searchCnd == '1'}">selected="selected"</c:if> >내용</option>             
								           <option value="2" <c:if test="${searchVO.searchCnd == '2'}">selected="selected"</c:if> >작성자</option>            
                                    </select>
                                </li>
                                <li>
                                    <input name="searchWrd" type="text" size="35" value='<c:out value="${searchVO.searchWrd}"/>' maxlength="35" onkeypress="press(event);" title="검색어 입력"> 
                                </li>
                                <li>
                                    <div class="buttons" style="position:absolute;left:870px;top:182px;">
                                        <a href="#LINK" onclick="javascript:fn_egov_select_noticeList('1'); return false;"><img src="<c:url value='/images/img_search.gif' />" alt="search" />조회 </a>
                                        <% if(null != session.getAttribute("LoginVO")){ %>
                                        <c:if test="${brdMstrVO.authFlag == 'Y'}">
                                           <!-- <a href="<c:url value='/cop/bbs${prefix}/addBoardArticle.do'/>?bbsId=<c:out value="${boardVO.bbsId}"/>">등록</a> -->
                                        </c:if>
                                        <%} %>
                                    </div>                              
                                </li>      
                            </ul>
                        </div>
                        </fieldset>
                    </form>
                </div>
                <!-- //검색 필드 박스 끝 -->
                <!-- 
                <div id="page_info"><div id="page_info_align"></div></div>                    
                 -->
                 
					<div id="search_field">
						<div id="sheetDiv" style="width: 100%; height: 500px;"></div>
					</div>
                 
                <!-- table add start -->
                <!--<div class="default_tablestyle">
                    <table summary="번호, 제목, 게시시작일, 게시종료일, 작성자, 작성일, 조회수   입니다" cellpadding="0" cellspacing="0">
                    <caption>게시물 목록</caption>
                    <colgroup>
                    <col width="10%">
                    <col>  
                    <c:if test="${brdMstrVO.bbsAttrbCode == 'BBSA01'}">
	                    <col width="10%">
	                    <col width="10%">
				    </c:if>
				    <c:if test="${anonymous != 'true'}">
                        <col width="10%">
                    </c:if>
                    <col width="15%">
                    <col width="8%">
                    </colgroup>
                    <thead>
                    <tr>
                        <th scope="col" class="f_field" nowrap="nowrap">번호</th>
                        <th scope="col" nowrap="nowrap">제목</th>
                        <c:if test="${brdMstrVO.bbsAttrbCode == 'BBSA01'}">
	                        <th scope="col" nowrap="nowrap">게시시작일</th>
	                        <th scope="col" nowrap="nowrap">게시종료일</th>
	                    </c:if>
	                    <c:if test="${anonymous != 'true'}">
	                        <th scope="col" nowrap="nowrap">작성자</th>
	                    </c:if>
                        <th scope="col" nowrap="nowrap">작성일</th>
                        <th scope="col" nowrap="nowrap">조회수</th>
                    </tr>
                    </thead>
                    <tbody>                 

                    <c:forEach var="result" items="${resultList}" varStatus="status">
                                                   
                      <tr>
				        <td><b><c:out value="${paginationInfo.totalRecordCount+1 - ((searchVO.pageIndex-1) * searchVO.pageSize + status.count)}"/></b></td>            
				        <td align="left">
				            <form name="subForm" method="post" action="<c:url value='/cop/bbs${prefix}/selectBoardArticle.do'/>">
				            <c:if test="${result.replyLc!=0}">
				                <c:forEach begin="0" end="${result.replyLc}" step="1">
				                    &nbsp;
				                </c:forEach>
				                <img src="<c:url value='/images/reply_arrow.gif'/>" alt="reply arrow"/>
				            </c:if>
				            <c:choose>
				                <c:when test="${result.isExpired=='Y' || result.useAt == 'N'}">
				                    <c:out value="${result.nttSj}" />
				                </c:when>
				                <c:otherwise>
				                        <input type="hidden" name="bbsId" value="<c:out value='${result.bbsId}'/>" />
				                        <input type="hidden" name="nttId"  value="<c:out value="${result.nttId}"/>" />
				                        <input type="hidden" name="bbsTyCode" value="<c:out value='${brdMstrVO.bbsTyCode}'/>" />
				                        <input type="hidden" name="bbsAttrbCode" value="<c:out value='${brdMstrVO.bbsAttrbCode}'/>" />
				                        <input type="hidden" name="authFlag" value="<c:out value='${brdMstrVO.authFlag}'/>" />
				                        <input name="pageIndex" type="hidden" value="<c:out value='${searchVO.pageIndex}'/>"/>
				                        

										<input type="hidden" name="parnts" value="<c:out value='${result.parnts}'/>" >
										<input type="hidden" name="sortOrdr" value="<c:out value='${result.sortOrdr}'/>" >
										<input type="hidden" name="replyLc" value="<c:out value='${result.replyLc}'/>" >
				                        
				                        
				                        <span class="link"><input type="submit" style="width:320px;border:solid 0px black;text-align:left;" value="<c:out value="${result.nttSj}"/>" ></span>
				                </c:otherwise>
				            </c:choose>
				            </form>
				        </td>
				        <c:if test="${brdMstrVO.bbsAttrbCode == 'BBSA01'}">
				            <td ><c:out value="${result.ntceBgnde}"/></td>
				            <td ><c:out value="${result.ntceEndde}"/></td>
				        </c:if>
				        <c:if test="${anonymous != 'true'}">
				            <td ><c:out value="${result.frstRegisterNm}"/></td>
				        </c:if>
				        <td ><c:out value="${result.frstRegisterPnttm}"/></td>
				        <td ><c:out value="${result.inqireCo}"/></td>
				      </tr>
				     </c:forEach>     
				     <c:if test="${fn:length(resultList) == 0}">
				      <tr>
				        <c:choose>
				            <c:when test="${brdMstrVO.bbsAttrbCode == 'BBSA01'}">
				                <td colspan="7" ><spring:message code="common.nodata.msg" /></td>
				            </c:when>
				            <c:otherwise>
				                <c:choose>
				                    <c:when test="${anonymous == 'true'}">
				                        <td colspan="4" ><spring:message code="common.nodata.msg" /></td>
				                    </c:when>
				                    <c:otherwise>
				                        <td colspan="5" ><spring:message code="common.nodata.msg" /></td>
				                    </c:otherwise>
				                </c:choose>     
				            </c:otherwise>
				        </c:choose>       
				          </tr>      
				     </c:if>  
                    </tbody>
                    </table>
                </div>--> 
				
                <!-- 페이지 네비게이션 시작
                <div id="paging_div">
                    <ul class="paging_align">
                        <ui:pagination paginationInfo="${paginationInfo}" type="image" jsFunction="fn_egov_select_noticeList" />    
                    </ul>
                </div> -->
                <!-- //페이지 네비게이션 끝 -->  
            </div>
            <!-- //content 끝 -->    
        </div>
        <!-- //container 끝 -->
        <!-- footer 시작 -->
        <div id="footer"><jsp:include page="/WEB-INF/jsp/main/inc/EgovIncFooter.jsp"/></div>
        <!-- //footer 끝 -->
    </div>
    <!-- //전체 레이어 끝 -->
 </body>
</html>

 

 

 


 

 

이렇게 나오면 jsp 설정은 끝!

 


4. 저장

화면에 [게시글 추가], [저장] 버튼을 만들고 제목 클릭 시 EditDialog를 띄우게 만들었다.

ibsheet는 상태값이라는게 있어서 추가/수정/삭제 와 같이 상태값이 변화한 항목들을 뽑아 DB에 반영할 수 있다.

기존에는 추가 / 수정 / 삭제가 다 로직이 따로 있는데, 이걸 그냥 하나로 묶어버리려고 함.

 


1) 컨트롤러에 저장로직 추가

▼ EgovBBSManageController.java 에 ibsheet 전용 저장로직인 filesaveIBSheet 추가

더보기

 

상단에 ServletContext 는 수기로 추가해주자.

 

@Autowired
private ServletContext servletContext;

 

저장로직 추가(각 내용 주석 확인)

  /**
  * IBSheet8 저장로직
  *
  * @param boardVO
  * @param model
  * @return
  * @throws Exception
  */
	@RequestMapping(value = "/cop/bbs/sheetfileSave.do", method = RequestMethod.POST, produces = "application/json; charset=UTF-8")
	@ResponseBody
	public String filesaveIBSheet(@RequestParam Map<String,String> map,
			HttpServletRequest request, 
			final MultipartHttpServletRequest multiRequest, 
							@ModelAttribute("searchVO") BoardVO boardVO,
						    @ModelAttribute("bdMstr") BoardMaster bdMstr, @ModelAttribute("board") Board board, 
						    BindingResult bindingResult, ModelMap model) throws Exception {
		

		LoginVO user = (LoginVO)EgovUserDetailsHelper.getAuthenticatedUser();
		Map<String, Object> saveResult = new HashMap<>();
		Map<String, Object> IO = new HashMap<>();
		
		try {
			// ---------------------- 시트 데이터 추출 -------------------------
			String formData = map.get("Data");
			String decodedData = HtmlUtils.htmlUnescape(formData); //form 데이터 디코딩
			ObjectMapper objectMapper = new ObjectMapper(); // JSON 문자열을 파싱
			Map<String, Object> jsonMap = objectMapper.readValue(decodedData, Map.class); // JSON을 Map으로 변환
			 
			// 'data' 배열에서 첫 번째 항목을 가져와 Map으로 변환
			Map<String, Object> firstItem = ((List<Map<String, Object>>) jsonMap.get("data")).get(0);
			
			// 각 항목을 Map<String, String>으로 변환
			Map<String, String> resultMap = new HashMap<>();
			for (Map.Entry<String, Object> entry : firstItem.entrySet()) {
			    resultMap.put(entry.getKey(), entry.getValue().toString());
			}
			// 시트데이터 결과 출력
			System.out.println("저장할 데이터 : " + jsonMap);
		 
		 
			// ---------------------- 기본 데이터 세팅 -------------------------
			board.setLastUpdusrId(user.getUniqId());
			
			board.setNtcrNm("");	// dummy 오류 수정 (익명이 아닌 경우 validator 처리를 위해 dummy로 지정됨)
			board.setPassword("");	// dummy 오류 수정 (익명이 아닌 경우 validator 처리를 위해 dummy로 지정됨)
			board.setNttCn(resultMap.get("nttCn"));
			board.setNttSj(resultMap.get("nttSj"));
			board.setNtcrId((String)map.get("ntcrId"));
			board.setNtceBgnde(board.getNtceBgnde());
			board.setNtceEndde(board.getNtceEndde());
			board.setBbsId(board.getBbsId());
		
			//nttid 이 long 타입이기 때문에 형변환
			int nttIdx = Integer.parseInt(resultMap.get("nttIdx"));
			if (nttIdx != 0) {
				long longNumber = (long) nttIdx; 
			    board.setNttId(longNumber);
			} else {
			    // 기본값 설정 (예: 0)
			        board.setNttId(0);
			    }
		 
		 

			// ---------------------- 파일 추출 -------------------------
			/* 시트에 추가된 파일은 저장시 서버로 전송되며, 파일이름과 저장경로를 응답하면 파일 다운로드 가능
			 * 파일이 있는 경우, 신규파일인지, 파일수정인지 여부에 따라 파일명 및 파일경로 업데이트
			 */
			
			//ibsheet의 file 이름은 [행ID + $ + file컬럼명]
			String fileName = resultMap.get("id") + "$lblistFile";
			// 파일을 받기 위한 MultipartFile 객체 생성
			MultipartFile file = multiRequest.getFile(fileName);
			
			FileVO fvo = new FileVO();
			
			// file ID 유무
			String atchFileId_new = ""; //신규 파일이면 ID 생성
			String atchFileId = boardVO.getAtchFileId(); //기존파일 ID
			String atchFileId_old = resultMap.get("atchFileId"); //기존파일 ID(추가x)
			String fileName_old = resultMap.get("streFileNm"); //기존 파일 이름(추가x)
			
			// 현재 프로젝트의 루트 경로 가져오기
			String webappPath = servletContext.getRealPath("/files");
			System.out.println("파일 저장 경로 설정 webappPath : " + webappPath);
			
			// 파일이 존재할 때
			if (file != null && !file.isEmpty()) {
				
			    //1. 기존 파일 ID가 없음(신규 저장)
				if ("".equals(atchFileId)) {
				    // 새로운 첨부 파일 ID 생성 및 파일 저장
				    List<FileVO> result = fileUtil.parseFileInf(
				        Collections.singletonMap("fileName", file), // 파일을 Map 형태로 전달
				        "BBS_", // 파일 prefix
				        0, // 파일 순번 (기본값)
				        atchFileId, // 첨부 파일 ID
				        webappPath // 파일 저장 경로
				    );
					
				    if (!result.isEmpty()) {
				        FileVO fileVO = result.get(0);
				        //파일 정보 추가
				        atchFileId_new = fileMngService.insertFileInf(fileVO);
				        // Board 객체에 새 첨부 파일 ID 설정
				        board.setAtchFileId(atchFileId_new);
				    }
				
				
				//2. 기존 파일이 있는 상태에서 신규파일로 변경
				} else {
				    // 기존 첨부 파일 ID에 파일 추가
				    fvo.setAtchFileId(atchFileId);
				    int cnt = fileMngService.getMaxFileSN(fvo);
				    List<FileVO> _result = fileUtil.parseFileInf(
				        Collections.singletonMap("fileName", file), // 파일을 Map 형태로 전달
				        "BBS_", // 파일 prefix
				        cnt, // 파일 순번 
				        atchFileId, // 첨부 파일 ID
				        webappPath // 파일 저장 경로
				    );
				    //파일 정보 업데이트
				        fileMngService.updateFileInfs(_result);
				    }
			    
			} else { // 파일이 없음
			    System.out.println("파일이 없습니다.");
				if(atchFileId_old != null && (resultMap.get("isLblistFile")).equals("true") ) { //기존에 파일이 있었으면 파일명 유지
				
					 board.setAtchFileId(atchFileId_old); //기존에 있던 파일id 유지
					 board.setStreFileNm(fileName_old); //기존에 있던 파일이름 유지
		        }
			 
			}
		    
			
		    
			// ---------------------- 상태값에 따라 저장 -------------------------
		
			if("Added".equals(resultMap.get("STATUS"))) {
				
				System.out.println("################# 추가 #################");
				board.setNttNo(4L); // 게시물 번호 long
				board.setFrstRegisterId(user.getUniqId()); //여기에 알아서 최초등록자아이디		

				bbsMngService.insertBoardArticle(board);
			
			}else if("Changed".equals(resultMap.get("STATUS"))) {
			
				System.out.println("################# 수정 #################");
				bbsMngService.updateBoardArticle(board);
			
			}else {
			
				System.out.println("################# 삭제 #################");
			    board.setLastUpdusrId(user.getUniqId());
			    bbsMngService.deleteBoardArticle(board);
			}
		 
			
			// ---------------------- 응답 -------------------------
		    IO.put("Result", "0");
		    IO.put("Message", "저장되었습니다.");
		
			//파일이 있는 경우 파일 응답 추가
		    if (file != null && !file.isEmpty()) {
		    	List<Map<String, String>> fileArr = new ArrayList<>();
		    	Map<String, String> fileData1 = new HashMap<>();
				String originalFilename = file.getOriginalFilename(); //파일이름
		
				fileData1.put("file", (String) originalFilename);
		    	fileData1.put("filePath", webappPath + '/');
		    	fileData1.put("id", (String) resultMap.get("id"));
		    	fileArr.add(fileData1);
		
		    	// IO 객체에 파일데이터 추가
		    	IO.put("data", fileArr);
		    }
		
			
			saveResult.put("IO", IO);
		    
			
		}catch(Exception ex){
		     IO.put("Result", "-10");
		     IO.put("Message", "저장 중 오류가 발생했습니다.");
		     saveResult.put("IO", IO);
			
		} finally {}
		
		ObjectMapper objectMapper = new ObjectMapper();
		String jsonString = objectMapper.writeValueAsString(saveResult); 
		
		return jsonString;
	}

 

 

그럼 이렇게 뜨는데

 

원래 게시글 목록에는 첨부파일 이름도 확인할 수가 없다.

내용은 그냥 동일 테이블에서 불러오면 되는데 첨부파일은 vo 부터 추가해줘야함.

 

따라서 공지사항 리스트에서 첨부파일도 받을 수 있도록 Board.java를 수정해줘야함.

 

2) Board.java 수정

▼ Board.java 에 file name을 받을 수 있도록 변수 및 getter Setter 추가

    /**
     * 게시물 첨부파일 명
     */
    private String streFileNm = "";
    
    
    ...
    
    
    /**
	 * streFileNm attribute를 리턴한다.
	 *
	 * @return the streFileNm
	 */
	public String getStreFileNm() {
		return streFileNm;
	}

	/**
	 * streFileNm attribute 값을 설정한다.
	 *
	 * @param streFileNm
	 *            the streFileNm to set
	 */
	public void setStreFileNm(String streFileNm) {
		this.streFileNm = streFileNm;
	}

 

3) EgovBoard_SQL_hsql.xml 수정

여기까지 하면 저장은 잘된다. 문제는 [내용]과 [첨부파일]이 제대로 조회되지 않음.

이유는 데이터를 조회하는 selectBoardArticleList 의 SELECT 문에서 [내용]과 [파일명]을 불러오지 않기 때문.(원래 안불러옴)

 

따라서 EgovBoard_SQL_hsql.xml 에서 공지사항 목록을 조회하는 부분을 약간 수정해서 [내용]과 [파일명] 이 나올 수 있도록 한다.


▼ EgovBoard_SQL_hsql.xml  에 a.NTT_CN, f.STRE_FILE_NM 추가

(상단에 resultMap도 추가할 것)

더보기

(MyBatis 기반임)

 

resultMap에서 NTT_CN, STRE_FILE_NM 컬럼을 추가하고,

SELECT 쪽에서

 

a.NTT_CN, f.STRE_FILE_NM 

 

LEFT OUTER JOIN
LETTNFILEDETAIL f
ON a.ATCH_FILE_ID = f.ATCH_FILE_ID

 

만 추가한 것.

File 쪽은 LETTNFILEDETAIL 테이블에서 관리하기 때문에 JOIN 해줬다.

	<resultMap id="boardList" class="egovframework.let.cop.bbs.service.BoardVO">
		<result property="bbsId" column="BBS_ID" columnIndex="1"/>
		<result property="nttId" column="NTT_ID" columnIndex="2"/>
		<result property="nttSj" column="NTT_SJ" columnIndex="3"/>
		<result property="frstRegisterId" column="FRST_REGISTER_ID" columnIndex="4"/>
		<result property="frstRegisterNm" column="FRST_REGISTER_NM" columnIndex="5"/>
		<result property="frstRegisterPnttm" column="FRST_REGIST_PNTTM" columnIndex="6"/>
		<result property="inqireCo" column="RDCNT" columnIndex="7"/>
		<result property="parnts" column="PARNTSCTT_NO" columnIndex="8"/>
		<result property="replyAt" column="ANSWER_AT" columnIndex="9"/>		
		<result property="replyLc" column="ANSWER_LC" columnIndex="10"/>
		<result property="useAt" column="USE_AT" columnIndex="11"/>
		<result property="atchFileId" column="ATCH_FILE_ID" columnIndex="12"/>
		<result property="ntceBgnde" column="NTCE_BGNDE" columnIndex="13"/>
		<result property="ntceEndde" column="NTCE_ENDDE" columnIndex="14"/>
		<result property="nttCn" column="NTT_CN" columnIndex="15"/>
		<result property="streFileNm" column="STRE_FILE_NM" columnIndex="16"/>
	</resultMap>


....

    <select id="selectBoardArticleList" parameterType="egovframework.let.cop.bbs.service.BoardVO" resultMap="boardList">
		
			SELECT
				a.NTT_ID, a.NTT_SJ, a.FRST_REGISTER_ID, IFNULL(b.USER_NM, a.NTCR_NM) as FRST_REGISTER_NM,
				YEAR(a.FRST_REGIST_PNTTM)||'-'||MONTH(a.FRST_REGIST_PNTTM)||'-'||DAYOFMONTH(a.FRST_REGIST_PNTTM) as FRST_REGIST_PNTTM,
				a.RDCNT, a.PARNTSCTT_NO, a.ANSWER_AT, a.ANSWER_LC, a.USE_AT, a.ATCH_FILE_ID,
				a.BBS_ID, a.NTCE_BGNDE, a.NTCE_ENDDE,
				
				a.NTT_CN,
        		f.STRE_FILE_NM  
			FROM
				LETTNBBS a
			LEFT OUTER JOIN 
				LETTNEMPLYRINFO b
			ON a.FRST_REGISTER_ID = b.ESNTL_ID 

			LEFT OUTER JOIN
				LETTNFILEDETAIL f
			ON a.ATCH_FILE_ID = f.ATCH_FILE_ID


			WHERE
				a.BBS_ID = #{bbsId}
		
			<if test="searchCnd == 0">AND
					a.NTT_SJ LIKE '%' || #{searchWrd} || '%' 		
			</if>
			<if test="searchCnd == 1">AND
					a.NTT_CN LIKE  '%' || #{searchWrd} || '%' 		
			</if>
			<if test="searchCnd == 2">AND
					b.USER_NM LIKE '%' || #{searchWrd} || '%' 		
			</if>
					
			ORDER BY a.SORT_ORDR DESC, NTT_NO ASC
			LIMIT #{recordCountPerPage} OFFSET #{firstIndex}
						
	</select>

 

SELECT 문만 수정해주면 된다.

 

이렇게 되면 조회할 때 게시글 내용과 파일까지 함께 넘어온다.

 


4. 파일 설정

 

아까 게시글 첨부파일 명을 board에서 처리하기 위해 Board.java에서 FIle이름을 받을 수 있는 인자를 추가해줬었다.

    /**
     * 게시물 첨부파일 명
     */
    private String streFileNm = "";
    
    .....
    
    
    	/**
	 * streFileNm attribute를 리턴한다.
	 *
	 * @return the streFileNm
	 */
	public String getStreFileNm() {
		return streFileNm;
	}

	/**
	 * streFileNm attribute 값을 설정한다.
	 *
	 * @param streFileNm
	 *            the streFileNm to set
	 */
	public void setStreFileNm(String streFileNm) {
		this.streFileNm = streFileNm;
	}

 

 

파일명만 문제가 아니라, 해당 파일 경로와 파일명을 페이지로 리턴해줘야하기 때문에

첨부파일 목록에서 파일 저장 경로와 이름을 수정해줘야 한다.

 

1) EgovFileMngUtil.java 수정


첨부파일에 대한 목록 정보를 취득하는  parseFileInf를 아래와 같이 수정한다.

    /**
     * 첨부파일에 대한 목록 정보를 취득한다.
     *
     * @param files
     * @return
     * @throws Exception
     */
    public List<FileVO> parseFileInf ~~~~

 

EgovFileMngUtil.java 에서 아래부분 수정

더보기

일부 파일 경로 및 폴더설정을 따라간다.

    /**
     * 첨부파일에 대한 목록 정보를 취득한다.
     *
     * @param files
     * @return
     * @throws Exception
     */
    public List<FileVO> parseFileInf(Map<String, MultipartFile> files, String KeyStr, int fileKeyParam, String atchFileId, String storePath) throws Exception {
	int fileKey = fileKeyParam;

	String storePathString = "";
	String atchFileIdString = "";

	if ("".equals(storePath) || storePath == null) {
	    storePathString = propertyService.getString("Globals.fileStorePath");
	} else {
	    //storePathString = propertyService.getString(storePath);
		storePathString = storePath; //IBSheet) Cfg.FilePath에 설정한 경로 그대로 설정
	}

	if ("".equals(atchFileId) || atchFileId == null) {
	    atchFileIdString = idgenService.getNextStringId();
	} else {
	    atchFileIdString = atchFileId;
	}

	//File saveFolder = new File(storePathString);
	File saveFolder = new File(EgovWebUtil.filePathBlackList(storePathString)); //IBSheet) 폴더 설정

	if (!saveFolder.exists() || saveFolder.isFile()) {
	    saveFolder.mkdirs();
	}

	Iterator<Entry<String, MultipartFile>> itr = files.entrySet().iterator();
	MultipartFile file;
	String filePath = "";
	List<FileVO> result  = new ArrayList<FileVO>();
	FileVO fvo;

	while (itr.hasNext()) {
	    Entry<String, MultipartFile> entry = itr.next();

	    file = entry.getValue();
	    String orginFileName = file.getOriginalFilename();

	    //--------------------------------------
	    // 원 파일명이 없는 경우 처리
	    // (첨부가 되지 않은 input file type)
	    //--------------------------------------
	    if ("".equals(orginFileName)) {
		continue;
	    }
	    ////------------------------------------

	    int index = orginFileName.lastIndexOf(".");
	    //String fileName = orginFileName.substring(0, index);
	    String fileExt = orginFileName.substring(index + 1);
	    String newName = KeyStr + EgovStringUtil.getTimeStamp() + fileKey;
	    long _size = file.getSize();

	    if (!"".equals(orginFileName)) {
		//filePath = storePathString + File.separator + newName;
		//file.transferTo(new File(filePath));
		filePath = storePathString + File.separator + orginFileName; //IBSheet)기존이름으로 설정
		file.transferTo(new File(EgovWebUtil.filePathBlackList(filePath))); //IBSheet)파일 path 설정
	    }
	    fvo = new FileVO();
	    fvo.setFileExtsn(fileExt);
	    fvo.setFileStreCours(storePathString);
	    fvo.setFileMg(Long.toString(_size));
	    fvo.setOrignlFileNm(orginFileName);
	    //fvo.setStreFileNm(newName);
	    fvo.setStreFileNm(orginFileName); //IBSheet) 파일 이름을 [파일이름.확장자] 형식으로 그대로 전달
	    
	    fvo.setAtchFileId(atchFileIdString);
	    fvo.setFileSn(String.valueOf(fileKey));

	    //writeFile(file, newName, storePathString);
	    result.add(fvo);

	    fileKey++;
	}

	return result;
    }

 

 

이렇게 하면 설정 끝.

 


5. 정리

정리하자면 수정한 파일 목록은 아래와 같다.(제품은 추가한 것)

 

 

동작확인

 

로그인하고 시트 모양 인

 

 

게시글 추가

 

 

첨부파일 등록 후 클릭하면 다운로드 됨

 

 

수정

 

 

삭제

반응형
LIST

댓글