Quick example of jQuery/ColdFusion 9 multifile uploader

This post is more than 2 years old.

I was talking to a reader earlier today about ColdFusion 9's new multi-file uploader. I mentioned my earlier blog post which goes into details about the "multiple post" nature of this control, specifically if you have other form fields involved. He came back with an interesting scenario. How would you handle allowing for metadata about each file upload. By that I mean imagine the following: You've got a form with a few basic fields in (name, email, etc), and then you have the multi-file uploader. For each file you upload you want to ask the user to enter data about the file, like perhaps a nicer name. How could you handle that? Here is one simple example that makes use of jQuery. I wrote this very quickly so please forgive the ugliness.

Ok - the code:

<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script> <script> var counter=0

function handleComplete(res) { console.dir(res) if(res.STATUS==200) { counter++ var newRow = '<tr><td><input type="hidden" name="file_'+counter+'" value="'+res.FILENAME+'">' newRow += 'Label the file: '+res.FILENAME+' <input type="text" name="filename_'+counter+'"></td></tr>' $("table#detail").append(newRow) } } </script>

<form action="test.cfm" method="post"> Name: <input type="text" name="name"><br/> Email: <input type="text" name="email"><br/> Attachments: <cffileupload url="uploadall.cfm" name="files" oncomplete="handleComplete"><br/> <table id="detail"> </table> <input type="submit"> </form>

<cfdump var="#form#">

Starting at the bottom, we have our basic form with the multi-file control. Notice I've added a oncomplete attribute. This will be run after every file is uploaded. This runs a function called handleComplete. I get passed an object that contains a status code, a message, and the file name. So the next part is simple. If the status is 200, simply add a row of data where we can ask for more information. Notice I use a hidden form field. This let's me connect, numerically, a file name along with the meta data. You will see the connection in the sample below. The screen shot below shows the result of uploading 3 files and me entering information about them.

And after submitting, note the form data:

I hope this is helpful. Let me know if you have any questions, or improvements, on the technique.

Edit: For the 'nice name' label I used filename_X. That's a poor choice there since file_X is the filename. Just pretend I used something nice like, oh, filelabel_X.

Raymond Camden's Picture

About Raymond Camden

Raymond is a senior developer evangelist for Adobe. He focuses on document services, JavaScript, and enterprise cat demos. If you like this article, please consider visiting my Amazon Wishlist or donating via PayPal to show your support. You can even buy me a coffee!

Lafayette, LA https://www.raymondcamden.com

Archived Comments

Comment 1 by Steve Caldwell posted on 12/11/2009 at 8:36 PM

did you try this in IE? my fileuploader works fine until I add oncomplete="handleComplete" then it breaks. nothing shows at all javascript error shows 'handleComplete' is undefined.

Comment 2 by Raymond Camden posted on 12/11/2009 at 8:38 PM

If you switch my code in handleComplete to ONLY have alert('IE SUCKS') does it work?

Comment 3 by Steve Caldwell posted on 12/11/2009 at 8:53 PM

nothing happened when I added the alert up in the function, so I removed the entire script block and only included a basic alert in the oncomplete attribute.

<code><cffileupload url="./handlers/add_files.cfm?uuid=#url.uuid#" name="files" oncomplete="alert('IE SUCKS')"></code>

I get the alert when the page first loads. the files _do_ upload in IE now but won't upload in FF, which previously was the only browser which would upload with the oncomplete attribute included.

the alert does NOT re-fire on each upload completion.

this tag is really picky.

Comment 4 by Raymond Camden posted on 12/11/2009 at 9:06 PM

Ok, so I found the issue, for me, in IE. The dang console.log. It is Firebug only. When I removed it, it worked for me in IE.

Comment 5 by Steve Caldwell posted on 12/12/2009 at 12:10 AM

just to follow-up, I got it working by removing the console.dir(res) as well

My issue isn't 100% solved but I have narrowed it down some more. I'm trying to load my swf uploader inside of a jQuery ui tab loaded via AJAX. Do you have much experience with the UI tabs and AJAX functionality? When I first load the page then click the tab with the uploader (tab 2) it doesn't show, but when I go click tab 3 then come back to tab 2, it shows, and uploads and js works fine.

Comment 6 by Raymond Camden posted on 12/12/2009 at 1:23 AM

Got an example online?

Comment 7 by Steve Caldwell posted on 12/12/2009 at 1:56 AM

no, I don't have any public facing CF9 servers...I'm going to go with a popup window to hold the uploader for now. Too much time wasted on this already.

Comment 8 by Steve Caldwell posted on 12/14/2009 at 8:22 PM

I'm pretty noobish when it comes to Structs and Arrays, so please forgive the dumb question.

<cfloop from="1" to="#listlen(form.filecount)#" index="i">

<cfset fileTag[#i#] = {

file = #form['file#i#']#,
type = #form['filetype#i#']#

}>

</cfloop>

Gives me a nice array with a 2 key struct on each row.

How do I loop over each array row and pull out the 'file' and 'filetype' from the struct?

I tried this:

<cfloop array="fileTag" index="i">
<cfquery name="tagit" datasource="foo">
INSERT INTO files (baa_uuid, file_path, file_revision, file_type_id)
VALUES (#url.uuid#, '#fileTag.file[i]#', 1, '#fileTag.type[i]#')
</cfquery>
</cfloop>

but get...
500 java.lang.String cannot be used as an array

Am I heading in the right direction?

Comment 9 by Steve Caldwell posted on 12/14/2009 at 10:47 PM

Ok I got a working solution for posting the file tag results to db

First, up in the handleComplete function, add this to the newrow var:

<input type="hidden" name="filecount" value="1">

Then on the form post page:

<cfloop from="1" to="#listlen(form.filecount)#" index="i">
<cfset fileTag[#i#] = {file = #form['file#i#']#, type = #form['filetype#i#']#}>
</cfloop>
<cfloop from="1" to="#ArrayLen(fileTag)#" index="i">
<cfquery name="tagit" datasource="shebaa">
INSERT INTO files (uuid, file_path, file_revision, file_type_id)
VALUES ('#url.uuid#', '#form['file#i#']#', 1, '#form['filetype#i#']#')
</cfquery></cfloop>

Comment 10 by Steve Caldwell posted on 12/14/2009 at 11:34 PM

Sorry to keep rambling on ... but on my way back into the office just now I had a thought...why am I using an array when I just post the form values anyway? All I really was needing to do is count how many form fields were dynamically created.

So I can skip the whole array thing, and just use the length of my hidden form value...duh...

<cfloop from="1" to="#listlen(form.filecount)#" index="i">
<cfquery ...

Comment 11 by Raymond Camden posted on 12/15/2009 at 1:09 AM

I don't call that rambling. I call it 'talking it out' and 'documenting the process' - which to me I think is helpful to all my readers!

Comment 12 by Alex Yohn posted on 12/21/2009 at 9:12 PM

I finally figured out how to capture each file from the cffileupload and enter them into a database without having to do a wrapper form. For the cffileupload onComplete javascript, I have:

<cfajaxproxy cfc="upload" jsclassname="jsobj" />
<script language="JavaScript1.2">
function addfile(supfiles){
var upload = supfiles.FILENAME;
var cfcUpload = new jsobj();
cfcUpload.uploadfiles(upload);
}
</script>

The CFC is:

<cfcomponent output="false">
<!--- Record Uploaded File Details --->
<cffunction name="uploadfiles" output="false" access="remote" returntype="struct">
<cfargument name="fileupload" />
<cfquery name="addfile" datasource="#application.dsn#">
INSERT INTO ext_supportfiles
(programid, userid, filename)
VALUES (#cookie.programid#, #cookie.userid#, '#arguments.fileupload#')
</cfquery>
</cffunction>
</cfcomponent>

Comment 13 by Alex Yohn posted on 12/22/2009 at 12:24 AM

Small problem, if you use the nameconflict="MakeUnique" option of the cffile uploadall process, you cannot capture the new Unique name.

Comment 14 by Raymond Camden posted on 12/22/2009 at 12:26 AM

Why? It isn't returned in the array?

Comment 15 by Alex Yohn posted on 12/22/2009 at 12:29 AM

Not that I can find, only STATUS, MESSAGE, and FILENAME (orginial filename not unique filename).

Comment 16 by Raymond Camden posted on 12/22/2009 at 12:36 AM

Are we talking about the same thing? I mean the result in cffile/action=uploadall. It should be an array of structs much like you get from cffile/action=upload.

Comment 17 by Alex Yohn posted on 12/22/2009 at 12:43 AM

not really, when you use the cffileupload tag, it calls a processing url that contains the cffile uploadall, but only returns STATUS, MESSAGE, and FILENAME back to the originating cffileupload page. You lose all of the cffile variables (in my particular case serverFile). If you know a way to capture those and pass back to the originating file for the JS to run the CFC for the DB insert, I would be very interested.

Comment 18 by Raymond Camden posted on 12/22/2009 at 12:46 AM

Right, that's what is returned to the client, but you should be able to get access to the original file object. The cffileupload tag actually sends one file at a time. On the CFM where you post to, use cffile/action=upload and if you cfdump that result to a file, you will see everything.

Comment 19 by Alex Yohn posted on 12/22/2009 at 12:52 AM

If I use cffile/action=upload, the cffileupload returns a 500 error. If I use cffile/action=uploadall, the files upload, make unique names, but do not return any dump. Here is the page:
http://zeus.wvu.edu/support...

Comment 20 by Alex Yohn posted on 12/22/2009 at 12:55 AM

upload.cfm content:
<cfajaxproxy cfc="Support.upload" jsclassname="jsobj" />

<cffileupload
name="SupportFiles"
title = "Support Document Upload"
align="center"
BGCOLOR="FFFFFF"
extensionfilter=".jpg,.doc,.docx,.pdf,.xls,.xlsx,.ppt,.pptx,.rtf,.txt"
hideUploadButton="false"
clearbuttonlabel="Clear List"
deletebuttonlabel="Delete"
addbuttonlabel="Add A File"
uploadbuttonlabel = "Click to Upload"
progressbar = "true"
stoponerror = "false"
url = "/support/processupload.cfm"
height="400"
width = "800"
wmode = "window"
onComplete="addfile"
onError="errorissue">
</cffileupload>

<script language="JavaScript">
function addfile(supfiles){
var upload = supfiles.FILENAME;
var cfcUpload = new jsobj();
//alert(upload+' Uploaded Successfully.')
cfcUpload.uploadfiles(upload);
}
function errorissue(supfiles){
alert(supfiles.STATUS+': '+supfiles.MESSAGE);
}
</script>

Comment 21 by Alex Yohn posted on 12/22/2009 at 12:55 AM

processupload.cfm content:

<cffile action="uploadall" destination="D:\inetpub\wwwroot\Uploads" nameConflict="makeunique" result="supportfiles"
accept="jpg, doc, docx, xls, xlsx, rtf, txt, pdf">

<cfdump var="#supportfiles#">

Comment 22 by Raymond Camden posted on 12/22/2009 at 1:07 AM

Don't dump to screen. Dump to a file (read the docs for cfdump, this was added in cf8).

Comment 23 by Alex Yohn posted on 12/22/2009 at 1:15 AM

No joy...

struct [empty]

**************************************************************************

Comment 24 by Raymond Camden posted on 12/22/2009 at 1:23 AM

Show me the code for processupload.cfm again please?

Comment 25 by Alex Yohn posted on 12/22/2009 at 1:27 AM

<cffile action="uploadall" destination="D:\inetpub\wwwroot\Uploads" nameConflict="makeunique"
accept="jpg, doc, docx, xls, xlsx, rtf, txt, pdf">

<cfdump var="#cffile#" output="d:\inetpub\wwwroot\support\dump.txt">

Comment 26 by Raymond Camden posted on 12/22/2009 at 1:28 AM

If you switch to action=upload, what do you get? The multifile uploader actually only sends one file at a time.

Comment 27 by Alex Yohn posted on 12/22/2009 at 1:35 AM

Getting results, but not passing the SERVERFILE back to the originating page...should I move the CFC call to the processing page?

Comment 28 by Raymond Camden posted on 12/23/2009 at 7:04 AM

Ok, had to dig a bit. Your server-side can can send back the data, but it must follow the format the client side expects, ie, status and message. So you CAN'T send custom keys back, but you could send the data you want in status. Here is the code an Adobian sent me:

<cffile action = "upload" destination = "#Expandpath('./uploads')#" nameconflict="makeunique">
<cfset str = {}>
<cfset str.STATUS = 200>
<cfset str.MESSAGE = "File Upload Successful">
<cfoutput>#serializeJSON(str)#</cfoutput>

Comment 29 by Ryan Harris posted on 6/24/2010 at 1:45 AM

Hi Ray,
I used this code to try to return the correct filename from the server (cffile.serverfile) and received a 500 error. Do you know of a way to get the server filename after the upload completes? I would prefer not setting a session variable for this. My issue seems to be the same as most people in this situation: I'm using makeunique on the cffile tag and the server filename is not returning to the calling page.

Thanks!

Comment 30 by Raymond Camden posted on 6/28/2010 at 5:20 PM

Can you find more info about the error? Does Firebug show anything - do your CF logs show anything?

Comment 31 by Ranga posted on 7/13/2010 at 1:05 AM

Hi Ray

I have the following code:

<cffileupload extensionfilter="jpg,jpeg,gif,png,bmp,tiff,pdf" name="artfile" maxfileselect="1" title="Art" url="art.cfm?#urlEncodedFormat(session.urltoken)#" height="175" onError="dispError" onUploadComplete="uploadComplete" onComplete="uploadSuccess" addbuttonlabel="Add">

In art.cfm:

<cffile action="upload" filefield="filedata" destination="#hiresdir#" nameconflict="makeunique" result="result">

<cfif structKeyExists(form, "filedata")>
<cffile action="upload" filefield="filedata" destination="#artdir#" nameconflict="makeunique" result="result">
<cfset str = {}>
<cfset str.STATUS = 200>
<cfset str.MESSAGE = "File Upload Successful">
<cfset str.FILENAME = result.serverFile>
<cfoutput>#serializeJSON(str)#</cfoutput>
</cfif>

When I do run the above code, I get "Error" in the progress of the uploader. But the file is uploaded to the correct path. When I comment <cfoutput>#serializeJSON(str)#</cfoutput>, I don't get any error and I don't get the unique file name that got uploaded rather I get the filename that was uploaded from my machine.

Is there any way to get the unique filename passed from the server to JS? Since this is a flash based file uploader, I don't get anything available in the firebug console.

Comment 32 by Raymond Camden posted on 7/13/2010 at 1:11 AM

Why do you have 2 uploads? That doesn't make sense. It may also be causing an error.

Comment 33 by Ranga posted on 7/13/2010 at 1:13 AM

My mistake there is only one cffile action="upload". The one inside the <cfif structKeyExists(form, "filedata")> is the correct one.

Comment 34 by Raymond Camden posted on 7/13/2010 at 1:15 AM

It works for me. Try this for your onError:

function dispError() {
console.log('ERROR')
console.dir(arguments)
}

and look in the dump. If you see an error there, check your exception.log.

Comment 35 by Ranga posted on 7/13/2010 at 1:25 AM

Actually it kind of works for me too but in a weird way. But, what is happening is
1. the UI progress shows error.
2. onComplete JS runs, not the onError.
3. The filename returned is not the unique filename it is the filename I uploaded.

The CF 9 version I am running is ColdFusion 9,0,0,251028 standard edition.

Comment 36 by Raymond Camden posted on 7/13/2010 at 1:28 AM

You sure the file is being renamed? Use cflog to log result.fileWasRenamed.

Comment 37 by Ranga posted on 7/13/2010 at 1:34 AM

The log output was YES.
07/12 14:33:48 Information [jrpp-446] - result.fileWasRenamed: YES

Comment 38 by Raymond Camden posted on 7/13/2010 at 1:37 AM

Also log servername - ensure it is different than what you are seeing.

Unfortunately _all_ of my machines are... um, not 9. So it may be a version update. You should check to see if you have the latest hot fixes, etc installed.

Comment 39 by Ranga posted on 7/13/2010 at 3:33 AM

It looks like FILENAME could never be changed. In the cf docs for the tag - onComplete: You can also pass the JavaScript object by creating a struct with parameters "status" and "message" and call serializeJSON() on the JavaScript object.

It doesn't say you can change FILENAME. So, I guess FILENAME can never be changed.

So, I've decided to pass the filename through message.

Secondly, the reason I was getting error on upload even though it was successful. I found that it was due to the fact that I had my debug output settings ON and it was messing with flash.

Comment 40 by Ranga posted on 7/13/2010 at 4:34 AM

And Oh, it also doesn't this.secureJSON = "true" in Application.cfc

I need it for my other CFC calls. I don't know how to overcome it.

Thanks for your help, Ray!

Comment 41 by Raymond Camden posted on 7/13/2010 at 3:44 PM

If turning on secure JSON breaks, then the best you can do is file a bug report. All CF AJAX UI controls should work if that option is on.

Comment 42 by sfadullah posted on 10/26/2010 at 7:16 AM

hai ray,
it'm me again. i'm new to coldfusion. can u help me on my programming. i try using the cffileupload to upload an image to the folder in server and in the same time i need to store the image name into the database. i using ms sql for the database.

if i just using the cffileupload to upload multiple image, it going well. but it i try to query the name, it giving me error 500. do u have any idea how to insert the filename to my database?

Comment 43 by Raymond Camden posted on 10/27/2010 at 11:05 AM

Well if you use cffile/action-upload on the server side, you get access to the filename. It should just plain work. I'd need to know more about why you get an error.

Comment 44 by Mike posted on 10/27/2010 at 10:08 PM

this.secureJSON = "true" in Application.cfc breaks the upload.
actually the upload is done but then an error is thrown in the progress bar.

Thanks for the post. I was going nuts trying to figure this out

Comment 45 by Raymond Camden posted on 10/27/2010 at 10:18 PM

Ah yes - it preprends JSON results with a token. CF's built in AJAX stuff picks up on this automatically. You _can_ use it with jQuery stuff too - you just need to account for it.

Comment 46 by Alam posted on 5/13/2011 at 9:21 PM

I'm trying to use the multifile script every thing is ok except $("table#detail").append(newRow) which is not appending any field on the detail table.

Can anyone help me please?

<script type="text/javascript" src="http://ajax.googleapis.com/..."></script>
<script>
var counter=0

function handleComplete(res) {
console.dir(res)
if(res.STATUS==200) {
counter++
var newRow = '<tr><td><input type="hidden" name="file_'+counter+'" value="'+res.FILENAME+'">'
newRow += 'Label the file: '+res.FILENAME+' <input type="text" name="filename_'+counter+'"></td></tr>'
$("table#detail").append(newRow)
}
}
</script>

Comment 47 by Steve posted on 5/13/2011 at 9:29 PM

Alam,
Are you getting any js errors in the browser? Take out the console.dir(res) if you aren't using Firefox with Firebug

Comment 48 by Alam posted on 5/16/2011 at 5:28 PM

Steve, you r great! it's uploading successfully!! However, the progress bar displaying Error and Red color instead of green.

Thanks,
Alam

Comment 49 by Steve posted on 5/16/2011 at 6:19 PM

Alam,
It is uploading successfully and still showing a red progress bar? What is the exact text of the error? Try a web debugger like Fiddler to see what is going on behind the scenes.

Comment 50 by Alam posted on 5/16/2011 at 6:48 PM

After commenting the following line it is working! Thank you Steve and also I would like to thank Raymond.
<!---<cfoutput>#serializeJSON(str)#</cfoutput>--->

Comment 51 by Bill posted on 3/9/2012 at 12:09 AM

Ray,
I have something similar to your multi-file upload created and working but how can I pass the uploaded file paths to a database? Thanks.

Comment 52 by Raymond Camden posted on 3/9/2012 at 4:49 PM

Well, when you use cffile to process the uploads, it gives you the paths to the uploads files. You can insert those paths into a database just like any other simple string.

Comment 53 by misty posted on 5/2/2012 at 11:49 AM

Hi ray, I have an Interesting scenario here, I am using this tag fine. Now i have created one query to check if the uploaded images are already 5 for the passed ID, if yes, then cffileupload processing should be aborted. while, it keeps uploading all the files, when i click the upload button, any idea

Comment 54 by Raymond Camden posted on 5/2/2012 at 3:11 PM

I'd make the server throw an error (using cfthrow), and use the stoponerror option for cffileupload.

Comment 55 by Misty posted on 5/12/2012 at 12:23 PM

Accomplished, Just added a query Before the cffileupload to check if images already 5, then show an message rather than upload, your cfabort showerror strategy is good, will try some other day.

Also, i have seen all your comments for above, But there seems to be trouble with the IE specific, because also i tried when i use onComplete, onUploadComplete, onError, it jsut hangs and shows nothing, not even the uploadfile flash thing

hope others have come out of some kind of hack to make it work

i am using IE 9, even tried this on IE 7 but failed

Comment 56 by Terry posted on 6/13/2013 at 5:51 AM

How do I get the file to upload to my web server? Sorry for the entry level question.

Comment 57 by Raymond Camden posted on 6/13/2013 at 5:56 AM

Eh? Are you asking how to even use the control? I'd say start with the related entries at the bottom of this entry.

Comment 58 by Ed posted on 10/30/2013 at 5:59 PM

Thanks Ray, this post was very helpful (yes even 3 years later!) to a beginner using for the first time cffileupload for multiple file handling onComplete.

By the way it was good to see you in person giving a talk at the NYCF meetup group in the city a while back.

Comment 59 by Raymond Camden posted on 10/30/2013 at 8:19 PM

Thanks Ed, glad this was helpful.