[FileUpload]_DataTransfer() 를 사용한 multiple 업로드 구현

2022. 5. 15. 18:06[프론트엔드]_/[Javascript]_ES6

728x90
반응형

목적 ] Jquery 와 ES6 를 사용하여 input type="file" 의 다중 업로드 및 제약조건 부여하기

 

Front 부분]

<span class="inputWrap fileBoxWrap">
   <input type="text" class="inp lineType" id="file_name"
                                   style="width:calc(100% - 85px);" placeholder="이미지 파일만 첨부 가능합니다." readonly/>
   <label for="filename" class="btn btn-whiteG"><span class="text">찾아보기</span></label>
   <input type="file" class="file hidden" id="filename" name="attach_file"
                                   accept=".avif,.gif,.jpeg,.png,.svg+xml,.webp,.bmp">
</span>

label 에 for를 이용하여 디자인을 입힐 수 있습니다.

input type="file" 에 accept 속성을 이용하여 파일 첨부시 보여지는 파일을 정할 수 있습니다.

단 완전하게 막지는 못하기 때문에 Controller 부분에서 제어를 해줘야 합니다.

 

 

Script 부분]

1] 사용할 DataTransfer 객체와 총 파일 업로드 가능한 수 선언

const dt = new DataTransfer();
let totalCount = 5;

2] 파일 업로드 될 때 이벤트 걸기

$('#filename').on('change', function () {

부분으로 파일이 업로드 ( = change) 될 때 이벤트를 겁니다.


3] 접근을 쉽게하기 위한 변수 선언

let files = this.files;

해당 선언을 통해 선택한 element 에서의 files 라는 목록을 가져옵니다.

 

input type="file" 의 경우 선택된 파일을 fileList 로 반환합니다.

해당 엘리먼트는 files 안에 리스트 구조로 값이 들어오게 됩니다. 이를 사용하기 쉽게 선언합니다.


4] 파일 선택시 선택된 파일 이름을 출력할 text 구간 만들기

$(this).closest('.fileBoxWrap').find('input.lineType').val($.map(files, function (item) {
    return item.name;
}).join(', '));

JQuery -> closest 함수

var closestElement = targetElement.closest(selectors);

해당 엘리먼트 에서부터 selectors에 지정한 선택자에 만족할 때 까지 탐색합니다. 이중 가장 조건에 만족한 부모 요소가 반환되며, 없을시 null을 반환합니다.

JQuery -> find 함수

.find( selector )

closest 로 반환된 부모 요소로부터 input.lineType 이라는 클래스를 가진 곳을 찾습니다.

해당 lineType 의 경우는 파일 업로드 시 해당 파일의 이름을 사용자에게 보여주기 위한 text 값 입니다.

 

JQuery -> val() 를 사용하여 해당 엘리먼트에 값을 집어넣습니다.

그 값은 JQuery -> map 을 사용하여, fileList 로 받아온 값 을 새로운 배열로 만들게 됩니다.

그 값은 해당 name 의 요소를 가져오며

JQuery -> Join을 이용하여 이를 배열로 만듭니다.


5] 현제 선택된 파일들의 배열

let filesArr = Array.from(dt.files);

Array.from 을 사용하여 dt.files 에 저장된 값들을 배열로 만듭니다.

 


6] 허용 가능한 이미지 값들의 배열

let imagearr = ['image/apng','image/avif','image/gif','image/jpeg','image/png','image/svg+xml','image/webp','image/bmp']; //등록 가능한 이미지 목록

7] 파일 크기 검사

if (files[0].size > 20000000) {
    common.alert("첨부 파일 개별 사이즈는 20MB를 넘을 수 없습니다.")
    $('#filename')[0].files = dt.files;
    $('#file_name').val(""); // 가이드 텍스트 비우기

}

선택한 파일 의 사이즈를 구분하기 위해서는 index 0번째의 size 요소의 값을 불러옵니다

해당 size는 byte 사이즈 이며 20MB 는 20000000  입니다.

여기에서 아래의 부분이 가장 중요합니다.

$('#filename')[0].files = dt.files;

해당 type="file" 에 files 부분에 dt.files 를 함으로써, 

선택된 파일을 무시하고, 이전에 담겼던 정보를 붙이는 방식입니다.

무조건 index 는 0 이여야 하며, 

 

예를들어 파일을 3개를 업로드 한 상태에서 이번 파일이 실패할 경우 이전의 정보를 붙임으로써,

사이즈를 넘긴 파일이 담긴 list 대신에 선택되기 전 상태의 list를 붙이는 방식입니다.


8] 이미지 양식 비교

else if(!(imagearr.includes(files[0].type))){
    common.alert("이미지 파일만 등록 가능합니다.")
    $('#filename')[0].files = dt.files;
    $('#file_name').val(""); // 가이드 텍스트 비우기

}

이전에 선언한 이미지 배열에 입력된 files[0] 의 타입을 비교합니다.

includes(target) 함수를 이용해서 입력된 값이 배열에 있을시 true를, 없을 시 false를 리턴합니다.

7번과 같은 로직을 사용하여 이미지 파일이 허용되지 않을 경우 선택되지 않도록 합니다.


9] 최대 등록 수 5개를 넘지 않도록 제어

else {
    if (filesArr.length >= totalCount) {
        common.alert("첨부파일은 " + totalCount + "개 까지만 등록 가능합니다.")
        $('#filename')[0].files = dt.files;
        $('#file_name').val(""); // 가이드 텍스트 비우기

    } else {

        $.each(files, function (i, item) {
            $('#fileList').append('<div class="w100p">' + item.name + '<button type="button" class="btn btn-white textBtn" style="margin-left: 5px;" onclick="removeFile(this, ' + (dt.items.length + i) + ');"><i class="ambicon-015_mark_times"></i></button></div>');
        });
        for (let file of files) {
            dt.items.add(file);
        }
        this.files = dt.files;
    }

}

크기, 타입 검사 이후 총 수량 검사부분입니다.

위에서 선언한 filesArr 의 경우 새로운 파일 이벤트 걸기전 files[0]의 상태를 나타낸다고 생각하면 됩니다.

따라서 이전까지 업로드한 수가 5개 이고 이번 이벤트로 추가시 6이 될것이기 때문에 >=를 사용하였습니다.

지우는 로직은 동일합니다.

 

위의 로직을 전부 통과 할 경우

$.each(files, function (i, item) {
    $('#fileList').append('<div class="w100p">' + item.name + '<button type="button" class="btn btn-white textBtn" style="margin-left: 5px;" onclick="removeFile(this, ' + (dt.items.length + i) + ');"><i class="ambicon-015_mark_times"></i></button></div>');
});

files의 총 요소들 만큼 반복하면서

append 함수를 통해 해당 엘리먼트를 생성합니다.

닫기 버튼 클릭시 removeFile라는 함수에 해당 엘리먼트와 해당 item의 인덱스 번호를 전달합니다.

for (let file of files) {
    dt.items.add(file);
}
this.files = dt.files;

해당 함수를 통해 dt 객체에 똑같이 저장시킵니다.

그리고 input 요소의 files 에 저장한 dt속의 files를 지정함으로써 실질적으로 input 요소에 저장되게 됩니다.


10] 삭제버튼 클릭 함수

function removeFile(element, index) {
    $(element).closest('div').remove();
    for (let i = 0; i < dt.items.length; i++) {
        if (index === i) {
            dt.items.remove(i);
            continue;
        }
    }
    $('#filename')[0].files = dt.files;
}

삭제시 해당 요소와 그 index 번호를 전달 받습니다.

우선 엘리먼트를 삭제합니다. 부모요소로부터 div는 한가지이니 해당 엘리먼트가 삭제되고,

 

for 문을 통해서 등록된 수 만큼 반복하게 됩니다.

만약 해당 전달받은 인덱스와 dt에 저장된 인덱스 번호가 같을 때 해당 인덱스의 아이템을 삭제합니다.

 

그리고 나서 실질적인 input 요소에 해당 dt.files를 전달함으로써 삭제가 이루어지게 됩니다.

 


이렇게 DataTransfer() 를 사용한 파일 업로드를 알아보았습니다.

 

dt.files가 이전에 저장한 정보들이라고 생각하게 되면 로직을 짜기 쉽습니다.

감사합니다.

728x90
반응형