상세 컨텐츠

본문 제목

외부파일 드래그 앤 드롭3 (File Drag and Drop)

자바스크립트

by e7e 2023. 12. 20. 18:36

본문

https://e-7-e.tistory.com/195

글에서 파일 드래그 앤 드랍한 파일들을 AJAX로 전송하는 법을 알아보았당.

 

구지~ 구지~ 하고 싶진 않지만 form태그를 이용하여 구현/전송하려는 호기심 만땅

소수 열정파들이 있어 form 태그로 전송하는 것도 억지로 구현해보장.

 

먼저 해결해야 할 문제를 인식해 보장

form 태그를 이용하여 전송하려면 input type=file을 이용해야 하는뎅, 이전 글처럼

사용자가 파일묶음을 마우스로 끌고 왔을 때, 개별 파일의 전송 여부를 체크박스로

제어하려면,  끌고 온 파일들을 개별로 input type=file에 넣어주어야 하는뎅,

요기서 어려움이 발생한당. 

 

input type=file의 files 속성(FileList 인터페이스)은 읽기 전용이란 사실이당

아마도 보안때문일 것인데, 먼저 이 부분이 해결가능한가? 를 알아봐야 한당.

우앙 천만다행이당.

files속성이 읽기 전용이긴 한데, 같은 타입으로 통째로 덮어쓰는 건 가능하단당.

곧 마우스로 끌고 온 파일들을 아래처럼 input type=file에 넣을 수 있단 이야기당.

합리적이당!  뭔가 될 것 같당. 

//input type=file 태그의 id가 myFiles라고 했을 때
myFiles.files = event.dataTransfer.files

 

하지만 우리가 원하는 건 개별 컨트롤을 위해, 끌고 온 파일들을 꺼내, 파일 1개당

1개의 input type=file에 맵핑 시켜야 자유롭게 선택이 가능하당.

여기서 그리 자주 쓰이지 않는 DataTransfer 의 직접 사용이 필요하당.

아래 코드를 한번 누느로 위에서 아래로 휘리릭 하장.

의미만 파악해서, 거의 그대로 사용하면 크게 문제 없을 거시당!.

// 끌고 온 파일들
let v_files = event.dataTransfer.files;
for (let i = 0; i < v_files.length; i++) {
    let dataTransfer = new DataTransfer();
    //1개의 dataTransfer에 1개의 file을 담는당
    dataTransfer.items.add(v_files[i]);

    //디버깅용 로그
    console.log("dataTransfer", dataTransfer.files);

    // input type=file 생성
    let inFile = document.createElement("input");
    inFile.type = "file";
    inFile.name = "myFiles";
    //개별 파일 할당!
    inFile.files = dataTransfer.files;

    //inFile을 원하는 DOM에 appendChild하면 눈으로 볼 수 있당.
}

 

 

파일의 전송여부는 input type=file 의 disabled 속성으로 제어할 것이당.

disabled된 것은 form 전송시 서버로 전송되지 않음을 알고 있디용?

아래처럼 체크박스의 체크여부로 disabled 속성을 부여하므로써, 

이전 글의 f_ckFileList 함수는 더이상 필요없게 된당.

chkBox.addEventListener("change", function () {

    //바뀐 부분
    let selectImg = document.querySelector("#img" + this.fileId);
    let selectFile = document.querySelector("#file" + this.fileId);
    if (this.checked) {
        selectImg.style.display = "inline-block";
        selectFile.disabled = false;
    } else {
        selectImg.style.display = "none";
        selectFile.disabled = true;
    }
})

 

 

떤송 버튼 클릭시 실행되는 f_send함수는 사실상 이젠 그냥 전송만 하면 되지만

백엔드를 구현하지 않았으니, input type=file 객체만을 로그로 찍어서,

전송될 것과 전송 되지 않을 것을 누느로 구분할 수 있는 코드로 아래처럼 맹글었당.

        // 체크박스에 체크된 파일만 전송
        function f_send() {
            //선택된 것만 전송대상인지 확인을 위한 로그
            for (let elem of myForm.elements) {
                if (elem.type == "file") {
                    if (elem.disabled) {
                        console.log("전송 안될것:", elem);
                    } else {
                        console.log("전송 될것:", elem);
                    }
                }
            }
            //myForm.submit(); // 백엔드를 준비했다면 열어랑!
        }

 

 

뽀인또  설명은 끝났으니, 이제 전체 소스를 붙인당.

복사/붙여넣기와 실행/코드해석  따라가기로 전체흐름을 이해 할 수 있을거시당.

 

formSubmitDragAndDrop.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>E7E Drag Drop</title>
    <style>
        #wrapper {
            width: 350px;
        }

        #preview {
            width: 100%;
            height: 300px;
            border: 2px solid pink;
            background-image: url("https://blog.kakaocdn.net/dn/bvQO1s/btraInYH9xW/oekuwou6IKTnzIHKD40ykK/img.png");
            background-size: 110% 110%;
            background-position: -20px -20px;
            overflow: auto;
        }

        #list {
            width: 100%;
            border: 2px solid black;
            height: 200px;
            overflow: auto;
        }
    </style>
    <script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
</head>

<body>
    <h1>외부파일 끌다노킹 이해</h1>
    <div id="wrapper">

        <div id="preview" ondragover="f_over()" ondrop="f_drop()">
        </div>
        <form id="form1" action="/서버UL" method="post" enctype="multipart/form-data">
            <div id="list">
                <h3>파일 리스트</h3>
                <hr>
            </div>
            <button type="button" onclick="f_send()">떤송</button>
        </form>
    </div>
    <script>
        // 고정 전역변수(동적생성 아닌 DOM)
        const myPreview = document.querySelector("#preview");
        const myList = document.querySelector("#list");
        const myForm = document.querySelector("#form1");

        // DROP 이벤트 처리
        let attachFileList = [];   // 첨부파일 리스트를 위한 전역변수
        let fileIdIndex = 1;       // 첨부파일(이미지) 아이디 시작넘버

        // 체크박스에 체크된 파일만 전송
        function f_send() {
            //선택된 것만 전송대상인지 확인을 위한 로그
            for (let elem of myForm.elements) {
                if (elem.type == "file") {
                    if (elem.disabled) {
                        console.log("전송 안될것:", elem);
                    } else {
                        console.log("전송 될것:", elem);
                    }
                }
            }
            //myForm.submit(); // 백엔드를 준비했다면 열어랑!
        }

        //파일 중복 여부 체크 함수, 일단 그냥 파일명으로 비교
        function f_isRepeat(pFile) {
            for (let i = 0; i < attachFileList.length; i++) {
                if (pFile.name == attachFileList[i].name) {
                    return true;  // 중복
                }
            }
            return false;      // 안 중복
        }

        // 파일 1개씩 비동기로 읽어서 처리
        function f_readOneFile(pInx, pFile) {

            // 파일중복여부 체크 
            if (f_isRepeat(pFile)) {
                Swal.fire({
                    title: "미안행~~",
                    text: "너가 이미 선택한 파일이얌!!~~ㅠㅠ",
                    icon: "warning"
                });
                return;  // 그냥 종료
            }
            attachFileList.push(pFile);

            // console.log("파일리스트:", attachFileList);

            // 파일 읽어주는 아저씨 생성, 사람은 바이너리 파일을 못 읽음
            let v_fileReader = new FileReader();
            // 끌어온 파일 읽으라고 지시 
            v_fileReader.readAsDataURL(pFile);
            // 파일 내용을 다 읽었다면, load이벤트 발생(비동기)
            // 읽은 결과는 result속성에 담기게 된당.
            v_fileReader.onload = function () {

                // 비동기로 처리됨을 확인할 로그
                // console.log("체에킁:", pInx, pFile);

                let v_img = document.createElement("img");
                v_img.setAttribute("src", v_fileReader.result);
                v_img.id = "img" + fileIdIndex;
                v_img.style.width = "50%";
                v_img.style.height = "50%";

                // checkbox와 text를 가진 div 맹글어서 추강!
                let fileDetail = document.createElement("div");
                let chkBox = document.createElement("input");
                chkBox.type = "checkbox";
                chkBox.value = pFile.name;
                chkBox.fileId = fileIdIndex;
                chkBox.checked = true;     // 디폴트 체크
                chkBox.addEventListener("change", function () {

                    //바뀐 부분
                    let selectImg = document.querySelector("#img" + this.fileId);
                    let selectFile = document.querySelector("#file" + this.fileId);
                    //console.log("체에킁:", selectFile);
                    if (this.checked) {
                        selectImg.style.display = "inline-block";
                        selectFile.disabled = false;
                    } else {
                        selectImg.style.display = "none";
                        selectFile.disabled = true;
                    }
                })

                // 중요하다기 보단, form태그 전송을 위한 어쩔 수 없는 선택!
                let dataTransfer = new DataTransfer();
                dataTransfer.items.add(pFile);

                let inFile = document.createElement("input");
                inFile.type = "file";
                inFile.name = "myFiles";
                inFile.id = "file" + fileIdIndex;
                inFile.style.display = "none"; // 화면에서 감추깅

                //읽기전용이지만 fileList 통째로 덮어쓰기 가능
                inFile.files = dataTransfer.files;

                //id값 증가
                fileIdIndex++;

                let txtBox = document.createElement("input");
                txtBox.type = "text";
                txtBox.readOnly = true;
                txtBox.style.border = "none";
                txtBox.value = pFile.name;

                fileDetail.appendChild(chkBox);
                fileDetail.appendChild(txtBox);
                fileDetail.appendChild(document.createElement("br"));

                myPreview.appendChild(v_img);
                myList.appendChild(fileDetail);
                console.log("체킁:", inFile.files);
                myList.appendChild(inFile);
                //스크롤바 아래로 내리깅!
                myPreview.scrollTo(0, myPreview.scrollHeight);
                myList.scrollTo(0, myList.scrollHeight);
            }
        }


        //브라우져가 지원하는 파일 자동으로 여는 거 막기 위함
        function f_over() {
            event.preventDefault();
            event.stopPropagation();
        }

        // Drop 이벤트 기본기능 막고, 원하는 기능 넣기
        function f_drop() {
            event.preventDefault();
            event.stopPropagation();

            // 마우스로 끌어온 파일, 일단 1개만
            let v_files = event.dataTransfer.files;
            for (let i = 0; i < v_files.length; i++) {
                f_readOneFile(i, v_files[i]);
            }
        }

        // Drop 영역외에 파일 끌어다 놓았을 때 브라우져 동작막깅
        window.addEventListener("dragover", function () {
            event.preventDefault();
        });

        window.addEventListener("drop", function () {
            event.preventDefault();
        });

    </script>
</body>

</html>

 

실행 결과 아래처럼 잘 되었당. 개인적으론 AJAX사용을 많이 선호해서

필요없지만, 필요한 사람에게 도움이 되었으면 하는 바램이당. 

이거스로 파일 드래그 앤 드랍 응용은 당신에게 넘겨졌당!(당신 손은 마술사!)

 

 

결정된 옳고 그름도

결정된 선과 악도

결정된 정답과 오답도

없는게 아니라, 있을 수 없음을 알게 된 나이

내 속의 나, 또 다른 나 그래서 우린

더이상 싸울 이유를 찾지 못했당.

오늘도 타협의 조그셔틀을 돌린당.

 

우린 더 이상 싸우지 않는당!

적어도 보이는 싸움은.....

 

 

https://www.youtube.com/watch?v=nnIHW-aDhZk

 

관련글 더보기