jQuery - 파일API : 드래그 앤드 드롭 구현
이 기능은 MS가 1999년 인터넷 익스플로러 5.0에 설계하고 구현한 기능으로 지금까지도 인터넷 익스플로러에서 지원하고 있습니다. HTML5 규격 명세는 기존의 드래그 앤드 드롭 기능을 문서화했고 현재 사파리, 파이어폭스, 크롬 등이 지원하고 있습니다.
드래그 앤드 드롭과 관련한 이벤트로는 dragstart, drag, dragover, dragenter. dragleave, drop, dragend 등이 있습니다. HTML5 파일 API를 지원하지 않는 구형 브라우저라도 드래그 앤드 드롭 API를 이용할 수 있습니다.
드래깅
드래깅은 상당히 간단합니다. 엘리먼트의 draggable 속성을 true로 설정하면 엘리먼트를 드래그할 수 있습니다.
<div id="mydrag" draggable="true">My Drag!</div>
드래그할 수 있는 엘리먼트를 데이터와 연결했으므로 dragstart 이벤트를 리스닝하면 이벤트의 setData() 함수를 호출할 수 있습니다.
var element = $("#mydrag");
element.bind("dragstart", function(event){
event = event.originalEvent;
event.dataTransfer.effectAllowed = "move";
event.dataTransfer.setData("text/plain", $(this).text());
event.dataTransfer.setData("text/html", $(this).html());
event.dataTransfer.setDragImage("/images/image.png", -10, -10);
});
jQuery는 이벤트 추상화를 제공하지만 필요한 dataTransfer 오브젝트는 포함하지 않습니다. 추상화된 이벤트는 드래그/드롭 API에 쉽게 접근할 수 있도록 originalEvent 속성을 제공합니다.
위 예제에서 볼 수 있듯이 이벤트는 우리에게 필요한 다양한 드래그 앤드 드롭 기능을 포함하는 dataTransfer 오브젝트를 갖고 있습니다. setData() 함수는 MIME 형식과 문자열 데이터를 인자로 받습니다.
위 예제에서는 drag 이벤트에 텍스트와 text/html 데이터를 설정했습니다. 엘리먼트를 드롭하면 drop 이벤트가 발생하면서 이 데이터를 읽을 수 있게 됩니다. 마찬가지로 엘리먼트를 브라우저 밖으로 드래그하면 다른 애플리케이션에서 지원하는 파일 타입에 따라 적절하게 드롭된 데이터를 처리할 수 있습니다.
텍스트를 드래그할 때는 만약의 경우를 대비해 기본적으로 드래그하는 데이터에 text/plain 형식을 사용해야 합니다. 그렇지 않으면 엘리먼트를 드롭한 대상에서 어떤 형식도 지원하지 못하는 상황이 발생할 수 있기 때문입니다.
드래그하는 링크는 text/plain과 text/uri-list 두 가지 형식을 가져야 합니다. 여러 링크를 드래그하려면 각 링크를 새로운 행에 합칩니다.
// 링크 드래그
event.dataTransfer.setData("text/uri-list", "http://example.com");
event.dataTransfer.setData("text/plain", "http://example.com");
// 여러 링크는 새로운 행으로 분리
event.dataTransfer.setData("text/uri-list", "http://example.com\nhttp://google.com");
event.dataTransfer.setData("text/plain", "http://example.com\nhttp://google.com");
setDagImage() 함수는 선택사항이며, 드래그 동작시 커서에 무엇을 표시할지 설정할 수 있습니다. setDragImage() 함수는 이미지 소스, x/y 좌표(커서에 상대적 이미지 좌표)를 인자로 받습니다. setDragImage() 함수를 이용하지 않으면 드래그하는 엘리먼트의 잔상(Ghostly Clone)을 사용합니다.
엘리먼트의 드래그 피드백 갱신에 활용하는 addElement(element, x, y)에 setDragImage()를 이용할 수 있습니다. 즉 드래그 동작을 수행하는 동안 커스텀 엘리먼트가 표시되게 할 수 있습니다.
DownloadURL 형식을 설정하면 사용자가 브라우저 밖으로 파일을 드래그할 수 있도록 허용할 수 있습니다. URL은 브라우저가 파일을 내려 받을 파일의 위치로 설정합니다.
지메일은 이 기능을 활용합니다. 따라서 지메일 사용자가 이메일 첨부를 브라우저 밖의 데스크톱으로 드래그 앤드 드롭할 수 있습니다. 다만, 이 기능은 크롬만 지원합니다.
DownloadURL 형식에서는 파일 정보(MIME, 이름, 위치)와 값을 콜론(:)으로 구분합니다.
$("#preview").bind("dragstart", function(e){
e.originalEvent.dataTransfer.setData("DownloadURL", [
"application/octet-stream", // MIME 형식
"File.exe", // 파일명
"http://example.com/image_file.png" // 파일 경로
].join(":"));
});
드롭
드래그/드롭 API로 파일이나 엘리먼트를 드롭 했을 때 발생하는 drop 이벤트를 리스닝할 수 있습니다. 다만, 기본 이벤트 dragover와 dragenter 이벤트를 모두 취소해야만 drop 이벤트가 발생합니다.
예를 들어 다음과 같이 두 이벤트를 취소할 수 있습니다.
var element = $("#dropzone");
element.bind("dragenter", function(e){
// 이벤트 취소
e.stopPropagation();
e.preventDefault();
});
element.bind("dragover", function(e){
// 커서 설정
e.originalEvent.dataTransfer.dropEffect = "copy";
// 이벤트 취소
e.stopPropagation();
e.preventDefault();
});
위 예제처럼 dragover 이벤트의 dropEffect로 커서 모양을 설정할 수 있습니다. dragenter, dragleave 이벤트를 리스닝하면서 상황에 맞게 대상 엘리먼트 클래스를 토글해서 사용자에게 파일 드롭을 지원하는 영역인지 여부를 시각적으로 표시할 수 있습니다.
dragenter, dragover 이벤트를 취소하면 drop 이벤트를 리스닝할 수 있습니다. 드래그하던 엘리먼트나 파일을 대상 엘리먼트에 드롭하면 drop 이벤트가 발생합니다. drop 이벤트의 dataTransfer 오브젝트는 files 속성(드롭한 모든 파일을 포함하는 FileList를 반환하는)을 반환합니다.
element.bind("drop", function(event){
// 재전송 취소
event.stopPropagation();
event.preventDefault();
event = event.originalEvent;
// 드래그된 파일에 접근
var files = event.dataTransfer.files;
for (var i=0; i < files.length; i++){
alert("Dropped " + files[i].name);
}
});
dataTransfer.getData() 함수에 지원하는 포맷을 전달해서 파일의 데이터에 접근할 수 있지만 만약, 포맷을 지원하지 않는다면 undefined를 반환합니다.
var text = event.dataTransfer.getData("Text");
dataTransfer 오브젝트는 읽기 전용 type 속성(dragstart 이벤트에서 설정한 마임 포맷 목록 DOMString List를 반환하는)을 포함합니다. 또한 파일을 드래그할 때에는 types의 값에 'Files'가 포함됩니다.
var dt = event.dataTransfer;
for (var i=0; i < dt.types.length; i++){
console.log( dt.types[i], dt.getData(dt.types[i]) );
}
드래그/드롭 취소하기
기본적으로 파일을 웹페이지로 드래그하면 브라우저는 그 파일을 탐색합니다. 사용자가 드롭 영역을 놓쳤을 때 웹 애플리케이션에서 떠나는 것을 원치 않기 때문에 이와 같은 기본 동작을 제거할 것입니다. body의 dragover 이벤트를 취소하면 기본 동작을 제거할 수 있습니다.
$("body").bind("dragover", function(e){
e.stopPropagation();
e.preventDefault();
return false;
});
복사와 붙여넣기
드래그 앤드 드롭 기능 말고도 어떤 브라우저는 복사와 붙여넣기 기능을 데스크톱과 통합했습니다. 복사와 붙여넣기 API는 표준화되지 않았으며 HTML5 규격 명세의 일부가 아닙니다. 따라서 다양한 브라우저에서 이 기능을 어떻게 제공할지를 스스로 결정해야 합니다.
복사와 붙여넣기에서는 dataTransfer 대신 clipboardData 오브젝트를 사용한다는 점만 제외하면 두 API는 동일하다 하겠습니다.
파이어폭스는 복사와 붙여넣기 API를 제공하지 않고 자신만의 API로 클립보드에 접근하지만 그리 널리 사용되는 방식은 아닙니다. 웹킷(사파리/크롬)은 복사와 붙여넣기 API를 잘 지원합니다.
복사하기
복사하기와 관련한 이벤트는 두 개며, 잘라내기와 관련한 이벤트도 두 개입니다.
- beforecopy
- copy
- beforecut
- cut
이벤트 이름에서도 알 수 있듯이 beforecopy와 beforecut은 클립보드 동작이 일어나기 전에 복사나 잘라내기를 취소할 때 사용할 수 있습니다. 사용자가 선택한 텍스트를 복사하면 copy 이벤트가 발생하며 커스텀 클립보드 데이터를 설정할 수 있도록 clipboardData 오브젝트를 애플리케이션에 전달합니다. dataTransfer 오브젝트와 마찬가지로 clipboardData도 MIME 포맷과 문자열 값을 인자로 받는 setData() 함수를 포함합니다. setData() 함수를 이용하려면 copy 이벤트를 취소해서 기본 동작을 막아야 합니다.
인터넷 익스플로러는 event가 아니라 window의 clipboardData 오브젝트에 데이터를 저장합니다. 따라서 이벤트에 오브젝트가 존재하는지 검사해보고 존재하지 않으면 윈도우 오브젝트를 검사해봐야 합니다.
파이어폭스는 copy 이벤트를 발생시키지만 clipboardData 오브젝트에는 접근할 수 없습니다. 크롬은 clipboardData에 접근할 수 있지만 clipboardData로 전송한 데이터를 모두 무시합니다.
$("textarea").bind("copy", function(event){
event.stopPropagation();
event.preventDefault();
var cd = event.originalEvent.clipboardData;
// 인터넷 익스플로러
if ( !cd ){
cd = window.clipboardData;
}
// 파이어폭스
if ( !cd ){
return;
}
cd.setData("text/plain", $(this).text());
});
붙여넣기
붙여넣기에는 beforepaste, paste라는 이벤트 두 개가 있습니다. 사용자가 붙여넣기를 시도하면 데이터를 실제 붙이넣기 전에 paste 이벤트가 발생합니다. 마찬가지로 붙여넣기 동작도 브라우저마다 구현이 다릅니다. 아무 엘리먼트에 포커스가 없는 상태에서도 paste 이벤트를 발생시키지만 인터넷 익스플로러와 사파리는 엘리먼트에 포커스가 있어야만 이벤트를 발생시킵니다.
paste 이벤트는 drop 이벤트 API와 비슷합니다. paste 이벤트는 clipboardData 프로퍼티를 포함하므로, clipboardData 프로퍼티의 getData() 함수(마임 포맷을 인자로 받는)로 붙여넣기한 데이터에 접근할 수 있습니다. 이벤트를 취소하지 않으면 평소처럼 붙여넣기 동작이 실행되면서 포커스를 가진 엘리먼트로 데이터를 붙여 넣습니다.
$("textarea").bind("paste", function(event){
event.stopPropagation();
event.preventDefault();
event = event.originalEvent;
var cd = event.clipboardData;
// 인터넷 익스플로러
if ( cd ){
cd = window.clipboardData;
}
// 파이어폭스
if ( cd ){
return;
}
$("#result").text( cd.getData("text/plain") );
// 사파리는 파일 붙여넣기를 지원합니다.
var files = cd.files;
});
0 댓글