SHARE
Facebook X Pinterest WhatsApp

Drag Files Into the Browser From the Desktop with HTML5

Feb 25, 2012


Drag Files into the Browser from the Desktop

HTML5 Drag & Drop lets you do basic drag & drop operations with a lot less JavaScript code. Two additional advantages to HTML5 Drag & Drop are the ability to combine it with other JavaScript utilities such as Ajax and the HTML5 FileReader and that it allows you to drag files directly from the Desktop, as in folders and Windows Explorer. Put that all together, and you can pull off some pretty impressive feats, like create a drop zone for image previews or file uploads. We’ll be tackling the former today and the next article will be dedicated to the uploading code. Today’s article will also suggest how to navigate the mine field that is asynchronous programming!

The File Display Page

In terms of HTML, all we need is a page that contains a few DIVs: One to display status messages, one in which to drop the files, and one to show the images:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset=utf-8 />
<title>A File Display Demo</title>
<script type="text/javascript">
//script code will go here...
</script>
<style>
#drop {
  min-height: 150px;
  width: 250px;
  border: 1px solid blue;
  margin: 10px;
  padding: 10px;
}
</style>
</head>
<body>
  <h1 align=center>A File Preview Demo</h1>
  <DIV id="status">Drag the files from a folder to a selected area ...</DIV>

  <DIV id="drop">Drop files here.</DIV>
  <DIV id="list"></DIV>
</body>
</html>

Designating the “drop” DIV as a Drop Zone

Before anything happens, there is a check to make sure that the browser supports the HTML5 FileReader. Assuming it does, code is attached to the window’s onload() event so that the drop element is declared after the page has finished loading. To enable a DIV to accept dropped items, we have to cancel the default behavior for the ‘dragover’ and ‘dragenter’ events:

if(window.FileReader) { 
  addEventHandler(window, 'load', function() {
    var status = document.getElementById('status');
    var drop   = document.getElementById('drop');
    var list   = document.getElementById('list');
  	
    function cancel(e) {
      if (e.preventDefault) { e.preventDefault(); }
      return false;
    }
  
    // Tells the browser that we *can* drop on this target
    addEventHandler(drop, 'dragover', cancel);
    addEventHandler(drop, 'dragenter', cancel);
  });
} else { 
  document.getElementById('status').innerHTML = 'Your browser does not support the HTML5 FileReader.';
}

The addEventHanlder() is my own cross-browser implementation for binding a handler to an event:

function addEventHandler(obj, evt, handler) {
    if(obj.addEventListener) {
        // W3C method
        obj.addEventListener(evt, handler, false);
    } else if(obj.attachEvent) {
        // IE method.
        obj.attachEvent('on'+evt, handler);
    } else {
        // Old school method.
        obj['on'+evt] = handler;
    }
}

Processing Dropped Files

The ondrop event is where we place code to process the imported files. Again, we have to cancel the browser’s default behavior, which is to redirect to the dropped file. That’s OK if you want to display the image in the browser, but it limits the number to a single file, provides no additional information about the file, and does not allow you to perform any additional processing on it. Both the e.preventDefault() call and “return false” line at the end of the event handler play a role in cancelling the default browser behavior.

The event’s dataTransfer property contains a files collection that we can iterate through to process one file at a time. The FileReader.readAsDataURL() method is the one to use to display the image:

addEventHandler(drop, 'drop', function (e) {
  e = e || window.event; // get window.event if e argument missing (in IE)   
  if (e.preventDefault) { e.preventDefault(); } // stops the browser from redirecting off to the image.

  var dt    = e.dataTransfer;
  var files = dt.files;
  for (var i=0; i<files.length; i++) {
    var file = files[i];
    var reader = new FileReader();
      
    //attach event handlers here...
   
    reader.readAsDataURL(file);
  }
  return false;
});

The onloadend Event

When the FileReader has finished reading the file, it fires the onloadend event. That’s the time to do what ever it is you wish to do with the file. In our case, we’re going to display a progress report, some file information, and append the binary file contents to a list of images. That sounds so straight forward, but it isn’t on account of the asynchronous nature of the FileReader. For instance, just determining where we’re at in the processing order is a challenge. As we go through the loop, the i variable is incremented to the current file being read, but that’s the file reading order, not the order in which the files are finished being read! Imagine that you have two files where the first is several megs in size and the second is only about 35 KB. Unless something weird happens, you can bet that the second file’s onloadend will fire first. So don’t depend on the reading order to tell us the order in which the files have finished being read. Instead, find something that reflects the status of the file reads at the time that the onloadend handler executes. In my case, I chose to count the number of DIV tags in the file list to determine the number of files already processed (each file produces a information DIV and an IMG). Hence, the current file is one greater than what’s already been processed:

addEventHandler(reader, 'loadend', function(e, file) {
    var bin           = this.result; 
    var newFile       = document.createElement('div');
    newFile.innerHTML = 'Loaded : '+file.name+' size '+file.size+' B';
    list.appendChild(newFile);  
    var fileNumber = list.getElementsByTagName('div').length;
    status.innerHTML = fileNumber < files.length 
                     ? 'Loaded 100% of file '+fileNumber+' of '+files.length+'...' 
                     : 'Done loading. processed '+fileNumber+' files.';

    var img = document.createElement("img"); 
    img.file = file;   
    img.src = bin;
    list.appendChild(img);
}.bindToEventHandler(file));

We can’t get the full file path due to security restrictions, but we can display the image by setting it’s SRC property directly to the binary file content:

firebug_html_source_after_file_drag.gif

Binding the File to the Event Handler

File ordering is not the only problem caused by the asynchronous reading. Just as the i incrementor is out of date, so is the current file. In other words, the file that we are currently reading in the for loop is not necessarily the same one that we just finished reading. This is the same problem that JavaScript developers first encountered with Ajax callbacks. The optimal solution was to bind stateful data to the callback handler. Binding to an event handler is almost the same idea, except that you also need to deal with the event object. As you know, in Internet Explorer it is referenced in the global window.event property, while other browsers pass it directly to the handler. What I like to do is to have an argument for the event, and then set is as follows:

e = e || window.event; // get window.event if e argument missing (in IE)   

In my bindToEventHandler() Function method, I convert the bound parameters into a proper Array using Array.prototype.slice.call(). The I prepend the event object to the array via unshift(). The function-within-a-function structure of the bindToEventHandler() method causes a closure so that when the returned function is executed, it still has a reference to the original bound parameters. The real handler is then called using Function.apply():

Function.prototype.bindToEventHandler = function bindToEventHandler() {
  var handler = this;
  var boundParameters = Array.prototype.slice.call(arguments);
  //create closure
  return function(e) {
      e = e || window.event; // get window.event if e argument missing (in IE)   
      boundParameters.unshift(e);
      handler.apply(this, boundParameters);
  }
};

 

Going Forward

Today we saw how to use HTML5 Drag & Drop and FileReader to accept image files from the Desktop and display them in the browser. The key to getting this to work was to understand the implications of asynchronous operations. Now that we do, we’re going to sweeten the pot next time, and add some additional functionality by adding upload capability, error handling, and more detailed progress reports.

Recommended for you...

Best VR Game Development Platforms
Enrique Corrales
Jul 21, 2022
Best Online Courses to Learn HTML
Ronnie Payne
Jul 7, 2022
Working with HTML Images
Octavia Anghel
Jun 30, 2022
Web 3.0 and the Future Of Web Development
Rob Gravelle
Jun 23, 2022
HTML Goodies Logo

The original home of HTML tutorials. HTMLGoodies is a website dedicated to publishing tutorials that cover every aspect of being a web developer. We cover programming and web development tutorials on languages and technologies such as HTML, JavaScript, and CSS. In addition, our articles cover web frameworks like Angular and React.JS, as well as popular Content Management Systems (CMS) that include WordPress, Drupal, and Joomla. Website development platforms like Shopify, Squarespace, and Wix are also featured. Topics related to solid web design and Internet Marketing also find a home on HTMLGoodies, as we discuss UX/UI Design, Search Engine Optimization (SEO), and web dev best practices.

Property of TechnologyAdvice. © 2025 TechnologyAdvice. All Rights Reserved

Advertiser Disclosure: Some of the products that appear on this site are from companies from which TechnologyAdvice receives compensation. This compensation may impact how and where products appear on this site including, for example, the order in which they appear. TechnologyAdvice does not include all companies or all types of products available in the marketplace.