Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at CFUNITED 2009 (Lansdowne, VA) with:

ColdFusion, jQuery, And "AJAX" File Upload Demo

By Ben Nadel on

As part of the project I am currently working on, I had to learn how to post files to the server using AJAX. I had never even attempted this before, so I was extra excited to learn something new. Of course, what you might learn quickly is that you cannot actually do this via "AJAX". Luckily, Rob Gonda warned me about this at the New York ColdFusion User Group when he came to talk about AJAX, and so, I went the "secret iFrame" route.

This technique sounds complicated, but it turns out that with jQuery it is actually quite easy (of course, what DOESN'T jQuery make easy, right?!?). The basic principal is that you hi-jack the form submission process and redirect it to point to a hidden iFrame that you create on the fly. This iFrame then handles the file upload the same way that any ColdFusion page would handle a file upload. Once the files are uploaded, you can either return the data, as I do, or just halt processing.

Ok, let's quickly review the code (I have to get back to work). Here is our XHTML page with the ColdFusion and AJAX demo upload form:

  • <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  • <html>
  • <head>
  • <title>ColdFusion And AJAX File Upload Demo</title>
  •  
  • <!-- Linked scripts. -->
  • <script type="text/javascript" src="jquery-1.2.2.pack.js"></script>
  • <script type="text/javascript" src="ajax_upload.js"></script>
  • </head>
  • <body>
  •  
  • <form>
  •  
  • <h1>
  • ColdFusion And AJAX File Upload Demo
  • </h1>
  •  
  • <input type="file" name="upload1" size="60" /><br />
  • <br />
  •  
  • <input type="file" name="upload2" size="60" /><br />
  • <br />
  •  
  • <input type="submit" value="Upload Files" />
  •  
  • </form>
  •  
  • </body>
  • </html>

Very simple HTML form. What you will notice is that our FORM tag does not have any attributes. These attributes will be manipulated via jQuery once the document object model (DOM) has loaded.

Now, before we get to the cool stuff, let's take a look at our ColdFusion page that will handle the AJAX style file uploads:

  • <!---
  • Create an array of files names that we are going to be
  • passing back to the client.
  • --->
  • <cfset arrFiles = [] />
  •  
  •  
  • <!---
  • Loop over form fields looking for files. We dont know if
  • or how many files are going to be uploaded at this point.
  • --->
  • <cfloop
  • index="strFileIndex"
  • from="1"
  • to="10"
  • step="1">
  •  
  • <!--- Build dynamic file field (form field key). --->
  • <cfset strField = "upload#strFileIndex#" />
  •  
  • <!---
  • Check to see if file field exists and that it has
  • value in it (file path).
  • --->
  • <cfif (
  • StructKeyExists( FORM, strField ) AND
  • Len( FORM[ strField ] )
  • )>
  •  
  • <!--- Upload file. --->
  • <cffile
  • action="upload"
  • filefield="#strField#"
  • destination="#ExpandPath( './files/' )#"
  • nameconflict="makeunique"
  • />
  •  
  • <!---
  • Add the generated server file name to the array of
  • file names that we are going to return.
  • --->
  • <cfset ArrayAppend( arrFiles, CFFILE.ServerFile ) />
  •  
  • </cfif>
  •  
  • </cfloop>
  •  
  •  
  •  
  • <!---
  • Create the return HTML. Remember, we are going to be
  • treating the BODY of the returned document as if it
  • were a JSON string.
  • --->
  • <cfsavecontent variable="strHTML">
  • <cfoutput>
  •  
  • <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  • <html>
  • <head></head>
  • <body>#SerializeJSON( arrFiles )#</body>
  • </html>
  •  
  • </cfoutput>
  • </cfsavecontent>
  •  
  •  
  • <!--- Create binary response data. --->
  • <cfset binResponse = ToBinary( ToBase64( strHTML ) ) />
  •  
  • <!--- Tell the client how much data to expect. --->
  • <cfheader
  • name="content-length"
  • value="#ArrayLen( binResponse )#"
  • />
  •  
  • <!---
  • Stream the "plain text" back to the client. It's actually
  • HTML and it is important that we announce it as HTML
  • otherwise the client might not know how to work with it.
  • --->
  • <cfcontent
  • type="text/html"
  • variable="#binResponse#"
  • />

This page takes any form data and searches for form fields that are in the form of "uploadX", where "X" can be a number (in between 1 and 10 in our demo). The ColdFusion template then uploads the files and keeps a running array of the server file names that get generated from the upload process. The key thing to notice is that when the files are done being uploaded, the data is returned as a HTML-wrapped JSON response. This point is very important because it is how our "AJAX" code gets data back as you would from a standard AJAX call.

Ok, now that we have our very basic XHTML file form and our ColdFusion file processing code, it's time to glue them together with a bit of jQuery magic. Here is the jQuery Javascript that hi-jacks the form upload and wires the client and server together:

  • // When the DOM loads, initailize the form to upload the files
  • // using an AJAX "like" call rather than a form submit.
  • $(
  • function(){
  • // Get a reference to the form we are going to be
  • // hooking into.
  • var jForm = $( "form:first" );
  •  
  • // Attach an event to the submit method. Instead of
  • // submitting the actual form to the primary page, we
  • // are going to be submitting the form to a hidden
  • // iFrame that we dynamically create.
  • jForm.submit(
  • function( objEvent ){
  • var jThis = $( this );
  •  
  • // Create a unique name for our iFrame. We can
  • // do this by using the tick count from the date.
  • var strName = ("uploader" + (new Date()).getTime());
  •  
  • // Create an iFrame with the given name that does
  • // not point to any page - we can use the address
  • // "about:blank" to get this to happen.
  • var jFrame = $( "<iframe name=\"" + strName + "\" src=\"about:blank\" />" );
  •  
  • // We now have an iFrame that is not attached to
  • // the document. Before we attach it, let's make
  • // sure it will not be seen.
  • jFrame.css( "display", "none" );
  •  
  • // Since we submitting the form to the iFrame, we
  • // will want to be able to get back data from the
  • // form submission. To do this, we will have to
  • // set up an event listener for the LOAD event
  • // of the iFrame.
  • jFrame.load(
  • function( objEvent ){
  • // Get a reference to the body tag of the
  • // loaded iFrame. We are doing to assume
  • // that this element will contain our
  • // return data in JSON format.
  • var objUploadBody = window.frames[ strName ].document.getElementsByTagName( "body" )[ 0 ];
  •  
  • // Get a jQuery object of the body so
  • // that we can have better access to it.
  • var jBody = $( objUploadBody );
  •  
  • // Assuming that our return data is in
  • // JSON format, evaluate the body html
  • // to get our return data.
  • var objData = eval( "(" + jBody.html() + ")" );
  •  
  • // "Alert" the return data (this should
  • // be an array of the server-side files
  • // that were uploaded).
  • prompt( "Return Data:", objData );
  •  
    // Remove the iFrame from the document.
  • // Because FireFox has some issues with
  • // "Infinite thinking", let's put a small
  • // delay on the frame removal.
  • setTimeout(
  • function(){
  • jFrame.remove();
  • },
  • 100
  • );
  • }
  • );
  •  
  •  
  • // Attach to body.
  • $( "body:first" ).append( jFrame );
  •  
  • // Now that our iFrame it totally in place, hook
  • // up the frame to post to the iFrame.
  • jThis
  • .attr( "action", "upload_act.cfm" )
  • .attr( "method", "post" )
  • .attr( "enctype", "multipart/form-data" )
  • .attr( "encoding", "multipart/form-data" )
  • .attr( "target", strName )
  • ;
  • }
  • );
  • }
  • );

There are a lot of comments in this code, which makes it seem big, but there's really only like 10 lines of functional code. I am just trying to be as clear as I can about what is going on. Basically, as I said before, we are taking over the form submission process and pointing it towards an iFrame that we are creating on the fly. Once the iFrame is done processing, it will contain the HTML/JSON response that we created via ColdFusion. This response is then extracted via the iFrame load() event listener (wired via jQuery) and evaluated (converting the JSON data into actual Javascript objects). Then, for our demo purposes, we are simply alerting the file names that we created.

Well, that's all there is to it. A little ColdFusion, a little jQuery, and suddenly, uploading files using an AJAX-like methodology is quite easy, and surprisingly fast.



Looking For A New Job?

100% of job board revenue is donated to Kiva. Loans that change livesFind out more »

Reader Comments

@Rob,

I am thinking of trying the SWF upload next because I heard you can rock out a "Upload Progress" bar for that :)

Thanks again for the AJAX presentation up here in NYC. It definitely got me excited about trying to make my applications more "AJAXy" in the DHTML sense.

Reply to this Comment

I was unable to get this to work. Each time it generated an error that the .js file was improperly parenthesized on line 53 - the Prompt call.

I took out the leading $( and ending ); and it worked without errors, but the file is nowhere to be found (already looked in temp).

Reply to this Comment

@All,

I just found a small bug in IE. Apparently, in IE, you cannot set the enctype attribute of the form. You have to set the "encoding" attribute. I am updating the code to reflect this.

Reply to this Comment

In Firefox 3, after the prompt appears, the browser keeps trying to connect with the site, unlike in IE where it properly just stops. So in FF3 you get a constant wait/loading mouse pointer...

Reply to this Comment

@Steve,

I get that occasionally, but it seems to be hit or miss. I don't think it affects the functionality at all.

Reply to this Comment

Ben very nice! Thank you for the tip.... ! Worked really like a charm. I also referred to Apples famous Remote Scripting Basics and came up with something similar but with the Prototype library. So if someone is looking for that.

File 1: client.php

<form id="form">
<input name="file_upload" id="file_upload" type="file" size="60" />
<span id="upload_update"></span>
</form>

<script type="text/javascript">
Event.observe(window, 'load', function(){
Event.observe('file_upload', 'change', function(){
$('form').writeAttribute({enctype: "multipart/form-data", method:"post", action:"server.php", target:"RSIFrame"});
$('form').insert(new Element("iframe", {id: "RSIFrame", name: "RSIFrame", style: "width: 0px; height: 0px; border: 0px;", src: "blank.html"}));
$('form').submit();
});
});

function showResult(result){
$('file_upload').remove();
$('upload_update').insert(new Element("a", {href: "http://localhost/upload/" + result}).update("Uploaded Image"));
}
</script>

File 2: server.php

<?php
$uploadDir = 'C:/wamp/www/upload/';

$fileName = $_FILES['file_upload']['name'];
$tmpName = $_FILES['file_upload']['tmp_name'];
$fileSize = $_FILES['file_upload']['size'];
$fileType = $_FILES['file_upload']['type'];

$filePath = $uploadDir . $fileName;

$result = move_uploaded_file($tmpName, $filePath);
?>
<script type="text/javascript">
window.parent.showResult('<?php echo $fileName;?>');
</script>

Reply to this Comment

@Mahesh,

Very cool. I have been playing around a bit with Prototype lately because of some client work and have found it to be very confusing. Granted, the client is using a slightly earlier version of prototype which means that the documentation is not as readily available.

Glad you got this up and running so easily with Prototype. In my experiments, I couldn't even get the dom:loaded event to fire :(

Reply to this Comment

Ben the easiest thing to get the latest version of prototype without having to bother about the version is using Google AJAX Library APIs.

Put this into any page and you have the library hooked up with the latest version. Is also available for other frameworks like jQuery and so on.

Check out: http://code.google.com/apis/ajaxlibs/documentation/

These two lines get you Prototypes latest verson 1.6

<script type="text/javascript" src="http://www.google.com/jsapi"></script>
<script type="text/javascript"> google.load("prototype", "1.6"); </script>

Thanks and looking forward to your other posts and if you have any questions on Prototype I can help. Have been using it for a long time.

Reply to this Comment

@Mahesh,

Thanks man. I have seen some buzz about the Google JS stuff, but have not looked into it just yet.

Reply to this Comment

For the above script to work across browsers, do not set the form enctype in the prototype writeAttribute, instead add it to the form, as IE will not do the upload, if the form is set with the encType through JS.

Reply to this Comment

Hey there, nice code. I used it as a starting point and converted it to PHP, as well as cleaned up the jQuery a bit. The code is essentially the same however is used for image uploading.

Here's a bit cleaner jQuery:
function gogo(){
$upform = $("form");
$upform.bind("submit", function(event){
$ifrm = $("<iframe name=\"up-" + (new Date()).getTime() + "\" src=\"about:blank\" />")
.hide()
.bind("load", function(event){
$bdy = $(window.frames[$ifrm.attr("name")].document.getElementsByTagName("body")[0]);
alert($bdy.html());
$ifrm.remove();
});
$("body").append($ifrm);
nm = $ifrm.attr("name");
$upform.attr({
action: 'upload.php',
method: 'post',
enctype: 'multipart/form-data',
ecoding: 'multipart/form-data',
target: $ifrm.attr("name")
});

});

The main differences are binding instead of setting (jQuery.click is supposed to be used to trigger a click, jQuery.bind is supposed to be used to bind an action in the event of a click for example), less variables, and the proper setting of attributes. My naming convention generally follows the rule that if the variable holds a jQuery object, start it with a $. ($var as opposed to var).

I don't know if you will use it or not, but hey I thought I'd show my tweaks.

Reply to this Comment

Seems there is either a problem with my script and Opera 9.5 on Linux (Ubuntu 8.04), or with Opera on Linux in general. Opera does not wait until the file is loaded to continue with the script, and so there is no response before the javascript finishes loading. To solve this (which also solves ff3 constantly loading) I added in a window.setTimeout and wrapped it around the contents of the binding to the iframe. General improved syntax:

$ifrm.bind("load", function(event){
window.setTimeout(function(event){
.........
}, 500);
});

Seems less than 500 milliseconds and my short script will not have passed back. I haven't tried on a slower computer but I'd imagine a half a second would be ample time for all scripts to be run.

Reply to this Comment

@Josh,

I am not sure if there is any difference between using the [object].load() and the [object].bind( "load" syntax. I think one might just act as a short-hand notation for the other. I believe that .load() actually calls .bind() behind the scenes.

Thanks for the tip about the setTimeout() on the load function. I have noticed some continuous loading on FF3 (and sometimes on earlier browsers). I will try putting that into my code.

Thanks.

Reply to this Comment

Oh, I don't believe there is anything wrong with it. I just think it is frowned upon standards wise. It might just be me, but I reserve a direct call for when I am instigating a click or something along those lines.

On further look at the jQuery site, it would appear they don't care. Oh well, I'll continue binding anyways.

Cheers
-Josh

Reply to this Comment

@Sam,

No problem man. I will try to implement the code that prevents it from *thinking* for ever post-form submission. If I do, I'll let you know.

Reply to this Comment

@Ben: I was merging this with a whole bunch of other JS and things got a little too complicated. So I added a permanent iframe to the page and always post to that (which is fine for my use case) and it solved the "always thinking" issue. Not sure what the cause was but thought that might help.

Lots of people say its good to be always thinking though...

Reply to this Comment

@Sam,

Hmm, maybe if I don't remove the iFrame, it will help. I don't think there is any harm to letting several IFrames build up, especially when the main page will refresh from time to time.

... it took me several reads to get the "always be thinking" joke :) It's hot in here.

Reply to this Comment

I updated the example code to have the a slight delay on the frame removal:

setTimeout(
. . . . function(){
. . . . . . . . jFrame.remove();
. . . . },
. . . . 100
. . . . );

This slight delay (could be less than 100, but it doesn't matter) is enough to let FireFox stop "thinking". I am not sure if this would also fix the Opera issue mentioned above.

Reply to this Comment

@Steve,

In order to integrate a progress bar, I think you'd need to use something like SWFUpload. I believe that Flash-based uploading is the only way to get at that kind of information.

Reply to this Comment

This doesn't seem to work in Google Chrome, too bad. Any thoughts on why not? Not allowed to dynamically add iframes?

Reply to this Comment

@rnstr,

I find that a lot of AJAX stuff doesn't work in Google Chrome. Not sure why. I've definitely found it to be a hit and miss browser for this sort of thing.

Reply to this Comment

Hi, Ben...

I've been trying to implement your code above, but I keep running into an error that was mentioned in the comments, but no resolution was ever mentioned.

After I've browsed and selected two images and click the "Upload Files" button, I get an error in Firebug:

"Missing ) in parenthetical"
<font face="arial"></font>\n

That looks like a problem with the data being returned from "upload_act.cfm". It looks like part of an error message from CF, however, with this style of data handling, there's no "Response" tab for me to click and view the JSON data coming back.

I'm using your code from the .zip file and haven't changed anything, except the names of the files to ajax_upload.js, upload_act.cfm, and form.cfm.

Suggestions on what to look for?

Thanks for the tutorial!

Rick

Reply to this Comment

I figured out what the problem was, Ben. The error message, indeed, was code from CF error.

The CF error was occurring because I didn't have the path correct for the folder the files would be written to the in the ajax_upload.js file.

Now I'm trying to figure out the best way to handle a form with filefield(s) and other types of fields.

I guess, upon submission, I could process the filefields, then handle the other fields in another function? I would need to get the names of the uploaded files to put into my database, along with other information.

Would that be a recommended approach?

Reply to this Comment

@Rick,

Usually what I do, when dealing with complex forms, is to upload the file and return some sort file ID. I then store this ID into the form and then resubmit the whole form using AJAX. So, when dealing with files, I have a two passes:

1. Upload files and store resultant IDs.
2. Send form data (including new IDs) to server using AJAX.

Reply to this Comment

The demo works fine independently. However I want to bind it into a complex application, I cannot get it work.

The details as follows:

I use <cfmenu> which is in a<cflayoutarea> to invoke a file uploading form in another <cflayoutarea>. then it won't upload files.

I doubt that form submit event in "ajax_upload.js" is not triggered, since the form is loaded only user choosing the menu item lately.

I tried to change variable in ajax_upload.js:

var jForm = $( "form:last" );

but still won't work.

Is that any way we can catch the later added object ?

thanks

Reply to this Comment

Am I the only one that can't get this to work?
the ajax_upload.js file doesn't even seem to be acknowledging that a file is being sent to send as an attachment. sorry if i'm buying ignorant here.

Reply to this Comment

Just kidding.

What I did was put in the absolute path for the secondary page that takes the upload...

Reply to this Comment

Thanks Ben for the great tips, I've been coming back a bunch.

I just noticed that it doesn't work with CFFORM. I'm trying to attach this to my Mach-II framework as well, and I just started using cflayout and all the new CF8 features, and I guess it's just a matter of figuring out the nuances.

So I'm guessing if there's more than one form here that it won't work as is correct?

Reply to this Comment

What if the form is not the first one encountered within a larger CFLAYOUT framework? How do you alter

var jForm = $( "form:first" );

so that it references the correct form? I've tried using both ID and Name with no luck.

Cheers,
Ty

Reply to this Comment

I had another form but used an if else statement to hide it. i also tried to set the frame src dynamically but don't think it liked that too much. i got it to work by itself and update the database table... but i have to doublecheck my code because i want it to redirect to another page afterwards but it's not doing it correctly. well right now it's not working correctly in the cflayout either. ill keep you posted on my progress

Reply to this Comment

Soooo if you have this code in a cflayout tab; it reloads the page (and not the view page that is called via the tab). What's weird is that it's not updating or not able to run the JS code to create the iframe. it's weird because when you submit a form within the cflayout area it goes within itself. I assumed this would do the same for this code but instead it submits it entire page and reloads the url from the browser regardless of tab state.

I'm new to cf8 stuff so I apologize for the ignorance if this has been stated before. i ran the source file called by the tab in a separate window and it works fine.

Can you guys give me some insight or help me with this issue?
Thanks,
Dave

Reply to this Comment

@Dave: All cfforms within cflayout or cfdiv will submit via AJAX. Have you tried putting the iframe outside of all the cflayout tags?

Reply to this Comment

@Dave,

I have not worked with CFForm, so I cannot say at this time why it might not work. Ultimately, it just renders HTML, so it should be ok. However, since CFForm does use Javascript validation, it's possible that the submit event handlers are conflicting.

@Ty,

You just need to come up with some way to identify the form so that it can be referenced later. You could use an ID for example $( "#formID" ).

Reply to this Comment

CFFORM does not work with this workaround for uploads, since it performs it's own JS as Bend said... Does anyone have a clue as to whether they were able to add something to the cfform so it can add to the CF's event handlers?

so basically if i use form instead of cfform it won't stay within the cflayout as well (to sam)? Target of the form doesn't seem to work either but i can't remember if i did that with cfform or not in cflayout. there's gotta be a way around this...

Reply to this Comment

Bah i gave up. I just had a <form> go to a view page not inside a cflayout with the tabs (pretty much the overview page. I just understand why CFGRID fails in IE. Another couple sleepless nights. haha

thanks again guys.

Reply to this Comment

I must be doing something wrong, because when I try to submit the form and getting nothing. Matter of fact the form all I'm getting is the following in my url:

https://lions/test_dev/form/form.cfm?upload1=C%3A%5CDocuments+and+Settings%5Codthomps.DPMOU%5CMy+Documents%5CDisys%5Ctimesheet_guide.docx

its like everything is submitted via the url and I know that's not what I'm looking for:

I'm using the 4 files required:

1. form.cfm
2. upload_act.cfm
3. ajax_upload.js
4. jquery-1.3.2.min.js

Help needed...I do have one question, how does the form know to communicate with the ajax_upload.js ... without any function trigger...

Reply to this Comment

Without seeing how you've implemented the 4 files it's hard to troubleshoot the issue. The only item I had to alter in getting the file upload to work was the destination attribute in the <cffile> tag. The other common problem is having more than one form going. If the upload form is not the FIRST form encountered it will not work.

I can answer your question about how the form communicates with the ajax_upload.js file. Since it is included at the start of the file Form.cfm

<script type="text/javascript" src="ajax_upload.js"></script>

the jForm.submit function "steals/hijacks" control when the submit button is triggered thereby running the js functions and ultimately running the upload_act.cfm file.

Cheers,
Ty

Reply to this Comment

Ty,

thanks for the quick reply..see code below...maybe you see something I don't...

melemel

----------------------
Form.cfm
---------------------

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>ColdFusion And AJAX File Upload Demo</title>

<!-- Linked scripts. -->
<script type="text/javascript" src="jquery-1.2.2.pack.js"></script>
<script type="text/javascript" src="ajax_upload.js"></script>
</head>
<body>

<form>

<h1>
ColdFusion And AJAX File Upload Demo
</h1>

<input type="file" name="upload1" size="60" /><br />
<br />

<input type="file" name="upload2" size="60" /><br />
<br />

<input type="submit" value="Upload Files" />

</form>

</body>
</html>

--------------------------
upload_act.cfm
--------------------------

<!-- Create an array of files names that we are going to be passing back to the client -->

<cfset arrFiles = [] />


<!-- loop over form fields looking for files. We dont know if or how many files
are going to be uploaded at this point -->
<cfloop index="strFileIndex" from="1" to="10" step="1">

<!-- Build dynamic file filed (form field key). -->

<cfset strField = "upload#strFileIndex#" />

<!-- Check to see if file field exists and that
it has value in it (file path). -->

<cfif ( StructKeyExists( FORM, strField ) AND
Len( FORM[ strField ] ) )>

<!-- Upload File -->

<cffile action="upload" filefield="#strField#"
destination="D:\WWWSite\test_dev\form\"

nameconflict="overwrite"/>
<!-- Add the generated server file name of the array of file
names that we are going to return
-->

<cfset ArrayAppend( arrFiles, CFFILE.ServerFile ) />

</cfif>
</cfloop>

<!--
Create the return HTML. Remember, we are going to be
treating the BODY of the returned document as if it were a
JSON String
-->

<cfsavecontent variable="strHTML">
<cfoutput>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html>

<head></head>

<body>#SerializeJSON( arrFiles )#</body>

</html>

</cfoutput>

</cfsavecontent>

<!-- Create binary response data. -->

<cfset binResponse = ToBinary( ToBase64( strHTML ) ) />

<!-- Tell the client how much data to expect -->

<cfheader
name="content-length"
value="#ArrayLen( binResponse )#"
/>

<!-- Stream the "plain text" back to the client. It's actually
HTML and it is important that we announce it as HTML
otherwise the client might not know how to work with it.
-->

<cfcontent
type="text/html"
variable="#binResponse#"
/>

-------------------------------
ajax_upload.js
----------------------------
// When the DOM loads, initialize the form to upload the files
// using an AJAX "like" call rather than a form submit.

$(

function(){
// Get a reference to the form we are going to be
// hooking into.
var jForm = $( "form:first" );

//Attach an event to the submit method. Instead of
// submitting the actual form to the primary page, we
// are going to be submitting the form to a hidden
// iFrame that we dynamically create.
jForm.submit(
function( objEvent ){
var jThis = $( this );

// Create a unique name for our iFrame. We can
// do this by using the tick count from the date.
var strName = ("uploader" + (new Date()).getTime());

// Create an iFrame with the given name that does not point to any
// page - we can use the address
// "about:blank" to get this to happen.
var jFrame = $( "<iframe name=\"" + strName + "\"
src=\"about:blank\" />" );

// We now have an iFrame htat is not attached to
// the document. Before we attach it, let's make
// sure it will not be seen.
jFrame.css( "display", "none" );

// Since we submitting the form to the iFrame, we
// will want to be able to get back data from the
// form submission. To do this, we will have to
// set up an event listener for the LOAD event of
// the iFrame.
jFrame.load(
function( objEvent ){

// Get a reference to the body tag of the
// loaded iFrame. We are doing to assume that
// this element will contain our return
// data in JSON format.
var objUploadBody = window.frames[ strName
].document.getElementsByTagName( "body" )[ 0 ];

// Get a JQuery object of the body so
// that we can have a better access to it.
var jBody = $( objUploadBody );

// Assuming that our return data is in
// JSON format, evaluate the body html
// to get our return data.
var objData = eval( "(" + jBody.html() + ")"
);

// "Alert" the return dat (this should be
// an array of the server-side files
// that were uploaded).
prompt( "Return Data:", objData );

// Remove the iFrame from the document.
// Because FireFox has some issues with
// "Infinite thinking", let's put a small
// delay on the frame removal
setTimeout(
function(){
jFrame.remove();
},
100
);
}
);


// Attach to body.
$( "body:first" ).append( jFrame );

// Now that our iFrame it totally in place, hook
// up the frame to post to the iFrame.

jThis
.attr( "action", "cf_file_handler.cfm" )
.attr( "method", "post" )
.attr( "enctype", "multipart/form-data" )
.attr( "encoding", "multipart/form-data" )
.attr( "target", strName );
}
);
}
);

Reply to this Comment

2. upload_act.cfm

.attr( "action", "cf_file_handler.cfm" )
you need to rename your file upload_act if you haven't already

Reply to this Comment

Hey Dave...thanks for the info...I made the change and save upload_act as "cf_file_handler.cfm" and still no good results...getting the same jargon in the url link.

Any other ideas, Sir....

Melemel

Reply to this Comment

I was able to use this in a recently completed site, but now I want to use this in a jQuery modal window. I'm having problems figuring out how to target the modal window.

Anyone tried to use Ben's technique within a modal window?

Since the code is just too long and complex to show here, I'd be willing to pay or buy someone a wish-list gift if you're willing to hook up via Eclipse/CFEclipse through Real-Time Shared Editing, or otherwise if you can't hook up that way.

Anyone accomplished this and willing to work with me to get my code working?

Reply to this Comment

Hi, Ben...

I think showing you the code that opens the Thickbox modal window will answer your question better than I could. The code below is modified to allow me to add some animation (fades) to the showing and hiding of the window.

<script>

function tb_fadeIn() {

var tb_pathToImage = "../images/loadingAnimation.gif";
imgLoader = new Image(); //preload image
imgLoader.src = tb_pathToImage;

tb_show("AddDeal", "../modals/dealAddForm.cfm?height=425&width=500&modal=true", false);

$('#TB_overlay').fadeIn(750);
$('#TB_window').fadeIn(750);

};

</script>

Then, this code prepares the tb_fadeIn function above to be called on document ready:

<script>

$(document).ready(function() {

$('#addDeal').click(function() {
tb_fadeIn();
});

});

</script>

I can get the modal window up, complete with form. I can complete the form, send the results via ajax (minus the filefield being filled in right now) and display those results in an alert, but can't get any ajax response results to show up in the modal window.

I'm just not sure how to target the modal window. I've done this sort of thing via ajax with a login dialog, complete with validation responses, etc., but that didn't involve an image.

I'm trying to set this up so I can preview the form data, including the image file in the same modal window that contains the submitted form.

Just can't quite seem to make the connection.

Thanks for any insight...

Rick

Reply to this Comment

Oh, and btw, I love the way you've incorporated your images of yourself with various people into your site...always fresh and interesting...great idea!

Rick

Reply to this Comment

@Rick,

Because you can alert() the results, it looks like AJAX method is working properly. If you want them to then show up in the modal window, you probably have to inject the returned HTML (I am assuming its HTML you get back).

In that case, you need to get a reference to response container element in the Modal window and inject the HTML. Example:

$('#TB_window').find( "div.ajax-response" ).html( YOUR_AJAX_RESPONSE );

Does that make sense?

Reply to this Comment

Thanks, Ben. I'll test out your suggestion to see if I *really* understand. It seems to make sense, but the proof is in the, uh...modal window!

Reply to this Comment

Oh, and btw, I love the way you've incorporated your images of yourself with various people into your site...always fresh and interesting...great idea!

tuzcuoglu nakliyat

Reply to this Comment

Hi Ben,

Many thanks for this very useful post!

Would it be possible to have the jquery-1.2.2.pack.js as well please?

Thanks in advance.

Best regards!

Reply to this Comment

@Andrew,

This sounds very interesting. I don't know much about socket connections at all. I know that some things DO use Flash to send files (in fact, any file upload that uses progress bars probably uses that technique); but I have not tried it myself.

I really like the idea. I have often thought of doing something similar in Flash (although I don't have the know-how to do it). You can get things like SWFUpload, but they are all way too complicated (IMO).

Reply to this Comment

Actually...have modified code to remove the alert pop up box on click...

The form seems to be getting submitted, as the page reloads, is there anyway of preventing this?

I generally use return false at the end of the function, but that doesnt seem to be working in this instance

Thanks

Reply to this Comment

@John,

That probably means you are getting a Javascript error. When you try to prevent a form submission and it goes through anyway, it typically means that Javascript is breaking.

Reply to this Comment

Yeah, think am getting error message

ajax_upload.js(line 53)

Syntax Error

()

Line 53 = var objData = eval( "(" + jBody.html() + ")" );

Thanks

Reply to this Comment

@John,

If you open up FireBug when making the upload, you should be able to see the request/response in the NET activity. Check to see what actually comes back in the iFrame response HTML.

Reply to this Comment

Hi, Ben & John...

I always look in the HTML tab of Firebug to view the content returned to the iFrame. I look in the iFrame itself (after commenting out the code in your jQuery that hides the iFrame) and I can see the entire CF error message. (Took me a looong time to find that message...I didn't know where it was showing up!)

I don't think I actually looked in the NET activity area...perhaps that's a better way to get to the CF error messages.

@John... did you find the CF error message?

Rick

Reply to this Comment

Hi

Yes, i took out the code in the Jquery that hides the frame as well, to see the previous coldfusion errors, but there are none at present...

The form works and the image gets uploaded, but the page seems to be submitted, instead of firing through ajax...

The response i get on the net tab in firebug for "POST upload_act.cfm" is

RESPONSE

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head></head>
<body>["food dye10.jpg"]</body>
</html>

HTML

["food dye10.jpg"]

Does anyone have a working demo online maybe?

Cheers

Reply to this Comment

My processing of images and data is now done with modal windows and such, so the HTML, jQuery, and CF are more complex and I've modified my approach to this a lot.

My current coding isn't of much help with showing code that very directly relates to Ben's.

Is your code (HTML/CF Frontend, jQuery, and CF backend *exactly* like Ben's in his example?

If you've modified it some, is there some place you could post all your code involed so we can see everything, and perhaps run it locally to troubleshoot it?

Reply to this Comment

@John,

If the IFrame is showing the File name returned in the BODY of the document, the upload appears to be working normally. So is the issue just that *both* the IFrame and the main form are being submitted?

Reply to this Comment

Yes ben, this seems to be the case..

I usually add return false to prevent this, but doesnt seem to function in this instance..

Rick, i have the files to send over, wondering where i should send them to for you to grab

Thanks

Reply to this Comment

If you can't post them online somewhere, then just email them to me directly to

rick
at
whitestonemedia
.com

(Wonder if that little gimmic will keep my email address safe from bots...probably not, but, oh well...)

Reply to this Comment

I'm having a bugger of a time with this. I'm trying to use it with PHP instead. I know the PHP script works fine without the javascript (just submitting a regular form) and according to FireBug, the data is being posted just fine, including the file. I even get the email sent from the PHP script, so I know the script is getting passed everything but the file. The $_FILES["fileResume"]["tmp_name"] variable is empty. Maybe I don't have the right ajax_upload.js file? Where can I get the code you used here?

Biggest problem I'm having is trying to find an upload solution that works when the form has other fields to submit, too. This is the closest I've gotten to getting it to work.

Thanks in advance :)

Reply to this Comment

Oh, for crying out loud! Never mind, I figured it out! (slapping myself up side the head). It's been a really long day.

Ok, so this works perfectly. Thank you for the great code! And for letting me talk my problem out. Why is that we usually find the solution staring us right in the face only after we've asked for help? Thanks again!

Reply to this Comment

I like this approach and think it's very cool...I just don't know what the practical value of this is. If you're going to use an iframe, what's the point of generating it in JS on the fly? Why not just use an iframe?

Reply to this Comment

@Nicole,

No worries at all - sometimes all it takes is a second set of ears to solve a problem.

@Charlie,

In a situation like this, I suppose there's not benefit to building the iFrame on the fly. However, in a more complex applications where you might have several forms on the page, there might be a benefit to creating iframes on the fly. After all, by creating it on the fly, it's one *less* thing the end-programmer has to think about.

Reply to this Comment

@Ben Nadel,

Yeah, that's true. I didn't really think of the multiple-form angle. Or, if you can abstract it out a bit using JS classes, you could use this approach to generate these kinds of iframes for forms anywhere in the entire site. Now that would be pretty cool...

Reply to this Comment

@Charlie,

Yeah, exactly. Ideally, what would be great is if the whole file upload thing was encapsulated behind some sort of jQuery plugin (of which I am sure several have already been created). That way, you don't have to know - you could just use the FORM (or Classes as you suggested) as a way to "progressively enhance" the page.

Reply to this Comment

Hi,
I'm trying to put a cfinput type="file" inside a cfwindow. But the submit doesn't work because of an ectype submit error, of course it's due to the cfwindow and the ajaxSubmit of the form.I found that i have to work with ajax upload file. Can you help please.

Reply to this Comment

@Abderahmen,

I am sorry - I have not experience with CFWindow or the special use cases that it might entail. Is there a way you can just change the enctype of the form?

Reply to this Comment

Ben,

How do I extract the html from the objData variable so I can update a div on my page with the image I just uploaded?

Reply to this Comment

@Ofeargall,

objData IS the HTML contained within the BODY of the returned frame. If you want to add that image to the existing page, you need to use something like jQuery to insert a new IMG tag with the resultant SRC.

Reply to this Comment

Hi Ben,

Sorry to disturb.

Would it be possible to put the files for download again? The link to download the code snippet zip file seems to be broken.

Thanks in advance.

Best regards,
Yogesh

Reply to this Comment

Hi Ben,

Thanks for the reply.

Still doesn't work. It opens a blank page.

Thanks in advance for doing the needful.

Best regards,
Yogesh.

Reply to this Comment

@Yogesh,

The link appears to be working for me. I have tested it in both Chrome and Firefox. I am not sure why it would be broken for you. In any case, the ZIP file simply has the same code that is in the blog post.

Reply to this Comment

I got the code to work, but I would like the upload to happen with an onchange on the file input box (basically need to make a it a function instead of using the jForm.submit). How can I modify the js file to allow this? I have been playing with it, but keep getting errors. Thanks

Reply to this Comment

@Chad,

I am not sure how well File inputs play with onChange event handlers. Or maybe I'm thinking of trying to access the file paths internally (which might be a security issue). Have you tried just hooking the onChange into the submit event handler... something like this (pseudo code):

  • $( fileInput ).change(
  • function( event ){
  • $( this.form ).triggerHandler( "submit" );
  • }
  • );

This way, you can keep the submit logic and simply augment it with the input-change handler.

Reply to this Comment

@Reza,

Once you POST the file, all of the file name, type, and size information should be available in ColdFusion's CFFile scope or the result of the CFFile scope.

Reply to this Comment

I hate to bump an old topic but...
I got this demo working great in FF Mac/PC and Safari - but for the life of me I can't figure out why it's not working in IE8. Any pointers?

Reply to this Comment

Sorry... please ignore my previous post. Seems my version of IE running on Parallels is screwy and I didn't troubleshoot it first.

Reply to this Comment

Hi Ben

Where is everyone getting the ajax_upload.js file from? Are you using Andrew Valums Ajax Upload plugin?
I don't see any reference to where this file comes from in the post or blog comments, yet everyone seems to have not had issues finding the file, so I must be missing something very obvious here.

Thanks

Reply to this Comment

Scrap my previous comment. I see now that ajax_upload obviously contains the iFrame jquery code.
Thanks - code works great so far!

Reply to this Comment

Hi Ben.

Thanks - the code works nicely, however I did run into one issue that I would just like to let everyone know about that caused issues, and how I resolved it.

In your example, you simply return an array of file names, arrFiles and then SerializeJson the array.

In my case, I returned a struct called sResponse which is a struct (actually based on your ajax response struct) instead of an array like you did. The struct contained, amongst others, a field called sResponse.data which contained the uploaded image's HTML surrounded by lots of other HTML for formatting.

However, calling this code now failed:
<body>#SerializeJSON( sResponse )#</body>

To get around this and allow HTML to be parsed / serialized, I used this code:
<body><script id="data" type="json">#SerializeJSON( sResponse )#</script>

then in the javascript I used the line below, with your original code commented out below that:

var objUploadBody = window.frames[ strName ].document.getElementById( "data" );
//var objUploadBody = window.frames[ strName ].document.getElementsByTagName( "body" )[ 0 ];

I thought I'd post this in case someone else encountered this scenario. I think it's quite common for someone to try and upload a photo, and then get the html of that uploaded photo back so that it can be appended to the page to display the image. Ideally it would be done via ajax, but as we all know with this post of yours, a file upload can only be done via a hidden frame, hence these ajax-like hacks.

Reply to this Comment

Hi, Paolo...

I read your comment to Ben on your approach to returning the data from the submit back to your submitting page by using a struct, etc.

I assume you did that to make it possible to display the image on the submitting page after the submission, based on your final comment, "it's quite common for some to try and upload a photo, and then get the html of that uploaded photo back so that it can be appended to the page to display the image"... correct?

I display the image after submission on the submitting page, as well, but I use cfsavecontent to save the HTML generated from with a component method and return that content in a struct variable that I then append to the DOM in the callback of the AJAX function.

I guess we're both getting to the same place by different means. I'm just always curious to learn a new approach to accomplishing something.

Rick

Reply to this Comment

@Rick,

Hi Rick. Yeah exactly. Normally, like with adding a comment or status update for example, you would submit the comment via ajax and then in the callback receive the HTML of the comment and append it to the DOM. e.g.
<div id="comment_12345">Hello World</div>"

Fairly simple in that example, but in this case there was no actual ajax taking place, more of a spoof. Hence, when passing the HTML for my image upload back, the code was bombing slightly and thus my changes were necessary.

However, I see you just mentioned "then append to the DOM in the callback of the AJAX function". How are you doing yours with an ajax callback?? Like you I'm keen to see other ways of accomplishing the same thing.

Ciao
Paolo

Reply to this Comment

@Paolo...

I used Ben's iFrame code for submitting the fields and image, then in the component method I process the image and run the insert query.

Then I run another query to get the last_insert_id() of the inserted record (MySQL)
and get the data from a query using the last_insert_id() and do one of two things, depending on the situation:

1) In the success callback of the AJAX function, I will use jQuery to empty the container which holds the records of whatever I'm displaying, and then run another AJAX call inside the success function to obtain all the records again, after the addition or update, and re-insert those records into the DOM

2) In another case, I might just obtain the last_insert_id() of the inserted record and re-query the data after the insert, and, using HTML with cfoutput inside the insert method, I'll output the query data and HTML and save that into a variable using cfsavecontent and include it as part of the return struct. When the returned content is received back in the succes callback of the AJAX function, I just insert that HTML into the DOM.

The first method, if there's not too much data, is just easier because I don't have to worry about inserting the new record in the correct place in the already displayed records.
(I used a modal window to display the forms for adding, updating, and deleting so the user
never has to leave the initial page that displays the records).

I've been doing it this way for awhile and just hacked this approach together from Ben's post and other sources.

I learn enough to get the task at hand accomplished satisfactorily and then be dangerous in threads like this. :o)

Reply to this Comment

@Rick,

Hi again Rick. I follow you on everything except the part: "In the success callback of the AJAX function".

I'm just a little confused - this method of Ben's doesn't use AJAX, so there is no ajax post and thus no ajax callback as far as I can see. But obviously I'm missing something because you keep talking about the "ajax callback function"

I'm assuming, like what I did, you simply mean a function that you call once you have the hidden frame response. For example:

// Assuming that our return data is in
// JSON format, evaluate the body html
// to get our return data.
var objData = eval( "(" + jBody.html() + ")" );

// Ajax-like callback function
MyCustomCallBackFunction(objData);

Is that what you mean? :)

Reply to this Comment

@Paolo...

Yes, you're right.

The first submission uses Ben's iFrame approach, with a function call added that triggers a true AJAX call to retrieve the updated data and display it on the initial records display page.

So, the iFrame method is used to process the initial form, then the process is handed off to an AJAX function for retrieval of updated data.

Does that make sense? :o)

Reply to this Comment

@Rick,

Ah ok, I follow. So in your case, once the post has been done via the iFrame, you don't really get anything back. Then you call another ajax function that retrieves the data and sends back the formatted html as required.Whereas in my example I send back the data directly via the iFrame. So no ajax actually takes place.

Interesting method Rick. Could definitely work in my case too. I'm actually surprised it didn't cross my mind to do it that way when I was struggling with it the iframe way. So there we have it - two ways to skin a cat :)

Reply to this Comment

I really like this post.. Thanks Ben and Rick..

For one of my projects I had to build a form with ton of information to gather that would also accept an image file. The image file needed to be cropped using jCrop jQuery plugin.

The process of cropping entails an upload of temp file onto the server, which would require ajax post.

So, I have gone pretty much the same route and ended up using jQuery's upload plugin. GOt everything to work just fine but then when I did the push to the staging server, I started getting the "missing ) in parenthetical".

That's where Rick Faircloth and his comments came in handy - thanks..

I had a typo in the variable defining the path on the staging server where the temp files would be stored. But because of iframes how would you see that from the response window of firebug?!? :)

One useful thing I discovered for this is using HttpFox plugin. I tracks any http interaction going on between your client browser and the server.

Reply to this Comment

@Sasha...

Hi, Sasha... glad the comments in the post helped you!

I haven't heard of HttpFox, but I'll check it out. *Anything* that helps debugging of AJAX and iFrame work is always welcome.

Concerning finding errors generated using the iFrame... I finally figured out that I could look in the HTML after a submission was attempted. Normally, the iFrame is removed after the submission, but when an error occurs, the iFrame is left in the HTML code, along with the ColdFusion error message, if something happened in the ColdFusion processing.

So, if you get no response from Firebug, try looking at the bottom of the HTML code in Firebug and find the iFrame and see if there is any error code in there.

Rick

Reply to this Comment

@Rick

.. totally makes sense.. I saw the iframe disappear when things were working, thus I gave up and didn't know an iframe would actually remain in the html if an error occurred. So that's good to know..

But yeah, the HttpFox add-on for FireFox would show that in the Content tab on the lower panel. That's how I saw that ColdFusion error message that it couldn't write to the path specified.

Reply to this Comment

I want to config upload file size larger, more than 100MB, but with size large . It not work
I need help. Thanks all.

Reply to this Comment

@Tính Nguy?n
There are a couple things you should check:
1. In the ColdFusion administrator under Java and JVM (ColdFusion 8 Admin) make sure your heap size is bigger than the file you want to upload. 1000 or 2000mb is good for large file uploads.

2. If you're using IIS7, you may need to change a variable which limits the "post" size for forms. Here's an article which discusses this very topic:
http://www.webtrenches.com/post.cfm/iis7-file-upload-size-limits

Reply to this Comment

Ben, script works great but what if I have a form of other items?

What I'd like to happen after all fields are filled:
submit form
database insert (get ID of insert)
trigger upload image
rename image with insert id(.jpg)

But this script hijacks form submit, I cant trigger it with anything it seems. So with this approach my steps would then be...

submit form
image uploads
trigger database insert (get ID of insert)
rename image with insert id(.jpg)

if so, how would I get the image info to the original form after upload? The only thing that gets returned is the name of the file that was uploaded.

Reply to this Comment

The way you are getting the content of the iframe

  • jBody.html()

results in characters such as & < and > being converted to &amp; &lt; and &gt;. To prevent that, I used

  • jBody.text()

The only place where that doesn't work is if you have html inside the json. In that case, you just have to make sure you are properly encoding the return page.

  • <cfcontent type="application/json">

"text/plain" will work too.

If you were instead returning html, .html() would be fine.

I guess in most cases this wouldn't present a problem anyway if you were simply taking text from the json and appending it to an html element, the characters would get encoded anyway.

Reply to this Comment

Hey Ben, I just managed to make it work for Google Chrome.

Looks like Chrome is picky on the jQuery call order.

Just place this code BEFORE the jQuery.load method:

  •  
  • // Attach to body.
  • $( "body:first" ).append( jFrame );
  •  
  • // Now that our iFrame it totally in place, hook
  • // up the frame to post to the iFrame.
  • jThis
  • .attr( "action", "upload_act.cfm" )
  • .attr( "method", "post" )
  • .attr( "enctype", "multipart/form-data" )
  • .attr( "encoding", "multipart/form-data" )
  • .attr( "target", strName )
  • ;
  • }
  • );

Many thanks for the ajax upload!

FP

Reply to this Comment

Hey Ben, I just managed to make it work for Google Chrome.

Looks like Chrome is picky on the jQuery call order.

Just place this code BEFORE the jQuery.load method instead of after:

  •  
  • // Attach to body.
  • $( "body:first" ).append( jFrame );
  •  
  • // Now that our iFrame it totally in place, hook
  • // up the frame to post to the iFrame.
  • jThis
  • .attr( "action", "upload_act.cfm" )
  • .attr( "method", "post" )
  • .attr( "enctype", "multipart/form-data" )
  • .attr( "encoding", "multipart/form-data" )
  • .attr( "target", strName )
  • ;

Many thanks for the ajax upload!

FP

Reply to this Comment

Hi Ben,
I want to use the above upload file plugin in a form which I will be submitting to a Servlet.I was able to get that in action we have to give the url of the Servlet but I am not able to understand that what is going on in .cfm file as I am unaware of coldfusion. Any Help with a demo code to use this plugin with a Servlet will be of great help to me.

TIA
Nazar

Reply to this Comment

hey Ben Nadel thanks for this tutorial.
i have downloaded your code also but no luck..
i am new to this so , can you explain me some thing about this cf prefix ?

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.