Twitter: raymondcamden


Address: Lafayette, LA, USA

Quick example of jQuery/ColdFusion 9 multifile uploader

11-20-2009 12,873 views jQuery, ColdFusion 59 Comments

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:

view plain print about
1<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
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.

Related Blog Entries

59 Comments

  • Commented on 12-11-2009 at 9:36 AM
    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.
  • Commented on 12-11-2009 at 9:38 AM
    If you switch my code in handleComplete to ONLY have alert('IE SUCKS') does it work?
  • Commented on 12-11-2009 at 9:53 AM
    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/addfiles.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.
  • Commented on 12-11-2009 at 10:06 AM
    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.
  • Commented on 12-11-2009 at 1:10 PM
    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.
  • Commented on 12-11-2009 at 2:23 PM
    Got an example online?
  • Commented on 12-11-2009 at 2:56 PM
    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.
  • Commented on 12-14-2009 at 9:22 AM
    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 (baauuid, filepath, filerevision, filetype_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?
  • Commented on 12-14-2009 at 11:47 AM
    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, filepath, filerevision, filetypeid)
    VALUES ('#url.uuid#', '#form['file#i#']#', 1, '#form['filetype#i#']#')
    </cfquery></cfloop>
  • Commented on 12-14-2009 at 12: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 ...
  • Commented on 12-14-2009 at 2:09 PM
    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!
  • Commented on 12-21-2009 at 10:12 AM
    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>
  • Commented on 12-21-2009 at 1:24 PM
    Small problem, if you use the nameconflict="MakeUnique" option of the cffile uploadall process, you cannot capture the new Unique name.
  • Commented on 12-21-2009 at 1:26 PM
    Why? It isn't returned in the array?
  • Commented on 12-21-2009 at 1:29 PM
    Not that I can find, only STATUS, MESSAGE, and FILENAME (orginial filename not unique filename).
  • Commented on 12-21-2009 at 1:36 PM
    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.
  • Commented on 12-21-2009 at 1:43 PM
    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.
  • Commented on 12-21-2009 at 1:46 PM
    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.
  • Commented on 12-21-2009 at 1:52 PM
    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
  • Commented on 12-21-2009 at 1:55 PM
    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>
  • Commented on 12-21-2009 at 1:55 PM
    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#">
  • Commented on 12-21-2009 at 2:07 PM
    Don't dump to screen. Dump to a file (read the docs for cfdump, this was added in cf8).
  • Commented on 12-21-2009 at 2:15 PM
    No joy...

    struct [empty]


  • Commented on 12-21-2009 at 2:23 PM
    Show me the code for processupload.cfm again please?
  • Commented on 12-21-2009 at 2:27 PM
    <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">
  • Commented on 12-21-2009 at 2:28 PM
    If you switch to action=upload, what do you get? The multifile uploader actually only sends one file at a time.
  • Commented on 12-21-2009 at 2:35 PM
    Getting results, but not passing the SERVERFILE back to the originating page...should I move the CFC call to the processing page?
  • Commented on 12-22-2009 at 8:04 PM
    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>
  • Ryan Harris #
    Commented on 06-23-2010 at 4:45 PM
    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!
  • Commented on 06-28-2010 at 8:20 AM
    Can you find more info about the error? Does Firebug show anything - do your CF logs show anything?
  • Ranga #
    Commented on 07-12-2010 at 4:05 PM
    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.
  • Commented on 07-12-2010 at 4:11 PM
    Why do you have 2 uploads? That doesn't make sense. It may also be causing an error.
  • Ranga #
    Commented on 07-12-2010 at 4:13 PM
    My mistake there is only one cffile action="upload". The one inside the <cfif structKeyExists(form, "filedata")> is the correct one.
  • Commented on 07-12-2010 at 4:15 PM
    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.
  • Ranga #
    Commented on 07-12-2010 at 4:25 PM
    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.
  • Commented on 07-12-2010 at 4:28 PM
    You sure the file is being renamed? Use cflog to log result.fileWasRenamed.
  • Ranga #
    Commented on 07-12-2010 at 4:34 PM
    The log output was YES.
    07/12 14:33:48 Information [jrpp-446] - result.fileWasRenamed: YES
  • Commented on 07-12-2010 at 4:37 PM
    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.
  • Ranga #
    Commented on 07-12-2010 at 6:33 PM
    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.
  • Ranga #
    Commented on 07-12-2010 at 7:34 PM
    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!
  • Commented on 07-13-2010 at 6:44 AM
    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.
  • sfadullah #
    Commented on 10-25-2010 at 10:16 PM
    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?
  • Commented on 10-27-2010 at 2: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.
  • Mike #
    Commented on 10-27-2010 at 1: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
  • Commented on 10-27-2010 at 1: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.
  • Alam #
    Commented on 05-13-2011 at 12: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/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>
  • Steve #
    Commented on 05-13-2011 at 12: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
  • Alam #
    Commented on 05-16-2011 at 8:28 AM
    Steve, you r great! it's uploading successfully!! However, the progress bar displaying Error and Red color instead of green.

    Thanks,
    Alam
  • Steve #
    Commented on 05-16-2011 at 9:19 AM
    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.
  • Alam #
    Commented on 05-16-2011 at 9:48 AM
    After commenting the following line it is working! Thank you Steve and also I would like to thank Raymond.
    <!---<cfoutput>#serializeJSON(str)#</cfoutput>--->
  • Bill #
    Commented on 03-08-2012 at 1:09 PM
    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.
  • Commented on 03-09-2012 at 5:49 AM
    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.
  • misty #
    Commented on 05-02-2012 at 2: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
  • Commented on 05-02-2012 at 6:11 AM
    I'd make the server throw an error (using cfthrow), and use the stoponerror option for cffileupload.
  • Misty #
    Commented on 05-12-2012 at 3:23 AM
    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
  • Terry #
    Commented on 06-12-2013 at 8:51 PM
    How do I get the file to upload to my web server? Sorry for the entry level question.
  • Commented on 06-12-2013 at 8:56 PM
    Eh? Are you asking how to even use the control? I'd say start with the related entries at the bottom of this entry.
  • Ed #
    Commented on 10-30-2013 at 8:59 AM
    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.
  • Commented on 10-30-2013 at 11:19 AM
    Thanks Ed, glad this was helpful.

Post Reply

Please refrain from posting large blocks of code as a comment. Use Pastebin or Gists instead. Text wrapped in asterisks (*) will be bold and text wrapped in underscores (_) will be italicized.

Leave this field empty