Flex Multi-file upload

Screenshot of Flex Multi File Upload

As part of a project where I work, I had the opportunity to refactor and open-source a Flex application that is used as a multi-file upload tool. This beats the pants off of N <input type="file" /> fields. It works somewhat like the flash upload you might be used to on Flickr. You can use it with ColdFusion, PHP, Ruby, or any other server-side language that can accept file uploads.

... and it can be yours for the low, low price of free! You can include it in any application to make uploading multiple files easier and more usable.

Using it requires 2 separate templates: one to display the Flex widget, and another to accept the incoming files, just as if they were posted from a standard web form (it is posted to once per file). Since most people are somewhat familiar with traditional file upload, let's start there.

Accepting uploaded files

Here's some sample code in CFML that handles the posts from the Flex application. It is exactly the same as what you would use with a traditional form-based file upload, with the exception that flash uploads everything with the mime type "application/octet-stream," so we can't use that to determine the uploaded file type and instead have to rely on the file extension.

<!---
    Flex Multi-File Upload Server Side File Handler
    ===============================================
    This file is where the upload action from the Flex Multi-File Upload
    UI points. This is the server side half of the upload process.
--->
<cftry>
    <!---
        Flash uploads all files with a binary mime type
        ("application/ocet-stream"), so we can't set cffile to accept 
        specfic mime types. The workaround is to check file type after
        it arrives on the server and if it is not desireable, delete it.
    --->
    <cffile action="upload"
        filefield="filedata"
        destination="#application.xmlUploadDest#"
        nameconflict="makeunique"
        accept="application/octet-stream"
    />
    <!--- If the file extension isn't allowed, delete it --->
    <cfif not listFindNoCase(acceptedFileExtensions,File.ServerFileExt)>
        <cffile
            action="delete" 
            file="#application.xmlUploadDest#/#File.ServerFile#"
        />
    </cfif>
    <cfcatch type="any">
        <!--- bubble up errors to onError in Application.cfc --->
        <cfthrow object="#cfcatch#">
    </cfcatch>
</cftry>

It's pretty straight-forward. A file is uploaded by the Flash player, very similar to posting a form with an <input type="file" name="filedata" />. The server will see the request almost exactly as if a form was submitted with a file upload -- the form field name for the file is "filedata", that is hard coded. The script above is hit once per file being uploaded. The script saves the file to its final destination, and if there are any errors they bubble up to the onError method in Application.cfc.

The error bubbling is important, because the flash player won't display issues to the user, and it won't let you -- the developer -- know if there are problems. By throwing an error, you can catch it and email or otherwise log the issue so that you have some information to help you resolve whatever the problem may be.

See? I told you it was simple.

Embedding the Flex Widget

Next up, we need to put that Flex widget in your web application, and configure it to look and run as you need it to. There are a few parameters you can specify in order to change its appearance and behavior:

Parameter Required Default Description
uploadDest yes n/a The URL for the script that accepts each uploaded file (the one we discussed above).
fileTypeFilter yes n/a A semicolon-delimited list of file types to show in the browse dialog. (Eg. "*.xml; *.pdf; *.doc")
fileTypeFilterName yes n/a A string that describes what filters are being applied. Often, this also includes the filters. (Eg. "Documents (*.xml, *.pdf, *.doc)")
sessionParams probably ;) n/a This semi-optional parameter is only required for Alternative Browser support. If you are working in a closed environment and IE will be mandated, you can ignore it. It is used to work around a Flash file upload bug. I'll discuss the details of what you should include here further below.
title no "File Upload" Displayed at the top of the panel. In the screenshot above, this was set to "SABRE File Upload".
uploadCompleteMessage no "Upload Complete" A string to display on the progress bar once upload is complete.
maxSize no 2048 Max file size that Flash will let the user upload. Specify in kilobytes. (Default of 2048 = 2mb)
callback no n/a The name of a JavaScript function defined (or included) on the page that includes the Flex application. This function will be run when the upload is complete, and gives you the ability to have your application be aware of when files have been uploaded.

How do you set these values when including a Flash/Flex widget on your page? Via FlashVars. They look and act a lot like a standard URL, so even if you've never heard of them before, you're already half way there. FlashVars look like this: foo=bar&now=then. See? Familiar!

You'll want to use JavaScript to embed the flash player on the page, like so:

<cfscript>
    variables.flashvars = "uploadDest=#urlEncodedFormat(expandPath('uploadedFiles/'))#";
    variables.flashvars &= "&title=SABRE%20File%20Upload";
    variables.flashvars &= "&fileTypeFilter=#urlEncodedFormat('*.xml')#";
    variables.flashvars &= "&fileTypeFilterName=#urlEncodedFormat('XML Documents (*.xml)')#";
    //... and so on, for other flashvars
</cfscript>
<script src="AC_OETags.js" language="javascript"></script>
<script type="text/javascript">
    AC_FL_RunContent(
        "src", "FlexFileUpload",  //path to SWF without ".swf" -- in this case, it's in the same directory
        "width", "660",
        "height", "350",
        "align", "middle",
        "id", "FlexFileUpload",
        "quality", "high",
        "bgcolor", "##ffffff",    //enter the bg color of your page so it will match while loading
        "name", "FlexFileUpload",
        "flashvars",'#variables.flashVars#', //this is where flashvar values are sent to the flash player
        "allowScriptAccess","sameDomain",
        "type", "application/x-shockwave-flash",
        "pluginspage", "http://www.adobe.com/go/getflashplayer"
    );
</script>
<noscript>
    <object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"
    id="FlexFileUpload" width="100%" height="100%"
    codebase="http://fpdownload.macromedia.com/get/flashplayer/current/swflash.cab">
        <param name="movie" value="FlexFileUpload.swf" />
        <param name="FlashVars" value="#variables.flashVars#" />
        <param name="quality" value="high" />
        <param name="bgcolor" value="##869ca7" />
        <param name="allowScriptAccess" value="sameDomain" />
        <embed src="FlexFileUpload.swf" 
            FlashVars="#variables.flashVars#"
            quality="high" bgcolor="##ffffff"
            width="660" height="350" name="FlexFileUpload" align="middle"
            play="true"
            loop="false"
            quality="high"
            allowScriptAccess="sameDomain"
            type="application/x-shockwave-flash"
            pluginspage="http://www.adobe.com/go/getflashplayer">
        </embed>
    </object>
</noscript>

There may be better ways of embedding the Flash/Flex widget on the page, but I'm still new to this. Feel free to enlighten me with cleaner, more elegant code.

Alternative Browser Support

As I mentioned in the table above, there is a bug in recent versions of the Flash player that prevents file uploads from working out of the box in Alternative (Non-IE) browsers. I could go into more detail of why, but unless you want to hear about Network Stacks (in which case, email me), I'll spare you.

The short version of the story is that the Flash player doesn't maintain your existing website session, so if an authenticated session is required to access the template that accepts the uploaded file(s), the web server refuses access, and thus uploads won't work. To get around this, you have to include some extra URL parameters when uploading the file so that the client's local machine will include certain cookies in the request, which will tell the web server to tie this request to your existing session, and allow the files to be uploaded.

In order for the flash player to include those URL parameters, you have to tell it what they are. (They differ per server technology. In ColdFusion with J2EE sessions enabled, it's jsessionid. In ColdFusion without J2EE sessions enabled, you need CFID and CFToken.) So for example, in SABRE -- the application that I've refactored this widget out of -- here are my flashvars. Pay special attention to variables.sessionParams. That is what we're using to work around the Flash upload bug.

<cfscript>
    variables.uploadDest = urlEncodedFormat("https://domain.com/path/to/upload_bulk_xml.cfm");
    variables.fileTypeFilter = urlEncodedFormat("*.xml");
    variables.fileTypeFilterName = urlEncodedFormat("XML Files (*.xml)");
    variables.uploadCompleteMessage = urlEncodedFormat("Upload complete!");
    variables.sessionParams = "";
    if (structKeyExists(session, "sessionid")){
    variables.sessionParams = urlEncodedFormat("jsessionid=#session.sessionid#");
    }
    variables.flashvars = "fileTypeFilter=#variables.fileTypeFilter#&fileTypeFilterName=#variables.fileTypeFilterName#";
    variables.flashvars &= "&adminUploadDestination=#variables.uploadDest#&uploadCompleteMessage=#variables.uploadCompleteMessage#";
    variables.flashvars &= "&callback=uploadComplete&title=SABRE File Upload&sessionParams=#variables.sessionParams#";
</cfscript>

Licensing

Now, I'm no expert on licensing, and generally I release my open source stuff under the Apache License. However, this application is heavily based on some code written by "Ryan Favro and New Media Team Inc." (no url provided). Their code was released under GPLv2, which I believe requires that modifications such as this one also be released under the same license. As such, this application and all supporting files are released under the GNU General Purpose License (GPL) version 2. You can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.

Download

The zip contains a working example that you can put on a ColdFusion server to see in action. You can also try a demo. Of course, all uploaded files will be automatically and immediately deleted, because you can't trust the internet -- so don't get any ideas.

The example also shows how to use the JavaScript callback on upload completion, if you want to use that to automate further action in your application.

Download the project at RIAForge

in Flex | My projects Posted 2009-04-08 01:00

30 responses:

Mark Aplet
Mark Aplet 2009-04-08 1:31 PM #
Awesome uploader Adam. I know I have needed this exact thing in the past and had to hack something together. This will make it much easier the next time.
Eric Cobb
Eric Cobb 2009-04-08 2:27 PM #
This is great! I already know where I'm going to use it.

Thanks!
Manfred Stanfield
Manfred Stanfield 2009-04-08 3:15 PM #
Great tool. Very easy to setup and use without a bunch of configuration just to get it to run as a demo.
Aaron West
Aaron West 2009-04-08 3:20 PM #
@Adam - It's totally doable to have error messages thrown from ColdFusion up to Flash on the client. This is true whether the Flash was built with the Flash Authoring tool or Flex.

We do this were I work all the time and we're heavily invested in Flash and ColdFusion (with a little Flex mixed in).

Our CFCATCH code calls error message APIs that e-mail the dev team, and our CFTHROW results in Flash receiving an error message from ColdFusion in the AMF response over Flash Remoting.

Then, we simply respond to the error in Flash. Our CFCATCH might look something like this (inside a CFC used for Flash Remoting calls):

<cfcatch type="any">
<cfscript>
errorCode = Application.someCFC.getErrorCode(cfcatch.type, cfcatch);
errorMessage = Application.someCFC.getErrorMessage(errorCode);
Application.someCFC.errorMailer(session.sessionID, "App with the error", errorCode, cfcatch);
</cfscript>
<cfthrow message="#errorMessage#">
</cfcatch>

Inside of Flash we use an event listener (llooking for an error response) and show display the error returned from ColdFusion to the end user.
John Gag
John Gag 2009-04-14 12:25 AM #
Great work. I am def. going to find a use for this. Thanks!
Dan Vega
Dan Vega 2009-04-14 8:58 AM #
I took a similar approach as you but instead of having the user add the flash vars I created a custom tag that encapsulates that. Check out my project, It has some pretty cool features. http://cfmu.riaforge.org
Adam
Adam 2009-04-14 9:02 AM #
Yeah, Dan, I found yours when I was setting mine up on RIAForge. :)
Gary Fenton
Gary Fenton 2009-04-14 11:41 AM #
This looks good. :-) Can it be used purely on a ColdFusion server? I have never used Flex before and aren't sure if I need to buy an extra software to run Flex based apps and gadgets like this one? Sorry if this is a FAQ.
Dan Vega
Dan Vega 2009-04-14 11:45 AM #
@Gary - Flex just compiles down to a swf file. Any browser that has the flash player installed can run a swf file.
Adam
Adam 2009-04-14 11:47 AM #
@Gary, and to build on what Dan said, the flash player sort of pretends to be a browser uploading the file(s)... so nothing is required server-side that wouldn't be required for a normal file upload.
Gary Fenton
Gary Fenton 2009-04-16 2:45 PM #
Sweet, I get it. :-) This well presented tool could be very, very handy. Great job! I just wish some of my clients' firewall policies didn't block Flash.
Aaron Greenlee
Aaron Greenlee 2009-04-23 4:26 PM #
I'm trying out the uploaded. Looks cool. One note on the documentation.

The attribute 'adminUploadDestination' seemed to be required by code; however, you list 'uploadDest' in your post and the application errors 'uploadDestionation'.

Thanks,

-A
Adam
Adam 2009-05-01 11:50 PM #
Thanks for the comment, Aaron. I'll see if I can get that sorted out soon. As it is, you should provide the variable named "adminUploadDestination". This will change (dropping the "admin" got missed in the refactoring), and the documentation will be updated to reflect the new name... eventually.

It is a little funny that the documentation says one thing, the error message says another, and the actual variable name it's expecting is something else entirely. Ha! :)
Brian
Brian 2009-07-15 3:14 PM #
Adam - is it possible to have a parameter to define how many files can be uploaded at once? For example, sometimes maybe we only want up to 5 files, or we could even specify 1 so we could use this anywhere we need a file upload, even if it's only a single file?
Adam
Adam 2009-07-15 3:18 PM #
Brian, that is not something that is currently supported. It shouldn't be too difficult for you to add, though! ;)
Brian
Brian 2009-07-15 3:41 PM #
I forgot to mention - I know nothing about flex. :)
Harry
Harry 2010-01-25 6:19 AM #
Zip file is destroyed. Downloaded it 3 times with the same result : "not a zip file". Could you please correct the problem?
Adam Tuttle
Adam Tuttle 2010-01-25 8:26 AM #
Harry, it works fine for me on 2 different machines. Try downloading with another browser or on another machine; or using a different program to unzip it.
Harry
Harry 2010-01-25 11:30 AM #
It didn't work with winzip and 7Zip and Universal Extractor. It finally worked with ZipGenius. That's one funny zip there.
Ryan
Ryan 2010-07-25 8:38 PM #
Great tool, i got this working with PHP, only issues i can see is once the uploads have complete, the browse button becomes disabled.
Work around - refresh the page!
Gary Fenton
Gary Fenton 2010-10-24 4:26 PM #
The example script I downloaded from RiaForge to my CF8 on Win2008 R2 server runs well on IE8 and IE6 with Flash 9 and 10. However, from Firefox 3.6 or Chrome or Safari on the same PCs when I click on Upload the swf presents an error:

[IOErrorEvent type="ioError" bubbles=false cancelable=false eventPhase=2 text="Error #2038"]

I can't think why IE has the upper hand and all other browsers cause an error to be shown. I've tried little and big files, IE uploads them all, the others error before any data is uploaded.

Any thoughts or suggestions would be most appreciated. Thanks in advance.
Adam
Adam 2010-10-24 4:55 PM #
Hi Gary, the section "Alternative Browser Support" (above) explains why this happens and how you can fix it.
Gary Fenton
Gary Fenton 2010-10-24 5:17 PM #
Hi Adam. After a lot of Googling I found the problem was down to non-IE browsers disallowing Flash to upload over SSL if a self-signed certificate is being used. (Which is what I use on my dev server) Thanks for your quick reply.
Kevin
Kevin 2010-12-23 12:13 AM #
I get this error after clicking the "Upload" button:

[IOErrorEvent type="ioError" bubbles=false cancelable=false eventPhase=2 text="Error #2038"]

Then, after I click OK on that error, I get this error:

[HTTPStatusEvent type="httpStatus" bubbles=false cancelable=false eventPhase=2 status=500 responseURL=null]

What have I done wrong?
Mike
Mike 2011-01-09 12:28 PM #
Very nice tool.
I am now debugging someone's implementation of this and I have a dumb question for you. How does the "#ExpandPath concept work? Where and how does it get set?
Thanks for your help.
Mike
Tommy Trucks
Tommy Trucks 2011-02-14 5:26 PM #
Hats off to you for this script! I have been looking at several different ones... and I must say this is the easiest and best.
Calvin
Calvin 2011-06-13 6:48 AM #
Any of you have a JSP version? I need to implement this function on JBoss

Thanks,

Calvin
kevin
kevin 2011-09-27 7:14 AM #
is it possible to re-enable the browse button after an upload - without having to refresh the page.
Adam
Adam 2011-09-27 10:03 AM #
Mike, expandPath is a ColdFusion function that translates a relative path into a system absolute path. So expandPath('a/b/c.jpg') might equate to "c:\inetpub\wwwroot\a\b\c.jpg", depending on the server configuration. See the documentation here for more information: http://tinyurl.com/6jkfmsp

Calvin, there's nothing stopping you from implementing the widget against JBoss/jsp. It's just flex (flash), posting to a form. The ColdFusion just makes it easier to pass in the relevant input parameters. But you could do it manually.

Kevin, the version currently available does not support that, no. But it's open source. :)

Leave a comment:

Leave this field empty: