게시글 수정를 위해 먼저, 주소를 작성할 예정이다.
어제와 같이 location.href 을 이용해서 URL을 설정해주면된다. (log 참고)
📚 VS code
📕 boardDetail.jsp
// 게시글 수정 버튼 클릭 시
const updateBtn = document.getElementById("updateBtn");
updateBtn.addEventListener("click",()=>{
console.log()
location.href=location.pathname.replace("board","board2")
+"/update"
+location.search;
/* '/board2/1/2008/update?cp=1' */
})
- location.pathname.replace("board","board2") : board → board2로 변경
- location.search : ? 뒤 파라미터 값을 가져온다
📕 boardUpdate.jsp
📑 이 부분 추가 !
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<c:forEach items="${boardTypeList}" var="boardType">
<c:if test="${boardType.BOARD_CODE == boardCode}" >
<c:set var="boardName" value="${boardType.BOARD_NAME}"/>
</c:if>
</c:forEach>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${boardName}</title>
<link rel="stylesheet" href="/resources/css/board/boardWrite-style.css">
</head>
<body>
<main>
<jsp:include page="/WEB-INF/views/common/header.jsp"/>
<form action="update" method="POST"
class="board-write" id="boardUpdateFrm" enctype = "multipart/form-data">
<%-- 무언가를 업로드 해야한다면 ! enctype = "multipart/form-date" : 제출 데이터 인코딩 x 생각하기 !
-> 파일 제출 가능
-> MultiPartResolver 가 문자열, 파일을 구분
--> 문자열 -> String, int, DTO, Map(HttpMessageConverter)
--> 파일 -> MultiPartFile 객체 -> transferTo() (파일을 서버에 저장!)
--%>
<h1 class="board-name">${boardName}</h1>
<!-- 제목 -->
<h1 class="board-title">
<input type="text" name="boardTitle" placeholder="제목" value="${board.boardTitle}" >
</h1>
<%--
board.imageList에 존재하는 이미지 객체를 얻어와
순서(imageOrder) 별로 변수 생성
--%>
📑<c:forEach items="${board.imageList}" var="img">
<c:choose>
<c:when test="${img.imageOrder == 0}">
<c:set var="img0" value="${img.imagePath}${img.imageReName}"/>
</c:when>
<c:when test="${img.imageOrder == 1}">
<c:set var="img1" value="${img.imagePath}${img.imageReName}"/>
</c:when>
<c:when test="${img.imageOrder == 2}">
<c:set var="img2" value="${img.imagePath}${img.imageReName}"/>
</c:when>
<c:when test="${img.imageOrder == 3}">
<c:set var="img3" value="${img.imagePath}${img.imageReName}"/>
</c:when>
<c:when test="${img.imageOrder == 4}">
<c:set var="img4" value="${img.imagePath}${img.imageReName}"/>
</c:when>
</c:choose>
</c:forEach>
<!-- 썸네일 영역 -->
<h5>썸네일</h5>
<div class="img-box">
<div class="boardImg thumbnail">
<label for="img0">
<img class="preview" src="${img0}">
</label>
<input type="file" name="images" class="inputImage" id="img0" accept="image/*">
<span class="delete-image">×</span>
</div>
</div>
<!-- 업로드 이미지 영역 -->
<h5>업로드 이미지</h5>
📑<div class="img-box">
<div class="boardImg">
<label for="img1">
<img class="preview" src="${img1}">
</label>
<input type="file" name="images" class="inputImage" id="img1" accept="image/*">
<span class="delete-image">×</span>
</div>
<div class="boardImg">
<label for="img2">
<img class="preview" src="${img2}">
</label>
<input type="file" name="images" class="inputImage" id="img2" accept="image/*">
<span class="delete-image">×</span>
</div>
<div class="boardImg">
<label for="img3">
<img class="preview" src="${img3}">
</label>
<input type="file" name="images" class="inputImage" id="img3" accept="image/*">
<span class="delete-image">×</span>
</div>
<div class="boardImg">
<label for="img4">
<img class="preview" src="${img4}">
</label>
<input type="file" name="images" class="inputImage" id="img4" accept="image/*">
<span class="delete-image">×</span>
</div>
</div>
<!-- 내용 -->
<div class="board-content">
<textarea name="boardContent" id="boardContent">${board.boardContent}</textarea>
</div>
<!-- 버튼 영역 -->
<div class="board-btn-area">
<button type="submit" id="writebtn">등록</button>
</div>
<%-- 기존 이미지가 있다가 삭제된 이미지의 순서를 기록 --%>
📑<input type = "hidden" name = "deleteList" value="">
<%-- 수정 성공 시 주소(쿼리스트링) 유지 용도--%>
📑<input type ="hidden" name="cp" value="${param.cp}">
</form>
</main>
📑<jsp:include page="/WEB-INF/views/common/footer.jsp"/>
📑<script src="/resources/js/board/boardUpdate.js"></script>
</body>
</html>
🤔 업로드 이미지에서 src 속성에 ${img0}을 쓸 수 있는 이유는 ?
<c:set var="img0" value="${img.imagePath}${img.imageReName}"/>
위에 <c:set> 태그를 사용하여 ${img0}, ${img1} 등과 같은 페이지 범위 변수로 설정하였다.
해서 해당 페이지에서 사용할 수 있다!
📕 boardUpdate.js
📑 이 부분 추가 !
// 미리보기 관련 요소 모두 얻어오기
// img 5개
const preview = document.getElementsByClassName("preview");
// file 5개
const inputImage = document.getElementsByClassName("inputImage");
// x버튼 5개
const deleteImage = document.getElementsByClassName("delete-image");
// 게시글 수정 시 삭제된 이미지의 순서를 기록할 Set 객체 생성
📑 const deleteSet = new Set(); // 순서X, 중복X
// -> x버튼 클릭 시 순서를 한 번만 저장하는 용도
// -> 위에 얻어온 요소들의 개수가 같음 == 인덱스가 일치함
for(let i = 0; i < inputImage.length; i++){
// 파일이 선택되거나, 선택 후 취소 되었을 때
inputImage[i].addEventListener("change", e=>{
const file = e.target.files[0]; // 선택된 파일의 데이터
if(file != undefined){ // 파일이 선택 되었을 때
const reader = new FileReader(); // 파일을 읽는 객체
reader.readAsDataURL(file);
// 지정된 파일을 읽은 후 result 변수에 URL 형식으로 저장
console.log(reader);
reader.onload = e =>{ // 파일을 다 읽은 후 수행
preview[i].setAttribute("src", e.target.result);
// 이미지가 성공적으로 읽어지면
// deleteSet에서 삭제
📑 deleteSet.delete(i);
}
}else{ // 선택 후 취소 되었을 때
// -> 선택된 파일 없음 -> 미리보기 삭제
preview[i].removeAttribute("src");
}
});
// 미리보기 삭제 버튼(x버튼)
deleteImage[i].addEventListener("click", ()=>{
// 미리보기 이미지가 있을 경우
if(preview[i].getAttribute("src") != ""){
// 미리보기 삭제
preview[i].removeAttribute("src");
// input type="file" 태그의 value를 삭제
// ** input type="file"의 value ""(빈칸)만 대입 가능 **
inputImage[i].value = "";
// deleteSet에 삭제된 이미지 순서(i) 추가
📑 deleteSet.add(i);
}
})
}
// 게시글 등록 시 제목, 내용 작성 여부 검사
const boardUpdateFrm = document.getElementById("boardUpdateFrm");
const boardTitle = document.querySelector("[name='boardTitle']");
const boardContent = document.querySelector("[name='boardContent']");
boardUpdateFrm.addEventListener("submit", e=>{
if(boardTitle.value.trim().length == 0){
alert("제목을 입력해주세요");
boardTitle.value = "";
boardTitle.focus();
e.preventDefault(); // form 기본 이벤트 제거
return;
}
if(boardContent.value.trim().length == 0){
alert("내용을 입력해주세요");
boardContent.value = "";
boardContent.focus();
e.preventDefault(); // form 기본 이벤트 제거
return;
}
// input type="hidden" 태그에
// deleteSet에 저장된 값을 "1,2,3" 형태로 변경해서 저장
// Array.from(deleteSet) : Set -> Array 변경
// JS 배열은 string에 대입되거나 출력될 때
// 요소,요소,요소 형태의 문자열을 반환한다!
📑 document.querySelector("[name='deleteList']").value
= Array.from(deleteSet);
// e.preventDefault(); // 확인만하고 지울 예정
})
💭 deleteSet에 대한 설명 💭
공통조건 : 수정 버튼을 클릭 시 !
1. 모든 이미지가 있을 때
deleteSet을 콘솔에 찍어보면 Set(0) {size: 0} 출력된다 !
2. 1번째 사진을 삭제를 눌렀을때
deleteSet을 콘솔에 찍어보면 Set(1) {1}출력된다 !
최종적으로 이것저것 수정을 해서
나는 2개를 삭제 했고 {1, 4}에 대한 삭제 값만 가지고 controller 로 갔다 !
이 상태에서 등록버튼을 클릭 했을때, 📕 boardUpdate.jsp 에 hidden에
숨겨 놓은 deleteList value 값에 "1,4"가 들어 간 것을 콘솔에서 확인할 수 있다.
📚 Spring
📗 boardController2. jsp
🌝 게시글 수정 화면 전환
❗ 게시글 수정 화면 전환할때, 게시글 상세 조회 화면으로 돌아가기 위해서
기존 boardService.selectBoardList을 이용하였다!
boardService.selectBoardList 보고 싶다면 아래 포스팅 참고 부탁 드린다
[ Spring ] 게시글 상세 조회 ⑦
이번 시간은 저번시간에 이어 게시글 상세 조회를 진행할 것이다. 이번에도 @PathVariable 이용해서 진행할 예정이다. 만약, @ PathVariable 이게 무엇인지 기억이 안난다면 저번시간에 작성한 내용을
jnaa.tistory.com
@GetMapping("/{boardCode}/{boardNo}/update")
public String boardUpdate(
@PathVariable("boardCode") int boardCode
,@PathVariable("boardNo")int boardNo
,Model model // 데이터 전달용 객체 (기본 scope : request)
) {
Map<String, Object> map = new HashMap<String, Object>();
map.put("boardCode", boardCode);
map.put("boardNo", boardNo);
Board board = boardService.selectBoardList(map);
model.addAttribute("board",board);
// foward(요청위임) -> request scope 유지
return "board/boardUpdate";
}
📚 Spring
📗 boardController2. jsp
🌝 게시글 수정
// 게시글 수정
@PostMapping("/{boardCode}/{boardNo}/update")
public String boardUpdate(
Board board // name 속성값이랑 dto랑 내용이 같을때 (name == 필드) 필드에 파라미터 자동 세팅
,@RequestParam(value="cp", required = false, defaultValue = "1") int cp // 쿼리스트링 유지
,@RequestParam(value="deleteList",required = false) String deleteList // 삭제할 이미지 순서
,@RequestParam(value="images",required = false)List<MultipartFile> images // 업로드된 파일 리스트
,@PathVariable("boardCode") int boardCode
,@PathVariable("boardNo")int boardNo
,HttpSession session // 서버 파일 저장 경로 얻어올 용도
,RedirectAttributes ra // 리다이렉트 시 값 전달 용
) throws IllegalStateException, IOException{
// 1. boardCode boardNo를 커맨드 객체(board)세팅
board.setBoardCode(boardCode);
board.setBoardNo(boardNo);
// board(boardCode,boardNo, boardTitle,boardContent)가 있다.
//2. 이미지 서버 저장 경로, 웹 접근 경로
String webPath = "/resources/images/board/";
String filePath = session.getServletContext().getRealPath(webPath);
//3. 게시글 수정 서비스 호출
int rowCount = service.boardUpdate(board,webPath,filePath,images,deleteList);
//4. 결과에 따라 message,path 설정
String message = null;
String path ="redirect:";
if(rowCount>0) {
message = "게시글이 수정되었습니다.";
path += "/board/"+boardCode + "/" + boardNo + "?cp=" + cp; //상세 조회 페이지
}else {
message = "게시글 수정 실패";
path += "update";
}
ra.addFlashAttribute("message",message);
return path;
}
🤔 hidden 값으로 숨겨 놓은 deleteList를 왜 String으로 받는거지 ?
@RequestParam(value="deleteList",required = false) String deleteList
input type="hidden" 태그에
deleteSet에 저장된 값을 "1,2,3" 형태로 변경해서 저장
Array.from(deleteSet) : Set -> Array 변경
JS 배열은 string에 대입되거나 출력될 때
요소,요소,요소 형태의 문자열을 반환한다!
📑 document.querySelector("[name='deleteList']").value = Array.from(deleteSet)
📗 BoardService2. jsp
/** 게시글 수정 서비스
* @param board
* @param webPath
* @param filePath
* @param images
* @param deleteList
* @return rowCount
*/
int boardUpdate(Board board, String webPath, String filePath,
List<MultipartFile> images, String deleteList) throws IllegalStateException, IOException;
📝🤔 이미지 수정에서 생각 해야할 것 🤔📝
1. 이미지가 없다 → 이미지가 있다 ' insert '
2. 이미지가 있다 → 이미지가 변경 ' update '
3. 이미지가 있다 → 이미지가 삭제 ' delete'
📗 BoardServiceImpl2. jsp
🌝 제목 + 내용만 수정
// 게시글 수정 서비스
@Transactional(rollbackFor = Exception.class)
@Override
public int boardUpdate(Board board, String webPath, String filePath
, List<MultipartFile> images
,String deleteList) throws IllegalStateException, IOException {
// 1.게시글 제목/내용만 수정
// 1) XSS방지 처리
board.setBoardContent(Util.XSSHandling(board.getBoardContent()));
board.setBoardTitle(Util.XSSHandling(board.getBoardTitle()));
// 2) DAO 호출
int rowCount = dao.boardUpdate(board);
🌝 deleteList 에 있는 값 삭제
위에서 deleteList에 3,4번 값을 가지고 왔다!
// 2. 게시글 부분이 수정 성공 했을 때
if (rowCount > 0) {
if (!deleteList.equals("")) {// 삭제할 이미지가 있다면
// 3. deleteList에 작성된 이미지
Map<String, Object> deleteMap = new HashMap<String, Object>();
deleteMap.put("boardNo", board.getBoardNo());
deleteMap.put("deleteList", deleteList);
rowCount = dao.imageDelete(deleteMap);
if (rowCount == 0) { // 이미지 삭제 실패 시 전체 롤백 (예외 강제 발생)
throw new FileUploadException(); // 클래스 사용하려면 객체로 사용해야해!
}
}
🌝 이미지 변경 했을때 ! 이미지가 삽입 했을떄 처리 하기
// 4. 새로 업로드된 이미지 분류 작업
// images : 실제 파일이 담긴 List
// -> input type = "file"개수 만큼 요소가 존재
// -> 제출된 파일이 없어도 MultipartFile 객체 존재
List<BoardImage> uploadList = new ArrayList<BoardImage>();
// images에 담겨 있는 파일 중 실제 업로드 된 파일만 분류
for (int i = 0; i < images.size(); i++) {
// i번째 요소에 업로드한 파일이 있다면
if (images.get(i).getSize() > 0) {
BoardImage img = new BoardImage();
// img에 파일 정보를 담아서 uploadList에 추가
img.setImagePath(webPath); // 웹 접근 경로
img.setBoardNo(board.getBoardNo()); // 게시글 번호
img.setImageOrder(i); // 이미지 순서
// 파일 원본명
String fileName = images.get(i).getOriginalFilename();
img.setImageOriginal(fileName); // 원본명
img.setImageReName(Util.fileRename(fileName)); // 변경명
uploadList.add(img);
// 오라클은 다중 UPDATE를 지원하지 않기 때문에
// 하나씩 UPDATE 수행
rowCount = dao.imageUpdate(img);
if(rowCount == 0) { // 수정실패 (db에 이미지가 없을 경우!)
// 이미지 삽입 !
rowCount = dao.imageInsert(img);
}
}
}
📗 BoardDAO2 jsp
/**게시글 수정
* @param board
* @return rowCount
*/
public int boardUpdate(Board board) {
return sqlSession.update("boardMapper.boardUpdate",board);
}
/**이미지 삭제
* @param deleteMap
* @return rowCount
*/
public int imageDelete(Map<String, Object> deleteMap) {
return sqlSession.delete("boardMapper.imageDelete",deleteMap);
}
/** 이미지 수정
* @param img
* @return rowCount
*/
public int imageUpdate(BoardImage img) {
return sqlSession.update("boardMapper.imageUpdate",img);
}
/**이미지 삽입(한개만!)
* @param img
* @return rowCount
*/
public int imageInsert(BoardImage img) {
return sqlSession.insert("boardMapper.imageInsert",img);
}
📗 board-mapperjsp
<!-- 게시글 수정 -->
<update id="boardUpdate">
UPDATE BOARD SET
BOARD_TITLE = #{boardTitle},
BOARD_CONTENT = #{boardContent},
B_UPDATE_DATE = SYSDATE
WHERE BOARD_NO = #{boardNo}
AND BOARD_CODE = #{boardCode}
</update>
<!-- 이미지삭제 -->
<delete id="imageDelete">
DELETE FROM BOARD_IMG
WHERE BOARD_NO = #{boardNo}
AND IMG_ORDER IN (${deleteList})
</delete>
<!-- 이미지 갱신 -->
<update id="imageUpdate">
UPDATE BOARD_IMG SET
IMG_PATH = #{imagePath},
IMG_RENAME = #{imageReName},
IMG_ORIGINAL =#{imageOriginal}
WHERE BOARD_NO =#{boardNo}
AND IMG_ORDER =#{imageOrder}
</update>
<!-- 이미지 삽입 -->
<insert id="imageInsert">
INSERT INTO BOARD_IMG
VALUES(SEQ_IMG_NO.NEXTVAL, #{imagePath}, #{imageReName},
#{imageOriginal}, #{imageOrder}, #{boardNo}
)
</insert>
'ON > spring' 카테고리의 다른 글
[ Spring ] 게시글 삭제 / 게시글 검색 ⑭ (0) | 2023.08.31 |
---|---|
[ Spring ] 댓글 / 대 댓글 기능 ⑬ (0) | 2023.08.30 |
[ Spring ] 게시글 작성(제목+내용+사진) ⑪ (0) | 2023.08.28 |
공공 데이터 - OPEN API (0) | 2023.08.25 |
[ Spring ] 프로필 이미지 추가 | 변경 | 삭제 ⑨ (1) | 2023.08.24 |