Quick example of jQuery/ColdFusion 9 multifile uploader
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:
2<script>
3var counter=0
4
5function handleComplete(res) {
6 console.dir(res)
7 if(res.STATUS==200) {
8 counter++
9 var newRow = '<tr><td><input type="hidden" name="file_'+counter+'" value="'+res.FILENAME+'">'
10 newRow += 'Label the file: '+res.FILENAME+' <input type="text" name="filename_'+counter+'"></td></tr>'
11 $("table#detail").append(newRow)
12 }
13}
14</script>
15
16 <form action="test.cfm" method="post">
17 Name: <input type="text" name="name"><br/>
18 Email: <input type="text" name="email"><br/>
19 Attachments: <cffileupload url="uploadall.cfm" name="files" oncomplete="handleComplete"><br/>
20 <table id="detail">
21 </table>
22 <input type="submit">
23 </form>
24
25 <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.

<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.
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.
<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?
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>
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 ...
<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>
http://zeus.wvu.edu/support/upload.cfm
<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>
<cffile action="uploadall" destination="D:\inetpub\wwwroot\Uploads" nameConflict="makeunique" result="supportfiles"
accept="jpg, doc, docx, xls, xlsx, rtf, txt, pdf">
<cfdump var="#supportfiles#">
struct [empty]
**************************************************************************
accept="jpg, doc, docx, xls, xlsx, rtf, txt, pdf">
<cfdump var="#cffile#" output="d:\inetpub\wwwroot\support\dump.txt">
<cffile action = "upload" destination = "#Expandpath('./uploads')#" nameconflict="makeunique">
<cfset str = {}>
<cfset str.STATUS = 200>
<cfset str.MESSAGE = "File Upload Successful">
<cfoutput>#serializeJSON(str)#</cfoutput>
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!
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.
function dispError() {
console.log('ERROR')
console.dir(arguments)
}
and look in the dump. If you see an error there, check your exception.log.
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.
07/12 14:33:48 Information [jrpp-446] - result.fileWasRenamed: YES
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 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.
I need it for my other CFC calls. I don't know how to overcome it.
Thanks for your help, 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?
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
Can anyone help me please?
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1/jque...;
<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>
Are you getting any js errors in the browser? Take out the console.dir(res) if you aren't using Firefox with Firebug
Thanks,
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.
<!---<cfoutput>#serializeJSON(str)#</cfoutput>--->
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.
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