Hello and Welcome to Sugar Mountain!

....a place of random thoughts

HTML5 and the File API (Firefox 3.6)

Filed Under (HTML5, Javascript) by Olle on 10-12-2009

So, I’m having my first encounter with HTML 5 and the great power of it.
I have mostly been looking into the Firefox 3.6 support for the File API, but also features like Copy/Paste, Undo etc looks really promising?

Firefox HTML 5 File API support through Drag n’ Drop

There are a number of blogs out there that has great examples on how to use the Drag n’ Drop  and the File API in HTML5, and more specify, HTML5 in Firefox 3.6. But I have not found any example on the combination of Drag n’ Drop in conjunction with the new File API, most often the File API is “called” from a standard <input type=”file”..> object.

Uploading files from the Desktop, through Drag n’ Drop is really simple with the use of the HTML5 events ondragenter, ondragover and ondrop. By using the file API we are able to read the files, get the name and also the size of any standard file formats, like txt, pdf, png or jpg for instance. In the long run, when the browsers out there will start to support HTML5 better, my hope is that we will not see any more browser specific plugins and such.

First of all we have the FileReader object. In short, it has three interfaces to read files asynchronous; readAsBinaryString, readAsText, and readAsDataURL. The three do the same thing, it reads files into memory(readAsText also has the extra attribute of specifying encoding).

Since the reading of the filedata into the FileReader object is connected to the “standard” event chain, the possibility to keep track of the progress of the loading of the file into memory is as easy as loading of any DOM object. The loaded data can now be saved/uploaded to a server through a Ajax call.

The only really sad thing right now, is have not found any browser supplier, except Firefox, that have implemented the FileReader interfaces, and we are left wishing and hoping that more browsers then Firefox 3.6 will have this.

My somewhat working example(for Firefox 3.6)

Instead of using the FileReader object like the example below, for reasons I will state later on, I have choosen to use the getAsBinary interface of the File API. getAsBinary is deprecated, and surely will be replaced by the FileReader interface, as below.

function readFileIntoMem(file){
var reader = new FileReader();
reader.readAsText(files, 'UTF-8');
}

But as said, my working example(for Firefox 3.6) uses some functions that are deprecated, but never mind.. Over to the demo app.

Demo
[Live Demo]

I have a html page that looks like this:

<html>
<head>
<style media="screen">
	#file-drop-target{
        font-family: Georgia;
		border:1px solid #000;
		height:30px;
        text-align:center;
        padding:40px;
	}
    h4{
        font-family: Georgia;
        text-align:center;
    }
</style>
<script language="javascript" type="text/javascript" src="handleFiles.js"></script>
</head>
<body>
<h4>Drag 'n' Drop file(Firefox 3.6)</h4>
<div id="file-drop-target">drop files here</div>
<div id="status-msg"></div>
</body>
</html>

Nothing really fancy happens here, the main thing really that I load a javascript file called handleFiles.js and that we have a div with the id of file-drop-target. In handleFiles.js I have added a function call to addOnLoadEvent, that will run onload, that setts the onload handler, the setEventHandlers function, for the window object.

function addOnLoadEvent(sFunc)
{
    if(window.addEventListener)
        window.addEventListener('load', sFunc, false);
    else if(window.attachEvent)
        window.attachEvent('onload', sFunc);
}

addOnLoadEvent(setEventHandlers);

When the window object loads we will add a call to setEventHandlers that will handle the three events ondragover, ondragenter and ondrop for the html object file-drop-target. The first two events we just cancel. Our main focus is the ondrop event, and by attaching an eventhandler to it we will get access to the objects being dropped. The function doing this is handleDrop function. The first thing I do it terminate the event and hinder any bubbling of the event through out the DOM.

After the preventDef function call we get the new dataTransfer object and get the files that are being draged into the drop target.
And after we have got the list of files being dropped, we need to access and store the files somehow.
You can find a lot of Ajax scripts on the web that will save a file on a server, but they all have one problem. They all have the problem that the filename will not be submitted to the server, a problem as I see it, if one would like to use this technic in a web service. So what I’m doing is faking a post of a form, and in a form post we have the possibility on the server to get the name of the file being uploaded.
After this has been done, a standard XMLHttpRequest call can be done, just make sure that content-type is multipart/form-data.
The fake-form-posting is very much inspired by a script found on http://www.captain.at/


function handleDrop(event)
{
	preventDef(event)

    var dt = event.dataTransfer;
  	var files = dt.files;

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

		http_request = new XMLHttpRequest();
		var boundaryString = 'the_boundery--';
		var boundary = '--' + boundaryString;
		var requestbody = boundary + '\n'

		+ 'Content-Disposition: form-data; name="thefilename"' + '\n'
		+ '\n'
		+ files[i].fileName + '\n'
		+ '\n'
		+ boundary + '\n'
		+ 'Content-Disposition: form-data; name="thefile"; filename="'
			+ files[i].fileName + '"' + '\n'
		+ 'Content-Type: application/octet-stream' + '\n'
		+ '\n'
		+ files[i].getAsBinary()
		+ '\n'
		+ boundary;

		http_request.onreadystatechange = _handleReadyState;
		http_request.open('POST', 's.php', true);
		http_request.setRequestHeader("Content-type", "multipart/form-data; \
			boundary=\"" + boundaryString + "\"");
		http_request.setRequestHeader("Connection", "close");
		http_request.setRequestHeader("Content-length", requestbody.length);
		http_request.sendAsBinary(requestbody);
    }
}

function _handleReadyState()
{
    if (this.readyState == 4)
    {
        if(this.status == 200)
        {
            var pathStored = this.responseText
            document.getElementById('status-msg').innerHTML += ' <br />';

            var linkToUpload = document.createElement('a');
            linkToUpload.href = pathStored;
            linkToUpload.appendChild(document.createTextNode(pathStored));
            document.getElementById('status-msg').appendChild(linkToUpload);
        }
    }
}

/**
* Event handlers
*/

function preventDef(event)
{
	event.preventDefault();
	event.stopPropagation();
}

function setEventHandlers()
{

	var dropTarget = document.getElementById('file-drop-target');
	addEvent(dropTarget,'dragover',preventDef, true);
	addEvent(dropTarget,'dragenter',preventDef, true);
	addEvent(dropTarget,'drop',handleDrop, true);
}

function addEvent(obj, evType, fn, useCapture)
{
  if (obj.addEventListener){
    obj.addEventListener(evType, fn, useCapture);
    return true;
  } else if (obj.attachEvent){
      var r = obj.attachEvent("on"+evType, fn);
    return r;
  }
}

function addOnLoadEvent(sFunc)
{
    if(window.addEventListener)
        window.addEventListener('load', sFunc, false);
    else if(window.attachEvent)
        window.attachEvent('onload', sFunc);
}

addOnLoadEvent(setEventHandlers);

I hope you like parts of this and share any great changes you might do to the script.. What I’d really like to see is a more browser none specific script then mine!

Comments:

2 Responses to “HTML5 and the File API (Firefox 3.6)”


  1. Good read, it’s great to see more people spreading the love of the HTML5 Drag’n'Drop functionality.

    However there is a bug in your code, the demo doesn’t quite work. I’m fairly certain the issue is you XHR send function, as your using send() instead of sendAsBinary().

    I’ve been working on a Drag’n'Drop Uploads module for Drupal for the last few months which uses the Firefox 3.6 take on HTML5s File API, the Google Gears take on the File API and a trick for Safari/Chrom support which might be of interest for you. A demo is available at http://demo.stuar.tc/dragndrop_uploads and you can see the source code at http://drupalcode.org/viewvc/drupal/contributions/modules/dragndrop_uploads/.

    Cheers,
    Stuart Clark.


  2. Hello and thanks for you comment Stuart.

    I agree with you, it shall be sendAsBinary(), but I’m on Firefox3.6 on a Mac and send() works as great so I missed this one.

    I will checkout you demos, sounds great!!

Leave a Reply