Update: I put this utility up at datauri.com for convenience. Update: The jQuery.event.props event property list has since been documented in the Event Object documentation.

This past weekend, I made a handy utility for converting files to data URLs. This utility is part of a larger tool that we are developing for a client.

I used the dragenter dragover and drop native host events to respond to drag/dropping files from the desktop. The event objects supplied to the drag host events have a special dataTransfer property which holds information about the user interaction, including what files (if any) the user dropped on the element to which the event is bound.

drag-drop-demo.js

var body = document.getElementByTagname('body')[0];

body.addEventListener('dragenter', function( event ) {
  // event is a reference to the event object
  // created for each event that triggers this callback
  // in a 'drop' event, event.dataTransfer.files is a FileList
  // of dropped files
}, false);

If there are files on the dataTransfer, you can pass thisFileList to a FileReader to using one of the FileReader methods like so:

file-reader-example.js

function eventHandler ( event ) {

  var myFileReader = new FileReader(),
      myFile = e.dataTransfer.files[0]

  console.log( myFileReader.readAsDataURL( myFile ) );

}

This is the second project on which I’ve worked with the native host drag events, and since I was using jQuery for other parts of the code, I decided to use jQuery to bind to the drag events.

drag-drop-file-reader-broken.js

jQuery(function(){
  var body = jQuery('body')
    .bind( 'dragenter dragover', false)
    .bind( 'drop', function( e ) {
      e.stopPropagation();
      e.preventDefault();

      jQuery.each( e.dataTransfer.files, function(index, file){
        var fileReader = new FileReader();
            fileReader.onload = (function(file) {
               return function(e) {
                 body.append('<div class="dataurl"><strong>' + file.fileName + '</strong>' + e.target.result + '</div>')
               };
            })(file);
        fileReader.readAsDataURL(file);
      });

    });

});

For performance reasons, jQuery copies the event object for every event, and only copies the minimum viable properties required for what it needs to do. UnfortunatelydataTransfer is not on this list, because it was not available three years ago when this decision was made. jQuery selects the properties to copy for each event object based on the jQuery.event.props string which is split into an array on initialization.

So, in order to get a FileList out of a native host drag event object when binding with jQuery, you have to push the dataTransfer property onto thejQuery.event.props array:

drag-drop-datatransfer-hack.js

// Originally solved by Tim Branyen in his drop file plugin
// http://dev.aboutnerd.com/jQuery.dropFile/jquery.dropFile.js
jQuery.event.props.push('dataTransfer');

After doing this, the jQuery copy of each event object will get adataTransfer property. SincejQuery.event.props is not formally exposed, it should not be considered stable for use in production code. I imagine that eventually some API for adding properties to the jQuery event object will be exposed, or perhaps a new approach will be taken to expose access to emerging native and native host event properties in the jQuery event callback system. It is not clear that this approach will work with future versions of jQuery, so use with caution. There is currently an open ticket in the jQuery bug tracker dealing with this issue.

You can see a completed implementation below, or try out the data urlifier at http://static.bocoup.com/code/dataurl

drag-drop-file-reader-works.html

<!DOCTYPE html>
<html>
  <head>
    <title>File Converter</title>
    <script src="http://code.jquery.com/jquery.js"></script>
    <style>
      body {min-height: 800px;}
    </style>
    <script type="text/javascript">
    jQuery(function(){

      // jQuery creates it's own event object, and it doesn't have a
      // dataTransfer property yet. This adds dataTransfer to the event object.
      // Thanks to l4rk for figuring this out!
      jQuery.event.props.push('dataTransfer');

      var body = jQuery('body')
        .bind( 'dragenter dragover', false)
        .bind( 'drop', function( e ) {
          e.stopPropagation();
          e.preventDefault();


          body.html("<h2>You're welcome ;)</h2>")

          jQuery.each( e.dataTransfer.files, function(index, file){
            var fileReader = new FileReader();
                fileReader.onload = (function(file) {
                   return function(e) {
                     body.append('<div class="dataurl"><strong>' + file.fileName + '</strong>' + e.target.result + '</div>')
                   };
                })(file);
            fileReader.readAsDataURL(file);
          });

        });

    });
    </script>
  </head>
<body>
  <h1>Drop one or more files here to convert them to <a href="https://developer.mozilla.org/en/DOM/FileReader#section_10">Data URLs</a></h1>
</body>
</html>