- 6
- 이니스프리
- 조회 수 3506
obj.on('drop', function (e) { e.preventDefault(); var files = e.originalEvent.dataTransfer.files; for (var i = 0; i < files.length; i++) { ext = files[i].name.split('.').pop(); if (ext != 'jpg' && ext != 'jpeg' && ext != 'png' && ext != 'gif') { alert(ext + ' filetype is not allowed!'); throw new Error('Filetype error.'); } } if(files.length < 1) return; for (var i = 0; i < files.length; i++) { var data = new FormData(); // 이 부분에 대해 질문을 드립니다!!! data.append('up_load', files[i]); $.ajax({ url: '/bbs/dropbox.php', method: 'post', data: data, async: true, processData: false, contentType: false, success : function(datas) { $('#dropzone') .css('border', '1px dashed blue') .css('cursor', 'pointer'); $("input#file").val(""); var filename = datas.match(/.*?_.*?_(.+)\?raw=1/)[1]; $("#img_list").append("<img src='" + datas + "' class='dropbox_upload' data-toggle='tooltip' data-placement='bottom' title='" + filename + "' />"); img_focus(); } }); } })
안녕하세요?
자바스크립트의 FormData와 관련하여 질문 드립니다.
API가 멀티파일 업로드를 허용하지 않는 상황에서 위와 같은 방식으로 복수의 파일을 drag & drop을 하면
for문으로 ajax를 돌려서 하나씩 파일을 전송하려고 하는데요.
원래는 var data = new FormData();를 for문 밖에 놓고
data.set('up_load', files[i]);라고 하여 값을 덮어썼는데요.
모던 브라우저에서는 문제가 없는데 IE에서 작동을 안 하길래 확인해보니 IE에서는 set 메서드를 지원하지 않네요 ㅠㅠ
(IE 11에서 테스트했습니다)
게다가 IE에서는 delete 메서드도 지원하지 않구요.
그래서 IE의 호환성 때문에 부득이 다음과 같이 for문을 돌릴 때마다 data 변수를 새로 생성하는 방식을 택했는데 이것이 최선의 방법일까요?
var data = new FormData();
data.append('up_load', files[i]);
제가 자바스크립트 & jQuery 실력이 부족하다보니 보다 효율적인 방법이 있을 것 같아서 여쭤봅니다 ^^
이런 부분의 처리와 관련하여 넓은 의미에서의 코딩 컨벤션이 있을 것 같아서요.
그럼 폭염경보가 발령되었다는데 건강 유의하시고 좋은 오후 되시고, 이번 한 주도 화이팅입니다 ^-^
답변 달아주실 부들께 미리 감사드려요~
https://developer.mozilla.org/ko/docs/Web/API/FormData
작성자
댓글 6
안녕하세요?
날씨도 덥고 바쁘신데 바닐라 자바스크립트로 다시 작성해주셔서 감사합니다!!
formdata에 append 메서드를 사용하는 것이 IE의 호환성 관련하여 최선이라는 말씀이시죠? ^^
개발자분들의 커뮤니티에 올라오는 글을 보면 요샌 jQuery를 버리려는 경향이 있더군요~
저는 빠른 개발(이라고 쓰지만 실제로는 개발할 능력이 안 되어서 구글링 후 복붙)을 하느라 jQuery를 불필요하게 남발한 것 같네요.
덕분에 많이 배우고 갑니다 ^-^
+)
아참 죄송한데 하나만 더 여쭤볼게요~
for문 안에 ajax의 success가 들어가서 발생하는 문제 같은데요.
$('#dropzone')
.css('border', '1px dashed blue')
.css('cursor', 'pointer');
이 부분 때문에 여러 개의 파일 중에 하나만 전송이 완료되어도 CSS 속성이 변경되는데요.
혹시 여러 개의 파일 모두가 전송이 완료된 후에 CSS 속성을 변화시키는 방법이 있을까요?
$.ajax를 사용해서 통신을 하면 $.ajax가 가장 마지막에 호출되어 동작하기 때문인지
$.ajax가 포함된 for문의 뒤에 $("body").css("cursor", "default");을 넣으면
커서가 progress로 아예 바뀌지 않는 것처럼 보이더군요 ㅠㅠ
XHR을 HTTP synchronous request 요청을 사용하면 구현이 정말 쉽지만 AJAX를 사용하면 비동기라는 특성때문에 구현이 약간 힘듭니다.
obj.addEventListener("drop",e => { /* Vanila JS 로 작성 * registedLastReq라는 전역변수로 for문의 마지막에서 Ajax의 전송대기열에 올린것을 선언하고 success에서 이를 구별함. */ e.preventDefault(); var files = e.originalEvent.dataTransfer.files; var allowedExt = /(jpg|jpeg|png|gif|webp|bmp)/i; registedLastReq = false; for (var count = 0; count < files.length; count++) { extension = files[count].name.split(".").pop(); if (!allowed.test(extension)) { window.alert(extension+" filetype isn't allowed!"); throw new Error("Filetype Error."); } } if (files.length < 1) return; for (var i = 0; i < files.length; i++) { var data = new FormData(); var xmlHttp = new XMLHttpRequest(); data.append("up_load",files[i]); xmlHttp.open("POST","/bbs/dropbox.php",true); xmlHttp.send(data); if (i == files.length-1) registedLastReq = true; xmlHttp.onreadystatechange = function() { if (this.readyState === this.DONE && this.status === 200) { var filename = this.response.match(/.*?_.*?_(.+)\?raw=1/)[1]; document.querySelector('input#file').value = ""; document.getElementById('img_list').innerHTML += "<img src='" + this.responseText + "' class='dropbox_upload' data-toggle='tooltip' data-placement='bottom' title='" + filename + "'>"; img_focus(); if (registedLastReq) { document.getElementById('dropzone').style.border = "1px dashed blue"; document.getElementById('dropzone').style.cursor = "pointer";registedLastReq=false; } } } } });
음.. 일단 이렇게 해봤어요...
이 코드의 문제점은 이 Ajax가 비동기요청으로 작동한다는 겁니다.
for 문에서 마지막 ajax전송부분까지 요청을 Send했다고 하더라도..
그 요청은 아직 완료된게 아닙니다. 요청대기열에 올렸을 뿐이죠...
그래서 마지막 요청을 요청대기열에 올리고 registedLaseReq라는 변수를 선언하여 success될때 이를 구별한다 하더라도 마지막 요청의 앞에
대기타고있는 요청이 있다면 그 요청이 이 변수를 가로채서(...) 완료되었다고 치고 스타일을 바꿀 수 있는거죠....
그래서 이를 좀 개선한 코드를 추가해봤습니다...
obj.addEventListener("drop",e => { /* Vanila JS 로 작성 * */ e.preventDefault(); var files = e.originalEvent.dataTransfer.files; var allowedExt = /(jpg|jpeg|png|gif|webp|bmp)/i; for (var count = 0; count < files.length; count++) { extension = files[count].name.split(".").pop(); if (!allowed.test(extension)) { window.alert(extension+" filetype isn't allowed!"); throw new Error("Filetype Error."); } } if (files.length < 1) return; for (var i = 0; i < files.length; i++) { var data = new FormData(); var xmlHttp = new XMLHttpRequest(); data.append("up_load",files[i]); xmlHttp.open("POST","/bbs/dropbox.php",true); xmlHttp.send(data); if (i == files.length-1) { xmlHttp.onreadystatechange = function() { if (this.readyState === this.DONE && this.status === 200) { var filename = this.response.match(/.*?_.*?_(.+)\?raw=1/)[1]; document.getElementById('dropzone').style.border = "1px dashed blue"; document.getElementById('dropzone').style.cursor = "pointer"; document.querySelector('input#file').value = ""; document.getElementById('img_list').innerHTML += "<img src='" + this.responseText + "' class='dropbox_upload' data-toggle='tooltip' data-placement='bottom' title='" + filename + "'>"; img_focus(); } } } else { xmlHttp.onreadystatechange = function() { if (this.readyState === this.DONE && this.status === 200) { var filename = this.response.match(/.*?_.*?_(.+)\?raw=1/)[1]; document.querySelector('input#file').value = ""; document.getElementById('img_list').innerHTML += "<img src='" + this.responseText + "' class='dropbox_upload' data-toggle='tooltip' data-placement='bottom' title='" + filename + "'>"; img_focus(); } } } } });
이건 좀 무식한 방법입니다...
for문에서 if문으로 마지막 요청을 구별하여 스타일하나 바꾸기위해 통으로 복붙한 후 마지막 요청 success에 스타일을 바꾸는 코드를 삽입한겁니다.
하지만 이짓때문에 success 같은 코드가 생겼습니다.....
success에서 마지막 요청을 구별할 수 있는 방법은 없느냐? 없습니다...
이런저런 문제(비동기적 작동, 클로저)가 있어서 우리가 상상하는 코드는 못짤겄같습니다.
하지만 비동기만 포기하면 말이 좀 달라집니다.
obj.addEventListener("drop",e => { /* Vanila JS 로 작성 * Synchronous request */ e.preventDefault(); var files = e.originalEvent.dataTransfer.files; var allowedExt = /(jpg|jpeg|png|gif|webp|bmp)/i; for (var count = 0; count < files.length; count++) { extension = files[count].name.split(".").pop(); if (!allowed.test(extension)) { window.alert(extension+" filetype isn't allowed!"); throw new Error("Filetype Error."); } } if (files.length < 1) return; for (var i = 0; i < files.length; i++) { var data = new FormData(); var xmlHttp = new XMLHttpRequest(); data.append("up_load",files[i]); xmlHttp.open("POST","/bbs/dropbox.php",false); xmlHttp.send(data); if (xmlHttp.status === 200) { var filename = this.response.match(/.*?_.*?_(.+)\?raw=1/)[1]; if (i === files.length-1) { document.getElementById('dropzone').style.border = "1px dashed blue"; document.getElementById('dropzone').style.cursor = "pointer"; } document.querySelector('input#file').value = ""; document.getElementById('img_list').innerHTML += "<img src='" + this.responseText + "' class='dropbox_upload' data-toggle='tooltip' data-placement='bottom' title='" + filename + "'>"; img_focus(); } } });
이 정도로 코드가 맑아집니다(...)
다만 동기적요청은 일부 웹 엔진에서 사용자 경험에 좋지않은 영향을 발생시킬 수 있다는 이유로 비권장 사항입니다. 다만 Naver도 씁니다.
참고로.... 동기요청에서 서버가 빨리 응답 안해주면 코드 실행자체가 얼어붙습니다.
오오~ 날씨도 무더운데 상세한 설명을 해주셔서 정말 감사드립니다! ^-^
바닐라 자바스크립트로 보니 코드 자체는 길어져도 오히려 이해는 쏙쏙 잘 되네요~
결과적으로 synchronous로 처리하면 간단히 해결되는 문제이군요 ㄷㄷ
어차피 Dropbox API가 멀티파일 업로드를 허용하지 않아서
결국 제 서버의 PHP cURL과 Dropbox 서버와의 통신은 사실상 동기적으로 이루어질텐데 말이죠.
이 스크립트를 작성하면서 이런저런 문제로 jQuery ajax에서 async: false로 설정을 변경한 적이 있었는데요.
(명쾌하게 이해는 안 되지만 ajax 구문만 따로 함수로 선언해서 호출할 때에는
async를 끄지 않으면 완전 뒤죽박죽이 되어버려서 부득이 false로 설정해야 되더군요 ㅜㅜ)
크롬 최신버전에서는 depreciated라는 warning이 뜨면서도 작동은 하는 경우도 있고, 아예 멈추는 경우도 있고 케바케더군요.
(제가 테스트한 환경에서는 파폭 최신버전으로는 작동은 잘 되는 것 같더군요)
이에 대해 구체적으로 크롬에서 어떤 경우에 작동을 멈추는지에 대해서는 구글링을 해봐도 명확한 답이 없더군요 ㅠㅠ
모든 문제는 제가 자바스크립트의 동작방식과 ajax가 어떻게 돌아가는지에 대해 잘 모르기 때문인 것 같네요.
(자꾸 PHP나 파이썬 돌아가듯이 생각하는 경향이 있어서요 ㅠㅠ)
애당초 ajax를 for문으로 반복을 돌리는 것이 아니라 선택된 여러 개의 파일을 한꺼번에 PHP로 넘기고
PHP에서 반복문으로 cURL을 돌리는 것이 보다 간명한 방법이라는 생각이 들기는 하는데
이제 와서 그렇게 수정하려면 상당히 많이 뜯어고쳐야겠네요 ㅜㅜ
일단 올려주신 스크립트를 바탕으로 더 공부하면서 계속 수정해보겠습니다!
책을 사놓고 제대로 읽지 못했는데 클로저에 대한 공부도 더 해야겠네요~
다시 한 번 감사드립니다 ^^
그럼 저녁식사 맛있게 드세요~
음.. 제가 솔직히 말하자면... 동기요청을 추천드리지는 않습니다.
물론 서버가 에러처리 잘하고 요청을 값넘기고 요청을 빠르게 닫는다면 정말 좋은 방식이긴 합니다.
문제는 서버 지연속도에 따라 클라이언트 쪽도 덩달아 얼어버리는 상황이 발생될 수 있습니다.
무슨뜻이냐면, a 라는 브라우저가 동기 XHR로 서버에 요청했을때, 서버가 값을 주고 요청을 닫기 전까지는 클라이언트는 그 어떤 js도 실행시킬 수 없습니다. 서버가 응답을 안해서 아직 finished상태가 되지 않았고 js 인터프리터는 구문을 순차적으로 실행하기 때문에 작업이 완료되지 않으면 새로운 작업을 시작할 수 없습니다. 그래서 비동기작업이 존재하는거구요.
그럼,
Good night :)
옙 잘 알겠습니다! 번번이 많이 배워서 감사합니다 ^^
당장 불편하게 느껴지더라도 depreciated 된 것은 나름대로의 이유가 있겠죠.
CSS & 자바스크립트 애니메이션이 제대로 작동하게 하려면
결국 ajax로 한꺼번에 파일들을 넘기고 PHP에서 cURL을 반복문으로 돌리는 방향으로 새로 짜는 것이 가장 바람직하겠네요~
멀티파일 업로드를 지원하지 않는 API를 클라이언트 단에서는 멀티파일을 선택하는 방식으로 작동하게 하는 것을
제가 처음 경험하다보니 시행착오가 꽤 있었네요 ㅠㅠ
그럼 오늘 밤에 태풍이 온다는데 비 조심하시고 굿밤 되세요!!
덕분에 이것저것 많이 배워서 다시 한 번 감사드립니다 :)
음 값을 바꾸긴 어렵겠네요!