백엔드/Spring Framework
게시판 구현 / 8-1. '글 등록' 파일 업로드(미리보기, 수정하기, 삭제)
maverick11471
2024. 7. 23. 12:39
프론트 수정
글 등록 시 파일첨부를 미리보기 추가, 수정, 삭제하는 방법에 대해 알아보자.
글 등록 시 미리보기 추가
[post.jsp 수정]
- 1. 선택된 파일을 배열로 변환
- 2. imageLoader 메소드 호출
- 3. FileReader를 사용해 Base64로 읽기. 읽은 후 미리보기 이미지와 삭제 버튼을 포함한 div 생성 + 미리보기 영역 추가
* img, btn, p 를 div 태그에 자식으로 추가
- (참고) id = post-form 내용 / id = preview 내용 / id = uploadFiles 내용
// id = post-form 내용
<form id="post-form" action="/board/post.do" method="post" enctype="multipart/form-data">
<c:if test="${loginMember.role eq 'ADMIN'}">
<div class="form-group">
<label for="type">카테고리</label>
<select class="form-select" name="type" id="type">
<option value="free" selected>자유게시판</option>
<option value="notice">공지사항</option>
</select>
</div>
</c:if>
<div class="form-group mt-3">
<label for="title">제목</label>
<input type="text" class="form-control" id="title" name="title" required>
</div>
<div class="form-group mt-3">
<label for="nickname">작성자</label>
<input type="text" class="form-control" id="nickname" name="nickname"
value="${loginMember.nickname}" readonly required>
</div>
<div class="form-group mt-3">
<label for="content">내용</label>
<textarea class="form-control" id="content" name="content" rows="10" required></textarea>
</div>
<div class="form-group mt-3">
<label for="uploadFiles">파일첨부</label>
// (참고) id = uploadFiles 내용
<input class="form-control" type="file" name="uploadFiles" id="uploadFiles" multiple>
// (참고) id = preview 내용
<div id="preview" class="mt-3 text-center"
data-placeholder="파일을 첨부하려면 파일선택 버튼을 누르세요.">
</div>
</div>
<div class="container mt-3 mb-5 w-50 d-flex justify-content-center">
<button type="submit" class="btn btn-outline-secondary">등록</button>
</div>
</form>
- script 동작 구현
<script>
// 추가된 파일들을 하나씩 담아줄 배열. File 객체가 저장
// 전역변수로 선언
const uploadFiles = [];
// DOM 요소가 완전히 로드되기 전에 JavaScript 코드가 실행되면,
// 해당 요소를 찾지 못하거나 조작할 수 없는 상황이 발생할 수 있습니다.
// 따라서, $(...) 내부에 코드를 작성함으로써,
// 페이지의 모든 요소가 로드된 후에 안전하게 JavaScript를 실행하도록 보장합니다.
$(() => {
// change 이벤트는 <input>, <select>, <textarea>와 같은 폼 요소의 값이 변경되었을 때 발생
$("#uploadFiles").on("change", (e) => {
// input에 추가된 파일들 변수로 받기
const files = e.target.files;
// 변수로 받아온 파일들 배열로 변환
// Array.prototype은 모든 배열 객체가 공유하는 메서드를 포함
// Array 객체의 prototype에 있는 slice 메서드를 참조합니다.
// slice 메서드는 배열의 일부분을 새로운 배열로 반환하는 기능을 가지고 있습니다.
// .call(files):
// slice 메서드를 files 객체에 호출합니다.
// call 메서드는 특정 객체를 this로 설정하여 함수를 호출할 수 있게 해줍니다.
// 여기서 files는 일반적으로 FileList 객체로, 이는 HTML 파일 입력 요소에서
// 선택된 파일들을 나타냅니다.
// FileList는 배열과 유사하지만, 실제 배열은 아니기 때문에 Array의 메서드를
// 직접 사용할 수 없습니다. slice 메서드를 사용하여 FileList를 배열로 변환합니다.
const fileArr = Array.prototype.slice.call(files);
for(file of fileArr) {
// 미리보기 메소드 호출
imageLoader(file);
}
});
// dataTransfer를 쓰는 이유: 드로그앤드랍이 편리하기 때문에
// 폼이 서브밋될 때 uploadFiles 배열에 담겨있는 File 객체들을
// input에 담아서 서브밋
$("#post-form").on("submit", (e) => {
// DataTransfer는 HTML5에서 도입된 객체로, 드래그 앤 드롭 API의 일부로 사용됩니다.
// DataTransfer 객체는 드래그 앤 드롭 작업 중에 데이터(예: 파일)를 전송하는 데
// 사용됩니다. 이 객체를 통해 파일 목록을 추가하거나 제거할 수 있습니다.
let dataTransfer = new DataTransfer();
// 이 코드는 uploadFiles 배열에 있는 각 파일 객체를 반복하면서
// dataTransfer 객체에 추가하는 과정을 수행합니다.
// 이를 통해 드래그 앤 드롭 작업에서 파일을 전송할 수 있도록 준비
for(i in uploadFiles) {
const file = uploadFiles[i];
dataTransfer.items.add(file);
}
$("#uploadFiles")[0].files = dataTransfer.files;
});
});
- 미리보기 처리하는 imageLoader 메소드 구현
// 미리보기 처리하는 메소드
// 미리보기될 파일은 서버나 데이터베이스에 저장된 상태가 아니기 때문에
// 파일 자체를 Base64 인코딩 방식으로 문자열로 변환해서 이미지로 호출해야 된다.
// 이미지가 들어갈 태그 생성과 파일을 Base64 인코딩
const imageLoader = (file) => {
// 추가된 파일 uploadFiles 배열에 담기
uploadFiles.push(file);
let reader = new FileReader();
// reader가 호출되면 실행될 이벤트 등록
// onload는 FileReader 객체에서 파일 읽기가 완료되었을 때 호출되는 이벤트 핸들러
reader.onload = (e) => {
// 이미지를 표출할 img 태그 생성
let img = document.createElement("img");
img.classList.add("upload-file");
// 이미지인지 아닌지 판단
// /(.*?)\./: 이 정규 표현식은 파일 이름의 시작 부분에서 점(.)이 나타나기 전까지의
// 모든 문자를 비탐욕적으로 매칭합니다. 즉, 파일 이름의 확장자를 찾기 위해
// 파일 이름의 모든 문자(0개 이상)와 그 뒤에 오는 점(.)을 포함하는 패턴을 찾습니다.
if(file.name.toLowerCase().match(/(.*?)\.(jpg|jpeg|png|gif|svg|bmp)$/)) {
// e는 이벤트 객체로, change 이벤트와 같은 사용자 상호작용에 대한 정보를 포함합니다.
// target은 이벤트가 발생한 요소를 참조하고,
// result는 FileReader 객체가 파일을 읽은 후의 결과를 나타냅니다.
// 여기서는 파일을 데이터 URL 형식으로 읽은 결과입니다.
// 즉, 사용자가 선택한 파일이 이미지일 경우, FileReader가 해당 파일을 읽어들여
// 그 결과를 result 속성에 저장합니다.
img.src = e.target.result;
} else {
img.src = "/static/images/defaultFileImg.png";
}
// 미리보기 영역에 추가
// makeDiv 메소드를 호출해서 만들어진 div 자체를 preview 영역에 추가
// makeDiv 메소드는 아래에 있음
$("#preview").append(makeDiv(img, file));
}
// 파일을 Base64인코딩된 문자열로 로드
// 이 메소드가 실행되면서 위에서 등록한 onload 이벤트가 함께 동작한다.
reader.readAsDataURL(file);
}
* 이미지파일이 DB로 넘어간 것이 아니기 때문에 Base64로 전송해야 한다. 전송 방식은 글자로 변환해 전송하는 방식이다.
- 미리보기 영역에 추가될 div를 생성하는 makeDiv 메소드 구현
// 미리보기 영역에 추가될 div를 생성하는 메소드
const makeDiv = (img, file) => {
let div = document.createElement("div");
div.classList.add("upload-file-div");
// 삭제 버튼 추가
let btn = document.createElement("input");
btn.classList.add("upload-file-delete-btn");
btn.setAttribute("type", "button");
btn.setAttribute("value", "x");
// 사용자 정의 속성 추가
// 사용자가 버튼을 클릭할 때 어떤 파일이 삭제될지를 알 수 있도록
// 파일 이름을 속성으로 지정하는 것
btn.setAttribute("deleteFile", file.name);
// 파일 이름을 표출할 p 태그 생성
let p = document.createElement("p");
p.classList.add("upload-file-name");
// 사용자가 선택한 파일의 이름이 해당 <p> 요소에 텍스트로 나타나게 됩니다.
p.textContent = file.name;
// div태그에 img, btn, p 태그 자식으로 추가
div.appendChild(img);
div.appendChild(btn);
div.appendChild(p);
return div;
}
- x 버튼에 클릭했을 떄 삭제하는 기능 추가
// x 버튼에 클릭했을 때 삭제하는 기능 추가
btn.onclick = (e) => {
// 클릭된 버튼 변수로 받기
const element = e.target;
// file.name 얻기
const deleteFileName = element.getAttribute("deleteFile");
// 배열에서 파일 삭제
for(let i = 0; i < uploadFiles.length; i++) {
if(deleteFileName === uploadFiles[i].name) {
// splice 메서드를 사용하여 uploadFiles 배열에서 인덱스 i에 해당하는 요소를
// 하나(1) 삭제합니다. 이로 인해 배열에서 해당 파일이 제거됩니다.
uploadFiles.splice(i, 1);
}
}
// input에서도 파일 삭제
// input type="file"은 첨부된 파일들을 fileList 형태로 관리
// fileList는 File 객체에 바로 담을수 없기 때문에
// DataTransfer라는 클래스를 사용해서 변환 후에 담아줘야한다.
let dataTransfer = new DataTransfer();
for(i in uploadFiles) {
// uploadFiles 배열에 있는 File 객체를 하나씩 DataTransfer 객체에 담아준다.
const file = uploadFiles[i];
dataTransfer.items.add(file);
}
// input type="file"에 fileList 형태로 밀어넣기
$("#uploadFiles")[0].files = dataTransfer.files;
// 클릭된 btn 태그를 소유하고 있는 부모 div 태그 삭제
const parentDiv = element.parentNode;
$(parentDiv).remove();
}