게시판 그룹 리스트의 핵심은 데이터를 트리 구조로 표현하는 것이다.

즉, 리스트 기능을 표 형태가 아닌 트리 구조의 계층형으로 구현한다.

부서별 게시판들(공지사항, 토론방, QNA 등)로 구성하거나

하위 토론방을 주제별로 구분해서 사용할 수 있기 때문이다.

리스트를 트리로 구현하면서 신규, 읽기, 수정, 삭제를 하나의 페이지로 구현하고

신규, 읽기, 수정, 삭제가 발생할때 마다 트리를 매번 다시 생성하는 것은

비효율적이고 복잡해서

신규, 읽기, 수정, 삭제를 Ajax로 구현한다.


따라서 멀티 게시판 관리자 기능의 핵심은 트리 사용과 Ajax 활용이 된다.


개인적으로 사용해본 트리 라이브러리 중

dynatree가 무료(MIT) 중에서 가장 무난하다고 생각해서 사용한다.

자세한 사용법은 찾아보길 바라고 여기서는 관련된 내용만 다룬다.

http://wwwendt.de/tech/dynatree/doc/sample-select.html
http://jwgye.tistory.com/13

이외에 jstree나 bootstrap-treeview도 좋다고 생각한다.

    https://www.jstree.com/demo/
    https://github.com/jonmiles/bootstrap-treeview


다음 그림과 같이 트리 구조로 게시판 그룹이 있다고 하면

dynatree는 다음과 같이 JSON을 이용하여 트리 구조를 표현해서 넘겨만 주면 된다.

    {“key”: “1”, “title”: “공지사항”},
    {"key": "2", "title": "동아리", "isFolder": true,
        "children": [
            {"key": "3", "title": "기타"},
            {"key": "4", "title": "축구"}
            ]
        },
    {"key": "5", "title": "토론방"} 

Key는 그룹번호(bgno), title는 그룹 명(bgname)을 의미하고

2번 동아리는 자식(children)으로 기타(3)와 축구(4)를 가지는 트리 구조이다.

1과 5는 출력만 하면 된다.

이상과 같이 JSON 데이터를 생성하는 것이 트리 표현의 핵심인데

Java의 ObjectMapper를 이용하면 쉽게 처리할 수 있다.

ObjectMapper는 리스트(List)등의 구조체를 문자열로 변환하는 클래스인데

리스트의 항목이 클래스(VO)로 되어 있기 때문에

변환을 하면 JSON형태가 되게 된다.

클래스의 변수(메소드) 명이 JSON의 키로 생성되는 것이다.

변수의 값은 JSON의 키 값으로 생성된다.


따라서, 다음과 같이 TreeVO를 생성하면 쉽게 해결 된다.

dynatree에서 사용하는 key, title, isFolder, children로 클래스 멤버를 구성하고

데이터베이스에서 데이터를 가지고 올 때에도 이와 같이 가지고 오는 것이다.

public class TreeVO {
    private String key, title, parent;
    private boolean isFolder;
    private List children;
   
    public String getKey() {
        return key;
    }
    public void setKey(String key) {
        this.key = key;
    }
    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }
    ~~ 생략 ~~

TreeVO.java

List로 지정된 children이 트리 변환의 핵심인데

ObjectMapper에 의해

List는 하위 JSON 집합으로 변환된다.


데이터 베이스에서 danatree의 JSON 키 생성구조(TreeVO)로 데이터 리스트를 조회하고

이 리스트를 계층 구조로 정리하고(children에 넣어주고)

ObjectMapper를 이용해서 JSON으로 변환하는 것이

게시판 그룹 리스트의 핵심 사항이다.


이 개념을 가지고 하나씩 구현해 본다.

먼저, 메인 페이지이자 게시판 그룹 리스트를 구현한다.

하나의 게시판 그룹이 추가될 때 마다

게시판 그룹 리스트 페이지를 새로 실행하고

게시판 그룹 트리를 다시 생성하는 것은 비효율적이고 직관성이 떨어져

이상의 그림과 같이

왼쪽에 리스트(트리), 오른쪽에는 입력/수정 화면이 하나로 묶어서 구현한다.

왼쪽의 트리에서 게시판을 선택하면

오른쪽에 해당 정보가 나오고 수정/삭제 할 수 있는 방식이다.

게시판에서는 각각의 화면이 다른 페이지로 있었지만

여기서는 모든 페이지가 하나에 구현된다.

따라서 Ajax가 많이 사용된다.


먼저, 게시판 그룹 리스트를 가지고 오는(selectBoardGroupList) 컨트롤을 작성한다.

@RequestMapping(value = "/boardGroupList")
public String boardList(ModelMap modelMap){
    List<?> listview   = boardSvc.selectBoardGroupList();

    TreeMaker tm = new TreeMaker();
    String treeStr = tm.makeTreeByHierarchy(listview);
   
    modelMap.addAttribute("treeStr", treeStr);
   
    return "board9/BoardGroupList";

BoardGroupCtr.java

public List<?> selectBoardGroupList(){
    return sqlSession.selectList("selectBoardGroupList");
}

BoardGroupSvc.java

<select id="selectBoardGroupList" resultType="gu.common.TreeVO" >
    SELECT BGNO 'KEY', BGNAME TITLE, BGPARENT PARENT
      FROM TBL_BOARDGROUP
     WHERE BGDELETEFLAG='N'
     ORDER BY BGNO 
</select> 

board9.xml

selectBoardGroupList를 이용해 가지고 온 데이터는

dynatree에 필요한 데이터 구조(KEY, TITLE)로 맞춘 TreeVO에 담겨 있다.

게시판 그룹의 부모(BGPARENT) 정보를 이용하여 계층 구조를 생성하게 된다.


Listview의 개수만큼(size) 하나씩(item=TreeVO) 반복하며

새로운 리스트(rootlist)에 하나씩 추가한다.

이때, 하나의 데이터(TreeVO)의 부모 정보(bgparent)가 NULL이 아니면

부모가 있다는 의미로

부모 데이터를 찾아서 (for ~~~ mtDO.getParent().equals(ptDO.getKey()))

children(List)에 내가 너의 자식이라고 추가(add)해 주는 방식으로 계층을 형성한다.

부모가 없는 경우는 최상위 노드(Root)를 의미하며

새로운 리스트(rootlist)에 추가하고 다음으로 넘어간다.

public class TreeMaker {
    public String makeTreeByHierarchy(List<?> listview){
        List<TreeVO> rootlist= new ArrayList<TreeVO>();
       
        for (Integer i=0; i<listview.size(); i++) {           
            TreeVO mtDO = (TreeVO)listview.get(i);           
           
            if (mtDO.getParent()==null) {
                rootlist.add(mtDO);
                continue;
            }   
             for (Integer j=0; j<listview.size(); j++) {
                 TreeVO ptDO = (TreeVO) listview.get(j);
                 if (mtDO.getParent().equals(ptDO.getKey())) {
                     if (ptDO.getChildren()==null) ptDO.setChildren(new ArrayList<Object>() );
                     List<TreeVO> a = ptDO.getChildren();
                     a.add(mtDO);
                     ptDO.setIsFolder(true);
                     break;
                 }
             }     
         }

        ObjectMapper mapper = new ObjectMapper();
        String str="";
        try {
            str = mapper.writeValueAsString(rootlist);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return str;
    }

TreeMaker.java

계층 구조로 데이터가 정리되면

ObjectMapper로 JSON형태의 문자열로 변환하면 된다.

일반 리스트 데이터를 위와 같은 방식으로 변환하는 것은

자주 사용하는 것이라 common폴더에 생성하였다.

설명이 어려워서 이해되지 않는 경우

boardGroupList 컨트롤에서 사용하는 것처럼

makeTreeByHierarchy를 호출하는 것만 기억해도 된다.


이렇게 생성한 정보는 modelMap을 이용하여 JSP로 보낸다.

JSP 파일에서는 넘겨 받은 treeStr의 값을

다음과 같이 dynatree의 문법에 맞춰 넣어주면 트리가 생성된다.

#tree는 트리를 만들 대상 DIV의 ID이다.

$(function(){
    $("#tree").dynatree({
        children: <c:out value="${treeStr}" escapeXml="false"/>,
        onActivate: TreenodeActivate
    });
            fn_groupNew();
});
~~ 생략 ~~
<div id="tree"></div> 

BoardGroupList.jsp

Dynatree를 사용하기 위해서는

다음과 같이 CSS와 JS 파일을 포함해야 한다.

<link rel="stylesheet" href="js/dynatree/ui.dynatree.css" id="skinSheet"/>
<script src="js/jquery-2.2.3.min.js"></script>
<script src="js/jquery-ui.js"></script>
<script src="js/dynatree/jquery.dynatree.js"></script> 

BoardGroupList.jsp

Dynatree는 JQuery와 JQueryUI를 기반으로 제작되었기 때문에

위와 같이 JQuery도 지정해야 한다.


웹 페이지를 실행해 보면 이상의 그림과 같이 트리가 생성된 것을 볼 수 있다.

(데이터가 없어서 못 볼 수도 있다.)

onActivate는 트리의 노드가 선택되면 발생하는 이벤트로 

TreenodeActivate를 아직 구현하지 않아서 노드를 클릭하면 오류가 발생한다.











'Java > 게시판 9: 멀티 & 관리' 카테고리의 다른 글

1. 멀티게시판 관리자(그룹) 준비  (2) 2016.08.14
3. 게시판 그룹 읽기 / 삭제  (0) 2016.08.14
4. 게시판 그룹 쓰기와 수정  (0) 2016.08.14
5. 게시판 적용  (0) 2016.08.14

사용자가 트리의 노드를 선택하면

onActivate 이벤트에 지정된 TreenodeActivate함수가 실행된다.

파라메터로 사용자가 선택한 노드(node)가 넘어오고

전역변수(selectedNode)에 넣어서 다른 함수에서 사용할 수 있게 했다.

selectedNode(node).data.key는 TreeVO 구성시 key를 의미하고

그룹번호(bgno)가 저장되어 있다.

이 값(key== bgno)을 파라메터로

게시판 그룹 정보 읽기 컨트롤(boardGroupRead)을 호출한다.

콜백 함수는 receiveData로 반환에 성공하면

지정된 그룹번호(bgno)에 해당하는 데이터가 JSON 형태로 반환된다(receiveData).

Function TreenodeActivate(node) {
    selectedNode = node;
   
    if (selectedNode==null || selectedNode.data.key==0) return;
    $.ajax({
        url: "boardGroupRead",
        cache: false,
        data: { bgno : selectedNode.data.key }       
    }).done(receiveData);
}

function receiveData(data){
    $("#bgno").val(data.bgno);
    $("#bgname").val(data.bgname);
    $('input:radio[name="bgused"][value="' + data.bgused + '"]').prop('checked', true);
    $('input:radio[name="bgreadonly"][value="' + data.bgreadonly + '"]').prop('checked', true);
    $('input:radio[name="bgreply"][value="' + data.bgreply + '"]').prop('checked', true);
}

BoardGroupList.jsp

수신된 데이터(receiveData)는 JQuery 적당하게 값을 설정하면 된다.

코드가 복잡하게 보이는 것은

Radio를 ID가 아닌 Name으로 접근해서 그렇다.

게시판 사용여부(BGUSED), 댓글 기능 사용여부(BGREPLY), 글쓰기 금지(BGREADONLY) 는 Y/N로 저장되고

Radio의 선택여부에 따라서 Y/N으로 설정하면 된다.


사용된 HTML 코드를 보면

사용여부의 경우 name은 bgused이고 ID는 bgusedY와 bgusedN인 두 개의 radio가 있다.

각각은 Label이 사용/사용중지로 되어 있으며

Label의 for 속성이 bgusedY와 bgusedN를 가리킨다.

이것은 작은 radio를 선택할 수도 있고

글자를 표현한 Label를 선택해도 해당(for) radio를 선택하게 하기 위한 것이다.

따라서 radio의 ID가 달라야 한다.

댓글 기능 사용여부(BGREPLY), 글쓰기 금지(BGREADONLY)도 동일한 방식으로 구현하였다.

 <tr>
    <th>사용여부</th>
    <td>
        <input name="bgused" id="bgusedY" type="radio" checked="checked" value="Y"><label for="bgusedY">사용</label>
        <input name="bgused" id="bgusedN" type="radio" value="N"><label for="bgusedN">사용중지</label>
    </td>
</tr>
<tr>
    <th>등록가능</th>
    <td>
        <input name="bgreadonly" id="bgreadonlyN" type="radio" checked="checked" value="N"><label for="bgreadonlyN">사용</label>
        <input name="bgreadonly" id="bgreadonlyY" type="radio" value="Y"><label for="bgreadonlyY">사용안함</label>
    </td>
</tr>
<tr>
    <th>댓글</th>
    <td>
        <input name="bgreply" id="bgreplyY" type="radio" checked="checked" value="Y"><label for="bgreplyY">사용</label>
        <input name="bgreply" id="bgreplyN" type="radio" value="N"><label for="bgreplyN">사용안함</label>
    </td>
</tr>

BoardGroupList.jsp

JQuery는 ID를 이용하여 쉽게 사용할 수 있는데 ($(“bgused"]))

이상의 내용 때문에 다음과 같이 Name을 사용해야 한다.

$('input:radio[name="bgused"])


receiveData에서 다소 복잡해 보이는 다음의 코드는

이름(name)이 그룹번호(bgused)인 태그 중에서

값(value)이 Y/N인 태그의 체크(checked) 속성(prop)을 true로 설정하여

선택된 모습으로 출력해 주는 것이다.

    $('input:radio[name="bgused"][value="' + data.bgused + '"]').prop('checked', true);
    $('input:radio[name="bgreadonly"][value="' + data.bgreadonly + '"]').prop('checked', true);
    $('input:radio[name="bgreply"][value="' + data.bgreply + '"]').prop('checked', true);

JQuery가 복잡해 보일 뿐

컨트롤과 서비스, SQL문은 게시판과 동일하다.

컨트롤에서 BoardGroupVO로 받아온 그룹 정보(bgInfo)를

ObjectMapper를 이용하여 JSON으로 변환하여 전송하는 것이 다르다.

       @RequestMapping(value = "/boardGroupRead")
       public void boardRead(HttpServletRequest request, HttpServletResponse response){
       
        String bgno = request.getParameter("bgno");
       
        BoardGroupVO bgInfo = boardSvc.selectBoardGroupOne(bgno);
       
        ObjectMapper mapper = new ObjectMapper();
        response.setContentType("application/json;charset=UTF-8");
       
        try {
            response.getWriter().print( mapper.writeValueAsString(bgInfo));
        } catch (IOException ex) {
             System.out.println("오류: 게시판 그룹에 문제가 발생했습니다.");
        }
    } 

BoardGroupCtr.java

public BoardGroupVO selectBoardGroupOne(String param){
        return sqlSession.selectOne("selectBoardGroupOne", param);

BoardGroupSvc.java

<select id="selectBoardGroupOne" parameterType="String" resultType="gu.board9.BoardGroupVO">
        SELECT BGNO, BGNAME, BGUSED, BGREPLY, BGREADONLY
          FROM TBL_BOARDGROUP
         WHERE BGDELETEFLAG='N' AND BGNO=#{bgno}
 </select>

board9.xml

삭제(fn_groupDelete)는 지정된 트리 노드의 값(key, bgno)을

Ajax로 서버로 보내(boardGroupDelete)서 삭제하고

적절한 메시지를 출력해 주면 된다.

그리고, 트리에서 현재 선택된 노드를 제거하고(remove)

선택되면서 출력된 값들을 지워준다.

유사한 코드로 구성되어 상세한 설명을 생략한다.



'Java > 게시판 9: 멀티 & 관리' 카테고리의 다른 글

1. 멀티게시판 관리자(그룹) 준비  (2) 2016.08.14
2. 게시판 그룹 리스트  (2) 2016.08.14
4. 게시판 그룹 쓰기와 수정  (0) 2016.08.14
5. 게시판 적용  (0) 2016.08.14

게시판 그룹 쓰기/수정은 게시판 새 글 쓰기/수정과 동일하다.

사용자가 입력한 값을 받아서

새 번호(그룹 - bgno)를 부여하고 INSERT 문을 실행하거나

그룹 번호가 있으면 UPDATE문으로 수정하면 된다.

다만, 댓글처럼 Ajax로 처리하기 때문에

JavaScript가 많이 사용된다.

더욱이 HTML Radio를 사용하면서

작성해야 하는 코드가 조금 더 많아져 복잡해 보인다.


다음 코드를 보면 일반적인 Ajax 사용과 같다.

function fn_groupSave(){
    if($("#bgname").val() == ""){
        alert("게시판 이름을 입력해주세요.");
        return;
    }
    var bgno =null;
    if (selectedNode!=null) bgno =selectedNode.data.key;

    if (!confirm("저장하시겠습니까?")) return;

    $.ajax({
        url: "boardGroupSave",
        cache: false,
        type: "POST",
        data: { bgno:$("#bgno").val(), bgname:$("#bgname").val(), bgparent: bgno,
                bgused : $("input:radio[name=bgused]:checked").val(),
                bgreadonly : $("input:radio[name=bgreadonly]:checked").val(),
                bgreply : $("input:radio[name=bgreply]:checked").val()}       
    }).done(receiveData4Save);

BoardGroupList.jsp

selectedNode는 트리의 노드가 선택되면 발생하는 이벤트(onActivate)에서

선택된 노드를 가지고 있도록 한 전역변수이다.

따라서, selectedNode에 값이 있으면

게시판 그룹 수정이고(bgno 값 존재), 없으면 신규가 된다.

사용자가 노드를 선택하면

선택한 노드가 selectedNode에 지정되고

입력상자(input)의 ID가 bgno에도 값이 저장된다.

저장할 데이터를 구성하여 boardGroupSave를 호출하면

게시판과 유사한 다음 SQL문으로 저장하게 된다.


<insert id="insertBoardGroup" parameterType="gu.board9.BoardGroupVO" >
    <selectKey resultType="String" keyProperty="bgno" order="BEFORE">
        SELECT IFNULL(MAX(BGNO),0)+1 FROM TBL_BOARDGROUP
    </selectKey>   
    INSERT INTO TBL_BOARDGROUP(BGNO, BGNAME, BGUSED, BGREPLY, BGREADONLY, BGPARENT, BGDELETEFLAG, BGDATE)
    VALUES (#{bgno}, #{bgname}, #{bgused}, #{bgreply}, #{bgreadonly}, #{bgparent}, 'N', NOW() )
</insert>


<update id="updateBoardGroup" parameterType="gu.board9.BoardGroupVO">
    UPDATE TBL_BOARDGROUP
       SET BGNAME=#{bgname}, BGUSED=#{bgused}, BGREPLY=#{bgreply}, BGREADONLY=#{bgreadonly}
     WHERE BGNO=#{bgno}
</update>  

board9.xml

SQL문은 별도의 이름을 부여했지만

서비스(DTO)의 이름은 게시판 글쓰기/수정과 이름이 같다(insertBoard).

insertBoard에서는 게시판과 같이 그룹번호(bgno) 값이

없으면 신규 등록 (INSERT)

있으면 수정(UPDATE)을 실행한다.

    public void insertBoard(BoardGroupVO param){
        if ("".equals(param.getBgparent())) param.setBgparent(null);
           
        if (param.getBgno()==null || "".equals(param.getBgno()))
             sqlSession.insert("insertBoardGroup", param);
        else sqlSession.insert("updateBoardGroup", param);
    } 

BoardGroupSvc.java

이렇게 처리한 뒤 컨드롤(boardGroupSave)에서는

Json 형태(ObjectMapper)로 저장한 그룹 정보를 반환한다(bgInfo).

신규일경우는 MyBatis가 글번호를 반환 받아서 bgInfo에 저장해서 반환한다(selectKey).

     @RequestMapping(value = "/boardGroupSave")
       public void boardSave(HttpServletResponse response, BoardGroupVO bgInfo){
        
        boardSvc.insertBoard(bgInfo);
        
        ObjectMapper mapper = new ObjectMapper();
        response.setContentType("application/json;charset=UTF-8");
        
        try {
            response.getWriter().print( mapper.writeValueAsString(bgInfo));
        } catch (IOException ex) {
             System.out.println("오류: 게시판 그룹에 문제가 발생했습니다.");
        }
    }

BoardGroupCtr.java

컨트롤이 반환한 정보(bgInfo)는

클라이언트에서 Ajax 호출시 지정된 receiveData4Save 함수에 파라메타(data)로 넘겨 진다.

선택된 노드(selectedNode)가 있거나(!==null),

선택된 노드의 수정이면(선택된 노드와 반환된 데이터의 키 값이 같으면)

선택된 노드의 제목이 수정될수 있어서 무조건 수정해준다.

IF문 처리를 해도 되지만 간단한 데이터 변경이라 무조건 하도록 했다.

function receiveData4Save(data){
    if (selectedNode!==null && selectedNode.data.key===data.bgno) {
        selectedNode.data.title=data.bgname;
        selectedNode.render();
    } else {
        addNode(data.bgno, data.bgname);
    }
   
    alert("저장되었습니다.");

BoardGroupList.jsp

수정이 아닌 경우

즉, 최상위 노드에 추가(선택 노드 없음, selectedNode===null)하거나

선택노드의 자식 노드를 추가할 경우

(선택노드와 반환된 데이터 키 값이 다르면, selectedNode.data.key!==data.bgno)

새로운 노드를 생성해서 추가해 준다 (addNode).


addNode함수의 내용은 다음과 같다.

dynatree의 사용법으로 별도의 설명을 하지 않으니 찾아보길 바라고

선탠된 노드($("#tree").dynatree("getActiveNode"))가 있으면

자식으로 추가하고

없으면 최상위($("#tree").dynatree("getRoot"))에 추가한다.

function addNode(nodeNo, nodeTitle){
    var node = $("#tree").dynatree("getActiveNode");
    if (!node) node = $("#tree").dynatree("getRoot");
    var childNode = node.addChild({key: nodeNo, title: nodeTitle});
    node.expand() ;
    node.data.isFolder=true;
    node.tree.redraw();

BoardGroupList.jsp










'Java > 게시판 9: 멀티 & 관리' 카테고리의 다른 글

1. 멀티게시판 관리자(그룹) 준비  (2) 2016.08.14
2. 게시판 그룹 리스트  (2) 2016.08.14
3. 게시판 그룹 읽기 / 삭제  (0) 2016.08.14
5. 게시판 적용  (0) 2016.08.14

웹 페이지(JSP파일)가 존재하는

리스트(board9List), 글쓰기(board9Form), 글읽기(board9Read) 컨트롤(Board9Ctr.java)에

다음 코드를 각각 넣어줘야 한다.

        BoardGroupVO bgInfo = boardGroupSvc.selectBoardGroupOne4Used(boardInfo.getBgno());
        if (bgInfo==null) return "board9/BoardGroupFail";
        
        ~~ 생략 ~~
        modelMap.addAttribute("bgInfo", bgInfo);

Board9Ctr.java

게시판 그룹 번호(getBgno)에 대한 정보를 찾아보고(selectBoardGroupOne4Used)

관리자에서 등록된 정보가 없으면(bgInfo==null)

오류 페이지(BoardGroupFail)를 보여주고 종료한다.

정보가 있는 경우  modelMap에 넣어서 적절한 웹 페이지로 넘겨준다.


그룹 번호에 해당하는 정보를 찾는 SQL문은 다음과 같고

삭제(BGDELETEFLAG)되지 않고, 사용가능(BGREPLY)으로 지정된 데이터를 

조회하게 구성한다.

    <select id="selectBoardGroupOne4Used" parameterType="String" resultType="gu.board9.BoardGroupVO">
        SELECT BGNO, BGNAME, BGUSED, BGREPLY, BGREADONLY
          FROM TBL_BOARDGROUP
         WHERE BGDELETEFLAG='N' AND BGNO=#{bgno} AND BGUSED='Y'
    </select>  

board9.xml

모든 게시판 페이지(JSP)에는 게시판 이름(bgname)을 보여주는 다음 코드를 넣어줘야 한다.

    <h1><c:out value="${bgInfo.bgname}"/></h1>

마지막으로

게시판 그룹 관리자에서 지정된 설정(등록가능, 댓글)에 따라

게시판이 운영되도록 해야 한다.


리스트에는 기존의 글쓰기 버튼에 다음 코드를 추가해 준다.


    <c:if test="${bgInfo.bgreadonly=='N'}">
        <a href="board9Form?bgno=<c:out value="${searchVO.bgno}"/>">글쓰기</a>
    </c:if> 

BoardList.jsp

관리 기능에서 [등록가능 (bgreadonly)]이 설정되면(Y)

글쓰기 링크가 활성화 되는 코드를 넣어준다.

기존 코드에 IF문을 넣어서 구현하였다.

제대로 구현하기 위해서는 board9Save 컨트롤에서도 구현할 것이 있지만

이정도 수준에서 처리하고 넘어간다.


[댓글 (bgreply)] 기능 사용여부도 기존 코드에 IF문을 넣어서 처리한다.

<c:if test="${bgInfo.bgreply=='Y'}">
    <div style="border: 1px solid; width: 600px; padding: 5px">
        <form id="form1" name="form1">
            <input type="hidden" id="brdno1" name="brdno" value="<c:out value="${boardInfo.brdno}"/>">
            작성자: <input type="text" id="rewriter1" name="rewriter" size="20" maxlength="20"> <br/>
            <textarea id="rememo1" name="rememo" rows="3" cols="60" maxlength="500" placeholder="댓글을 달아주세요."></textarea>
            <a href="#" onclick="fn_formSubmit()">저장</a>
        </form>
    </div>
</c:if> 

BoardList.jsp

댓글 모든 기능에 IF문을 넣는게 아니고

게시물에 대한 첫 댓글 작성 부문을 보이지 않게 해서 댓글을 작성할 수 없도록했다.

좀더 제대로 구현하려면 컨트롤 처리도 필요하고

댓글 리스트(replyList)도 보이지 않게 하면 된다.


그림과 같이 웹 브라우저에서 접속해 보면 

http://localhost:8080/board/board9List?bgno=2로 접속하면

공지사항이란 게시판 제목이 출력되고

http://localhost:8080/board/board9List?bgno=3으로 접속하면

일반게시판이라는 제목이 출력된다.

또, 공지사항은 글쓰기 버튼이 보이지 않고

일반 게시판은 글쓰기 버튼이 보인다.


글쓰기, 글읽기에서도 적용된 내용을 확인해 보길 바란다.



2019 년에 기능을 보강하면서 문서를 새로 작성하였습니다.




gu-upload는 HTML5 기반으로 파일을 업로드하는 JavaScript 라이브러리로

IE10 이상이나 Firefox, Chrome등 에서 작동한다.

IE9 이하에서는 SWFUpload를 이용하였다.

js 폴더에 있는 guupload 폴더를 복사해서 사용하거나

github에서 다운받아 사용하고자 하는 폴더에 복사한다.


개발하는 자신의 웹 페이지의 Head에

다음과 같이 CSS와 JavaScript를 포함한다.

<link rel="stylesheet" type="text/css" href="js/guupload/css/guupload.css"/>

<script type="text/javascript" src="js/guupload/guUploadManager.js"></script>

guupload를 복사할 경로를 설정해 줘야 한다.

예제에서는 js 폴더 하위에 있기 때문에

위 코드와 같이 각 경로가 js로 시작하고 있다.


웹 페이지 실행이 완료되면(window.onload)

guUploadManager를 생성하도록(new) 한다.

guUploadManager는 화면 구성과

웹 브라우저에 따라 guuplaod, swfupload를 실행하도록 하는 역할 등을 한다.

<script type="text/javascript">
var guManager=null;

window.onload = function() {
    var option = {
            listtype: "thumbnail",
            fileid: "guupload",
            uploadURL: "upload",
            form: document.form1
    }
    guManager = new guUploadManager(option);
}   

function formSubmit(){
    guManager.uploadFiles();
}
</script>

~~ 생략 ~~

        <tr>
            <td>Attach File</td>
            <td>
                <div id="guupload" class="guupload" style="width: 500px; height: 120px;">
                </div>
            </td>
        </tr>

생성시 파라미터는 listtype, fileid, uploadURL, form 4가지가 JSON 형태로 제공되어야 한다.

먼저 listtype은 파일 리스트 방식을 선택하는 것이다.

thumbnail로 지정하면 이미지 파일에 대하여 미리보기를 사용할 수 있다.

값을 지정하지 않으면 파일명과 파일크기가 리스트 형태로 출력된다.

fileid는 DIV id로 파일 업로드 라이브러리를 그리는 div를 의미하며,

지정된 DIV에 사용자가 보는 화면을 구성하게 된다.

uploadURL은 파일을 업로드 하면

서버에서 전송 받아 실제 파일로 저장하는 URL, 즉 컨트롤을 의미한다.

form은 제목, 내용, 파일 등 게시글과 관련된 항목을 가지고 있는 form 태그를 의미한다.

form은 사용자가 커밋을 하게 되면 formSubmit함수를 호출하여

파일을 먼저 서버에 저장하고,

파일 전송이 완료되면 guupload가 대신 submit을 하기 위한 것이다.


guuplaod는 파일명(filename), 실제 파일명(realname), 파일크기(filesize)의 세가지 정보를 제공한다.

실제 파일명(realname)는 파일을 서버에 저장하고 반환 받은 파일명을 의미한다.

이 값들은 다수의 파일을 전송 하기 때문에 콤마(,)로 구분하여 전송된다.

따라서 다음 코드와 같이 split로 구별하여 처리한다.

<%
    String filename = request.getParameter("filename");
    String realname = request.getParameter("realname");
    String filesize = request.getParameter("filesize");
    String[] reallist = realname.split(",");
    String[] filelist = filename.split(",");
    String[] sizelist = filesize.split(",");

    for (int i=0; i<filelist.length; i++) {
        out.println(filelist[i] + " : " + reallist[i] + " : " + sizelist[i] + "<br/>");
    }               
%>

upload_save.jsp


제공되는 디자인이 맘에 들지 않고, CSS를 알고 있다면

guupload.css 파일에서 수정하여 사용하면 된다.

대부분의 디자인을 수정할 수 있는 CSS 클래스를 포함하고 있다.

gu-upload는 HTML5 기반으로 파일을 업로드하는 JavaScript 라이브러리이다.

여기서는 gu-upload를 사용하는 예제 설치 방법을 설명한다.


먼저, Eclipse에서 다음 주소로 github 소스를 다운로드 받는다.

https://github.com/gujc71/guupload_sample.git

예제 소스는 JDK 1.7, Spring 4, Maven 환경에서 작성되었다.

gu-upload는 JavaScript 라이브러리이기 때문에

서버는 Struts, PHP, 닷넷(.NET) 등 무엇이든 관계 없지만 

개인적인 편의로 Java Spring 기반으로 예제를 작성하였다.


프로젝트를 Eclipse Tomcat에 추가 해서 실행한 후

다음과 같이 웹브라우저로 접속하면 데모 화면을 볼 수 있다.

http://localhost:8080/guupload/

예제는 게시판(자료실) 글 등록 화면처럼 구성하였다.

먼저, 예제 1은 IE 9 이하(호환성 모드)의 웹 브라우저에서 실행되는 것으로 예제는 IE8로 지정하였다.

<meta http-equiv="X-UA-Compatible" content="IE=8" />

내부적으로는 SWFUpload가 실행 된다.

예제 2는 IE 10 이상이나 Firefox, Chrome등(HTML 5)에서 실행되는 것으로

Gu-Upload의 List모드가 실행된 예제이다.

탐색기에서 파일을 끌어다 놓을 수 있다(Drag & Drop).

예제 3은 Gu-Upload의 Thumbnail모드가 실행된 예제이다.

이미지를 첨부한 경우 미리보기를 할 수 있다.


Submit 버튼을 눌러 서버로 파일을 전송해서 저장하려면

Upload.java 파일에 설정된 저장 경로를 수정해야 한다.

기본으로 D 드라이브가 지정되어 있으니 자신의 디렉터리에 맞추어 수정해야 한다.

   @RequestMapping(value = "/upload")
    public void upload(HttpServletResponse response, HttpServletRequest request, @RequestParam("Filedata") MultipartFile Filedata) {
           SimpleDateFormat df = new SimpleDateFormat("yyyyMMddHHmmssSSS");
           String newfilename = df.format(new Date()) + Integer.toString((int) (Math.random()*10));
          
           File f = new File("d:\\" + newfilename);
           try {
               Filedata.transferTo(f);
               response.getWriter().write(newfilename);
           } catch (IllegalStateException | IOException e) {
               e.printStackTrace();
           }
    }

guupload_sample\src\main\java\gu\Upload.java

업로드 컨트롤에서는 날자와 시간으로 파일을 저장한다.

파일 확장자가 없이 새로운 이름으로 저장되는데

보안을 위해 실제 파일명(realname), 파일명(filename)을 구분하여 처리하기 때문이다.

자세한 내용은 블로그의 자료실 관련 내용을 참고하면 된다.


Submit 버튼을 누르면 upload_save.jsp 가 실행되어

서버로 전송된 값을 확인할 수 있다.


서버로 전송된 값은

파일명(filename), 실제 서버에 저장한 파일명(realname), 파일 크기(filesize) 이다.


멀티 게시판은 말 그대로 게시판이 많다는 의미로,

비슷하게 생기고 비슷한 기능을 가진

공지사항, 일반 게시판, FAQ, 커뮤니티 등을 하나의 게시판으로 구현하는 것이다.

또는 토론 주제별로 게시판을 만들어 사용할 수도 있다.

멀티 게시판이 적용된 게시판은

화면은 리스트, 글읽기, 글쓰기/수정 등의 화면이 공통으로 사용되고,

기능도 유사하기 때문에 쉽게 구현할 수 있다.

게시물을 구별할 수 있는 구별자만 있으면 된다.

즉, 게시물을 구별할 수 있는 필드를 추가해주고

다음 그림과 같이 이 필드의 값을 항상 가지고 다니면 된다.

이러한 구분 필드를 여기에서는 게시판 그룹 번호(bgno)라고 명명했다.



- 글 리스트는 그룹 번호(bgno)에 해당하는 내용들만 조회해서 출력하고,
  읽기/쓰기로 넘어갈 때 그룹 번호(bgno)를 넘겨 준다.

- 글 읽기는 게시물 번호(brdno)에 해당하는 글을 조회해서 출력하고,
  그룹 번호는 전달(게시물 삭제/수정)을 위해서 가지고 있는다.

- 글 쓰기/수정은 그룹 번호 필드를 추가해서 저장하고, 그룹 번호를 가지고 글 리스트로 돌아간다.

- 글 삭제는 게시물 번호에 해당하는 글을 지우고, 그룹 번호를 가지고 글 리스트로 돌아간다.

이상의 내용을 통해 다시 강조하면

링크를 통해 움직일 때

항상 그룹 번호(bgno)를 넘겨주거나 넘겨주기 위해 가지고 있어야 한다.

따라서, 그룹 번호만 신경 쓰면 되기 때문에 매우 간단하게 개발할 수 있다.


프로그램을 작성하기 전에 다음 SQL문을 이용하여

그룹 번호(bgno)를 테이블(tbl_board)에 추가한 뒤 1로 채워준다.

기존 게시판 데이터는 모두 1로 저장하겠다는 의미이다.

             ALTER TABLE TBL_BOARD ADD BGNO INTEGER;

    UPDATE TBL_BOARD SET BGNO = 1;


BoardVO클래스에도 다음과 같이 그룹 번호(bgno) 변수를 추가한다.

public class BoardVO {

    private String bgno;
    private String brdno;
    private String brdtitle;

~~ 생략 ~~
    public String getBgno() {
        return bgno;
    }

    public void setBgno(String bgno) {
        this.bgno = bgno;
    }
~~ 생략 ~~

BoardVO.java



'Java > 게시판 8: 멀티게시판' 카테고리의 다른 글

2. 글 리스트  (0) 2016.06.04
3. 글 읽기 / 삭제  (0) 2016.06.04
4. 글 쓰기/수정  (0) 2016.06.04

기본적으로 글 리스트는 그룹 번호(bgno)에 해당하는 내용들만 조회하면 된다.

따라서 다음 코드와 같이 기존 SELECT문의 WHERE절에 조건 하나만 추가해 주면 된다.

<sql id=”includeBoard”>
     WHERE BGNO=#{bgno} AND BRDDELETEFLAG='N'
     <if test="searchKeyword!=null and searchKeyword!='' and searchType!=''">
          <foreach item="item" index="index" collection="searchTypeArr" separator=" OR " open="AND (" close=")">
             ${item} LIKE CONCAT('%', #{searchKeyword},'%' )
          </foreach>
    </if>              
</sql>

board8.xml

이렇게 수정한 후 웹 브라우저에서 다음과 같이 실행하면 결과를 확인할 수 있다.

http://localhost:8080/board/board8List?bgno=1

파라미터인 그룹 번호(bgno)를 1이 아닌 다른 값을 넣어서 실행해 보면

데이터가 유무에 따라 다른 결과를 얻는 것을 확인할 수 있다.


이 외에 그룹 번호(bgno) 값이 없을 때 처리와

글 읽기/쓰기 시 그룹 번호(bgno)값을 넘겨 주는 코드가 필요하다.

다음 코드와 같이 컨트롤에서

그룹 번호가 없는 경우(null) 1을 기본값으로 넣어줬다.

    @RequestMapping(value = "/board8List")
    public String boardList(SearchVO searchVO, ModelMap modelMap) {

       
        if (searchVO.getBgno() == null) {
            searchVO.setBgno("1");
        }
        searchVO.pageCalculate( boardSvc.selectBoardCount(searchVO) ); // startRow, endRow

        List<?> listview  = boardSvc.selectBoardList(searchVO);
       
        modelMap.addAttribute("listview", listview);
        modelMap.addAttribute("searchVO", searchVO);
       
        return "board8/BoardList";
    }

Board8Ctr.java

간단하게 처리하기 위해 이렇게 처리했을 뿐이고,

이 방식 외에도 없는 그룹 번호일 경우 오류 메시지를 출력할 수도 있다.

글쓰기 링크에 다음과 같이 그룹번호(bgno)를 파라미터로 넘겨주는 코드를 추가한다.

<a href="board8Form?bgno=<c:out value="${searchVO.bgno}"/>">글쓰기</a>

BoardList.jsp


개념상 글 리스트에서 글 읽기로 가는 링크에도

파라미터를 추가해 주는 것이 맞지만 구현하지 않았다.

모든 게시물은 그룹번호(bgno)를 가지고 저장되고

글 읽기용 SELECT문(selectBoard8One)에서

그룹번호를 가지고 오도록 처리했기 때문이다.


'Java > 게시판 8: 멀티게시판' 카테고리의 다른 글

1. 멀티 게시판 준비  (0) 2016.06.04
3. 글 읽기 / 삭제  (0) 2016.06.04
4. 글 쓰기/수정  (0) 2016.06.04

글 읽기는 게시물 번호(brdno)에 해당하는 글을 조회해서 출력하는 기능을 하는데,

그룹번호(bgno)별로 게시물 번호(brdno)를 따로 부여하는 것이 아니라서

파라미터로 받은 게시물만 조회해서 보여주면 된다.

다만, 앞서 언급한 바와 같이 그룹번호(bgno)를 파라미터로 받지 않고

데이터를 조회할 때(selectBoard8One) 그룹번호를 가지고 오도록 처리했다.

<select id="selectBoard8One" parameterType="String" resultType="gu.board8.BoardVO">
        SELECT BGNO, BRDNO, BRDTITLE, BRDWRITER, BRDMEMO, DATE_FORMAT(BRDDATE,'%Y-%m-%d') BRDDATE
          FROM TBL_BOARD
         WHERE BRDDELETEFLAG='N'
           AND BRDNO=#{brdno}
</select>

board8.xml

JSP 파일에서 게시판 기능과 관련된 돌아가기/삭제 링크에

다음과 같이 URL 파라미터로 그룹번호를 추가해 준다.

글 수정에 그룹번호를 지정하지 않는 것은

글 읽기와 마찬가지로 수정 데이터를 조회할 때 그룹번호를 같이 가지고 오기 때문이다.

<a href="board8List?bgno=<c:out value="${boardInfo.bgno}"/>">돌아가기</a>
<a href="board8Delete?bgno=<c:out value="${boardInfo.bgno}"/>&brdno=<c:out value="${boardInfo.brdno}"/>">삭제</a>
<a href="board8Form?brdno=<c:out value="${boardInfo.brdno}"/>">수정</a>

BoardRead.jsp


마지막으로

댓글 관련 기능에도 그룹번호(bgno)를 전달해야 할 것 같지만 필요하지 않다.


첫 번째 이유는 댓글은 게시 글 번호(brdno)을 대상으로 생성하기 때문이다.

해당 글 번호에 대하여 리스트를 조회하고 쓰기/수정/삭제가 이루어 진다.


두 번째 이유는 글 읽기(Board8Read)를 호출할 때

그룹번호가 없어도 되도록 구성했기 때문이다.

댓글 저장 후 다시 게시 글 읽기로 돌아올 때

게시 글 번호만 있어도 되기 때문이다.

더욱이 Ajax를 사용해 서버로 게시 글 번호와 필요 정보만 전송하면 된다.


글 삭제는 게시물 번호(brdno)에 해당하는 글을 지우고,

글 읽기에서 받은(getParameter) 그룹 번호를 가지고 글 리스트로 돌아간다(redirect).

    @RequestMapping(value = "/board8Delete")
    public String boardDelete(HttpServletRequest request) {

        String brdno = request.getParameter("brdno");
        String bgno = request.getParameter("bgno");
       
        boardSvc.deleteBoardOne(brdno);
       
        return "redirect:/board8List?bgno=" + bgno;
    }

Board8Ctr.java


'Java > 게시판 8: 멀티게시판' 카테고리의 다른 글

1. 멀티 게시판 준비  (0) 2016.06.04
2. 글 리스트  (0) 2016.06.04
4. 글 쓰기/수정  (0) 2016.06.04

글 쓰기는 리스트에서 넘겨 받은 그룹 번호(bgno)를 hidden 태그로 가지고 있다가,

서버로 전송 시 게시 글과 같이 전송되어 저장되고,

글 리스트로 돌아갈 때 파라미터로 넘겨준다.

글 수정 일 때는 테이블에서 같이 읽어와서 bgno 변수로 넘겨 준다.

이중 처리 하는 이유는 다음 코드에서 보듯이

글쓰기 일 때는 파라미터로 받아오고(getParameter),

수정 일 때는 테이블에서 읽어오기 때문이다(boardInfo.getBgno()).

    @RequestMapping(value = "/board8Form")
    public String boardForm(HttpServletRequest request, ModelMap modelMap) {
        String bgno = request.getParameter("bgno");
        String brdno = request.getParameter("brdno");
        if (brdno != null) {
            BoardVO boardInfo = boardSvc.selectBoardOne(brdno);
            List<?> listview = boardSvc.selectBoard8FileList(brdno);
       
            modelMap.addAttribute("boardInfo", boardInfo);
            modelMap.addAttribute("listview", listview);
            bgno = boardInfo.getBgno();
        }
        modelMap.addAttribute("bgno", bgno);
       
        return "board8/BoardForm";
    }

Board8Ctr.java

JSP에서는 다음 코드와 같이

컨트롤에서 넘겨 받은 그룹 번호(bgno)를 hidden 태그로 가지고 있도록 하면 된다.

<input type="hidden" name="bgno" value="<c:out value="${bgno}"/>">
<input type="hidden" name="brdno" value="<c:out value="${boardInfo.brdno}"/>">
<a href="#" onclick="fn_formSubmit()">저장</a>

Board8Ctr.jsp

글을 저장하는 컨트롤(board8Save)이나 서비스에서 특별히 처리할 것은 없다.

그룹 번호(bgno)는 다른 게시물 정보(BoardVO boardInfo)와 같이 컨트롤로 전송되기 때문에

저장 SQL문만 다음과 같이 수정하면 된다.

<insert id="insertBoard8" parameterType="gu.board8.BoardVO" >
        <selectKey resultType="String" keyProperty="brdno" order="BEFORE">
            SELECT IFNULL(MAX(BRDNO),0)+1 FROM TBL_BOARD
        </selectKey>
   
        INSERT INTO TBL_BOARD(BGNO, BRDNO, BRDTITLE, BRDWRITER, BRDMEMO, BRDDATE, BRDHIT, BRDDELETEFLAG)
        VALUES (#{bgno}, #{brdno}, #{brdtitle}, #{brdwriter}, #{brdmemo}, NOW(), 0, 'N' )
</insert>

Board8Ctr.xml

글이 저장되고 나면 다음과 같이

글 리스트로 돌아갈 때(redirect) 파라메터로 그룹 번호(bgno)를 넘겨준다.

그렇지 않으면 오류가 발생하거나 등록한 글을 찾을 수 없다.

    @RequestMapping(value = "/board8Save")
    public String boardSave(HttpServletRequest request, BoardVO boardInfo) {
        String[] fileno = request.getParameterValues("fileno");
       
        FileUtil fs = new FileUtil();
        List<FileVO> filelist = fs.saveAllFiles(boardInfo.getUploadfile());

        boardSvc.insertBoard(boardInfo, filelist, fileno);

        return "redirect:/board8List?bgno=" + boardInfo.getBgno();
    }

Board8Ctr.java


'Java > 게시판 8: 멀티게시판' 카테고리의 다른 글

1. 멀티 게시판 준비  (0) 2016.06.04
2. 글 리스트  (0) 2016.06.04
3. 글 읽기 / 삭제  (0) 2016.06.04

+ Recent posts