Can you do file uploads with ColdFusion 8's Ajax features?

I've gotten this question about 200 times, as have other bloggers, so I thought I'd write up a quick blog post to discuss it.

The short answer is no. Ajax, specifically the feature used to make HTTP requests, cannot do file uploads. Period. This is a security feature enforced at the browser level.

However there are workarounds. The typical workaround uses JavaScript to create a hidden iframe. A new form is created here and then that form is posted to the server. You can then monitor the result of the form post and handle it.

If you Google, you will find about 2 million examples of this. I spent five minutes and picked an example that was simple and direct. This is - most probably - not the best example - but it worked. For this example I'll be using the code from:

AjaxFileUpload Demo

Yes, that's a PHP site. Get over it. ;) The example makes use of jQuery and runs as a jQuery plugin.

I downloaded the AjaxFileUpload zip. It provided everything. I copied the JS, CSS files, and images over, and then modified his main demo file to come up with the following base code:

<html>
<head>
<title>Ajax File Uploader Plugin For Jquery</title>
<script src="jquery.js"></script>
<script src="ajaxfileupload.js"></script>
<link href="ajaxfileupload.css" type="text/css" rel="stylesheet">
<script type="text/javascript">
   function ajaxFileUpload()
   {
      $("#loading")
      .ajaxStart(function(){
         $(this).show();
      })
      .ajaxComplete(function(){
         $(this).hide();
      });

      $.ajaxFileUpload
      (
         {
            url:'/testingzone/ajaxupload/doajaxfileupload.cfm',
            secureuri:false,
            fileElementId:'fileToUpload',
            dataType: 'json',
            success: function (data, status)
            {
               if(typeof(data.error) != 'undefined')
               {
                  if(data.error != '')
                  {
                     alert(data.error);
                  }else
                  {
                     alert(data.msg);
                  }
               }
            },
            error: function (data, status, e)
            {
               alert(e);
            }
         }
      )
      
      return false;

   }
</script>   
</head>

<body>
   
<div id="wrapper">
<div id="content">
   <h1>Ajax File Upload Demo</h1>
   <p>Jquery File Upload Plugin - upload your files with only one input field</p>
      <img id="loading" src="loading.gif" style="display:none;">
      <form name="form" action="" method="POST" enctype="multipart/form-data">
      <table cellpadding="0" cellspacing="0" class="tableForm">

      <thead>
         <tr>
            <th>Please select a file and click Upload button</th>
         </tr>
      </thead>
      <tbody>   
         <tr>
            <td><input id="fileToUpload" type="file" size="45" name="fileToUpload" class="input"></td>   

               
         </tr>

      </tbody>
         <tfoot>
            <tr>
               <td><button class="button" id="buttonUpload" onclick="return ajaxFileUpload();">Upload</button></td>
            </tr>
         </tfoot>
   
   </table>
      </form>    
</div>


</body>
</html>

You want to pay particular attention to the ajaxFileUpload function. The only thing I modified was the URL value. To handle the file upload on the server, you handle it like any other file upload. However, his code wanted a JSON structure back. Luckily that is incredibly easy in ColdFusion.

<cfset r = structNew()>
<cfset r["msg"] = "">
<cfset r["error"] = "">

<cfif structKeyExists(form, "fileToUpload") and len(form.filetoupload)>
   <cffile action="upload" filefield="fileToUpload" destination="#expandPath("./")#" nameconflict="makeunique">
   <cfset r["msg"] = "Your file was #file.clientfile# and had a size of #file.filesize#.">
<cfelse>
   <cfset r["error"] = "No file was uploaded.">
</cfif>

<cfoutput>#serializeJSON(r)#</cfoutput>

So obviously if you use some other method, your code will vary, but hopefully this simple example will be enough to get people started.

If readers would like to recommend their own scripts, please do so.

Comments

I think what throws people the most is why it wasn't included/integrated with CF8 and the EXT library, either at the time EXT didn't support it or Adobe didn't want to include it. Regardless, it would have been nice if it was there when CF8 was released... mabye next time?
# Posted By Don Q | 2/27/08 11:48 AM
Wow, yet *another* perfectly timed blog post Ray!

I am about to build the user image upload facility on my site which makes heavy use of Ajax. Being able to upload without the need for a 'real' popup window will be very useful.

Thanks for the post - yet again.
# Posted By James Allen | 2/27/08 12:04 PM
I usually hit AjaxRain and search when I need something like this.

Check their category for upload:

http://ajaxrain.com/search.php?seValc=upload

Many choices there - and usually they're pretty good choices.

They also have the Ext widget that Don mentioned (direct link: http://extjs.com/learn/Extension:UploadForm)

I've yet to play with it, but maybe one of the CF/Ext projects will take on simplifying the widget with a custom tag?
# Posted By todd sharp | 2/27/08 12:15 PM
And for the record, I've submitted an ER a few months ago for CF next to bake this in...
# Posted By todd sharp | 2/27/08 12:16 PM
I can do file uploads with CFDIV using Safari (dont know why it works on Safari)
# Posted By Raul Riera | 2/27/08 12:33 PM
While CF8 Ajax features are neat, I think it leads to a lot of developers not understanding technically what is really going on with the web page that they are creating. A lot of CF developers still don't know what Ajax is or what it is doing. They think it's some neat CF8 trick, but in reality is just basic web browser functionality.
I personally have elected to hand-code using ExtJS 2.0 and have learned a tremendous amount about Javascript since as well as giving me more flexibilty. Which way is better? As always... It just depends.
# Posted By Terry Schmitt | 2/27/08 1:24 PM
There's two ways people generally do this in extjs:

1) flash way (currently somewhat unstable: eg swfupload)
2) hidden iframe submit (via normal form post)

Ext 2.0.2 (out yesterday) now has a standard form submit, however cf8's version is 1.0 so you can use that to submit to a target of an iframe...

1.0 way...
Ext.get('form_id').fileUpload = true;
Ext.get('form_id').dom.submit();

in v2.0.2
Ext.get('form_id').standardSubmit = true;
Ext.get('form_id').submit();

or just add the standardSubmit: true config item and submit the normal extjs way :-)
# Posted By Chris Dawes | 2/27/08 5:43 PM
I have this working fine with a cfm page, but when I put it in a cfc page it complains about:
The cffile action="upload" requires forms to use enctype="multipart/form-data".

The upload goes through fine, but no manner of cftry/cfcatch will prevent this error from being returned. Surely this can be done in a cfc.
# Posted By Drew | 5/14/08 9:48 AM
How are you using the CFC? Does your form post right to the CFC?
# Posted By Raymond Camden | 5/14/08 9:54 AM