Ajax File Upload Response Handling

A while ago, I wrote an article on Ajax File Uploading - the method of uploading a file without refreshing the page using a hidden iframe. Since then people wanted to know how to get information about the uploaded file back into the javascript application. Consider this the second part of that article - this post will tell you how to get the information about the uploaded file in javascript.

A Sample Application

For example, say you are building a photo gallery. When a user uploads an image(using the above mentioned ajax method), you want to get its name and file size from the server side. First, lets create the Javascript uploading script(for explanation on this part, see the Ajax File Upload article)...

The Code

<script type="text/javascript">
function init() {
	document.getElementById("file_upload_form").onsubmit=function() {
		document.getElementById("file_upload_form").target = "upload_target";
	}
}
</script>

<form id="file_upload_form" method="post" enctype="multipart/form-data" action="upload.php">
<input name="file" id="file" size="27" type="file" /><br />
<input type="submit" name="action" value="Upload Image" /><br />
<iframe id="upload_target" name="upload_target" src="" style="width:100px;height:100px;border:1px solid #ccc;"></iframe>
</form>
<div id="image_details"></div>

And the server side(PHP in this case) script will look something like this...

<?php
list($name,$result) = upload('file','image_uploads','jpg,jpeg,gif,png');
if($name) { // Upload Successful
	$details = stat("image_uploads/$name");
	$size = $details['size'] / 1024;
	print json_encode(array(
		"success"	=>	$result,
		"failure"	=>	false,
		"file_name"	=>	$name,	// Name of the file - JS should get this value
		"size"		=>	$size	// Size of the file - JS should get this as well.
	));
} else { // Upload failed for some reason.
	print json_encode(array(
		"success"	=>	false,
		"failure"	=>	$result,
	));
}

Here we are printing the data that should be given to JS directly into the iframe. Javascript can access this data by accessing the iframe's DOM. Lets add that part to the JS code...


function init() {
	document.getElementById("file_upload_form").onsubmit=function() {
		document.getElementById("file_upload_form").target = "upload_target";
		document.getElementById("upload_target").onload = uploadDone; //This function should be called when the iframe has compleated loading
			// That will happen when the file is completely uploaded and the server has returned the data we need.
	}
}

function uploadDone() { //Function will be called when iframe is loaded
	var ret = frames['upload_target'].document.getElementsByTagName("body")[0].innerHTML;
	var data = eval("("+ret+")"); //Parse JSON // Read the below explanations before passing judgment on me
	
	if(data.success) { //This part happens when the image gets uploaded.
		document.getElementById("image_details").innerHTML = "<img src='image_uploads/" + data.file_name + "' /><br />Size: " + data.size + " KB";
	}
	else if(data.failure) { //Upload failed - show user the reason.
		alert("Upload Failed: " + data.failure);
	}	
}

Explanation

Lets see whats happening here - a play by play commentary...

document.getElementById("upload_target").onload = uploadDone;

Set an event handler that will be called when the iframe has compleated loading. That will happen when the file is completely uploaded and the server has returned the data we need. Now lets see the function uploadDone().

var ret = frames['upload_target'].document.getElementsByTagName("body")[0].innerHTML;
var data = eval("("+ret+")");

These two lines are an eyesore. No - it goes beyond 'eyesore' - this is an abomination. If these lines causes you to gouge out your eyes and run for the hills, I can understand completely. I had to wash my hands after writing those lines. Twice.

var ret = frames['upload_target'].document.getElementsByTagName("body")[0].innerHTML;

This will get the data the server side script put in the iframe. This line cannot be avoided as far as I know. You can write it in different ways - but in the end, you will have to take the innerHTML or the nodeValue or something of the body element of the iframe. I used the smallest code in the sample. Even if you specify the Content type of the iframe page as text/plain, the browser will 'domify' it.

One other thing - in frames['upload_target'] the 'upload_target' is the name of the iframe - not the ID. Its a gotcha you need to be aware of.

var data = eval("("+ret+")");

Thankfully, this line can be avoided - you can use some other format(in this particular case the best format might be plain HTML) so that you don't have to parse a string that comes out of innerHTML. Or you can use CSV. Or plain text. Or JSON as we are doing right now - just parse it without using eval(). Reason I choose it? Smallest code - and easier to understand.

Now we have a working system. The files are uploaded and data reaches the client side. Everything works perfectly. Oh, how I wish I could say that. But nooo - the nightmare of every javascript developer rears its ugly head again...

Internet Explorer

Internet Explorer, also known as IE, also known as the Beast, again manages to mess things up. They don't support the onload event for iframe. So the code...

document.getElementById("upload_target").onload = uploadDone;

will not work. WILL. NOT. WORK. Thanks IE, thanks very much.

So, what do we do? We use a small hack. We put a script tag inside the iframe with a onload event that calls the uploadDone() of the top frame. So now the server side script looks like this...

<html>
<head>
<script type="text/javascript">
function init() {
	if(top.uploadDone) top.uploadDone(); //top means parent frame.
}
window.onload=init;
</script>
<body>
<?php
list($name,$result) = upload('file','image_uploads','jpg,jpeg,gif,png');
if($name) { // Upload Successful
	// Put the PHP content from the last code sample here here
}
?>
</body>
</html>

Okay - now we have a IE-proof working system. Upload an image using the below demo application to see it in action.

If you have a better way of doing this, please, PLEASE let me know. I feel dirty doing it this way.

See it in Action



blog comments powered by Disqus