Posted in ColdFusion | Posted on 03-05-2010 | 9,764 views
I've done a few blog entries on ColdFusion 9's new multi file uploader. But for a while now I've wanted to build, and share, a complete example. As I've said before, putting a multi file uploader on your page is simple. Incredibly simple. Unfortunately, using the uploader is non-trivial. You've got multiple things going on at once that you have to manage. It is doable (I wrote up this demo in approximately 30 minutes), but you certainly need to do your planning ahead of time. So what will my little application do?
My application is a portfolio submission form. You can imagine a budding young artist bucking for a job at a top creative agency in the thrilling burg of Lafayette, LA. (It's thrilling - honest.) In order to apply for a job, s/he has to submit both biographical information as well as examples of their work. The application will take the user's information and their files and email it to the creative director. Right off the bat there you can see that we're going to need a form that mixes both traditional fields and the fancy new awesomeness of the multi file uploader.
I began by creating a simple form asking for 3 bits of biographical information:
2Your Name: <input type="text" name="name" value="#form.name#"><br/>
3Your Email: <input type="text" name="email" value="#form.email#"><br/>
4Your Bio:<br/>
5<textarea name="bio">#form.bio#</textarea><br/>
Notice that I'm using predefined form values for the three fields. They were set with a cfparam on the top of my template. (The entire code base is available via the download link below.) Next I added my multi file uploader:
I want you to notice a few things here. First, both the extensionfilter and maxfileselect are totally arbitrary. I used image file extensions because this is meant for a design job submission. I picked 5 because I have 5 fingers on one hand. The URL points to a separate CFM. That CFM will handle processing the uploads. Notice: Due to a bug in how the control is created, you must add the current Session URL token to the URL. If you do not, the upload request will be done with a new session.
Ok, so we've got a basic form. What's going to happen when the user actually picks some files to upload? Well since we are going to be emailing these files, we don't need to keep them around forever. I think using the new Virtual File System would be an excellent place to store those files. I added the following code to my onApplicationStart method of my Application.cfc:
2 <cfset application.portfolioUploadRoot = "ram:///portfoliouploads">
3 <cfif not directoryExists(application.portfolioUploadRoot)>
4 <cfdirectory action="create" directory="#application.portfolioUploadRoot#">
5 </cfif>
6 <cfreturn true>
7</cffunction>
As you can see, I've got an application variable that points to a path on the VFS. I then see if that directory exists and if not, I create it. Most likely it will never exist when the application starts, but I tend to rerun onApplicationStart manually during testing, and frankly, it doesn't hurt to be anal.
So now we have a root folder for our uploads, but obviously we may have more than one person using the form at once. I next created an onSessionStart that would make a unique subdirectory just for one person.
2
3 <cfset session.myuploadroot = application.portfolioUploadRoot & "/" & replace(createUUID(), "-", "_", "all")>
4 <cfif not directoryExists(session.myuploadroot)>
5 <cfdirectory action="create" directory="#session.myuploadroot#">
6 </cfif>
7
8</cffunction>
This method creates a new subdirectory using the Application's root folder and a new UUID. Like before, this folder will not exist, but I couldn't help going the extra step and wrapping it with a directoryExists().
So at this point, we have a safe storage place for the files. One that is unique per user. If we look at fileupload.cfm, we can see that it is rather trivial:
2 <cffile action="upload" filefield="filedata" destination="#session.myuploadroot#" nameconflict="overwrite" result="result">
3 <!--- optional post processing --->
4</cfif>
5
6<cfset str.STATUS = 200>
7<cfset str.MESSAGE = "passed">
8<cfoutput>#serializeJSON(str)#</cfoutput>
Two things to note here. I'm not doing any post-processing of the files. You may want to. In my case, I'm just going to leave them be. You should not trust that the user sent image files even with the extension filter. That being said, I'm not storing the files or executing them. I'm just emailing them. Secondly, and this is critical and not documented - be sure to output JSON with a 200 status. Big thanks to Brian Rinaldi and his blog post on the topic. If you don't have this, one file upload will work but the multi file uploader won't continue on to the next file.
Alright, so we've got the file uploads working, now let's circle back and look at how my form validates. I'm doing everything client side for simplicity's sake:
2
3 <cfset form.name = trim(htmlEditFormat(form.name))>
4 <cfset form.email = trim(htmlEditFormat(form.email))>
5 <cfset form.bio = trim(htmlEditFormat(form.bio))>
6
7 <cfset errors = []>
8 <cfif not len(form.name)>
9 <cfset arrayAppend(errors, "You must include your name.")>
10 </cfif>
11 <cfif not len(form.email) or not isValid("email", form.email)>
12 <cfset arrayAppend(errors, "You must include a valid email address.")>
13 </cfif>
14 <cfif not len(form.bio)>
15 <cfset arrayAppend(errors, "You must include your bio.")>
16 </cfif>
17 <cfdirectory action="list" name="myuploads" directory="#session.myuploadroot#">
18 <cfif myuploads.recordCount is 0>
19 <cfset arrayAppend(errors, "You must upload at least one file.")>
20 </cfif>
21
22 <cfif arrayLen(errors) is 0>
23 <cfmail to="someone@myorg.org" from="#form.email#" subject="Portfolio Submission">
24From: #form.name# (#form.email#)
25Bio:
26#form.bio#
27
28 <cfloop query="myuploads">
29 <cfmailparam file="#session.myuploadroot#/#name#">
30 </cfloop>
31 </cfmail>
32 <cfset showForm = false>
33 </cfif>
34</cfif>
So I begin by doing real simple validation on the 3 text fields. None of that should be new. But then check out how I validate if the user uploaded anything. I simply do a directory list on their personal storage. If it is empty, it means they didn't upload any files. Finally, if there are no errors, I send the email out. Notice how I use that previous query to create my list of attachments. I use both the errors and the directory list back on the bottom of the form when it is redisplayed:
2<p>
3You have uploaded the following files already: #valueList(myuploads.name)#.
4</p>
5</cfif>
6
7<cfif structKeyExists(variables, "errors")>
8<p>
9<b>Please correct the following error(s):</b>
10<ul>
11<cfloop index="e" array="#errors#">
12<li>#e#</li>
13</cfloop>
14</ul>
15</p>
16</cfif>
Now it's time for the final part of the puzzle - clean up. Remember that a user may upload files and never actually hit submit on the form itself. I use both onApplicationEnd and onSessionEnd to remove the files from the VFS:
2 <cfargument name="applicationScope" required="true">
3 <cfif directoryExists(arguments.applicationScope.portfolioUploadRoot)>
4 <cfdirectory action="delete" recurse="true" directory="#arguments.applicationScope.portfolioUploadRoot#">
5 </cfif>
6</cffunction>
7
8<cffunction name="onSessionEnd" returnType="void" output="false">
9 <cfargument name="sessionScope" type="struct" required="true">
10 <cfargument name="appScope" type="struct" required="false">
11
12 <cfif directoryExists(arguments.sessionScope.myuploadroot)>
13 <cfdirectory action="delete" recurse="true" directory="#arguments.sessionScope.myuploadroot#">
14 </cfif>
15
16</cffunction>
For the most part none of that code should be new to you, but do notice how you never directly access the Session or Application scope within these methods. They are always passed by reference instead.
So that's it. There are a few things that could be done to make it fancier of course, but hopefully this gives you a complete example of what it means to add a multi file uploader to your form. Questions and comments are definitely welcome.


Thanks for this blog post. It's a great help for me.
Thank you
Appreciate your help every time.
Sandy
Sorry for the random thought - great writeup.
Probably, what would be nice is if CFFileUpload had an "appendSessionToken" attribute or something.
@Ben2: I'm reading "Clean Code", and one of the points it makes is to NOT be counterintuitive. If a dev thinks that something is going to behave a certain way, you should (normally), make the code act that way. I get your point about most MFUs requiring this though.
In that case, upon reflection, perhaps it should, by default, append session tokens with the programmer's ability to turn it off (if they use custom session tokens or something else).
Then I hit the "send portfolio submission" button
I have a cf9 advanced class in May which should help me get up to speed. Thank you for your willingness to help.
<cflog file="application" text="The value of x is now #x#">
This will add a line of text to the Application.log file with the string specified above.
Does that make sense? Would a quick video demo help?
The app I am creating uses a form for creating ad text and uploading an image for dealership specials. It has form fields like Ad headline, Ad details, specifics for the Ad vehicle, like year, make , model, new used, blah,blah. I am using ORM and after the initial struggle, am loving it. Adding the upload UI on the same form using a custom tag was pretty simple and the files upload fine.
What I am struggling with is:
1. How to display the image and the file name beside the upload UI on the form asynchronously right after the upload, but before the form has been submitted. The div is ready to go, but the programmer is not...
2. Whatever size image has been uploaded, the image is re-sized to fit within 250px square (It doesn't need to be square, just the longest side no more than 250.)
3. Insert that freshly uploaded and modified image into the database. Maybe even give a field to the user to rename?
What am I looking for? Since the image was just uploaded and not yet in the database, what's it called?
That's it in a nutshell. One other note: I am looking at your app and wondering if a session is needed. I've wanted to learn how and why to use a session. But on this app, only one to two people at a time will be within admin.
Thank you in advance for your help!
2) Well that's just using imageResize, have you tried that
3) If you look at line 23, of the second to last code snippet, where I email stuff, that is basically the "I'm done, now do crap" part. That's where you would add additional logic. Remember that we used the VFS to store the images. You can copy these to a more permanent location and do any db inserts there. Really at that point it isn't an Ajax issue, it is a simple 'do what makes sense' issue.
4) The file is named based on the file upload result. We use nameconflict=overwrite so it will match the name of the user's file. Either way, remember that the cfdirectory result will have all the file names.
5) Yep, you definitely need to use sessions for my demo.
I hope that bit of info helps others having session problems
Tonight, I just realized that the session is carrying over when using I.E.
but not firefox.
My implementation was a modification from your example here; but in my site, I don't use the onSessionStart, instead I have a cfwindow to authenticate a user; then upon successful login I call a proxy to set the session...
<cfset session.myuploadroot = application.ramUploadRoot & "/" & session.member.getMemberID()>
<cfif not directoryExists(session.myuploadroot)>
<cfdirectory action="create" directory="#session.myuploadroot#">
</cfif>
I can't understand how my implementation would just not work in firefox. I must be missing something here. you have any thoughts?
this is my cffileupload tag:
<cffileupload extensionfilter="jpg,jpeg,gif,png,bmp,tiff"
name="portfoliofiles"
maxfileselect="5"
onError="errorissue"
title="Portfolio Images"
url="fileupload.cfm?nodeID=#nodeID#&#urlEncodedFormat(session.urltoken)#">
<cfdump var="token on uploadImages.cfm: #session.urltoken#" output="c:\dump5.txt">
here's a cfdump from the top of the page that the cffileupload URL points to:
<cfdump var="token on fileupload.cfm: #session.urltoken#" output="c:\dump5.txt">
and here is c:\dump5.txt after I upload an image:
token on uploadImages.cfm: CFID=11400&CFTOKEN=49045761&jsessionid=84308d6f4cb3951458c7764a5582227655c5
************************************************************************************
token on fileupload.cfm: CFID=11000&CFTOKEN=41029562&jsessionid=8430eac9b961800584772a4e3b43691c1a5d
************************************************************************************
the above is the cfdump results from firefox, as i said in my previous post, the results using I.E. show the same value for session.urltoken in the cfdumps.
thanks for any suggestions
Andrew
<cffileupload url="#urlencodedformat("upload.cfm?upload=true&foo=bar")#">
@RaymondCamden: I tried urlEncoding the entire url attribute's value before turning on "Use J2EE" and still got different session token from non-IE browsers.
I have this for file up loader page
<cffileupload url="insert_pictures.cfm?#urlEncodedFormat(session.urltoken)#" bgcolor="##0000" name="uploadfile" title="Image Uploader. Only *.jpg, *.gif Files. Max upload size at once 40MB" width="550px" extensionfilter="*.jpg, *.gif" align="center" onComplete="uploading" onUploadComplete="alluploaded" maxuploadsize="40" />
This on the process page
<cffile action="upload"
destination="#Application.Root#BBQ\TeamFiles\#Session.TeamID#\images\events\#Session.EventTeamName#.jpg" nameconflict="makeunique" />
Works great in IE Not Firefox. Which is surprising in itself. :)
Any thoughts greatly appreciated.
url="/bbq/maint/pictures/insert_pictures.cfm?#urlEncodedFormat(session.urltoken)#
Now it works, but i don't understand why i had to do this. I tried with different browsers and OSs. Without the J2EE sv the uploader worked on windows using IE and on any browser on mac. But it didn't work on any other browser on windows.
Does anybody know why this happens?
I am trying to use the multiple file upload, but on the action part I need to load the file into a SQL DB.
For 1 file I did this:
<cffile action="readbinary" file="#form.uploadFile#" variable="binaryFile" />
<cffile action="upload" destination="#application.UploadDir#" result="oUpload" nameconflict="overwrite" />
<cffile action="delete" file="#Application.UploadDir##oUpload.serverFile#" />
How do I use the new cf9 multi file upload, and cffile uploadall to do the same?
Thanks,
Darrin
I am working with the Multi-file uploader and the cffile action="uploadall" and I can get the basic functionality working. Which is a fine start, but I have a couple of questions for you regarding where I go next.
1) In Comment #27 - You mention that uploadall returns an array. How does one view the array? Since the page that processes the uploaded files is never seen, doing a dump is not the easiest. Any suggestions?
2) In your fileupload.cfm, you use action="upload". Should that be Uploadall or does the upload give you better file controls?
The reason I ask is that I want to receive any number of files from any number of users, store the files names in my DB, create a new truly unique name, and then store that name and those files on S3.
Any and all insight is always appreciated.
Thanks.
2) So that's the thing, uploadall works if you have N file uploads in the form page. But guess what - the Flash based uploader actually does one POST at a time. So you really just need upload. Does that make sense?
On part 2... thanks for the info. I do appreciate it.
Ok, thanks. I have an idea about what I can try.
Is the fileUpload page called everytime the flash uploader processes each file?
So if the Flash uploader has 10 files to work with,
the processing page gets called 10 times also?
Thanks.
Interesting, is that I changed the destination to the portfoliosubmission folder, and the files do end up there, so something is working. I just can't see what isn't, so I can do more than one upload.
Someone here mentioned the J2EE session variables, which we don't have on. I turned it on locally, and that fixed it.
Is there a place flash writes errors to?
this.secureJSON=false;
then try again and let me know. If it works, I'll epxlain.
...but not the uploader, if you want to try it again.
At this point not sure what to tell you. I asked Scott Stroz to test too (via IM) and he also reports it worked ok.
This is the bug I was referring to:
http://www.coldfusionjedi.com/index.cfm/2011/4/19/...
I really think that was it. As soon as you changed it it worked for me.
In any case, unless it starts working uniformly, we may not be able to use it for now.
Thanks for getting us this far.
http://www.uploadify.com/
I've blogged examples of it here before.
The whole reason we have been iso a new uploader (using http://www.cftagstore.com/demos/ProFlashUpload/) is because a) it throws errors for some users, and b) we suspect it may be responsible for hugely swelling our CF memory (it will hit 5Gb in 4-5 days and require a restart).
I can't say for sure, but reason A was enough for us to switch to something in hopes of fixing B.
First, thanks for the heads-up on Charles Proxy. I only had to use it once before I bought it. Great app! The fact that it shows Flash HTTP traffic is even better, because I was looking for something like that.
Second, using the "uploadall" action of cffile doesn't require you to pass a JSON string back, which seems to be the only point of using that action. The array returned from cffile is of the last uploaded file whether using action="upload" or action="uploadall", so capturing the file meta info isn't as easy as just accessing the result array.
To get around that, you can set a session variable. I cfparam a session variable as a 1 dimensional array and use arrayappend() on it after the cffile tag executes. This way my array, after processing, contains all file meta information.
Thanks for the posts, they were a big help.
No, cffile/action=uploadall is used in cases where you have a form post with N file upload fields. The multifile uploader actually sends one file at a time. So it isn't useful in this case, but isn't harmful either. That's why you see me using action=upload. You are supposed to return JSON though.
The docs seem a bit confusing for cffileupload because they basically tell you to use action="uploadall", which I thought originally was just for cffileupload. Now that you've clarified it, though, it all makes sense.
@Joseph2: Hmmm. Tricky. On one hand I'd say to just rewrite my logic, but what I used is isn't transferable to App.cfm. To be honest I'd have to see your App.cfm. If it isn't too complex, I'd maybe suggest copying some of the logic over. Copying is bad!! Blah blah blah. We all know that. But for this case it may be the most expedient.
http://www.bennadel.com/blog/726-ColdFusion-Applic...
It appears that maybe my existing application.cfm could be rewritten as you mentioned in the Application.cfc. Would I use the OnSessionStart() event in the cfc to build in the additional statements?
I am including my application.cfm as a public google docs:
https://docs.google.com/document/d/18KIJM8ZnFJB0Iw...
Thanks again!
Well... I don't believe it's supported by the Flash runtime - but I feel VERY low on my confidence on that. I know you can do it in Adobe AIR.
2) Well, my code involved multiple parts of app.cfc. You can't just put it all in onSessionStart. Or did you mean you would put YOUR code in there? It depends on what your statements are.
re Joseph2: I meant to add my code to your Application.cfc. i assume I could add most of my code to the OnSessionStart?
Joseph3: When I upload files they come through without the original filename and with no file extension. I have to rename a file to the .jpg extension in order to view it. I dont care so much about the filenames since those are in the output anyways, but is there a way to get that file extension back on those files when they are saved so I don't have to rename the files using the original file information later on in the process?
hope this makes sense, thanks!Joseph
(pseudo-code)
<cfapp name="myapp">
<cfif not isDefined("application.init")>
set a bunch of app vars
<cfset application.init = 1>
</cfif>
<cfif not isDefined("session.init")>
set some session vars
<cfset session.init=1>
</cfif>
In that case, you have 2 different things going on - application variable setup and Session variable setup. One would go into onApplicationStart and the other into onSessionStart.
re3: Woah, that doesn't make sense. You always have access to the original file name of the upload. Looking at this line here:
<cffile action="upload" filefield="filedata" destination="#session.myuploadroot#" nameconflict="overwrite" result="result">
If you dump result, or write the contents to a file, you should see it there. In the CF docs, look up cffile/action=upload. They talk about the CFFILE struct created after an upload.
Thanks for your help! Joseph
Quick example of jQuery/ColdFusion 9 multifile uploader
and adding 3 additional fields for each file, but I can't seem to get it to show the fields once I try to upload a file.
- and I can only get the code to work in IE. I also want to get each file loaded into the database as part of the processing - so I am a bit confused as to how to do this. DO I use action="upload" or action = "uploadAll" if I need all of the vars passed.
Thanks in advance
Your examples/blogs are always such a big help figuring out some of the tough stuff.
Thanks again
I used the Jedi's excellent post in order to construct an app I was building. In my particular app, it was possible for the end user to upload images and then go back and upload some more in the same session. This caused a problem because the first set of images were still in the VFS (session and app scopes were still active).
To solve this, in my CFC, I added a piece of code to clear out the directory, without deleting the VFS directory for future uploads. Here is my snippet...
<cfloop query="myuploads" startrow="1" endrow="#myuploads.RecordCount#">
<cffile action="delete" file="#directory#\#name#" />
</cfloop>
GET crossdomain.xml Aborted localhost:8080
GET MultiFileUpload.swf 200 OK www.foo.com
It is very odd...
/* <![CDATA[ */
2 var _cf_fileupload_init_1321906891556=function()
3 {
4 var _cf_fileupload=ColdFusion.FileUpload.create('cf_fileupload1321906891555','http%3A%2F%2Flocalhost%3A8080%2Fcfusion%2FXXXXX%2FXXXX%2Fdev%2Fprocess%2Ecfm',null,'CFID%3D15050%26CFTOKEN%3D13433490%26jsessionid%3D59526C32EBFAD91D912BC9B69A979E88',null,null,null,null,null,null,10485760,null,null,null,null,null,null,null,null,null,null,null,null,null,true,'/cfusion',false,true,null,'left');
5 };ColdFusion.Event.registerOnLoad(_cf_fileupload_init_1321906891556);
6/* ]]> */
[Add Comment] [Subscribe to Comments]