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:
<form action="portfoliosubmission.cfm" method="post">
Your Name: <input type="text" name="name" value="#form.name#"><br/>
Your Email: <input type="text" name="email" value="#form.email#"><br/>
Your Bio:<br/>
<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:
<cffileupload extensionfilter="jpg,jpeg,gif,png,bmp,tiff" name="portfoliofiles" maxfileselect="5" title="Portfolio Images" url="fileupload.cfm?#urlEncodedFormat(session.urltoken)#">
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:
<cffunction name="onApplicationStart" returnType="boolean" output="false">
<cfset application.portfolioUploadRoot = "ram:///portfoliouploads">
<cfif not directoryExists(application.portfolioUploadRoot)>
<cfdirectory action="create" directory="#application.portfolioUploadRoot#">
</cfif>
<cfreturn true>
</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.
<cffunction name="onSessionStart" returnType="void" output="false">
<cfset session.myuploadroot = application.portfolioUploadRoot & "/" & replace(createUUID(), "-", "_", "all")>
<cfif not directoryExists(session.myuploadroot)>
<cfdirectory action="create" directory="#session.myuploadroot#">
</cfif>
</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:
<cfif structKeyExists(form, "filedata")>
<cffile action="upload" filefield="filedata" destination="#session.myuploadroot#" nameconflict="overwrite" result="result">
<!--- optional post processing --->
</cfif>
<cfset str.STATUS = 200>
<cfset str.MESSAGE = "passed">
<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:
<cfif structKeyExists(form, "submit")>
<cfset form.name = trim(htmlEditFormat(form.name))>
<cfset form.email = trim(htmlEditFormat(form.email))>
<cfset form.bio = trim(htmlEditFormat(form.bio))>
<cfset errors = []>
<cfif not len(form.name)>
<cfset arrayAppend(errors, "You must include your name.")>
</cfif>
<cfif not len(form.email) or not isValid("email", form.email)>
<cfset arrayAppend(errors, "You must include a valid email address.")>
</cfif>
<cfif not len(form.bio)>
<cfset arrayAppend(errors, "You must include your bio.")>
</cfif>
<cfdirectory action="list" name="myuploads" directory="#session.myuploadroot#">
<cfif myuploads.recordCount is 0>
<cfset arrayAppend(errors, "You must upload at least one file.")>
</cfif>
<cfif arrayLen(errors) is 0>
<cfmail to="someone@myorg.org" from="#form.email#" subject="Portfolio Submission">
From: #form.name# (#form.email#)
Bio:
#form.bio#
<cfloop query="myuploads">
<cfmailparam file="#session.myuploadroot#/#name#">
</cfloop>
</cfmail>
<cfset showForm = false>
</cfif>
</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:
<cfif structKeyExists(variables, "myuploads") and myuploads.recordCount>
<p>
You have uploaded the following files already: #valueList(myuploads.name)#.
</p>
</cfif>
<cfif structKeyExists(variables, "errors")>
<p>
<b>Please correct the following error(s):</b>
<ul>
<cfloop index="e" array="#errors#">
<li>#e#</li>
</cfloop>
</ul>
</p>
</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:
<cffunction name="onApplicationEnd" returnType="void" output="false">
<cfargument name="applicationScope" required="true">
<cfif directoryExists(arguments.applicationScope.portfolioUploadRoot)>
<cfdirectory action="delete" recurse="true" directory="#arguments.applicationScope.portfolioUploadRoot#">
</cfif>
</cffunction>
<cffunction name="onSessionEnd" returnType="void" output="false">
<cfargument name="sessionScope" type="struct" required="true">
<cfargument name="appScope" type="struct" required="false">
<cfif directoryExists(arguments.sessionScope.myuploadroot)>
<cfdirectory action="delete" recurse="true" directory="#arguments.sessionScope.myuploadroot#">
</cfif>
</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.
Archived Comments
Awesome example, as always. Thanks!!
Thanks for this great write up! Two days in a row you have explained items on my to do list! I'm feeling lucky!
Hi Ray!!
Thanks for this blog post. It's a great help for me.
Thank you
Appreciate your help every time.
Sandy
Thanks @Ray - good, clean and simple example
I know this is totally off-topic, but as I was reading this, it occurred to me that using the VFS to store the uploaded files would be a great way to prevent that LoadTesting hack that was going around a couple of months ago. Upload to the VFS - check file extensions and what not there - then move to the final "upload" directory.
Sorry for the random thought - great writeup.
Oh, the other thought I had - I would not consider the need to explicitly pass the Session token as part of the upload processing URL a bug. Granted, it's ***not intuitive*** at all, but it is typical of most browser plugins (flash and activeX). The issue is that they don't post the browser cookies with their internal HTTP requests. As such, you need to tell it to post the cookie as a URL parameter.
Probably, what would be nice is if CFFileUpload had an "appendSessionToken" attribute or something.
@Ben1: Thanks - and yes, it is also a great temp folder for uploads in general. I do wish they had made VFS application specific. That way an application wouldn't have to worry about other apps using the same folders, _and_ it could rely on the app timeout for cleanup.
@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.
Agreed on the need to be intuitive. When I first ran into that bug trying to get XStandard (ActiveX WYSIWYG) to work, it probably took me a **WEEK** to figure out what the heck was going :)
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).
I have been wanting to set up a form for internet ads that includes an image file upload for a product shot, right on the form. Your code looked like a great starting point. I downloaded, set up the project, and tested, including uploading a couple of images. But I couldn't get past the form validation for the file upload. I kept getting the error: "You must upload at least one file." I am an apprentice/intermediate cf8/cf9 coder
So, I assume you did upload some files, right? You chose them, and hit the upload button _on_ the MFU control, right?
Yup.
Then I hit the "send portfolio submission" button
So as you know, the code uses cfdirectory to check the virtual folder for your uploads. It is returning 0 for you. So we need to debug. First off, I'd add a butt load of CFLOGs. I'd add a cflog after line 17 that records session.myuploadroot. Compare that to fileupload.cfm. If you add a cflog in there as well to record the upload, you can compare directories. They SHOULD match, but start adding a bunch of logs to be sure.
Sorry. That one honestly went past me. I am yet to be familiar with cflog, although I was able to successfully run the form by submitting the input fields first, getting the error thrown, then uploading an image. Curious, but it worked.
I have a cf9 advanced class in May which should help me get up to speed. Thank you for your willingness to help.
cflog is real simple. it takes a file argument and a text field. it then just adds the text to the file. It's a quick and dirty way to debug. So for example:
<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?
Wow! Sorry the reply took a bit.. I didn't expect you to respond. I am blown away that you did! Yes a video regarding log files certainly would help. Thank you for offering. I feel like I am missing just a couple key elements and it would work wonderfully. Let me give a quick overview on the app and my stuck points:
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!
1) You can use the onComplete function to provide a preview of the file uploaded. I can work up a demo for that. It may be a week or so before I get to it (this is a BAD week for me, details on why later), but it is doable.
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.
ok, took me awhile to figure out why I couldn't get the sessions to work. no matter what, I kept getting two DIFFERENT session instances; one from the form page and a different session instance on the URL being called from the cffileupload tag. well, I discovered it only works when you go into the cfadmin and turn on: Use J2EE session variables
I hope that bit of info helps others having session problems
Odd, session.urltoken should work with "Use J2EE" on or off.
well, i just might be incorrect afterall :)
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)#">
here's a cfdump from the form page with the cffileupload:
<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
I know what it is - but can't take credit. It was mentioned on the Model-Glue listserv by Chris Blackwell. You need to urlEncoded the ENTIRE value passed to the url:
<cffileupload url="#urlencodedformat("upload.cfm?upload=true&foo=bar")#">
@AndrewDuvall: Thanks for the tip on turning on "Use J2EE session variables". Fixed the session issue I had with the uploader in non-IE browsers.
@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.
Not sure what my problem is. I'm having the Non IE Issue and have tried several variances of things suggested here.
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.
Well problem solved It appears using path from the root works
url="/bbq/maint/pictures/insert_pictures.cfm?#urlEncodedFormat(session.urltoken)#
I had the non IE problem, i had to enable the J2EE session variables to fix this.
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?
Good Day,
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
Remember that cffile uploadall returns an array of the results. You would simply loop through the array and process each file.
I am about to go insane with what I have finally determined is a configuration issue. If I put your example in my webroot (/Library/WebServer/Documents/) in the subdirectory 'demo' and run it from http://localhost/demo everything is just fine. But if I add a vhost entry for Apache and the appropriate hosts file entry as well and then run it as http://demo I get a blank screen where the Flash widget belongs. This clearly has bigger implications for any and all CFAjaxian stuff. Please help.
In your virtual host, or at root level, create a mapping for /CFIDE.
Doh! Thanks.
Perhaps I am missing something simple, but I downloaded and ran on my own CF9 web server, and when I hit upload after selecting a file, nothing happens. The bar just stays still. Any ideas on how to debug this?
Use a network monitor tool like Charles or Service Capture. This can tell you what - if any - response was sent back from the server.
Thanks - will do! Also of note, I am now getting a red bar on the first file progress bar if uploading multiple files. Does this point to anything specific?
Should be an error. Check out the response in Charles. You can also check your CF application log for a logged error.
Thanks for your help! My application.cfc in a parent folder seemed to be mucking with your application. Once I moved it out, it worked fine!
Hi Ray -
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.
1) You can dump to a file. That's one way.
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?
Hi Ray -
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.
You are right - if you upload 10 files you will have 10 requests.
So In order to get the file name I would need to use the onComplete attribute as a call to a JavaScript file?
Are you talking about the filename as it is in the control or the filename after it's been uploaded?
The filename after it's been uploaded to the server.
You would need to return it in the MESSAGE field - where I used "passed".
Is there any other way to get error information? I downloaded the pages--both locally and on our live server--and when it tries to upload the first file, the bar turns into a red "error". The application log only has that onSessionEnd is being run, and the deleting of the ram folders.
Check your application log.
I checked, and the log only has that onSessionEnd is being run, and the deleting of the ram folders.
What log though - are you checking c:\coldfusion9\logs\application.log?
Also - don't forget you can use Firebug/Chrome Dev Tools to see what the response is... it should work for this. If not, use Charles, a cheap network tool that works well with Flash network calls.
I did check that log, and I installed firebug (and flash version), and it shows nothing.
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.
We finally turned on J2EE, but still nothing: you click upload, the bar goes to 100%, then turns red and says, "error". When I changed the upload destination, odd part is the file is in fact uploaded.
Is there a place flash writes errors to?
It has to be in how you return data to the control. Can I run this myself on your server?
http://www.listinglab.com/p...
I think I know what it is. In the Application.cfc file for this folder, add
this.secureJSON=false;
then try again and let me know. If it works, I'll epxlain.
We've added that, but it just blows up the application page. We added <cfset this.secureJSON = false> right below <cfset this.sessionTimeout = createTimeSpan(0,0,4,0)>.
Can you describe what 'blows up' mean? You get an error?
Nm...we added the quotes and the page works again...
...but not the uploader, if you want to try it again.
<cfset this.secureJSON = false> threw an error...<cfset this.secureJSON = "false"> fixed that page.
Try again. It works for me. :) Once you confirm, I will explain what it was.
Alright...it works in chrome on a Mac, but in nothing else. Works on nothing on a PC (ie, chrome, FF).
You may want to clear cache and such. I'm on a PC and it's working fine for me now.
I cleared chrome (which was running incognito), but still an error. I tried in IE and chrome on antoher computer that had never been to the page, but still the same error.
Weird - I just tested in FF too and it worked fine there. I cpicked two files and clicked Upload all. I saw em both go up ok.
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.c...
I really think that was it. As soon as you changed it it worked for me.
Since it's working sporatically, I can't think of anything else to ask. Maybe after I restart or something, it will start working.
In any case, unless it starts working uniformly, we may not be able to use it for now.
Thanks for getting us this far.
If you do switch, I recommend Uploadify:
http://www.uploadify.com/
I've blogged examples of it here before.
We may give that a try. Thx.
The whole reason we have been iso a new uploader (using http://www.cftagstore.com/d... 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.
Ray,
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.
"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."
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.
Yeah, I used Charles and saw that each file got POSTed by cffileupload one at a time. So, using either upload or uploadall with cffileupload doesn't change that fact. As you said, though, posting a regular form with multiple upload fields and using "uploadall" will handle all of those fields without having to loop run the tag multiple times yourself.
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.
As usual, Jedi...am learning a ton from you. I have never used this before and am trying it out. I have run into an issue that I do not know how to solve...I am dumping the 'myuploads' to the screen and sometimes it is empty. I cannot see any consistency in it. Do you know what would cause this behavior?
ok...disregard my previous question. I believe I found it. I had a typo in my CreateTimeSpan function. So my app was ending prematurely.
Glad you figured it out. :)
ty...I did run into another issue though...When I was running through your tutorial, I was simply using the uploader and once I got that working, I added a cfc hit inside the cfif form submitted statement. This cfc hits the DB and records the appropriate info for the images. All of that worked swimmingly. I then moved that code to my real form which has other form fields that I am submitting to the DB. Now when I click upload in the uploader, I am getting a 302 error. I am not doing any redirection with cflocation or anything and it was working before I moved it to my real form. Have you ever run into the 302 error before and how did you debug it?
Hmm. Not sure. Is this online where I can see?
You can see it here...http://65.110.91.104/users/addinventory.cfm. I eliminated everything except the cffileupload and it still throws the error. Charles reports a broken socket.
I should say specifically Charles says Socket:Broken Pipe
Please post your code for fileupload.cfm to pastebin. Note - I'm about to head to a meeting so it may be a while before I respond.
Oh - it looks to be trying to post to fileupload.cfm, but it looks like that file does not exist. Did you forget to include that file?
Aha...I see the force is still strong with you. LOL...When I =ran through your tutorial, I did it from the root. The "real" form was in a subdirectory and I never moved the fileupload. Thanks a heap for pointing me in the right direction...by the way, do you still have visions of Robocop riding a unicorn? ;)
No worries, glad to help.
Is there a way to open the cffileupload dialog box as usual, but have it start in a specific directory/path on the user's computer? i.e. every time any user attempts to upload on my site it starts in C:\ etc
Hey Raymond, your example is the only real example of a business use for cffileupload out there. I appreciate your work on this. I have one other question as well. I have an application that I must use my existing application.cfm file for. I noticed that you are using Application.cfc at the root level in your example. Of course the conflict when they are both placed at the root. How could I modify your example to use Application.cfc under the cfc folder? Thanks again!
@Joseph1: I don't believe so. As it stands, that wouldn't work well cross platform.
@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.
re: Joseph1 - I have a project which is internal to my company. Since it's internal we know there are only a few different configurations that the user can have, and where their folders for the upload of these files would be stored. We check the #cgi.HTTP_USER_AGENT# which gives us something like ..... Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; .NET CLR 2.0.50727; .NET CLR 1.1.4322; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; ATT; ATT) ..... we can then build a simple case statement to decide which starting path we want their default upload to be from, making it easier on some of our non-computer techie client base to navigate to the files to upload. Is there any way to force this, maybe by using cfdirectory beforehand, or is there another upload method that may be in order if we really need this feature? Thanks for your help!
re Joseph2: I've been doing some reading here:
http://www.bennadel.com/blo...
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/doc...
Thanks again!
1) choose dir:
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 Joseph1: okay, we will probably have to do without that functionality.
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
re2: No, that is not a safe assumption. Again, it depends on what your Application.cfm. In many cases, folks would write their App.cfms like so:
(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.
re Joseph3: Without modifying your code except for the target directory to save the file to being on the server instead of the vfs, each time I make an upload I get a file in my folder of 2B35061C_1018_7FBF_BEDF8DDA75CAABED with no file type. But I need the extension to be saved with the file so that I can launch the file later on. Any ideas why the extension would not be saving with the file?
Thanks for your help! Joseph
Did you try logging result? Since this is being called "behind the scenes", read the docs on cfdump and how it can write to a file. Do that for result and share what it stored. It should be about 10 or so keys.
Hi Ray, I am trying to modify your example a bit to add required fields for each file uploaded and not having much luck with the validation. I tried using one of your previous posts
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
You want to include a set of values with the files? If so - you don't to use the multiuploader being used here. In this example, I have a set of form fields that go along with the files, but they aren't associated with one individual file. You would need to use something different.
Thanks Ray, kinda thought so. Moving on to your adding fields with JQuery post - just have to figure out how to add a maximum number of files(additions) allowed!
Your examples/blogs are always such a big help figuring out some of the tough stuff.
Thanks again
If you keep track of the # added, then it should be simple to check that # versus some maximum. :)
Just thought I would post this in case anyone else runs into it...
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>
Ohhh darn good point there Clay. I wasn't thinking about N, distinct, uses of the form in one session.
I'm trying out the new cffile upload and according to Firebug, its dying because its looking for the crossdomain.xml file on localhost:8080. JBoss is running on that port in my case and I'm not running off localhost. Any ideas?
So your saying you see a request to localhost:8080 even though you aren't on there?
Correct.. my page is at: https://www.foo.com/index.cfm and in firebug after clicking the upload button I see:
GET crossdomain.xml Aborted localhost:8080
That's odd. I assume if you view source then you are seeing some of the scripts being loaded that way?
Every other request is correctly formed using the webservername.. ie
GET MultiFileUpload.swf 200 OK www.foo.com
It is very odd...
Just inspected the javascript CF generates.. it has localhost as well..
/* <![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/* ]]> */
I think this is most likely some type of configuration issue.. we have two instances of CF on this particular box... one standalone, and one deployed under JBoss. Both are getting the wrong value for the path in one way or another. ColdFusion seems to derive this in some way that is not working for us. Could be some type of ReverseProxy confusion.
Yeah, sorry man - not sure.
Just to bring some closure to this thread... CF uses the cgi.host variable when automatically generating the .swf and various bits. If you are doing any proxying behind the scenes with Apache, you will need to use the ProxyPreserveHost Directive to ensure the correct host name is used. Check Apache docs for more info.
Thanks for posting this - I'm sure it's going to hit others too.
Hi Ray, I'm using cffileupload with CF 9.0.1 and the interface will not load. If I right click the area where it should be I get 'movie will not load' and the flash version I'm running. Finally figured out that the problem was the extension filter. If I take it out the interface loads, if I put it back the interface doesn't. Problem is without the extension filter none of the other variables seem to be recognized. For example, changing the width changes the color; the delete button is barely readable; the clear list button is displaying a token, there is no upload buttton. Makes no difference what browser I use or version. I can run this on a dev server using just cffileupload and cffile tags just fine. No errors, uploading multiple files at one time. The only difference I have found is that my dev server is running CF 9.0.0. I have turned on the J2EE serssion variables in the CF administrator but that doesn't work. I've been playing around with this for 3 days to no avail. Any help you can provide would be greatly appreciated.
Thanks!
Not sure what I can recommend w/o being able to see it. Is it online where I can visit it?
Yes, sorry. www.archbalt.org/ultipro/te.... The first is the cffileupload tag without the extention filter and the second is the generated code from my dev site. I copied it into an embed code. That's what it should look like but if you actually try to upload a file the upload button toggles to the cancel button. The extension filter adds this to the code when it's generated: <embed src="*.pdf, *.xls, *.xlsx/CFIDE/scripts/ajax/resources/cf/assets/MultiFileUpload.swf"...>. I've checked the path in the CF administrator for the scripts and it's correct. It's got to be some setting somewhere that I'm missing. Thanks for looking at it.
When I test it now, I get a cross domain error which makes sense. It is trying to post to newev.archbalt.org, not archbalt.org.
Ok, stupid coder error on my part. Fixed that and it does work. One problem solved. Do you have any idea why it won't work with the cffileupload tag? I'll put the extension filter back in and you can see it that way. Or not as the case may be. You'll have to right click where it should be. Thanks!
Well, let me know when it is back. It may be better to switch to email (raymondcamden at gmail dot com).
Oh wow, I just found this post over 2 years late -- I'd seen others you have on this topic but missed this one. THANK YOU (my hero!) for this complete example. This is awesome!!
So looking at the code the only thing that seemed specific to 'me' (vs. generic and already covered) was the email address, so I changed that. I'm on shared hosting J2EE CF9.01 (or whatever is most current I'm pretty sure).
I made a new folder inside an existing one (the existing one is within session security on my site already), and dropped all four files into it. (fileupload.cfm, temp.cfm, Application.cfc and portfoliosubmission.cfm).
I get the following error (screenshot):
http://www.palyne.com/coldf...
I'm wondering: I don't have an application.cfc right now. I have an Application.cfm file (up a couple levels from where this test folder is). Do I need to extract your cfc code into it, and if so would it be ok to use the code in the cfm file? (I am a little fuzzy on the diff as far as that goes.)
Or might there be some other reason I'd get a 404 when uploading a simple image file from my desktop?
Thanks hugely, incredibly, for the example and for any help. I was so excited to find this it was ridiculous. This beautiful 'easy feature' has made me bleed.
Hmm. So the file uploader in portfoliosubmission.cfm should point to fileupload.cfm. Can you confirm you *really* copied it?
Yes sir. Though I didn't copy the text, I just downloaded your zip file, opened it up, replaced the email address in the cfmail tag (in portfoliosubmission.cfm line 29), saved the file, and then moved all 4 files via FTP to the server. Here's a screenshot of the file content (line 75), didn't touch that area but it seems to be correct. And a shot of the FTP (was still open from hours ago when I put 'em all there) just as sanity check.
http://www.palyne.com/coldf...
http://www.palyne.com/coldf...
Can you share the URL so I can try it?
Yes, and thank you. Fwiw:
I originally had it in a subsub folder, with the top folder being under login. I copied it into a folder just under root, which had no session/client/cookies enabled (my root app file doesn't by default), and got the same results. I turned those three values in cfapplication tag on, but it seems to be the same. I'm in WinXP at the moment, and I tested it via Chrome 21.0.1180.79m, Firefox 14.0.1 and MSIE 8.0.6001.18702.
I'm happy to provide FTP info in email if it is at all helpful.
http://dojopsi.com/ul/portf...
This is what I did.
I used the Charles network proxy tool to monitor my hits of your site.
I loaded up your demo and confirmed I saw it in Charles.
I then picked a file and clicked upload. I got a 404. I saw why pretty quickly:
http://www.evernote.com/sha...
Notice the "sites" branch - that showed up when I hit upload. For some reason it is trying to go to
http://dojopsi.com/sites/do...
I'm guessing your server is getting confused about what the site root is.
Easy solution is - in the cffileupload tag - use a full URL (http://dojopsi.com/ul/fileu... and see if that works. Or maybe even "./fileupload.cfm".
Oh wow, thank you incredibly for your input. I tried:
./fileupload.cfm
../ul/fileupload.cfm
http://www.dojopsi.com/ul/f...
http://dojopsi.com/ul/fileu...
All were 404 except the last one which did work. Glory!
I can't tell you how many times over the years I've spent eons ripping my hair out about something, bugging support, only to later find out it is some setting the shared server corp didn't have set right on my site (and didn't notice during my pleading with them for help since my code seemed identical to that working perfectly elsewhere). I don't know if this situation means that is the case or not. I cannot figure out why using a simple filename link in the uploader would post to that duplicate-like address your tool showed, but it seems like a server mapping issue I guess. I'll write the host.
Thank you so much for the ref to the proxy tool. I didn't even know that kind of thing was available. At least this particular issue will never be the cause without my seeing it promptly again!
Best regards,
Palyne
A follow-up for other finding via search.
I'm new to CF9, and this is actually the first flash element I've used in CF, so a couple things prevented this working right at first. One of the hardest things when learning is that all the 'answers' on the internet often assume you already know at least 2 underlying things you actually don't, so for the other clueless I'm adding:
The above solution only worked in chrome and ie for me, it turns out. Reason? Firefox was "so helpfully" forcing my browser to add www to the URL. Even if I forcibly removed it, it put it back. I never noticed it "overrode" my entry like that before, even if http:// was present.
If I added the www to the URL manually in chrome or ie, when the page loaded, the url would auto-change to http://domain.com. But it didn't 'remove' the www in firefox, I suppose because the browser was overriding my server and forcing it or something.
I know I'm late to this party obviously but as I only do little shared-hosting hobby sites (webwork isn't my job for too many eons, and I only just got a diff server config recently) it just never came up for me before.
This is "domain guessing" and to turn it off on the local ffx browser put "about:config" (no quotes) in the url bar and press enter. Agree to the button that comes up. Put "browser.fixup.alternate.enabled" (no quotes) in the 'search' field at the top of the page you get, to pull up this option. Double-click that option line to change it to false. Then close the browser fully and reopen. Now ffx won't "force" .com or www. onto your URLs anymore. At that point, it worked in firefox for me.
Alas I can't expect all my users to do that, so clearly there was something else.
Thanks to the charles proxy tool I was able to see that it was calling 'crossdomain.xml' just before the upload element did nothing-at-all.
I didn't know that you must have a crossdomain.xml file in the root folder of the domain. This is on my local cf9 install at the wwwroot level but not on the shared server. So using the one on my local install with a mod I made:
<?xml version="1.0"?>
<!DOCTYPE cross-domain-policy SYSTEM "http://www.macromedia.com/x...">
<cross-domain-policy>
<allow-access-from domain="*.mydomain.com"/>
</cross-domain-policy>
And put it in the root folder. Now anybody's firefox seems to run my cf9 cffileupload flash-based file uploader as well as chrome and ie. Little things...
I guess now it's clear why the header of my rarely used blog says, "CFML is a beautiful code language, designed so even an idiot can use it. I'm that idiot. Here's my answers to stupidly simple stuff I struggled to figure out."
Thanks again for your awesome files and help Ray.
P
Ray...have you ever run into the following error when using the multiple file upload?
access denied (coldfusion.vfs.VFilePermission...
I have a site on a CF10 server (shared hosting) and am using the exact same application.cfc code I used on a site that is within a CF9 environment. Here is my code (or I should say your code)...
In the OnApplicationStart...
<cfset Application.portfolioUploadRoot = "ram:///portfoliouploads">
<cfif not directoryExists(Application.portfolioUploadRoot)>
<cfdirectory action="create" directory="#Application.portfolioUploadRoot#">
</cfif>
In the OnSessionStart...
<cfset session.myuploadroot = application.portfolioUploadRoot & "/" & replace(createUUID(), "-", "_", "all")>
<cfif not directoryExists(session.myuploadroot)>
<cfdirectory action="create" directory="#session.myuploadroot#">
</cfif>
Thoughts?
VFS can be turned off at the server level. That looks to be your issue based on the error.
Ray...ty. That was the issue and my host fixed it. For those readers that might run into this, I found the following snippet in the docs that was helpful...
<cfdump var="#getVFSMetaData("ram")#">
------
I ran into another issue...I am creating the session.myuploadroot variable like you did using the VFS I grabbed in the OnApplicationStart and the UUID. I then utilize the session.myuploadroot variable throughout my code just like you did. Everything seemed to work, but no files were uploaded. I dumped the session to the screen and used Charles to watch the flow of things and noticed that my fileupload.cfm page is using a session.myuploadroot variable with a different UUID than my upload page. How can that happen if I am creating the variable within my application.cfc? Ever run into this?
Not sure what to tell you. Maybe ensure your session isn't timing out. Add logging to onSessionStart and ensuring it is just running the one time. Maybe add a foo.cfm page that does cfdump on session and ensure it matches with what you think it should be.
Ok...just in terms of followup...I used cfdump along with Charles/JSON to trace session.myuploadroot through the process. I did it on my own CF10 development server and it worked like a charm. The CF10 at my host is where the problem lies. The session variable seems to be created twice from my OnSessionStart method. The fileupload.cfm file uses a different session.myuploadroot than the page containing the cffileupload. So it seems to be creating two sessions for the same site/session. Since it works on my test server, I obviously think it has something to do with my hosts CF server config. I have a site using this successfully with this host in a CF9 environment. So I figured it had to be some setting in CF10. I know that CF10 had a lot of security changes, etc., but I could not seem to find any information that would answer why two sessions at once. Anyone have any thoughts? I am stumped.
Is your host perhaps using J2EE Session variables?
Raymond... any time I try to add any code to 'process' the images i get an error 500 back to the flash uploader...
for instance.
<cfscript>
if ( structKeyExists( URL, 'g' ) {
//if ( !isDefined( 'objFiles' ) ) { objFiles = createObject( 'component', 'cfc.file' ); }
//objFiles.processGallery( URL.g );
}
</cfscript>
if I run this same code from a separate cfm page, no issues, so the method call doesnt return an error... and it doesnt matter id i wrap it in cfsilent, or try/catch and suppress any errors... if i put ANY code after the <cffile action="upload" > then I get an error... making me have to add an extra step..
any ideas?
do you have code examples of you process the uploaded images.
What do you see in your exception.log?
...well, as usual, when i try to recreate something that plagued me for hours b4 I posted, it works. Now I know where to look first b4 I repost. thx
Glad this old post helped. :)
Ray ~
I ran into an odd error upon which I hope you can shed some light. I have a site that will sporadically display a blank screen as if it does not have any content. I am creating the VFS space like you are in the application.cfc. When I comment out the VFS code in the application.cfc, the site displays just fine (although my file uploader won't work obviously).
I am wondering if you have run into this before and have a thought or two as to a fix?
FYI...The site is on a CF10 shared hosting plan at Newtek. I am thinking it is a config issue with sandboxing, but cannot be sure. I am having them email me the log files as I have error logging set up in the application.cfc.
So to be clear, the Flash uploader UI doesn't show? The VFS is being used to store the uploads - it shouldn't impact the display of the control.
Hello, Ray
I want to say something to newbies (like me).
If debug is enabled, and want to send status back, it is a must to use
cfsetting showdebugoutput="false"
If not, the json will be compromised.
Is there a way to send more data back? I want to upload a file, and after some iteration to send back some result. in str.Data .
Actually, my json Response is ripped out. Is used only STATUS and MESSAGE. And MESSAGE key is apparently limited string.
Thanks.
As far as I know, no. You would need to use something else to have to more control.
Too bad... It's a great control... with limited power...
Thank you very much