반응형
11-21 18:09
Today
Total
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
관리 메뉴

개발하는 고라니

[Javascript] Drag & Drop 파일 업로드 본문

Languages/JS

[Javascript] Drag & Drop 파일 업로드

조용한고라니 2021. 4. 27. 16:43
반응형

이벤트 트리거로 파일을 업로드하는 방법도 있다면 요즘엔 드래그 앤 드랍 방식으로도 많이 사용한다. 그 방법을 알아보자.

먼저 이벤트 중에 drag라는 이벤트가 있는데 이를 좀더 자세히 보면 다음과 같이 있다.

출처: https://developer.mozilla.org/ko/docs/Web/API/HTML_Drag_and_Drop_API

Drag & Drop 파일 업로드

네모난 박스에 파일을 드래그 했을 때 그 파일의 정보를 가져오는 것을 알아보기 전에 사전 준비를 해보자. HTML과 그에 필요한 Javascript 작성한다.

HTML / JS 작성

<section id="ex9">
        <style>
            #ex9 .upload-box{
                width:500px;
                height: 300px;
                border:1px solid gray;
                box-shadow: 2px 3px 9px hsl(0, 0%, 47%);
                padding:10px;
            }
        </style>
        <h1>파일업로드 : DND & Trigger</h1>
        <div class="upload-box">
            <button class="btn-upload">파일선택</button>
            <input class="btn-file d-none" type="file"> <!--파일 input box 형태-->     
        </div>
    </section>
    <hr>
    var sec9 = document.querySelector('#ex9');
    var btnUpload = sec9.querySelector('.btn-upload');
    var inputFile = sec9.querySelector('input[type="file"]');
    var uploadBox = sec9.querySelector('.upload-box');

    /* 박스 안에 Drag 들어왔을 때 */
    uploadBox.addEventListener('dragenter', function(e) {
        console.log('dragenter');
    });
    
    /* 박스 안에 Drag를 하고 있을 때 */
    uploadBox.addEventListener('dragover', function(e) {
        e.preventDefault();
        console.log('dragover');

        this.style.backgroundColor = 'green';
    });
    
    /* 박스 밖으로 Drag가 나갈 때 */
    uploadBox.addEventListener('dragleave', function(e) {
        console.log('dragleave');

        this.style.backgroundColor = 'white';
    });
    
    /* 박스 안에서 Drag를 Drop했을 때 */
    uploadBox.addEventListener('drop', function(e) {
        e.preventDefault();

        console.log('drop');
        this.style.backgroundColor = 'white';
    });

위 코드를 준비하고 페이지를 본다면 위와같은 화면을 볼 수 있다. 이제 파일 탐색기에서 파일을 하나 드래그해서 위의 박스에 끌고가면 배경이 초록색으로 바뀌는 것을 볼 수 있다.

DataTransfer

저 박스에 파일을 드래그 앤 드랍 했을 때 파일에 대한 정보를 가져오고싶을 수 있는데, 그 파일에 대한 정보는 어디서 얻어올까? 그 정보는 바로 event 객체가 갖는 DataTransfer 라는 녀석이 가지고있다. 이를 조금 자세히 보도록 하자.

 

콘솔 로그에 출력되는 것을 보니 'files'라는 속성을 갖는다. 아마 저곳에 내가 원하는 정보가 담겨있으리라 생각되므로 콘솔에 찍어보도록 하자.

 

예상대로 파일에 대한 정보를 갖고있었다. 우리는 이 정보를 활용해서 파일을 업로드하기 위한 준비를 마칠 수 있다.

    uploadBox.addEventListener('dragenter', function(e) {
        console.log('dragenter');
    });
    
    uploadBox.addEventListener('dragover', function(e) {
        e.preventDefault();
        console.log('dragover');

        this.style.backgroundColor = 'green';
    });
    
    uploadBox.addEventListener('dragleave', function(e) {
        console.log('dragleave');

        this.style.backgroundColor = 'white';
    });
    
    uploadBox.addEventListener('drop', function(e) {
        e.preventDefault();

        console.log('drop');
        this.style.backgroundColor = 'white';
        
        console.dir(e.dataTransfer);

        var data = e.dataTransfer.files[0];
        console.dir(data);        
    });

유효하지 않은 것과 유효한 것

아까 박스안에 드래그를 가져오면 색깔이 초록색으로 변환되게 했는데, 이를 유요한 파일을 가져왔을 때와 그렇지 않을 때로 나누어 색을 칠해보자.

오직 파일을 드래그 해왔을 때만 유효하다고 하고, 나머지는 유효하지 않다고 하면, Drag over 했을 때 조건을 줄 수 있다.

 

위에서 언급한 DataTransfer의 types는 MIME을 갖는데, 만약 파일일 경우 'Files'라는 값을 갖는다.

따라서 다음과 같이 변수에 boolean값을 줄 수 있다.

var vaild = e.dataTransfer.types.indexOf('Files') >= 0;

dataTransfer.type의 값이 'Files'라면 그 위치를 반환해줄 것이고, 없다면 -1을 반환하게 되므로 이를 통해 내가 현재 드래그 해온 것이 파일인지 아닌지를 알 수 있는 것이다.

 

※ 단, dragover에서는 File의 대한 자세한 정보(예: size, MIME...)를 알 수 없다. drop에서만 알 수 있다.

    uploadBox.addEventListener('dragover', function(e) {
        e.preventDefault();

        var vaild = e.dataTransfer.types.indexOf('Files') >= 0;

        if(!vaild){
            this.style.backgroundColor = 'red';
        }
        else{
            this.style.backgroundColor = 'green';
        }
        
    });

파일 업로드

위의 내용을 정리해보면, dragEnter, dragleave는 실질적으로 큰 의미는 없고, dragover와 drop 이벤트를 잘 활용한다면 파일 업로드를 할 수 있을 것 같다. ajax를 이용해 비동기적인 방법으로 파일을 업로드 해보자. 우선 서버 사이드에서 파일 업로드 요청을 받아주는 것을 세팅한다. 자바 서블릿 코드로 생성을 할 것인데, 이에 대한 설명은 생략하도록 한다.

import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
import java.util.UUID;

import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;

import com.stardy.entity.Member;
import com.stardy.service.MemberService;
import com.stardy.service.MemberServiceImpl;
import com.stardy.util.Logger;
import com.stardy.util.UploadUtil;

@WebServlet("/mypage/upload")
@MultipartConfig(
    fileSizeThreshold = 1024*1024,
    maxFileSize = 1024*1024*50, //50메가
    maxRequestSize = 1024*1024*50*5 // 50메가 5개까지
)
public class CommonController extends HttpServlet{

	MemberService service = new MemberServiceImpl();
	Logger log = new Logger();
	
	@Override
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		
		request.setCharacterEncoding("UTF-8");
		response.setContentType("text/html; charset=UTF-8");
		PrintWriter out = response.getWriter();
		
		UploadUtil util = UploadUtil.create(request.getServletContext());

		/* 파일 저장 로직 */
		Part part = request.getPart("uploadFile");
		
		int memberId = (int) request.getSession().getAttribute("id");
		String uuid = UUID.randomUUID().toString();
		String fileName = part.getSubmittedFileName();
		String filePath = util.createFilePath();
		
		util.saveFiles(part, uuid, filePath);
		
		//MEMBER DB UPDATE logic
		Member member = service.get(memberId);
		
		member.setProfile(uuid + "_" + fileName);
		member.setPath(filePath);
		service.modify(member);
		
		request.getSession().setAttribute("profile", member.getProfile());
		request.getSession().setAttribute("path", member.getPath());
		
		out.print("upload success");
	}
}

"/mypage/upload" 라는 URL로 POST요청이 들어오면 실행되는 메서드이다.

 

회원 프로필에 들어갈 이미지만을 올리는 작업을 살펴보도록 할 예정이므로, 유효성 검사 처리와 로직을 보도록 한다.

유효성 검사

파일을 업로드할 때 마구잡이로 업로드하게 된다면 낭패를 보게 될 수도 있다. 그러므로 먼저 적절한 파일인지 체크 후 요청을 보내는 것이 올바를 것이다. 다음은 

1) 파일인지?

2) 이미지 파일인지?

3) 50MB가 넘는지?

4) 1개만 올리는지?

를 체크하는 유효성 검사 함수이다.

	function isValid(data){
		
		//파일인지 유효성 검사
		if(data.types.indexOf('Files') < 0)
			return false;
		
		//이미지인지 유효성 검사
		if(data.files[0].type.indexOf('image') < 0){
			alert('이미지 파일만 업로드 가능합니다.');
			return false;
		}
		
		//파일의 개수는 1개씩만 가능하도록 유효성 검사
		if(data.files.length > 1){
			alert('파일은 하나씩 전송이 가능합니다.');
			return false;
		}
		
		//파일의 사이즈는 50MB 미만
		if(data.files[0].size >= 1024 * 1024 * 50){
			alert('50MB 이상인 파일은 업로드할 수 없습니다.');
			return false;
		}
		
		return true;
	}

DROP 이벤트 - 요청 보내기

drop 이벤트가 발동하면 먼저 유효성 검사를 체크하고, 모두 통과되면 파일 업로드를 요청하게 된다. 파일에 대한 정보는 e.dataTransfer.files[0]가 갖고 있는데, 이를 FormData 객체를 생성에 거기게 추가하고 보내면 된다. 다음은 내가 만든 ajax 모듈인데 참고하면 도움이 될 듯 하다.

//참고 ajax 커스텀 모듈
function ajax(obj){
	
	const xhr = new XMLHttpRequest();
	
	var method = obj.method || 'GET';
	var url = obj.url || '';
	var data = obj.data || null;
	
	/* 성공/에러 */
	xhr.addEventListener('load', function() {
		
		const data = xhr.responseText;
		
		if(obj.load)
			obj.load(data);
	});
	
	/* 성공 */
	xhr.addEventListener('loadend', function() {
		
		const data = xhr.responseText;
		
		//console.log(data);
		
		if(obj.loadend)
			obj.loadend(data);
	});
	
	/* 실패 */
	xhr.addEventListener('error', function() {
		
		console.log('Ajax 중 에러 발생 : ' + xhr.status + ' / ' + xhr.statusText);
		
		if(obj.error){
			obj.error(xhr, xhr.status, xhr.statusText);
		}
	});
	
	/* 중단 */
	xhr.addEventListener('abort', function() {
		
		if(obj.abort){
			obj.abort(xhr);
		}
	});
	
	/* 진행 */
	xhr.upload.addEventListener('progress', function() {
		
		if(obj.progress){
			obj.progress(xhr);
		}
	});
	
	/* 요청 시작 */
	xhr.addEventListener('loadstart', function() {
		
		if(obj.loadstart)
			obj.loadstart(xhr);
	});
	
	if(obj.async === false)
		xhr.open(method, url, obj.async);
	else
		xhr.open(method, url, true);
	
	if(obj.contentType)
		xhr.setRequestHeader('Content-Type', obj.contentType);	
		
	xhr.send(data);	
}

=> drop event

    uploadBox.addEventListener('drop', function(e) {
		
        e.preventDefault();

        uncheck();
        
		const data = e.dataTransfer;
        
		//유효성 Check
		if(!isValid(data)) return;

		const formData = new FormData();
		formData.append('uploadFile', data.files[0]);
		
		ajax({
			url: '/mypage/upload',
			method: 'POST',
			data: formData,
			progress: () => {
				
			},
			loadend: () => {
				
			}
		});
    });

정말 간단하다. 드래그 해서 들어온 파일의 데이터를 토대로 formData를 생성해 서버로 업로드 요청만 하면 된다. 이제 클라이언트 쪽에서 할 일은 끝이났다.

반응형

'Languages > JS' 카테고리의 다른 글

[Javascript] Ajax 모듈  (0) 2021.05.06
[Javascript] Ajax  (0) 2021.04.29
[Javascript] 이벤트 기반의 윈도우 프로그래밍  (0) 2021.04.14
[JQuery] checkbox  (0) 2021.02.23
[Javascript] 문자열 찾기  (0) 2021.01.04
Comments