fusiongrokker

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

Posted in My projects | Flex | 21 Responses  

21 responses:





Leave this field empty: