게시판의 본문을 블로그 작성하듯 도와주는 소프트웨어로 네이버에서 제공하는 스마트에디터가 있다. 이것을 스프링 MVC 프로젝트에서 적용하는 방법을 기술한다.

1. 이클립스에서 File>New>Spring Legacy Project>Spring MVC Project로 새로운 스프링 프로젝트 se를 생성한다.

  - 새로 만든 스프링 프로젝트는 기본적으로 "/"가 HomeController에서 동작하여 home.jsp를 처움에 보여준다.

2. home.jsp를 "게시글 쓰기" 형태로 수정한다.

1 <%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>

 2 <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

 3 <%@ page session="false" %>

 4 <html>

 5 <head>

 6  <title>Home</title>

 7  <script type="text/javascript" src="<c:url value="/resources/js/jquery-1.11.3.min.js"/>"></script>

 8  <script type="text/javascript" src="<c:url value="/resources/smarteditor/js/HuskyEZCreator.js"/>" charset="utf-8"></script>

 9  <script type="text/javascript" charset="utf-8" src="<c:url value="/resources/js/writeForm.js"/>"></script>

10  <link rel="stylesheet" href="<c:url value="/resources/css/common.css"/>"/>

11  <script type="text/javascript" charset="utf-8">

12  sessionStorage.setItem("contextpath", "${pageContext.request.contextPath}");

13  </script>

14 </head>

15 <body>

16 <form action="write" method="post" id="frm">

17  <table>

18  <tr>

19  <td>제목</td>

20 <td><input type="text" name="title" id="title" placeholder="제목"></td>

21 </tr>

22 <tr>

23 <td style="margin:0; padding:0;" colspan="2"><textarea name="smarteditor" id="smarteditor" rows="10" cols="100" style="width:100%; height:412px;"></textarea></td>

24 </tr>

26 </table>

27 <input id="addBtn" type="submit" value="전송">

28 </form>

29 </body>

30 </html>


1줄 : pageEncoding을 UTF-8로 설정

 7줄 : Ajax Operation을 위해 jquery js (jquery-1.11.3.min.js)를 사용

 8줄 : 스마트에디터를 사용하기 위해 필요

 9줄 : javascript를 정의하기 위해 writeForm.js를 사용

10줄 : 디자인을 깨끗하게 하기 위해 common.css를 사용

11줄~13줄 : contextPath를 javascript에서 사용하기 위해 sessionStorage에 저장

16줄 : form을 사용. "전송" 버튼을 누르면 javascript에서 textarea에 입력된 데이터를 가져와 request에 추가하도록 처리를 한다. 이후 서버로 입력 파라미터들이 전달되고 action=write가 서버에서 수행됨.

23줄 : 스마트에디터를 사용하여 본문을 입력하도록 정의해 준다.

27줄 : 전송 버튼을 누르면 javascript가 동작

3. smarteditor 파일들 : http://naver.github.io/smarteditor2/user_guide/ 참조

  - SmartEditor Basic 2.0은 JavaScript로 구현된 웹 기반의 WYSIWYG 편집기이다. 글꼴, 글자 크기, 줄 간격 등을 자유롭게 설정할 수 있으며, 단어 찾기/바꾸기와 같은 편리한 기능을 제공한다.

  - https://github.com/naver/smarteditor2에서 최신 버전을 다운로드

    -> 이 버전은 사진(이미지) 업로드 기능이 삭제된 것이다. 무슨 이유인지 모르겠지만 네이버가 이 기능을 배포판에서 빼 버렸다.

se.zip

  - 디렉토리 구조는 다음과 같다.

    + src/main/webapp/resources/smarteditor 디렉토리 밑에 다음과 같은 파일이 존재

    + css

    + img

    + js

    + photo_uploader

      * plugin

      * popup

        # attach_photo.js      // 사진 추가에 대한 javascript 처리 (서버와 연동하여 파일 저장)

        # callback.html         // 사진 추가에 대한 결과 처리

        # file_uploader.php    // 다중 사진(이미지) 본문 추가에 대한 서버 처리 루틴 (PHP 방식)

        # file_uploader_html5.php    // 단일 사진(이미지) 본문 추가에 대한 서버 처리 루틴 (PHP 방식)

        # jindo.fileuploader.js

        # jindo.min.js

        # photo_uploader.html        // popup 창에 대한 HTML 파일

    + smartEditor2Skin.html

    + 일부 파일 생략 (...)


4. writeForm.js

 1 $(function() {

 2   //전역변수선언

 3   var editor_object = [];

 4   var ctx = getContextPath();

 5

 6   nhn.husky.EZCreator.createInIFrame({    // 입력 창을 구성하는 부분을 구동

 7     oAppRef: editor_object,

 8     elPlaceHolder: "smarteditor",

 9     sSkinURI: ctx + "/resources/smarteditor/SmartEditor2Skin.html",

10     htParams : {

11       // 툴바 사용 여부 (true:사용/ false:사용하지 않음)

12       bUseToolbar : true,             

13       // 입력창 크기 조절바 사용 여부 (true:사용/ false:사용하지 않음)

14       bUseVerticalResizer : true,     

15       // 모드 탭(Editor | HTML | TEXT) 사용 여부 (true:사용/ false:사용하지 않음)

16       bUseModeChanger : true,

17       fOnBeforeUnload : function(){

18                

19       }

20     }

21   });

22    

23   //전송버튼 클릭이벤트

24   $("#addBtn").click(function(){

25     //id가 smarteditor인 textarea에 에디터에서 대입

26     editor_object.getById["smarteditor"].exec("UPDATE_CONTENTS_FIELD", []);

27         

28     // 이부분에 에디터 validation 검증 : 때에 따라 가비지 correction 등에 대한 처리 필요

29     var el = document.createElement('html');

30     el.innerHTML = editor_object.getById["smarteditor"].elPlaceHolder.value;    // 본문 정보 넣기

31     //폼 submit

32     $("#frm").submit();    // 서버로 전송 (게시글 추가 처리 요청)

33   });

34    

35   function getContextPath() {

36     return sessionStorage.getItem("contextpath");

37   }

38 });


6~21줄 : home.jsp에서 사용한 textarea 부분을 구성해 주는 부분

23~33줄 : textarea에 입력한 정보를 "smarteditor"란 parameter에 넣어주고 form 형태로 서버로 전송


5. 사진(이미지) 추가에 대한 동작 처리 : smarteditor/photo_uploader/popup/attach_photo.js

  - 사진(이미지)를 추가하는 방법은 하나의 이미지만 추가 하는 방법과 여러 개의 사진을 추가하는 방법으로 두 가지를 제공한다. 필요에 따라 자신에게 맞는 방법을 선택한다.

    + 아래 처럼 bSupportDragAndDropAPI 값을 true/false로 설정함에 따라 다르게 설정된다.

        //File API 지원 여부로 결정

function checkDragAndDropAPI(){

try{

if( !oNavigator.ie ){

if(!!oNavigator.safari && oNavigator.version <= 5){

bSupportDragAndDropAPI = false;

}else{

bSupportDragAndDropAPI = true; // multiple 파일 업로드

// bSupportDragAndDropAPI = false; // single 파일 업로드

}

} else {

bSupportDragAndDropAPI = false;

}

}catch(e){

bSupportDragAndDropAPI = false;

}

}

  - function html5Upload()    // 하나의 사진 이미지 추가에 대한 처리

    + popup 창이 뜨고, 다음을 선택한 후 확인 버튼을 누르면, 선택한 사진을 서버로 전송하는 부분

    + sUploadURL = '/se/file_uploader_html5';    // 사진 업로드 URL

  - function callFileUploader()    // 다중 사진 이미지 추가에 대한 처리

    + sUrl : '/se/file_uploader',    // 서버에서 처리하는 URL

    + sCallback : '/resources/smarteditor/photo_uploader/popup/callback.html',    // 처리 결과 응답

6. HomeController.java    // 사진 이미지 추가에 대한 처리

  - resources/upload 디렉토리에 사진 이미지 저장 (실제 저장되는 폴더는 workspace/.metadata/.plugins/\org.eclipse.wst.server.core\tmp1\wtpwebapps\se\resources\upload 밑에 저장됨

@RequestMapping(value="/file_uploader", method=RequestMethod.POST)

public void file_uploader(HttpServletRequest request, HttpServletResponse response) throws IOException, FileUploadException {

  request.setCharacterEncoding("utf-8");

  String return1="";

  String return2="";

  String return3="";

  String name = "";

  if (ServletFileUpload.isMultipartContent(request)){

    ServletFileUpload uploadHandler = new ServletFileUpload(new DiskFileItemFactory());

    //UTF-8 인코딩 설정

    uploadHandler.setHeaderEncoding("UTF-8");

    List<FileItem> items = uploadHandler.parseRequest(request);

    //각 필드태그들을 FOR문을 이용하여 비교를 합니다.

    for (FileItem item : items) {

      if(item.getFieldName().equals("callback")) {

return1 = item.getString("UTF-8");

      } else if(item.getFieldName().equals("callback_func")) {

return2 = "?callback_func="+item.getString("UTF-8");

      } else if(item.getFieldName().equals("Filedata")) {

//FILE 태그가 1개이상일 경우

if(item.getSize() > 0) {

  name = item.getName();

  String ext = item.getName().substring(item.getName().lastIndexOf(".")+1);

  //파일 기본경로

  String defaultPath = context.getRealPath("/");

  //파일 기본경로 _ 상세경로

  String path = defaultPath + "upload" + File.separator;

                

  File file = new File(path);

          //디렉토리 존재하지 않을경우 디렉토리 생성

          if(!file.exists()) {

            file.mkdirs();

          }

          //서버에 업로드 할 파일명(한글문제로 인해 원본파일은 올리지 않는것이 좋음)

          String realname = UUID.randomUUID().toString() + "." + ext;

          ///////////////// 서버에 파일쓰기 ///////////////// 

          InputStream is = item.getInputStream();

          OutputStream os=new FileOutputStream(path + realname);

          int numRead;

          byte b[] = new byte[(int)item.getSize()];

          while((numRead = is.read(b,0,b.length)) != -1){

            os.write(b,0,numRead);

          }

  if(is != null)  is.close();

  os.flush();

  os.close();

  ///////////////// 서버에 파일쓰기 /////////////////

  String root = request.getContextPath();

          return3 += "&bNewLine=true&sFileName="+name+"&sFileURL=" + root + "/upload/"+realname; // by ksseo

}else {

  return3 += "&errstr=error";

}

      }

    }

  }

  response.sendRedirect(return1+return2+return3);

}


  - 추후 가비지 correction이 진행되어야 함. (사진 이미지를 추가한 후 삭제할 경우, 또는 사진을 추가하면서 글을 작성하다가 다른 창으로 이동할 경우 등) -> writeForm.js에서 처리해 주어야 함


  - form에 대한 처리 : 데이터베이스에 게시글 정보 저장 (여기서는 생략...)

@RequestMapping(value = "/write", method = RequestMethod.POST)

public ModelAndView write(HttpServletRequest request) throws IOException, FileUploadException {

  String title = request.getParameter("title");

  String smarteditor = request.getParameter("smarteditor");


  ModelAndView model = new ModelAndView("write");

  model.addObject("title", title);

  model.addObject("smarteditor", smarteditor);

  return model;

}


7. pom.xml 고려사항 : file upload dependency 추가

<!-- file upload -->

<dependency>

  <groupId>commons-fileupload</groupId>

  <artifactId>commons-fileupload</artifactId>

  <version>1.3.1</version>

</dependency>


8. servlet-context.xml 고려사항 : multipartResolver 추가

<beans:bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">

  <beans:property name="maxUploadSize" value="52428800"></beans:property>

</beans:bean>


9. write.jsp : 게시글 추가 결과를 보여주는 뷰

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<!DOCTYPE html>

<html>

<head>

<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" />

<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

<title>홈 페이지</title>

</head>

<body>

게시글 작성 완료

제목 : ${title}<br>

본문 : <br>

${smarteditor}

</body>

</html>


10. common.css : 게시글 쓰기 CSS 파일

@CHARSET "UTF-8";

* {

margin: 0px;

padding: 0px;

border: 0;

}

html[lang^="ko"] {

font-family: "Nanum Gothic", "나눔 고딕", "NanumGothic", sans-serif;

}

/* content */

body {

width: 980px;

margin: 0 auto;

margin-top: 5px;

min-height: 770px;

}

form {

margin-top: 20px;

line-height: 2;

}

#addBtn {

width: 200px;

font-size: 14px;

padding: 10px;

background-color: blue;

color: white;

border-radius: 5px 5px 5px 5px;

margin-top:30px;

}

table {

border: 1px solid black;

border-collapse: collapse;

border-spacing: 0px;

margin-top: 20px;

width: 100%;

}

td {

border: 1px solid black;

border-collapse: collapse;

border-spacing: 0px;

padding: 5px;

line-height: 1.5;

}


가비지 correction 과정은 추후 기술한다.

수고 하셨습니다.


참고 : http://hellogk.tistory.com/63

Posted by 세상을 살아가는 사람
,

학원에서 스프링 MVC를 적용하여 프로젝트를 학생들이 하고 있는데 Ajax와 첨부파일을 적용하는 것이 어렵다고 하여 정리해 본다.

나의 작업환경은 다음과 같다.

1. 멀티 게시판 : 하나의 Article 데이터베이스 테이블에 여러 개의 게시판의 게시물을 한꺼번에 저장

  - 게시판 테이블 운영

  - ref, step, depth 체계를 가진 게시판

  - 네이버에서 공개한 스마트에디터 사용

  - 본문에 사진 추가 기능 사용

  - 첨부 파일은 5개 까지 적용 가능

  - 오라클 11g DBMS 사용 : Board, Article, PdsItem 테이블 및 각각의 sequence 운영

2. 스마트에디터에 대한 스프링 MVC 적용

  - 소스코드 위치 : src/main/webapp/resources/smarteditor

  - photo_uploader/popup/attach_photo.js 수정

    + html5Upload() function

      * 본문에 삽입되는 사진을 서버에 저장하기 위한 URL : /contextPath/board/file_uploader_html5 (내부적으로 Ajax 처리)

    + callFileUploader() function

      * sUrl : 'contextPath/board/file_uploader'

      * sCallback : 'resources/smarteditor/photo_uploader/popup/callback.html'

  - writeForm.js 수정 : 게시판 쓰기를 위한 jsp 파일의 javascript 파일

    + nhn.husky.EZCreator.createInIframe 수정

      nhn.husky.EZCreator.createInIFrame({

        oAppRef: editor_object,

        elPlaceHolder: "smarteditor",

        sSkinURI: ctx + "/resources/smarteditor/SmartEditor2Skin.html", 

        htParams : {

            // 툴바 사용 여부 (true:사용/ false:사용하지 않음)

            bUseToolbar : true,             

            // 입력창 크기 조절바 사용 여부 (true:사용/ false:사용하지 않음)

            bUseVerticalResizer : true,     

            // 모드 탭(Editor | HTML | TEXT) 사용 여부 (true:사용/ false:사용하지 않음)

            bUseModeChanger : true,

            fOnBeforeUnload : function(){

            }

         }

       });

    + 전송 버튼을 눌렀을 때 처리 : 가비지 컬렉션 처리를 위해 먼저 사진 파일에 대한 관리를 먼저하고, 이후 첨부파일에 대한 저장을 처리

      $("#addBtn").click(function(){

        //id가 smarteditor인 textarea에 에디터에서 대입

        editor_object.getById["smarteditor"].exec("UPDATE_CONTENTS_FIELD", []);

        // 이부분에 에디터 validation 검증

        var el = document.createElement('html');

        el.innerHTML = editor_object.getById["smarteditor"].elPlaceHolder.value;

        var imageF = el.getElementsByTagName('img');

        var url = ctx + "/board/file_uploader_real";

        var params = "";

        for(var i = 0;i < imageF.length;i++) {

          if(i == 0)

            params += ("src=" + imageF[i].src + "&title=" + imageF[i].title);

          else

            params += ("&src=" + imageF[i].src + "&title=" + imageF[i].title);

        }

        sendRequest(url, params, callbackFunction, "POST");

        callTimerProcessing();

      })

    

      function callTimerProcessing() {    // 사진에 대한 처리를 보장하기 위해 loop를 돌다, 응답이 오면 처리

    if(loop) {

      setTimeout(function() {

           callTimerProcessing();

          }, 100);

    }

    else {

          //폼 submit

          $("#frm").submit();

    }

      }


서버에서 처리는 다음에 ...

Posted by 세상을 살아가는 사람
,