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+")");

This 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



Comments

Amit Patil at 22 Oct, 2008 10:07
Ya man this is really excellent....!
Reply to this.
Anonymous at 29 Oct, 2008 07:20
Fatal error: Call to undefined function upload()

where's that function? is upload() a part of php-json?
Reply to this.
Binny V A at 30 Oct, 2008 12:12
You can get the upload function's code here
Reply to this.
Sam at 30 Oct, 2008 10:28
Thanks for the fast reply! Works Excellent
Reply to this.
Steven at 05 Nov, 2008 11:59
Very good explanation and code walk through!
Keep up the good work.
Reply to this.
Anonymous at 06 Nov, 2008 11:15
For IE, you need to add the onload event handler to the iframe explicitly.

var iframe = document.getElementById("upload_target");
if (iframe.addEventListener) {
iframe.addEventListener("load", uploadDone, false); // firefox
} else if (iframe.attachEvent) {
iframe.attachEvent("onload", uploadDone); // IE
}

Reply to this.
Anonymous at 19 Nov, 2008 01:03
Excellent work GOD bless
Reply to this.
Jangz at 27 Nov, 2008 09:13
Thank You Benny...
Reply to this.
nyros at 02 Dec, 2008 01:48
Thanks alot its working...........................thanq
Reply to this.
shainu at 03 Dec, 2008 11:34
i did as you told bu it shows the error

Fatal error: Call to undefined function: json_encode() in C:\Program Files\Apache Group\Apache2\htdocs\ajaxalbum\upload.php on line 17

her the php upload script

<html>
<head>
<script type="text/javascript">
function init() {
if(top.uploadDone) top.uploadDone(); //top means parent frame.
}
window.onload=init;
</script>
</head>
<body>
<?php
require_once('functions.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,
));
}
?>
</body>
</html>

Reply to this.
Binny V A at 05 Dec, 2008 02:34
You need PHP 5.2 or newer for that to work. Else, you can use some third party script to convert a PHP array to JSON.
Reply to this.
Anonymous at 11 Jun, 2009 04:20
i got same problem Fatal error: Call to undefined function: json_encode() in C:\Program Files\Apache Group\Apache2\htdocs\ajaxalbum\upload.php on line 17
i used your instruction now it has changed to {"success":"","failure":false,"file_name":"2adf0_Picture.jpg","size":7.861328125}
can you help me on this?
Reply to this.
Anonymous at 03 Mar, 2010 12:33
You need either a version of PHP >= 5.20 or you would have to install the pecl extension json
Reply to this.
Anonymous at 04 Dec, 2008 11:10
....very god...

link .zip download example ?????


PLEASE.....



Reply to this.
Vladimir at 06 Dec, 2008 01:57
Hallo, I have a little problem. My json response contains html code. Problem is that when the response string is inserted to the iframe, that browser changes html tag chars < and > to "<" and ">". It makes me problems for latest innerHTML function. Next problem is than browser prepends and appends my json string with PRE tag (when I set on php side content header text/plain ). Thanks for your ideas.
Reply to this.
Mike at 20 Dec, 2008 04:48
You mention the code:

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

will not work under IE since IE has no onload event for an iframe. However, IE does have the onreadystatechange event which should work just as well. In your function, you'll have to check if the readyState property of the iframe == 'complete' to make sure the iframe is completely loaded. I believe this code should be cross-browser as well, but you could always bind to both events as well. This should eliminate the need for your script hack.

Mike
Reply to this.
Anonymous at 21 Dec, 2008 09:49
Hi There, Have it all working perfectly as per your page instructions. Aswesome !!!

One problem. When running an upload page in a frame, I have the same issue that you had with IE and the upLoadDone function not firing. Fixed this with your hack and now seem to have same problem with Frames. Would you have a hack or workaround for this at all ?

Thanks, Marek
Reply to this.
Anonymous at 05 Jan, 2009 09:59
You lose me totally in the last bit where you talk about putting code into the iframe (the IE hack part) ... Where in the iframe? How in the iframe?

Should I create a seperate src-file for the iframe or what?

I simply can't guess what you mean ... and I really tried. If i create this src-file it just sends me to the php-file in the browser ... ?
Reply to this.
Anonymous at 08 Jan, 2009 02:44
Better not use "top.uploadDone" - use "window.parent.uploadDone".
"window.parent" is a reference to frames' parent, i.e.

window.parent.document.getElementById('preloading').style.display = 'none';
Reply to this.
Anonymous at 08 Jan, 2009 03:38
Very impressive, I have seen this iframe method in phpclasses also. My main question is about the security. The heart of the code is the upload.php that you did not show. My mistake if you did, but where is it? Mostly, about the security.
Reply to this.
Anonymous at 14 Apr, 2009 04:02
it doesnt' work for me. Can you put a working example to be downloaded?
Reply to this.
JBunin at 22 May, 2009 06:23
Hi,
It's great solution that I've recoded into the Mootools 1.11
But I need know how I can reset the iFrame target or could I return target to the Form element back?
Thanks.
Reply to this.
Anonymous at 11 Jun, 2009 02:14

I'm new to this whole issue, but i am getting there sum day.
please i used the instruction you gavee but i got this error after everything. can you help me clear this error?
Fatal error: Call to undefined function: upload() in
c:\inetpub\wwwroot\glis\finance\TEST\test1\upload.php on line 3
Reply to this.
Ganesh at 21 Aug, 2009 12:30
Hi,

thanks for the great solution..
Reply to this.
GoLDoRaK at 21 Aug, 2009 05:25
Thanks for your HELP !!!!
Reply to this.
RoB at 17 Sep, 2009 05:16
JSON ERRORS:

for the problem with it adding html tags, replace the similar lines with these ones:
if(io.contentWindow)
{
xml.responseText = io.contentWindow.document.body?$(io.contentWindow.document.body).text():null;
xml.responseXML = io.contentWindow.document.XMLDocument?io.contentWindow.document.XMLDocument:io.contentWindow.document;

}else if(io.contentDocument)
{
xml.responseText = io.contentDocument.document.body?$(io.contentDocument.document.body).text():null;
xml.responseXML = io.contentDocument.document.XMLDocument?io.contentDocument.document.XMLDocument:io.contentDocument.document;
}
Reply to this.
Anonymous at 08 Oct, 2009 11:30
For me it didn't work. The form send me to upload.php file and didn't upload image.

How can I fix it?

Thz
Reply to this.
Brad at 06 Jan, 2010 06:47
It was a little difficult to understand the part about putting the script in the IFrame but here is an explanation for everyone:

What he means by "put a script tag inside the iframe" is to put it in the page that handles the server side code. So I created a separate .aspx page called Upload.aspx. I put that script in the Upload.aspx page (along with my server side code). In his javascript the "action" of the form is set to "Upload.aspx" so when the "target" of the form is set to the IFrame (upload_target) and the file is uploaded your Server-side code executes and then you simply write the bytes out to disk.
Reply to this.
Anonymous at 11 Feb, 2010 12:54
Great work, well done! My issue is wth the eval("("+ret+")"); It doesn't work for me. I tried every other way no success. Any sugestions, please.
Reply to this.
Comment

Please dont enter you comments in this form - this is a fake form to confuse spamming bots. The next form is the real one.




Comment




Comment Formating : HTML tags a, strong, em, b, i, code, pre, p and br allowed. Other tags will be shown as code(< will become &lt;). Urls, Line breaks will be auto-formated.