Drag & Drop Uploads With XMLHttpRequest2 and PHP

I finally had some time to read through my ever growing list must read items and play with some new software. While reading up on the new Firefox 3.6 i noticed it came with the new XMLHttpRequest [2] object based on the new file API. And according to the new specs. This would allow for easy file uploads. Now there’s been some examples [2] on the web already. But i just wanted to get my hands dirty.

The new XMLHttpRequest object makes is possible to send files in a few different formats. The most important being the binary format. The code for sending a request with XMLHttpRequest2 looks the same as the previous version. Except for sendAsBinary() in this case.

var xhr = new XMLHttpRequest();
    
fileUpload = xhr.upload,
fileUpload.onload = function() {
  console.log("Sent!");
}
                                    
xhr.open("POST", "upload.php", true);
xhr.sendAsBinary(file.getAsBinary());

So let’s set things up for drag & drop. We need a div that will be the main drop point. And we need some event listeners to catch the drag * drop events. Let start by creating the drop zone. For this we use two simple divs. The outer div will listen for the drag & drop events. And the inner will catch the files.

<div id="container">
  <div id="drop"></div>
</div>

Now let’s create our upload code.

var upload = {
  setup : function() {},
  uploadFiles : function() {event}
}
window.addEventListener("load", upload.setup, false);

The setup method will set all event listeners for drag & drop. And register the upload handler.

var container = document.getElementById('container');
var drop = document.getElementById('drop');
                
container.addEventListener("dragenter", function(event) {
    drop.innerHTML = '';
    event.stopPropagation();
    event.preventDefault();
  }, 
  false
);

container.addEventListener("dragover", function(event) {
    event.stopPropagation(); 
    event.preventDefault();
  }, 
  false
);

container.addEventListener("drop", upload.uploadFiles, false);

As you could see above. the uploadFiles() method gets a event returned from the drag & drop action. This is where the new file APi comes in play. To get to the file property we access the dataTransfer object.

var files = event.dataTransfer.files;

The actual uploading is easy as cake.

for (var x = 0; x < files.length; x++) {

  var file = files.item(x);
  var xhr = new XMLHttpRequest();

  fileUpload = xhr.upload;
  fileUpload.onload = function() {
    console.log("Sent!");
  }
                        
  xhr.open("POST", "upload.php", true);

  xhr.setRequestHeader("Cache-Control", "no-cache");
  xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
  xhr.setRequestHeader("X-File-Name", file.fileName);
  xhr.setRequestHeader("X-File-Size", file.fileSize);
  xhr.setRequestHeader("Content-Type", "multipart/form-data");
                                
  xhr.sendAsBinary(file.getAsBinary());
}

That’s it for the client side. There is however a small problem on the receiving side. When handling uploaded files in PHP we expect the $_FILES array to be populated. This is not the case when streaming files from the client to the server. To get the needed file information we set some headers on the client side X-File-Name and X-File-Size. And since the $_FILES are is empty. We need an other way to get the file contents. So we will use php://input streams for that.

The contents of upload.php look like this:

require_once('Streamer.php');
    
$ft = new File_Streamer();
$ft->setDestination('data/');
$ft->receive();

With setDestination() the destination path for the uploaded files is set. And recieve() listens for any incoming files. Most of the magic is done in the recieve() method. So here’s the code.

public function receive()
{
  if (!$this->isValid()) {
    throw new Exception('No file uploaded!');
  }
        
  file_put_contents(
    $this->_destination . $this->_fileName, 
    file_get_contents("php://input")
  );
    
  return true;
}

I am impressed! This promises a lot of good. And offers some interesting options. Let’s hope all browsers implement this gem. I still have one issue though. I can’t get this to work in firefox under linux. The drag & drop events do not seem to function properly with files being dragged from the desktop. anybody know why?

If you interested in the complete code. you can find it here

**Small update** http://lenss.nl/2010/09/drag-drop-uploads-with-xmlhttprequest2-and-php-revised/

comments powered by Disqus