ColdFusion, jQuery, And "AJAX" File Upload Demo
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 );
// 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.
Want to use code from this post? Check out the license.
Reader Comments
Thanks for posting the "secret iFrame" route it works like a charm!
@Janet,
Glad you liked.
Pretty nice man ... I tend to use the flash route, using swfupload (http://swfupload.org/), but I like the plain jquery solution ... Good one.
@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.
I fiddled with jQuery plugin AjaxFileUpload until it worked. It doesn't work great with CFCs, but I'm able to upload and manipulate uploaded images.
http://www.phpletter.com/Our-Projects/AjaxFileUpload/
Thanks for posting an alternate method
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).
@Harvey,
Hmmm. You had the jQuery javascript file included, right?
@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.
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...
@Steve,
I get that occasionally, but it seems to be hit or miss. I don't think it affects the functionality at all.
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>
@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 :(
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.
@Mahesh,
Thanks man. I have seen some buzz about the Google JS stuff, but have not looked into it just yet.
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.
@Mahesh,
Corrent; I have found that you need to set the "encoding" as well with IE.
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.
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.
@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.
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
Awesome example. Thanks Ben...this helped out a great deal.
@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.
@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...
@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.
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.
Can we send text post data with file data?
Ben, how would you integrate progress bar? Any ideas?
Steve
@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.
This doesn't seem to work in Google Chrome, too bad. Any thoughts on why not? Not allowed to dynamically add iframes?
@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.
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
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?
@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.
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
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.
Just kidding.
What I did was put in the absolute path for the secondary page that takes the upload...
@Dave,
No worries :)
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?
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
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
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
@Dave: All cfforms within cflayout or cfdiv will submit via AJAX. Have you tried putting the iframe outside of all the cflayout tags?
@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" ).
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...
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.
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...
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
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 );
}
);
}
);
2. upload_act.cfm
.attr( "action", "cf_file_handler.cfm" )
you need to rename your file upload_act if you haven't already
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
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?
@Rick,
Are you loading the form dynamically into the modal window?
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
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
@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?
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!
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
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!
@Yogesh,
You can download the latest jQuery JS library from http://www.jquery.com. There is nothing special about the 1.2.2 pack.
I know this is an older post, however why not send the file via sockets to port 80 by getting javascript to initiate a java applet or flash movie like: http://stephengware.com/projects/javasocketbridge/
@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).
Just managed to get this code working on my site...works like a charm..
Thanks
@John,
Heck yeah! That's good hear.
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
@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.
Yeah, think am getting error message
ajax_upload.js(line 53)
Syntax Error
()
Line 53 = var objData = eval( "(" + jBody.html() + ")" );
Thanks
@John,
Looks like your server isn't returning any body in the iFrame document.
Ok, not sure why thats happening, but will have another go.
Thanks
@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.
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
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
What does your CF processing code look like?
Its exactly the same as the processing code in the example in bens demo above
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?
I would really appreciate that rick, good idea..
I will post the code at work tomorrow
Thanks
@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?
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
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...)
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 :)
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!
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?
@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.
@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...
@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.
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.
@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?
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?
@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.
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
@Yogesh,
My server was having some problems; it should work now.
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.
@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.
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
@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):
This way, you can keep the submit logic and simply augment it with the input-change handler.
hi...
how post a file name and filetype and filesize via $POST in jquery???
@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.
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?
Sorry... please ignore my previous post. Seems my version of IE running on Parallels is screwy and I didn't troubleshoot it first.
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
Scrap my previous comment. I see now that ajax_upload obviously contains the iFrame jquery code.
Thanks - code works great so far!
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.
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
@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
@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)
@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? :)
@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)
@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 :)
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.
@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
@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.
I want to config upload file size larger, more than 100MB, but with size large . It not work
I need help. Thanks all.
@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
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.
The way you are getting the content of the iframe
results in characters such as & < and > being converted to & < and >. To prevent that, I used
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.
"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.
Code doesn't work for me. Read the entire article. Tried code on CF8 and CF9 in FF and Chrome. No luck.
See here: http://aixen.com/_test/
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:
Many thanks for the ajax upload!
FP
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:
Many thanks for the ajax upload!
FP
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
How we can handle error returned by large file upload..
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 ?