HTML5 Tutorial – Drag and Drop with Files (Client)

image_thumb[3]In the last post, we showed you the basics of drag and drop. Drag-and-drop functionality is something that computer users have come to take for granted as “just working,” and there are a few ways to enable it within the browser.

You learned how you need to have a drag source and a drop target, and how to specify each in HTML5. And we learned how the dataTransfer object is used to send data between the drag source and the drop target.

In this chapter, we learn more about how to use Drag and Drop with files.

You will learn how to:

  • Use the dataTransfer.files object to get basic information about your files.
  • How you can process files on the client to perform some action.

And along the way, you’ll see how you can add more use interactivity to your application.

dataTransfer.files

The dataTransfer object also includes information about files that may dropped onto the target.

// Get the file(s) that are dropped.
var filelist = ev.dataTransfer.files;

Here’s an example of how you can get data about the file from the dataTransfer object.


<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Drop files on me</title>
<style type="text/css">
#dropOnMe {
width: 210px;
height: 136px;
padding: 10px;
border: 1px solid #aaaaaa;
}
</style>
<script>
window.addEventListener("load", function () {
// this function runs when the page loads to set up the drop area
dropOnMe.addEventListener("drop", dropHandler, false);
dropOnMe.addEventListener("dragover", function (ev) {
ev.preventDefault();
}, false);
function dropHandler(ev) {
// Prevent default processing.
ev.preventDefault();
// Get the file(s) that are dropped.
var filelist = ev.dataTransfer.files;
if (!filelist) return; // if null, exit now
// get number of dropped files
var filecount = filelist.length;
if (filecount > 0) {
// Do something with the files.
fileCount.innerHTML = "<div>" + filecount + " files dropped</div>";
for (var i = 0; i < filecount; i++) {
myFileList.innerHTML += "<li>" + filelist[i].name + " " +
filelist[i].size + " " +
filelist[i].type +
"</li>";
}
}
}
});
</script>
</head>
<body>
<h3>Drop Files on Me</h3>
<div id="dropOnMe" draggable="false"></div>
<div id="fileCount" draggable="false"></div>
<div draggable="false">
<ol draggable="false" id="myFileList"></ol>
</div>
</body>
</html>

In this example, we set up a drop target called dropOnMe. We set up the dragOver hander that prevents events from being propagated.

And we have dropHandler function that we use to do something with the files once the user had dropped them. We get the list of files from the dataTransfer object. We can get information about each file from the filelist, such as filename, file size, and file type. The type is returned as a MIME type, if it is valid.

NOTE: Some browsers treat <input type=”file”> elements as native drop targets.

Reading Dropped Files using FileReader

Once you have a files reference from dataTransfer object, you can instantiate a FileReader object and reach the contents into memory. When the load finishes, the reader’s onload event occurs and its result attribute can be used to access the file data.

FileReader includes three options for reading a file, asynchronously:

  • readAsText(Blob|File, opt_encoding) – The result property will contain the file/blob’s data as a text string. By default the string is decoded as ‘UTF-8’. Use the optional encoding parameter can specify a different format.
  • readAsDataURL(Blob|File) – The result property will contain the file/blob’s data encoded as a data URL.
  • readAsArrayBuffer(Blob|File) – The result property will contain the file/blob’s data as an ArrayBuffer object.

Side note: Some code refers to readAsBinaryString which is no longer part of the W3C draft and not supported in IE10.

You read the data in the file into a function that responses to the reader’s onload event, which is a function that will hand you the file’s data. (If you stare it long enough, and try it, it actually makes sense in an asynchronous kinda way.)

Here’s the key part of the code where f is the incoming file that you get from the dataTransfer object. I reader brings the file in as a text using readAsText(f) which puts the file data into a I  text string that I can read asynchronously in e.target.results. When the file is read, it fires the reader.onload event, that looks for a function of something to do with the file.

var reader = new FileReader();

// Closure to capture the file information.
reader.onload = (function (theFile) {
    return function (e) {
        myFileList.innerHTML += "<li>" + theFile.name + " " +
            countWords(e.target.result)
            "</li>";
    };
})(f);

// Be sure to actually read the file.
reader.readAsText(f);

 

For the whole example, when the user drags one or more text files on the target, the following code counts the number of words (more or less) in each text document.


<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Drop files on me</title>
<style type="text/css">
#dropOnMe {
width: 210px;
height: 136px;
padding: 10px;
border: 1px solid #aaaaaa;
}
</style>
<script>
window.addEventListener("load", function () {
// this function runs when the page loads to set up the drop area
dropOnMe.addEventListener("drop", dropHandler, false);
dropOnMe.addEventListener("dragover", function (ev) {
ev.preventDefault();
}, false);
function dropHandler(ev) {
// Prevent default processing.
ev.preventDefault();
// Get the file(s) that are dropped.
var filelist = ev.dataTransfer.files;
if (!filelist) return; // if null, exit now
// Loop through the FileList.
for (var i = 0, f; f = filelist[i]; i++) {
// Only process text files.
if (!f.type.match('text.*')) {
continue;
}
var reader = new FileReader();
// Closure to capture the file information.
reader.onload = (function (theFile) {
return function (e) {
myFileList.innerHTML += "<li>" + theFile.name + " " +
countWords(e.target.result)
"</li>";
};
})(f);
// Be sure to actually read the file.
reader.readAsText(f);
}
}
});
function countWords(s) {
s = s.replace(/(^\s*)|(\s*$)/gi, "");
s = s.replace(/[ ]{2,}/gi, " ");
s = s.replace(/\n /, "\n");
return s.split(' ').length;
}
</script>
</head>
<body>
<h3>Drop Files on Me</h3>
<div id="dropOnMe" draggable="false"></div>
<div id="fileCount" draggable="false"></div>
<div draggable="false">
<ol draggable="false" id="myFileList"></ol>
</div>
</body>
</html>

Memory might be tight and you may not be able to read the entire file into memory. A good example on how to slice your file is show in HTML5 Rocks article Reading files in JavaScript using the File APIs where Eric Bridelman also shows you how you can monitor the process of a file being read.

Resources

Sample Code

Sample code is available in the DevDays GitHub repository. See https://github.com/devdays/html5-tutorials