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.
Archived Comments
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.
If you switch my code in handleComplete to ONLY have alert('IE SUCKS') does it work?
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.
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.
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.
Got an example online?
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.
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?
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>
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 ...
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!
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>
Small problem, if you use the nameconflict="MakeUnique" option of the cffile uploadall process, you cannot capture the new Unique name.
Why? It isn't returned in the array?
Not that I can find, only STATUS, MESSAGE, and FILENAME (orginial filename not unique filename).
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.
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.
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.
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...
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>
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#">
Don't dump to screen. Dump to a file (read the docs for cfdump, this was added in cf8).
No joy...
struct [empty]
**************************************************************************
Show me the code for processupload.cfm again please?
<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">
If you switch to action=upload, what do you get? The multifile uploader actually only sends one file at a time.
Getting results, but not passing the SERVERFILE back to the originating page...should I move the CFC call to the processing page?
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>
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!
Can you find more info about the error? Does Firebug show anything - do your CF logs show anything?
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.
Why do you have 2 uploads? That doesn't make sense. It may also be causing an error.
My mistake there is only one cffile action="upload". The one inside the <cfif structKeyExists(form, "filedata")> is the correct one.
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.
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.
You sure the file is being renamed? Use cflog to log result.fileWasRenamed.
The log output was YES.
07/12 14:33:48 Information [jrpp-446] - result.fileWasRenamed: YES
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.
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.
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!
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.
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?
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.
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
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.
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>
Alam,
Are you getting any js errors in the browser? Take out the console.dir(res) if you aren't using Firefox with Firebug
Steve, you r great! it's uploading successfully!! However, the progress bar displaying Error and Red color instead of green.
Thanks,
Alam
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.
After commenting the following line it is working! Thank you Steve and also I would like to thank Raymond.
<!---<cfoutput>#serializeJSON(str)#</cfoutput>--->
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.
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.
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
I'd make the server throw an error (using cfthrow), and use the stoponerror option for cffileupload.
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
How do I get the file to upload to my web server? Sorry for the entry level question.
Eh? Are you asking how to even use the control? I'd say start with the related entries at the bottom of this entry.
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.
Thanks Ed, glad this was helpful.