Keep going

Ajax 댓글 처리 본문

Records/Spring Framework

Ajax 댓글 처리

코딩천재홍 2021. 3. 18. 01:41

 

  • REST 방식을 가장 많이 사용하는 형태는 역시 브라우저나 모바일 APP 등에서 Ajax를 이용해서 호출하는 것이다.
  • 댓글은 데이터베이스 상에서 전형적인 1:N의 관계로 구성된다.
  • 하나의 게시물에 여러 개의 댓글을 추가하는 형태로 구성하고, 화면은 조회 화면상에서 별도의 화면 이동 없이 처리하기 때문에 Ajax를 이용해서 호출한다.

17.1 댓글 처리를 위한 영속 영역

댓글을 추가하기 위해서 댓글 구조에 맞는 테이블을 설계한다. 댓글 테이블은 tbl_reply라는 이름의 테이블로 지정해서 생성한다.

create table tbl_reply (
    rno number(100),
    bno number(100not null,
    reply varchar2(1000not null,
    replyer varchar2(50not null,
    replyDate date default sysdate,
    updateDate date default sysdate
);
 
create sequence seq_reply;
 
alter table tbl_reply add constraint pk_reply primary key(rno);
 
alter table tbl_reply add constraint fk_reply_board
foreign key (bno) references tb1_board(bno);
cs

tbl_reply 테이블은 bno라는 칼럼을 이용해서 해당 댓글이 어떤 게시물의 댓글인지를 명시하도록 한다.

댓글 자체는 단독으로 CRUD가 가능하므로, 별도의 PK를 부여하도록 하고 외래키(FK) 설정을 통해서 tb1_board 테이블을 참조하도록 설정한다.

 

 

17.1.1 ReplyVO 클래스/ ReplyMapper 클래스와 XML/ ReplyMapper 테스트

 

  • ReplyVO 클래스의 추가
package org.zerock.domain;
 
import java.util.Date;
 
import lombok.Data;
 
@Data
public class ReplyVO {
    private Long rno;
    private Long bno;
    
    private String reply;
    private String replyer;
    private Date replyDate;
    private Date updateDate;
}
 
cs

 

 

 

  • ReplyMapper 인터페이스와 XML 처리
package org.zerock.mapper;
 
public interface ReplyMapper {
    
}
cs

댓글에 대한 처리 역시 화면상에서 페이지 처리가 필요할 수 있으므로 Criteria를 이용해서 처리하도록 한다.

실제 SQL은 src/main/resources 폴더 아래 ReplyMapper.xml 파일을 작성해서 처리한다.

 

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 
<mapper namespace="org.zerock.mapper.ReplyMapper">
 
 
</mapper>
cs

XML에서는 tbl_reply 테이블에 필요한 SQL을 작성한다.

페이징 처리에서는 조금 더 신경써야 하는 내용이 있으므로 우선은 특정 게시물 번호에 해당하는 모든 댓글을 가져오는 형태로 작성한다. 

 

 

 

  • ReplyMapper 테스트 
package org.zerock.mapper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
 
import lombok.Setter;
import lombok.extern.log4j.Log4j;
 
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("file:src/main/webapp/WEB-INF/spring/root-context.xml")
//Java Config
//@ContextConfiguration(classes={org.zerock.config.RootConfig.class})
@Log4j
public class ReplyMapperTests {
    @Setter(onMethod_ = {@Autowired})
    private ReplyMapper mapper;
    
    @Test
    public void testMapper( ) {
        log.info(mapper);
    }
}
cs

 

 

 

17.1.2 CRUD 작업

 

  • Create
    • 외래키를 사용하는 등록 작업
    • 테스트 코드는 기존에 존재하는 게시물 일부의 bno(게시물 번호)를 사용해서 작성 - 실제 데이터베이스에 있는 번호여야 한다.
  • Read
    • 테스트 코드는 tb1_reply에 있는 번호 중에서 하나를 이용해서 확인한다.
  • delete
    • 특정 댓글의 삭제는 댓글의 번호만으로 처리
  • update
    • 댓글의 수정은 현재의 tb1_reply 테이블의 구조에서는 댓글의 내용과 최종 수정시간을 수정

 

 

  • ReplyMapper 인터페이스
package org.zerock.mapper;
 
import org.zerock.domain.ReplyVO;
 
public interface ReplyMapper {
    
    //등록
    public int insert(ReplyVO vo); 
    
    // 조회
    public ReplyVO read(Long bno);
    
    //삭제
    public int delete (Long bno);
    
    //수정
    public int update(ReplyVO reply);
}
 
cs

 

 

 

  • ReplyMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 
<mapper namespace="org.zerock.mapper.ReplyMapper">
 
    <insert id="insert">
        insert into tbl_reply(rno, bno, reply, replyer)
        values (seq_reply.nextval, #{bno}, #{reply}, #{replyer})
    </insert>
    
    <select id="read" resultType="org.zerock.domain.ReplyVO">
        select * from tbl_reply where rno=#{rno}
    </select>
    
    <delete id="delete">
        delete from tbl_reply where rno=#{rno}
    </delete>
    
    <update id="update">
        update tbl_reply set reply=#{reply}, updatedate = sysdate where rno=#{rno}
    </update>
    
</mapper>
cs

 

 

 

  • ReplyMapper 테스트
package org.zerock.mapper;
import java.util.stream.IntStream;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import org.zerock.domain.ReplyVO;
 
 
import lombok.Setter;
import lombok.extern.log4j.Log4j;
 
@RunWith(SpringRunner.class)
@ContextConfiguration("file:src/main/webapp/WEB-INF/spring/root-context.xml")
//Java Config
//@ContextConfiguration(classes={org.zerock.config.RootConfig.class})
@Log4j
public class ReplyMapperTests {
    //테스트 전에 해당 번호 게시물이 존재하는지 꼭 확인
    private Long[] bnoArr = {3145744L, 3145743L, 3145741L, 3145740L, 3145739L };
        
    @Setter(onMethod_ = @Autowired)
    private ReplyMapper mapper;
    
    @Test 
    public void testCreate() {
        IntStream.rangeClosed(1,  10).forEach(i-> {
            ReplyVO vo = new ReplyVO();
            
            vo.setBno(bnoArr[i%5]);
            vo.setReply("댓글 테스트 " + i);
            vo.setReplyer("replyer" + i);
            
            mapper.insert(vo);
        });
    }
    
    @Test
    public void testRead() {
        Long targetRno = 5L;
        ReplyVO vo = mapper.read(targetRno);
        log.info(vo);
    }
    
    @Test
    public void testDelete() {
        Long targetRno = 2L;
        mapper.delete(targetRno);
    }
    
    
    @Test
    public void testUpdate() {
        Long targetRno = 10L;
        ReplyVO vo = mapper.read(targetRno);
        
        vo.setReply("Update Reply ");
        int count = mapper.update(vo);
        
        log.info("UPDATE COUNT: " + count);
    }
    
    @Test
    public void testMapper( ) {
        log.info(mapper);
    }
}
cs

 

 

 

17.1.3 @Param 어노테이션과 댓글 목록

댓글의 목록과 페이징 처리는 기존의 게시물 페이징 처리와 유사하지만, 추가적으로 특정한 게시물의 댓글들만을 대상으로 하기 때문에 추가로 게시물의 번호가 필요하게 된다.

 

MyBatis 두 개 이상이 데이터를 파라미터로 전달하는 방법

  1. 별도의 객체로 구성하는 방식
  2. Map을 이용하는 방식
  3. Param을 이용해서 이름을 사용하는 방식 (가장 간단함)

@Param의 속성값은 MyBatis에서 SQL을 이용할 때 #{ }의 이름으로 사용이 가능하다.

 

 

  • ReplyMapper 인터페이스
public List<ReplyVO> getListWithPaging (
            @Param("cri") Criteria cri,
            @Param("bno") Long bno);
cs

 

 

 

  • ReplyMapper.xml 파일
<select id = "getListWithPaging" resultType = "org.zerock.domain.ReplyVO">
        select rno, bno, reply, replyer, replyDate, updatedate
        from tbl_reply
        where bno =#{bno}
        order by rno asc
</select>
cs

 

 

 

  • ReplyMapper 테스트
@Test
    public void testList() {
        Criteria cri = new Criteria();
        List<ReplyVO> replies = mapper.getListWithPaging(cri, bnoArr[0]); //3145743L
        replies.forEach(reply->log.info(reply));
    }
cs

 

 


 

17.2 서비스 영역과 Controller 처리

서비스 영역과 Controller 처리는 ReplyService 인터페이스와 ReplyServiceImpl 클래스를 작성한다.

 

- 비즈니스 계층

 

  • ReplyService 인터페이스
package org.zerock.service;
 
import java.util.List;
 
import org.zerock.domain.Criteria;
import org.zerock.domain.ReplyVO;
 
public interface ReplyService {
    
    public int register(ReplyVO vo);
    public ReplyVO get(Long rno);
    public int modify(ReplyVO vo);
    public int remove(Long rno);
    public List<ReplyVO> getList(Criteria cri, Long bno);
}
cs

 

 

  • ReplyServiceImpl 클래스
package org.zerock.service;
 
import java.util.List;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.zerock.domain.Criteria;
import org.zerock.domain.ReplyVO;
import org.zerock.mapper.ReplyMapper;
 
import lombok.Setter;
import lombok.extern.log4j.Log4j;
 
@Service
@Log4j
public class ReplyServiceImpl implements ReplyService {
 
    @Setter(onMethod_ = @Autowired )
    private ReplyMapper mapper;
    
    @Override
    public int register(ReplyVO vo) {
        log.info("register......" + vo);
        return mapper.insert(vo);
    }
 
    @Override
    public ReplyVO get(Long rno) {
        log.info("get......" + rno);
        return mapper.read(rno);
    }
 
    @Override
    public int modify(ReplyVO vo) {
        log.info("modify......" + vo);
        return mapper.update(vo);
    }
 
    @Override
    public int remove(Long rno) {
        log.info("remove......" + rno);
        return mapper.delete(rno);
    }
 
    @Override
    public List<ReplyVO> getList(Criteria cri, Long bno) {
        log.info("get Reply List of a Board " + bno);
        return mapper.getListWithPaging(cri, bno);
    }
 
}
 
cs

 

 

 

-프레젠테이션 계층

 

작업 URL HTTP 전송 방식
등록 /replies/new POST
조회 /replies/:rno GET
삭제 /replies/:rno DELETE
수정 /replies/:rno PUT or PATCH
페이지 /replies/pages/:bno/:page GET
  • REST 방식으로 동작하는 URL을 설계할 때는 PK를 기준으로 작성하는 것이 좋다.
  • PK만으로 조회, 수정, 삭제가 가능하기 때문이다.
  • 다만 댓글의 목록은 PK를 사용할 수 없기 때문에 파라미터로 필요한 게시물의 번호(bno)와 페이지 번호 정보(page)들을 URL에서 표현하는 방식을 사용한다.

 

 

  • ReplyController 클래스
package org.zerock.controller;
 
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.zerock.service.ReplyService;
 
import lombok.AllArgsConstructor;
import lombok.extern.log4j.Log4j;
 
@RequestMapping("/replies/")
@RestController
@Log4j
@AllArgsConstructor
public class ReplyController {
 
    private ReplyService service;
 
}
 
cs

 

 

 

 

  • 등록 작업과 테스트
    • REST 방식으로 처리할 때 주의해야 하는 점은 브라우저나 외부에서 서버를 호출할 때 데이터의 포맷과 서버에서 보내주는 데이터의 타입을 명확히 설계해야 하는 점이다.
package org.zerock.controller;
 
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.zerock.domain.ReplyVO;
import org.zerock.service.ReplyService;
 
import lombok.AllArgsConstructor;
import lombok.extern.log4j.Log4j;
 
@RequestMapping("/replies/")
@RestController
@Log4j
@AllArgsConstructor
public class ReplyController {
 
    private ReplyService service;
    
    @PostMapping(value = "/new", consumes="application/json", produces= {MediaType.TEXT_PLAIN_VALUE})
    public ResponseEntity<String> create(@RequestBody ReplyVO vo) {
        
        log.info("ReplyVO: " + vo);
        int insertCount = service.register(vo);
        
        log.info("Reply INSERT COUNT: " + insertCount);
        
        return insertCount==1 ? new ResponseEntity<> ("success", HttpStatus.OK)
                : new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
    }
 
}
 
cs

- create( )는 @PostMapping 으로 POST 방식으로만 동작

- consumes 와 produces를 이용해서 JSON 방식의 데이터만 처리하고, 문자열을 반환하도록 설계

- create( )의 파라미터는 @RequestBody를 적용해서 JSON 데이터를 ReplyVO 타입으로 변환하도록 지정

- create( )는 내부적으로 ReplyServiceImpl을 호출해서 register( )를 호출하고, 댓글이 추가된 숫자를 확인해서 브라우저에서 '200 ok' 혹은 '500 Internal Server Error' 를 반환하도록 한다. 

 

 

 

 

 

 

 

  • 특정 게시물의 댓글 목록 확인
@GetMapping(value="/pages/{bno}/{page}", produces= {
            MediaType.APPLICATION_XML_VALUE, MediaType.APPLICATION_JSON_VALUE
    })
    public ResponseEntity<List<ReplyVO>> getList(
            @PathVariable("page"int page,
            @PathVariable("bno") Long bno
            ) {
        log.info("getList...............");
        Criteria cri = new Criteria(page, 10);
        log.info(cri);
        return new ResponseEntity<>(service.getList(cri, bno), HttpStatus.OK);
    }
cs

- getList( )는 Criteria를 이용해서 파라미터를 수집하는데 page 값은 Criteria를 생성해서 직접 처리해야 한다.

- 게시물의 번호는 @Pathvariable을 이용해서 파라미터로 처리한다.

 

 

 

 

 

  • 댓글 삭제/조회
    • RestController의 댓글의 수정/ 삭제/ 조회는 JSON이나 문자열을 반환하도록 설계한다.
@GetMapping(value="/{rno}", produces= {
            MediaType.APPLICATION_XML_VALUE, MediaType.APPLICATION_JSON_UTF8_VALUE
    })
    public ResponseEntity<ReplyVO> get(@PathVariable("rno") Long rno ) 
    {
            
        log.info("get : " + rno);
        return new ResponseEntity<>(service.get(rno), HttpStatus.OK);
    }
    
    
    @DeleteMapping(value="/{rno}", produces = {
            MediaType.TEXT_PLAIN_VALUE
    }) 
    public ResponseEntity<String> remove(@PathVariable("rno") Long rno){
        log.info("remove : " + rno);
        return service.remove(rno) == 1
                ? new ResponseEntity<>("success", HttpStatus.OK)
                        : new ResponseEntity<> (HttpStatus.INTERNAL_SERVER_ERROR);
    }
cs

 

 

 

 

  • 댓글 수정
    • 댓글 수정은 JSON 형태로 전달되는 데이터와 파라미터로 전달되는 댓글 번호를 처리한다.
@RequestMapping(method= {RequestMethod.PUT, RequestMethod.PATCH},
            value="/{rno}",
            consumes="application/json",
            produces = {MediaType.TEXT_PLAIN_VALUE})
    public ResponseEntity<String> modify(
            @RequestBody ReplyVO vo,
            @PathVariable("rno") Long rno) {
        vo.setRno(rno);
        log.info("rno : " + rno);
        log.info("modify : " + vo);
        
        return service.modify(vo) == 1
                ? new ResponseEntity<> ("success", HttpStatus.OK)
                        : new ResponseEntity<> (HttpStatus.INTERNAL_SERVER_ERROR);
    }
cs

- 댓글 수정은 'PUT' 방식이나 'PATCH' 방식을 이용하도록 처리하고, 실제 수정되는 데이터는 JSON 포맷이므로 @RequestBody를 이용해서 처리한다.

- @RequestBody로 처리되는 데이터는 일반 파라미터나 @PathVariable 파라미터를 처리할 수 없기 때문에 직접 처리해 주는 부분을 주의해야 한다.

 

 

 


17.3 JavaScript 준비

 

  • JavaScript의 모듈화

    • jQuery는 막강한 기능과 다양한 플러그인을 통해서 많은 프로젝트에서 기본으로 사용된다.
    • 특히 Ajax를 이용하는 경우에는 jQuery의 함수를 이용해서 너무나 쉽게 처리할 수 있기 때문에 많이 사용한다.
    • 화면 내에서 JavaScript 처리를 하다 보면 코드가 섞여 유지보수가 함기 힘들다. 이런 경우를 대비해 JavaScript를 하나의 모듈처럼 구성하는 방식을 이용하는 것이 좋다.
    • JavaScript에서 가장 많이 사용하는 패턴 중 하나는 모듈 패턴이다. 모듈 패턴은 쉽게 말해서 관련 있는 함수들을 하나의 모듈처럼 묶음으로 구성하는 걸을 의미한다. 가장 대표적인 방법은 클로저를 이용하는 방법이다.

 

 

  • reply.js (조회 페이지에서 사용)
console.log("Reply Module......");
 
var replyService = (function(){
    
    return {name : "AAAA"};
})();
cs

replyService라는 변수에 name 이라는 속성에 'AAAA'라는 속성값을 가진 객체가 할당된다.

 

 

  • board/get.jsp
<script type ="text/javascript" src="/resources/js/reply.js"></script>
 
<script type="text/javascript">
    $(document).ready(function() {
        console.log(replyService);
    });
</script>
cs

 

 

 

 

  • reply.js 등록 처리

    • 모듈 패턴은 즉시 실행하는 함수 내부에서 필요한 메서드를 구성해서 객체를 구성하는 방식이다.

 

  • reply.js
console.log("Reply Module......");
 
var replyService = (function(){
    
    function add(reply, callback){
        console.log("reply.............");
    }
    return {add:add};
})();
cs

 

개발자 도구에서 replyService 객체의 내부에는 add 라는 메서드가 존재하는 형태로 비이게 된다.

외부에서는 replyService.add(객체, 콜백)를 전달하는 형태로 호출할 수 있는데, Ajax 호출은 감춰져 있기 때문에 코드를 좀 더 깔끔하게 작성할 수 있다.

 

 

  • reply.js (add 함수 Ajax를 이용해서 POST 방식으로 호출하는 코드)
console.log("Reply Module...............");
var replyService = (function() {
    function add(reply, callback, error) {
        console.log("add reply.........");
        $.ajax({
            type: 'post',
            url: '/replies/new',
            data : JSON.stringify(reply),
            contentType : "application/json; charset=utf-8",
            success: function(result, status, xhr) {
                if(callback) {
                    callback(result);
                }
            },
            error : function(xhr, status, er){
                if(error) {
                    error(er);
                }
            }
        })
    }
    return {
        add:add
    };
})();
cs

add( )에서 주의 깊게 봐야 하는 부분

- 'application/json;charset=utf-8' 방식으로 전송한다는 점

- 파라미터로 callback과 error를 함수로 받을 것이라는 점

 

만일 Ajax 호출이 성공하고, callback 값으로 적절한 함수가 존재한다면 해당 함수를 호출해서 결과를 반영하는 방식

JavaScript는 특이하게 함수의 파라미터 개수를 일치시킬 필요가 없다.

 

 

  • board/get.jsp (테스트 위해 replyService.add( ) 호출)
<script type="text/javascript" src="/resources/js/reply.js"></script>
    
    <script type="text/javascript">
    console.log("=================");
    console.log("JS TEST");
    
    var bnoValue='<c:out value="${board.bno}"/>';
    //for replyService add test
    replyService.add(
            {reply: "JS Test", replyer:"tester", bno:bnoValue}
            ,
            function(result) {
                alert("RESULT : " + result);
            }
    );
    </script>
cs

get.jsp 내부에서는 Ajax 호출은 replyService라는 이름의 객체에 감춰져 있으므로 필요한 파라미터들만 전달하는 형태로 간결해 진다.

replyService의 add( )에 던져야 하는 파라미터는 JavaScript의 객체 타입으로 만들어서 전송해 주고, Ajax 전송 결과를 처리하는 함수를 파라미터로 같이 전달한다.

 

 

데이터 베이스에는 정상적으로 댓글이 추가되었고, 브라우저에도 성공했다는 경고창이 보인다.

브라우저에서 JSON 형태로 데이터가 전송되고 있는 것을 확인할 수 있다.

 

 

 

 

  • 댓글의 목록 처리 getJSON() 사용

    • 댓글 목록을 페이징 처리하기전, 전체 댓글을 가져오는 형태로 구현한다.
  • reply.js
console.log("Reply Module...............");
var replyService = (function() {
    function add(reply, callback, error) {
        console.log("add reply.........");
        $.ajax({
            type: 'post',
            url: '/replies/new',
            data : JSON.stringify(reply),
            contentType : "application/json; charset=utf-8",
            success: function(result, status, xhr) {
                if(callback) {
                    callback(result);
                }
            },
            error : function(xhr, status, er){
                if(error) {
                    error(er);
                }
            }
        })
    }
    
    function getList(param, callback, error) {
        var bno = param.bno;
        var page = param.page || 1;
        
        $.getJSON("/replies/pages/" + bno + "/" + page + ".json",
            function(data) {
                if(callback) {
                    callback(data);
                }
            }).fail(function(xhr, status, err) {
                if(error) {
                    error();
                }
            });
    }
    return {
        add:add,
        getList:getList
    };
})();
 
cs

reply.js에서는 Ajax 호출을 담당하므로, jQuery의 getJSON( )을 이용해서 처리할 수 있따.

getList( )는 param 이라는 객체를 통해서 필요한 파라미터를 전달받아 JSON 목록을 호출한다.

 

 

  • get.jsp (해당 게시물의 모든 댓글을 가져오는지 확인)
<script type="text/javascript" src="/resources/js/reply.js"></script>
    
    <script type="text/javascript">
    console.log("=================");
    console.log("JS TEST");
    
    var bnoValue='<c:out value="${board.bno}"/>';
    //for replyService add test
    replyService.getList({bno:bnoValue, page:1}, function(list) {
        for(var i=0, len=list.length || 0; i<len; i++) {
            console.log(list[i]);
        }
    });
    </script>
cs

 

 

 

  • 댓글 삭제와 갱신

 

  • reply.jsp - remove( ) 함수 추가
console.log("Reply Module...............");
var replyService = (function() {
    function add(reply, callback, error) {
        console.log("add reply.........");
        $.ajax({
            type: 'post',
            url: '/replies/new',
            data : JSON.stringify(reply),
            contentType : "application/json; charset=utf-8",
            success: function(result, status, xhr) {
                if(callback) {
                    callback(result);
                }
            },
            error : function(xhr, status, er){
                if(error) {
                    error(er);
                }
            }
        })
    }
    
    function getList(param, callback, error) {
        var bno = param.bno;
        var page = param.page || 1;
        
        $.getJSON("/replies/pages/" + bno + "/" + page + ".json",
            function(data) {
                if(callback) {
                    callback(data);
                }
            }).fail(function(xhr, status, err) {
                if(error) {
                    error();
                }
            });
    }
    
    function remove(rno, callback, error) {
        $.ajax( {
            type: 'delete',
            url : '/replies/' + rno,
            success : function(result, status, xhr) {
                if(callback) {
                    callback(result);
                }
            },
            error : function(xhr, status, er) {
                if(error) {
                    error(er);
                }
            }
        });
    }
    return {
        add:add,
        getList:getList,
        remove:remove
    };
})();
cs

remove( )는 DELETE 방식으로 데이터를 전달하므로, $.ajax( )를 이용해서 구체적으로 type 속성으로 'delete'를 지정한다.

실제 데이터베이스에 있는 댓글 번호를 이용해서 정상적으로 댓글이 삭제되는지를 확인한다.

 

//for replyService Remove test
    replyService.remove(23function(count) {
        console.log(count);
        
        if(count === "success") {
            alert("REMOVED");
        }
    }, function(err) {
        alert('ERROR...');
    });
    
    </script>
cs

 

 

 

  • 댓글 수정

    • 댓글 수정은 수정하는 내용과 함께 댓글의 번호를 전송한다. 

 

  • reply.js
function update(reply, callback, error) {
        console.log("RNO : " + reply.rno);
        $.ajax({
            type: 'put',
            url : '/replies/' + reply.rno,
            data : JSON.stringify(reply),
            contentType : "application/json; charset=utf-8",
            success : function(result, status, xhr) {
                if(callback) {
                    callback(result);
                }
            },
            error : function(xhr, status, er) {
                if(error) {
                    error(er);
                }
            }
        });
    }
cs

 

  • get.jsp
//for replyService Update test
    replyService.update({
        rno:66,
        bno: bnoValue,
        reply: "js Modified Reply...."
    }, function(result) {
        alert("수정 완료...");
    });
cs

 

 

 

  • 댓글 조회 처리 (GET 방식으로 동작)

 

  • reply.js
function get(rno, callback, error) {
        $.get("/replies/" + rno + ".json", function(result) {
            if(callback) {
                callback(result);
            }
        }).fail(function(xhr, status, err) {
            if(error) {
                error();
            }
        });
    }
cs

 

 

  • get.jsp
//for replyService get test
    replyService.get(11, function(data) {
        console.log(data);
    });
cs

 

 


17.4 이벤트 처리와 HTML 처리

남은 작업은 버튼 등에서 발생하는 이벤트를 감지하고, Ajax 호출의 결과를 화면에 반영하는 것이다.

 

  • 댓글 목록 처리

    • 댓글의 목록을 위해서는 별도의 <div> 를 생성해서 처리해야 한다.
    • 게시글과 관련된 화면 아래쪽에 <div>를 추가한다.
    • 댓글의 목록은 <ul> 태그 내에 <li> 태그를 이용해서 처리한다. 각 <li> 태그는 하나의 댓글을 의미하므로 수정이나 삭제 시 이를 클릭하게 된다.
    • 수정이나 삭제 시에는 반드시 댓글 번호(rno)가 필요하므로 data-rno 속성을 이용해서 처리한다.
<div class="row">
    <div class="col-lg-12">
        <div class="panel panel-default">
                <div class="panel-heading">
                    <i claass="fa fa-comments fa-fw"></i> Reply
                </div>
                <!-- /.panel-heading -->
                <div class="panel-body">
                    <ul class="chat">
                        <!-- start reply -->
                        <li class="left clearfix" data-rno='12'>
                            <div>
                                <div class="header">
                                    <strong class="primary-font">user00</strong>
                                    <small class="pull-right text-muted">2021-03-18 18:13</small>
                                </div>
                                <p>Good job!</p>
                            </div>
                        </li>
                        <!--  end reply -->
                    </ul>
                    <!--  ./end ul -->
                </div>
                <!-- ./ end row -->
            </div>
        </div>            
<!-- /.row -->
</div>
cs

 

 

- 이벤트 처리 -

  • 게시글의 조회 페이지가 열리면 자동으로 댓글 목록을 가져와서 <li> 태그를 구성해야 한다.
  • 이에 대한 처리는 $(document).ready( ) 내에서 이루어 지도록 한다.
<script type="text/javascript">
    
    $(document).ready(function() {
        var bnoValue = '<c:out value="${board.bno}"/>';
        var replyUL = $(".chat");
        
        showList(1);
        
        function showList(page) {
            replyService.getList({bno:bnoValue, page:page||1}, function(list) {
                var str = "";
                if(list == null || list.length==0) {
                    replyUL.html("");
                    return;
                }
                for(var i=0, len=list.length || 0; i<len; i++) {
                    str+= "<li class='left cleafix' data rno='"+list[i].rno+"'>";
                    str+= "    <div><div class='header'><string class='primary-font'>"+list[i].replyer+"</strong>";
                    str+= "        <small class='pull-right text-muted'>" + list[i].replyDate+"</small></div>";
                    str+= "            <p>"+list[i].reply+"</p></div></li>";
                }
                
                replyUL.html(str);
            });
        }
    });
    </script>
cs

- showList( )는 페이지 번호를 파라미터로 받도록 설계하고, 만일 파라미터가 없는 경우에는 자동으로 1페이지가 되도록 설정한다.

- 브라우저에서 DOM 처리가 끝나면 자동으로 showList( )가 호출되면서 <ul> 태그 내에 내용으로 처리된다.

- 위의 처리가 끝나면 브라우저에서 조회 페이지에는 아래와 같이 해당 게시글을 댓글 순번대로 보인다.

 

 

 

- 시간에 대한 처리 -

  • XML이나 JSON 형태로 데이터를 받을 때는 순수하게 숫자로 표현되는 시간 값이 나오게 되어 있으므로, 화면에서는 이를 변환해서 사용하는 것이 좋다.
  • 날짜 포맷의 경우 문화권마다 표기 순서 등이 다르기 때문에 화면에서 포맷을 처리하는 방식이 좋다.
  • 해당일은 시/분/초를 보여주고, 전날에 데이터들은 년/월/일 등을 보여주게 하겠다.

 

  • reply.js
function displayTime(timeValue) {
        var today = new Date();
        var gap = today.getTime() - timeValue;
        var dateObj = new Date(timeValue);
        var str= "";
        
        if(gap < (1000*60*60*24)) {
            var hh = dateObj.getHours();
            var mi = dateObj.getMinutes();
            var ss = dateObj.getSeconds();
            
            return [(hh > 9 ? '' : '0'+ hh, ':', (mi > 9 ? '' : '0'+ mi, ':', (ss>9'':'0'+ ss].join('');
        } else {
            var yy = dateObj.getFullYear();
            var mm = dateObj.getMonth() + 1//getMonth는 zero-based
            var dd = dateObj.getDate();
            return [yy, '/', (mm>9 ? '''0'+ mm, '/', (dd>9'':'0'+ dd].join('');
        }
    };
cs

displyTime( )은 Ajax에서 데이터를 가져와서 HTML을 만들어 주는 부분에 'replyService.displyTime(list[i].replyDate)'의 형태로 적용하도록 한다.

 

 

  • get.jsp
for(var i=0, len=list.length || 0; i<len; i++) {
    str+= "<li class='left cleafix' data-rno='"+list[i].rno+"'>";
    str+= "    <div><div class='header'><strong class='primary-font'>"+list[i].replyer+"</strong>";
    str+= "        <small class='pull-right text-muted'>" +replyService.displayTime(list[i].replyDate)+"</small></div>";
    str+= "            <p>"+list[i].reply+"</p></div></li>";
}
                
    
cs

작성된 displayTime( )을 적용하면 24시간이 지난 댓글은 날짜만 표시되고, 24시간 이내의 글은 시간으로 표시된다.

 

 

 

  • 새로운 댓글 처리

    • 댓글 목록 상단에 버튼을 하나 추가해 사용자들이 새로운 댓글을 추가할 수 있도록 준비한다.

 

  • get.jsp (댓글 목록 상단 버튼)
<div class="col-lg-12">
        <div class="panel panel-default">
            <div class="panel-heading">
                    <i class="fa fa-comments fa-fw"></i> Reply
                    <button id='addReplyBtn' class='btn btn-primary btn-xs pull-right'>New Reply</button>
            </div>
cs

 

 

  • get.jsp (모달창 코드)
<!-- Modal -->
    <div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
        <div class="modal-dialog">
            <div class="modal-content">
                <div class="modal-header">
                    <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
                    <h4 class="modal-title" id="myModalLabel">REPLY MODAL</h4>
                </div>
                <div class="modal-body">
                    <div class="form-group">
                        <label>Reply</label>
                        <input class="form-control" name='reply' value='New Reply!!!!'>
                    </div>
                    <div class="form-group">
                        <label>Replyer</label>
                        <input class="form-control" name='replyer' value='replyer'>
                    </div>
                    <div class="form-group">
                        <label>Reply Date</label>
                        <input class="form-control" name='replyDate' value=''>
                    </div>
                </div>
                <div class="modal-footer">
                    <button id='modalModBtn' type="button" class="btn btn-warning">Modify</button>
                    <button id='modalRemoveBtn' type="button" class="btn btn-danger">Remove</button>
                    <button id='modalRegisterBtn' type="button" class="btn btn-primary">Register</button>
                    
                    <button id='modalCloseBtn' type="button" class="btn btn-default">Close</button>
                </div>
            </div>
        </div>
    </div>
cs

모달창은 브라우저에서 댓글에 대한 여러 작업(CRUD)에서 활용할 것이므로 필요한 모든 내용을 담도록 하고 각 작업에 맞게 버튼이나 입력창이 보이거나 감춰지도록 한다.

 

 

- 새로운 댓글의 추가 버튼 이벤트 처리-

  • 댓글 목록 상단의 'New Reply'를 클릭하면 화면에서는 모달창을 보이게 처리 할 것이다.
  • 모달과 관련된 객체들은 여러 함수에서 사용해야 하므로 바깥쪽으로 빼두는 것이 좋다.
  • get.jsp 내의 댓글 추가 시작 시 버튼 이벤트 처리
var modal = $(".modal");
        var modalInputReply = modal.find("input[name='reply']");
        var modalInputReplyer = modal.find("input[name='replyer']");
        var modalInputReplyDate = modal.find("input[name='replyDate']");
        
        var modalModBtn = $("#modalModBtn");
        var modalRemoveBtn = $("#modalRemoveBtn");
        var modalRegisterBtn = $("#modalRegisterBtn");
        
        $("#addReplyBtn").on("click", function(e) {
            modal.find("input").val("");
            modalInputReplyDate.closest("div").hide();
            modal.find("button[id!='modalCloseBtn']").hide();
            
            modalRegisterBtn.show();
            $(".modal").modal("show");
        });
cs

 

 

- 댓글 등록 및 목록 갱신 -

  • 새로운 댓글의 추가는 필요한 댓글의 내용과 댓글의 작성자 항목만으로 추가해서 모달창 아래쪽의 Register 버튼을 클릭해서 처리한다.

 

  • get.jsp 내의 새로운 댓글 추가 처리
modalRegisterBtn.on("click", function(e) {
            var reply ={
                    reply: modalInputReply.val(),
                    replyer: modalInputReplyer.val(),
                    bno: bnoValue
            };
            replyService.add(reply, function (result){
                alert(result);
                modal.find("input").val("");
                modal.modal("hide");

showList(1);
            });
        });    

cs
 
 

- 댓글이 정상적으로 추가되면 경고창을 이용해서 성공했다는 사실을 알려주고, 등록한 내용으로 다시 등록할 수 없도록 입력항목을 비우고 모달 창을 닫아준다.

- 댓글이 정상적으로 처리되었지만 목록 자체는 갱신된 적이 없어 화면에서 새로 등록 된 댓글이 보이지 않으므로 다시 댓글의 목록을 갱신해야 한다.

 

 

  • 특정 댓글의 클릭 이벤트 처리

    • 댓글은 댓글의 목록에서 내용이 모두 출력되기 때문에 별도로 클릭한다는 것은 해당 댓글을 수정하거나 삭제하는 경우에 발생한다.
    • DOM에서 이벤트 리스너를 등록하는 것은 반드시 해당 DOM 요소가 존재해야만 가능하다.
    • 동적으로 Ajax를 통해서 <li> 태그들이 만들어지면 이후에 이벤트를 등록해야 하기 때문에 일반적 방식이 아닌 이벤트 위임의 형태로 작성해야 한다. 
      • 이벤트 위임 : 이벤트를 동적으로 생성되는 요소가 아닌 이미 존재하는 요소에 이벤트를 걸어주고, 나중에 이벤트의 대상을 변경해 주는 방식. jQuery는 on( )을 이용해서 쉽게 처리
    • 동적으로 생기는 요소들에 대해 파라미터 형식으로 지정한다. 

 

  • get.jsp 댓글 이벤트 처리
$(".chat").on("click", "li", function(e) {
            var rno = $(this).data("rno");
            replyService.get(rno, function(reply) {
                modalInputReply.val(reply.reply);
                modalInputReplyer.val(reply.replyer);
                modalInputReplyDate.val(replyService.displayTime(reply.replyDate)).attr("readonly", "readonly");
                modal.data("rno", reply.rno);
                
                modal.find("button[id != 'modalCloseBtn']").hide();
                modalModBtn.show();
                modalRemoveBtn.show();
                
                $(".modal").modal("show");
            });
        });
cs

 

 

 

  • 댓글의 수정/삭제 이벤트 처리

    • 삭제/ 수정 작업 모두 작업이 끝난 후에는 다시 댓글 목록을 갱신해야 한다.

 

  • get.jsp 내에서 댓글 수정 버튼
modalModBtn.on("click", function(e) {
            var reply = {rno:modal.data("rno"), reply: modalInputReply.val()};
            replyService.update(reply, function(result) {
                alert(result);
                modal.modal("hide");
                showList(1);
            });
        });
cs

 

  • get.jsp 내에서 댓글 삭제 버튼
modalRemoveBtn.on("click", function(e) {
            var rno = modal.data("rno");
            replyService.remove(rno, function(result) {
                alert(result);
                modal.modal("hide");
                showList(1);
            });
        });
cs

 

 


17.5 댓글의 페이징 처리

 

  • 데이터베이스의 인덱스 설계

    • tbl_reply 테이블을 접근할 때 댓글의 번호가 중심이 아니라, 게시물의 번호가 중심이 된다는 점이다. 댓글을 조회할 때 해당 게시물의 댓글을 가져오기 때문에, bno를 기준으로 where 문이 적용된다.
    • tbl_reply 테이블의 PK 는 rno 이므로, "tbl_reply where bno=200 order by rno asc"로 쿼리를 실행하면 PK_REPLY를 통해 TBL_REPLY에 접근한다.
    • PK_REPLY로 검색을 하다보니 중간에 있는 다른 게시물의 번호들을 건너뛰어 가며 특정 게시물의 댓글을 찾아야 한다. 만일 데이터가 많아 지면 성능에 문제가 생길 수 있다.

→ 효율을 높이기 위해 게시물의 번호에 맞게 댓글들을 모아서 빠르게 찾을 수 있는 구조로 만드는 것이 좋다.

"bno=200 order by rno asc' 와 같은 쿼리를 실행할 때 왼쪽의 구조에서 200에 해당하는 범위만 찾아서 사용하게 된다.

이러한 구조를 인덱스를 생성한다고 표현한다.

 

 

  • 인덱스 작성 sql문
create index idx_reply on tbl_reply (bno desc, rno asc);
cs

 

 

  • 인덱스를 이용한 페이징 쿼리

    • 인덱스를 이용하는 이유는 정렬을 피할 수 있기 때문이다.

 

  • 특정한 게시물의 rno의 순번대로 데이터를 조회하는 sql문
select /*+INDEX(tbl_reply idx_reply) */
    rownum rn, bno, rno, reply, replyer, replyDate, updatedate
    from tbl_reply
    where bno = 3145743
    and rno > 0
cs

 

SQL 실행 결과

 

계획 설명

- 실행된 결과를 보면 IDX_REPLY를 이용해서 테이블에 접근하는 것을 볼 수 있다.

- 테이블에 접근해서 결과를 만들 때 생성되는 ROWNUM은 가장 낮은 rno 값을 가지는 데이터가 1번이 된다.

- ROWNUM이 원하는 순서대로 나오기 때문에 페이징 처리는 이전에 게시물 페이징과 동일한 형태로 작성할 수 있다.

 

 

 

  • 10개씩 2페이지를 가져오는 sql문
select rno, bno, reply, replyer, replydate, updatedate
from
    (
    select /*+INDEX(tbl_reply idx_reply) */
        rownum rn, bno, rno, reply, replyer, replyDate, updatedate
    from tbl_reply
    where bno = 3145743
          and rno > 0
          and rownum <= 20
    ) where rn > 10;
cs

 

 

  • ReplyMapper.xml
<select id="getListWithPaging" resultType="org.zerock.domain.ReplyVO">
    <![CDATA[
        select rno, bno, reply, replyer, replydate, updatedate
        from
            (
            select /*+INDEX(tbl_reply idx_reply) */
            rownum rn, rno, bno, reply, replyer, replyDate, updatedate
            from tbl_reply
            where bno=#{bno}
            and rno>0
            and rownum <= #{cri.pageNum} * #{cri.amount}
            )
            where rn > (#{cri.pageNum} -1* #{cri.amount}
    ]]>
</select>
cs

 

 

  • ReplyMapperTests 
@Test
    public void testList2() {
        Criteria cri = new Criteria(210);
        List<ReplyVO> replies = mapper.getListWithPaging(cri, 3145743L );
        
        replies.forEach(reply -> log.info(reply));
    }
cs

 

 

 

  • 댓글의 숫자 파악

    • 댓글들을 페이징 처리하기 위해서는 해당 게시물의 전체 댓글의 숫자를 파악해서 화면에 보여줄 필요가 있다.
    • ReplyMapper 인터페이스에 getCountByBno( )를 추가한다.

 

  • ReplyMapper 인터페이스
public int getCountByBno(Long bno);
cs

 

 

  • ReplyMapper.xml 
<select id ="getCountByBno" resultType = "int">
    <![CDATA[
    select count(rno) from tbl_reply where bno =#{bno}
    ]]>
    </select>
cs

 

 

 

  • ReplyServiceImpl에서 댓글과 댓글 수 처리

    • 단순히 댓글 전체를 보여주는 방식과 달리 댓글의 페이징 처리가 필요한 경우에는 댓글 목록과 함께 전체 댓글의 수를 같이 전달해야 한다.
    • ReplyService 인터페이스와 구현 클래스인 ReplyServiceImpl 클래스는 List<ReplyVO>와 댓글의 수를 같이 전달할 수 있는 구조로 변경한다.

 

  • ReplyPageDTO 클래스 (두가지 정보 담기 위해)
package org.zerock.domain;
 
import java.util.List;
 
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
 
@Data
@AllArgsConstructor
@Getter
public class ReplyPageDTO {
    private int replyCnt;
    private List<ReplyVO> list;
}
cs

 

 

  • ReplyService 인터페이스
public ReplyPageDTO getListPage(Criteria cri, Long bno);
cs

 

 

  • ReplyServiceImpl 클래스
@Override
    public ReplyPageDTO getListPage(Criteria cri, Long bno) {
        
        return new ReplyPageDTO(
                mapper.getCountByBno(bno),
                mapper.getListWithPaging(cri, bno));
    }
cs

 

 

 

  • ReplyController 수정

    • ReplyController에서는 ReplyService에 새롭게 추가된 getListPage( )를 호출하고 데이터를 전송하는 형태로 수정한다.

 

  • ReplyController - getList 
@GetMapping(value="/pages/{bno}/{page}", produces= {
            MediaType.APPLICATION_XML_VALUE, MediaType.APPLICATION_JSON_UTF8_VALUE
    })
    public ResponseEntity<ReplyPageDTO> getList(
            @PathVariable("page"int page,
            @PathVariable("bno") Long bno
            ) {
        Criteria cri = new Criteria(page, 10);
        log.info("get Reply List bno : " + bno);
        log.info("cri : " + cri);
        return new ResponseEntity<>(service.getListPage(cri, bno), HttpStatus.OK);
    }
cs

기존과 동일하게 JSON 데이터를 전송하지만 ReplyPageDTO 객체를 JSON 으로 전송하게 되므로, 특정 게시물의 댓글 목록을 조회하면 replyCnt와 list라는 이름의 속성을 가지는 JSON 문자열이 전송된다.

 

 

 


17.6 댓글 페이지의 화면 처리

 

  • 댓글 화면 처리
    • 게시물을 조회하는 페이지에 들어오면 기본적으로 가장 오래된 댓글들을 가져와서 1페이지에 보여준다.
    • 1페이지의 게시물을 가져올 때 해당 게시물의 댓글의 숫자를 파악해서 댓글의 페이지 번호를 출력한다.
    • 댓글이 추가되면서 댓글의 숫자만을 가져와서 최종 페이지를 찾아서 이동한다.
    • 댓글의 수정과 삭제 후에는 다시 동일 페이지를 호출한다.

 

  • 댓글 페이지 계산과 출력

    • Ajax로 가져오는 데이터가 replyCnt와 list 데이터로 구성되므로 이를 처리하는 reply.js 역시 이를 처리하는 구조로 수정gkse

 

  • reply.js - replyCnt와 lsit 데이터를 처리하는 구조로 수정
function getList(param, callback, error) {
        var bno = param.bno;
        var page = param.page || 1;
        
        $.getJSON("/replies/pages/" + bno + "/" + page + ".json",
            function(data) {
                if(callback) {
                    //callback(data);
                    callback(data.replyCnt, data.list);
                }
            }).fail(function(xhr, status, err) {
                if(error) {
                    error();
                }
            });
    }
cs

call back 함수에 해당 게시물의 댓글 수와 페이지에 해당하는 댓글 데이터를 전달하도록 하는 부분이 수정되었다.

 

 

  • get.jsp - showList 함수 페이지 번호 출력하도록 수정
function showList(page) {
            
            console.log("show list " + page);
            replyService.getList({bno:bnoValue, page: page||1}, function(replyCnt, list) {
                console.log("replyCnt : " + replyCnt);
                console.log("list : " + list);
                console.log(list);
                
                if(page == -1) {
                    pageNum = Math.ceil(replyCnt/10.0);
                    showList(pageNum);
                    return;
                }
                var str = "";
                if(list == null || list.length==0) {
                    replyUL.html("");
                    return;
                }
                for(var i=0, len=list.length || 0; i<len; i++) {
                    str+= "<li class='left cleafix' data rno='"+list[i].rno+"'>";
                    str+= "    <div><div class='header'><strong class='primary-font'>"+list[i].replyer+"</strong>";
                    str+= "        <small class='pull-right text-muted'>" +ReplyService.displayTime(list[i].replyDate)+"</small></div>";
                    str+= "            <p>"+list[i].reply+"</p></div></li>";
                }
                
                replyUL.html(str);
            });
        }
cs

page 변수를 이용해서 원하는 댓글 페이지를 가져오게 된다.

page 번호가 -1로 전달되면 마지막 페이지를 찾아서 다시 호출한다.

사용자가 새로운 댓글을 추가하면 showList(-1);을 호출하여 전체 댓글의 숫자를 파악하게 된다. 이후 다시 마지막 페이지를 호출해서 이동시키는 방식으로 동작시킨다. 

 

 

 

  • get.jsp  - modalRegisterBtn
modalRegisterBtn.on("click", function(e) {
            var reply ={
                    reply: modalInputReply.val(),
                    replyer: modalInputReplyer.val(),
                    bno: bnoValue
            };
            replyService.add(reply, function(result){
                alert(result);
                modal.find("input").val("");
                modal.modal("hide");
                
                //showList(1);
                showList(-1);
            });
        });
cs

 

 

  • get.jsp - 댓글이 출력되는 영역 아래쪽 footer 추가
                    </ul>
                    <!--  ./end ul -->
                </div>
                <!-- ./ end row -->
                <div class="panel-footer"></div>
        </div>            
<!-- /.row -->
</div>
</div>
cs

 

 

  • get.jsp - 댓글 페이지 번호 출력하는 로직
var pageNum=1;
            var replyPageFooter=$(".panel-footer");
            
            function showReplyPage(replyCnt) {
                var endNum = Math.ceil(pageNum / 10.0) * 10;
                var startNum = endNum - 9;
 
                var prev = startNum != 1;
                var next = false;
 
                if(endNum * 10 >= replyCnt) {
                    endNum = Math.ceil(replyCnt/10.0);
                }
 
                if(endNum * 10 < replyCnt) {
                    next = true;
                }
                
                var str = "<ul class='pagination pull-right'>";
                if(prev) {
                    str += "<li class='page-item'><a class='page-link' href='"+(startNum-1)+"'>Previous</a></li>";
                }
                
                for(var i=startNum ; i<=endNum; i++){
                    var active = pageNum == i? "active":"";
                    str+="<li class='page-item "+active+" '><a class='page-link' href='"+i+"'>"+i+"</a></li>";
                }
                
                if(next) {
                    str+= "<li class='page-item'><a class='page-link' href='"+(endNum+1) + "'>Next</a></li>";
                }
 
                str += "</ul></div>";
                console.log(str);
                
                replyPageFooter.html(str);
            }
cs

 

 

 

  • get.jsp - 페이지 번호 클릭했을 때 새로운 댓글 가져오게 하기
replyPageFooter.on("click", "li a", function(e) {
                e.preventDefault();
                console.log("page click");
                var targetPageNum = $(this).attr("href");
                console.log("targetPageNum : " + targetPageNum);
                pageNum = targetPageNum;
                showList(pageNum);
            });
cs

댓글의 페이지 번호는 <a> 태그 내에 존재하므로 이벤트 처리에서는 <a> 태그의 기본 동작을 제한하고 댓글 페이지 번호를 변경한 후 해당 페이지의 댓글을 가져오도록 한다.

 

 

 

  • 댓글의 수정과 삭제

    • 댓글이 페이지 처리되면 댓글의 수정과 삭제 시에도 현재 댓글이 포함된 페이지로 이동하도록 수정

 

  • get.jsp
modalModBtn.on("click", function(e) {
            var reply = {rno:modal.data("rno"), reply: modalInputReply.val()};
            replyService.update(reply, function(result) {
                alert(result);
                modal.modal("hide");
                showList(pageNum);
            });
        });
        
        modalRemoveBtn.on("click", function(e) {
            var rno = modal.data("rno");
            replyService.remove(rno, function(result) {
                alert(result);
                modal.modal("hide");
                showList(pageNum);
            });
        });
cs

 

 


출처 : 코드로 배우는 스프링 웹 프로젝트 [구멍가게코딩단]

'Records > Spring Framework' 카테고리의 다른 글

스프링에서 트랜잭션 관리  (0) 2021.03.22
AOP라는 패러다임  (0) 2021.03.22
REST 방식으로 전환  (0) 2021.03.16
검색처리  (0) 2021.03.15
페이징 화면 처리  (0) 2021.03.11
Comments