Addons to Flex Single File Uploader
Not to make too much of a habit of it, but here is another post that features minor, though in many cases needful, changes to handy example components. In this case, the target is a single file upload component. You can get a pretty decent base component in a package of actionscript 3 samples from Adobe.
Contained in this handy zip of samples is the FileIO folder. We can get single file uploading up and going rather quickly by using the com.example.programmingas3.fileio.FileUpload (FileUpload.as). The base code looks like this:
package com.example.programmingas3.fileio {
import flash.events.*;
import flash.net.FileReference;
import flash.net.URLRequest;
import mx.controls.Button;
import mx.controls.ProgressBar;
import mx.core.UIComponent;
public class FileUpload extends UIComponent {
// Hard-code the URL of the remote upload script.
private const UPLOAD_URL:String = "http://www.yourdomain.com/upload_script.cfm";
private var fr:FileReference;
// Define reference to the upload ProgressBar component.
private var pb:ProgressBar;
// Define reference to the "Cancel" button which will immediately stop the upload in progress.
private var btn:Button;
public function FileUpload() {
}
/**
* Set references to the components, and add listeners for the SELECT,
* OPEN, PROGRESS, and COMPLETE events.
*/
public function init(pb:ProgressBar, btn:Button):void {
// Set up the references to the progress bar and cancel button, which are passed from the calling script.
this.pb = pb;
this.btn = btn;
fr = new FileReference();
fr.addEventListener(Event.SELECT, selectHandler);
fr.addEventListener(Event.OPEN, openHandler);
fr.addEventListener(ProgressEvent.PROGRESS, progressHandler);
fr.addEventListener(Event.COMPLETE, completeHandler);
}
/**
* Immediately cancel the upload in progress and disable the cancel button.
*/
public function cancelUpload():void {
fr.cancel();
pb.label = "UPLOAD CANCELLED";
btn.enabled = false;
}
/**
* Launch the browse dialog box which allows the user to select a file to upload to the server.
*/
public function startUpload():void {
fr.browse();
}
/**
* Begin uploading the file specified in the UPLOAD_URL constant.
*/
private function selectHandler(event:Event):void {
var request:URLRequest = new URLRequest();
request.url = UPLOAD_URL;
fr.upload(request);
}
/**
* When the OPEN event has dispatched, change the progress bar's label
* and enable the "Cancel" button, which allows the user to abort the
* upload operation.
*/
private function openHandler(event:Event):void {
pb.label = "UPLOADING";
btn.enabled = true;
}
/**
* While the file is uploading, update the progress bar's status and label.
*/
private function progressHandler(event:ProgressEvent):void {
pb.label = "UPLOADING %3%%";
pb.setProgress(event.bytesLoaded, event.bytesTotal);
}
/**
* Once the upload has completed, change the progress bar's label and
* disable the "Cancel" button since the upload is already completed.
*/
private function completeHandler(event:Event):void {
pb.label = "UPLOADING COMPLETE";
pb.setProgress(0, 100);
btn.enabled = false;
}
}
}
The example they provide for usage, less some file downloading extraneous stuff:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:example="com.example.programmingas3.fileio.*" xmlns="*" horizontalAlign="center">
<mx:Label id="title" text="File I/O Example" fontSize="24" fontStyle="bold" />
<mx:Text id="subtitle" text="From Programming ActionScript 3.0, Chapter 22: Networking and communication" width="100%" textAlign="center" fontSize="12" />
<example:FileUpload id="fileUpload" creationComplete="fileUpload.init(uploadProgress, cancelUpload);" />
<mx:HBox>
<mx:Panel title="Upload File" paddingTop="10" paddingBottom="10" paddingLeft="10" paddingRight="10">
<mx:ProgressBar id="uploadProgress" label="" mode="manual" />
<mx:ControlBar horizontalAlign="right">
<mx:Button id="startUpload" label="Upload..." click="fileUpload.startUpload();" />
<mx:Button id="cancelUpload" label="Cancel" click="fileUpload.cancelUpload();" enabled="false" />
</mx:ControlBar>
</mx:Panel>
</mx:HBox>
</mx:Application>
So, anyway, the above does the trick and you can use it to easily upload a single file. Multi-upload is all the rage, and I have had many uses for it, but, sometimes, a single upload scenario does the trick (at the very least,it requires a lot less real estate to support it). However, and this is the point of the post, there are several improvement opportunities. The ones that interested me are:
- Exposing the upload URL through a setter
- Adding support for specifying file type filters
- Receiving information back from the server after upload
Exposing the Upload URL
This'll be a quickie as creating a setter is a no brainer. For some reason, the good folks who create upload components like to store the target upload url as a constant. For a one-off, this is fine, but if you want a reusable component, the setter makes more sense.package com.example.programmingas3.fileio {
import flash.events.*;
import flash.net.FileReference;
import flash.net.URLRequest;
import mx.controls.Button;
import mx.controls.ProgressBar;
import mx.core.UIComponent;
public class FileUpload extends UIComponent {
// CONVERTED TO VARIABLE, CHANGED NAME TO AVOID CONFUSION
private var _upload_url:String = "http://www.yourdomain.com/upload_script.cfm";
private var fr:FileReference;
// Define reference to the upload ProgressBar component.
private var pb:ProgressBar;
// Define reference to the "Cancel" button which will immediately stop the upload in progress.
private var btn:Button;
public function FileUpload() {
}
/**
* Set references to the components, and add listeners for the SELECT,
* OPEN, PROGRESS, and COMPLETE events.
*/
public function init(pb:ProgressBar, btn:Button):void {
// Set up the references to the progress bar and cancel button, which are passed from the calling script.
this.pb = pb;
this.btn = btn;
fr = new FileReference();
fr.addEventListener(Event.SELECT, selectHandler);
fr.addEventListener(Event.OPEN, openHandler);
fr.addEventListener(ProgressEvent.PROGRESS, progressHandler);
fr.addEventListener(Event.COMPLETE, completeHandler);
}
/**
* Immediately cancel the upload in progress and disable the cancel button.
*/
public function cancelUpload():void {
fr.cancel();
pb.label = "UPLOAD CANCELLED";
btn.enabled = false;
}
/**
* Launch the browse dialog box which allows the user to select a file to upload to the server.
*/
public function startUpload():void {
fr.browse();
}
/**
* Begin uploading the file specified in the _upload_url variable.
*/
private function selectHandler(event:Event):void {
var request:URLRequest = new URLRequest();
request.url = _upload_url; // CHANGED TO REFLECT VARIABLE NAME
fr.upload(request);
}
/**
* When the OPEN event has dispatched, change the progress bar's label
* and enable the "Cancel" button, which allows the user to abort the
* upload operation.
*/
private function openHandler(event:Event):void {
pb.label = "UPLOADING";
btn.enabled = true;
}
/**
* While the file is uploading, update the progress bar's status and label.
*/
private function progressHandler(event:ProgressEvent):void {
pb.label = "UPLOADING %3%%";
pb.setProgress(event.bytesLoaded, event.bytesTotal);
}
/**
* Once the upload has completed, change the progress bar's label and
* disable the "Cancel" button since the upload is already completed.
*/
private function completeHandler(event:Event):void {
pb.label = "UPLOADING COMPLETE";
pb.setProgress(0, 100);
btn.enabled = false;
}
// PUBLIC FACING GETTER
public function set uploadURL(value:String):void {
_upload_url = value;
}
}
}
Again, it's a setter ... moving right along.
Adding FileFilters
FileFilters are handy in several situations as they enable you to limit the viewable files based on their file extensions. This can be helpful for guiding the user to the desired file to upload, but I wouldn't suggest using filtering for any security scheme - make sure that you have something server side.
Ok, the thing about filters is that you can have as many as you need, so we need to accomodate that while providing an interface for setting them. As a filter requires you to set a description string as well as an extension string. The easiest way to pass this information is as an array of objects. Using the array as the transport vector allows for a nice consistent way to inject as many filters as needed. So the goal would be, for a FileUpload instance with an id of "fileUpload", to look something like:
var _uploadFilters:Array = [];
_uploadFilters[0] = {};
_uploadFilters[0].description = "List Files (*.csv,*.txt,*.xls*,*mdb*)";
_uploadFilters[0].extensions = "*.csv;*.txt;*.xls*;*mdb*";
_uploadFilters[1] = {};
_uploadFilters[1].description = "Other Files (*.*)";
_uploadFilters[1].extensions = "*.*";
fileUpload.fileFilter = _uploadFilters;
So, now all we need to do is to create a "fileFilter" setter and then make sure that they get used:
package com.example.programmingas3.fileio {
import flash.events.*;
import flash.net.FileReference;
import flash.net.URLRequest;
import flash.net.FileFilter; // NEED THIS LIBRARY FOR FILTERS
import mx.controls.Button;
import mx.controls.ProgressBar;
import mx.core.UIComponent;
public class FileUpload extends UIComponent {
// CONVERTED TO VARIABLE, CHANGED NAME TO AVOID CONFUSION
private var _upload_url:String = "http://www.yourdomain.com/upload_script.cfm";
private var fr:FileReference;
// Define reference to the upload ProgressBar component.
private var pb:ProgressBar;
// Define reference to the "Cancel" button which will immediately stop the upload in progress.
private var btn:Button;
private var _filters:Array; // INTERNAL FILTER REFERENCE
public function FileUpload() {
_filters = []; // ENSURE THAT THERE IS AT LEAST AN EMPTY ARRAY
}
/**
* Set references to the components, and add listeners for the SELECT,
* OPEN, PROGRESS, and COMPLETE events.
*/
public function init(pb:ProgressBar, btn:Button):void {
// Set up the references to the progress bar and cancel button, which are passed from the calling script.
this.pb = pb;
this.btn = btn;
fr = new FileReference();
fr.addEventListener(Event.SELECT, selectHandler);
fr.addEventListener(Event.OPEN, openHandler);
fr.addEventListener(ProgressEvent.PROGRESS, progressHandler);
fr.addEventListener(Event.COMPLETE, completeHandler);
}
/**
* Immediately cancel the upload in progress and disable the cancel button.
*/
public function cancelUpload():void {
fr.cancel();
pb.label = "UPLOAD CANCELLED";
btn.enabled = false;
}
/**
* Launch the browse dialog box which allows the user to select a file to upload to the server.
*/
public function startUpload():void {
fr.browse(_filters); // ADDING THE FILTERS TO THE SYSTEM FILE DIALOG
}
/**
* Begin uploading the file specified in the _upload_url variable.
*/
private function selectHandler(event:Event):void {
var request:URLRequest = new URLRequest();
request.url = _upload_url; // CHANGED TO REFLECT VARIABLE NAME
fr.upload(request);
}
/**
* When the OPEN event has dispatched, change the progress bar's label
* and enable the "Cancel" button, which allows the user to abort the
* upload operation.
*/
private function openHandler(event:Event):void {
pb.label = "UPLOADING";
btn.enabled = true;
}
/**
* While the file is uploading, update the progress bar's status and label.
*/
private function progressHandler(event:ProgressEvent):void {
pb.label = "UPLOADING %3%%";
pb.setProgress(event.bytesLoaded, event.bytesTotal);
}
/**
* Once the upload has completed, change the progress bar's label and
* disable the "Cancel" button since the upload is already completed.
*/
private function completeHandler(event:Event):void {
pb.label = "UPLOADING COMPLETE";
pb.setProgress(0, 100);
btn.enabled = false;
}
// Public facing getter
public function set uploadURL(value:String):void {
_upload_url = value;
}
// THE SETTER FOR THE FILEFILTER
public function set fileFilter(filters:Array):void {
// want to receive an array of objects
// where each object has two members
// Member 1: description
// Member 2: extensions
_filters = []; // resetting to ensure that we start from scratch
for (var f:int=0;f<filters.length;f++) {
var tempFilter:FileFilter = new FileFilter(filters[f].description,filters[f].extensions);
_filters.push(tempFilter);
}
}
}
}
So, quick inventory:
- Import the flash.net.FileFilter library
- Private array to filters
- Initialization in the constructor
- Setter for the array that also converts array items into FileFilters
- Injection of filters when the FileReference.browse() method is called
And lastly ...
Getting Post-Upload Information
Perhaps the only useful tidbit of the three, getting information back from the server. For this guy, we need to listen for the flash.events.DataEvent.UPLOAD_COMPLETE_DATA event. Of course, the server has to actually return data for this guy to work, but I'm sure you'll work that out.
Putting the cart before the horse:
package com.example.programmingas3.fileio {
import flash.events.*;
import flash.net.FileReference;
import flash.net.URLRequest;
import flash.net.FileFilter;
import mx.controls.Button;
import mx.controls.ProgressBar;
import mx.core.UIComponent;
[Event(name="uploadDataReceived", type="flash.events.Event")]
public class FileUpload extends UIComponent {
// EVENT CONSTANT
public static const EVENT_DATA_RECEIVED:String = "uploadDataReceived";
private var _upload_url:String = "http://www.yourdomain.com/upload_script.cfm";
private var fr:FileReference;
// Define reference to the upload ProgressBar component.
private var pb:ProgressBar;
// Define reference to the "Cancel" button which will immediately stop the upload in progress.
private var btn:Button;
private var _filters:Array; // Internal filter reference
public function FileUpload() {
_filters = []; // Ensure that there is at least an empty array
}
/**
* Set references to the components, and add listeners for the SELECT,
* OPEN, PROGRESS, and COMPLETE events.
*/
public function init(pb:ProgressBar, btn:Button):void {
// Set up the references to the progress bar and cancel button, which are passed from the calling script.
this.pb = pb;
this.btn = btn;
fr = new FileReference();
fr.addEventListener(Event.SELECT, selectHandler);
fr.addEventListener(Event.OPEN, openHandler);
fr.addEventListener(ProgressEvent.PROGRESS, progressHandler);
fr.addEventListener(Event.COMPLETE, completeHandler);
// UPLOAD_COMPLETE_DATA listener
fr.addEventListener(flash.events.DataEvent.UPLOAD_COMPLETE_DATA,uploadCompleteData);
}
/**
* Immediately cancel the upload in progress and disable the cancel button.
*/
public function cancelUpload():void {
fr.cancel();
pb.label = "UPLOAD CANCELLED";
btn.enabled = false;
}
/**
* Launch the browse dialog box which allows the user to select a file to upload to the server.
*/
public function startUpload():void {
fr.browse(_filters); // Adding the filters to the system file dialog
}
/**
* Begin uploading the file specified in the _upload_url variable.
*/
private function selectHandler(event:Event):void {
var request:URLRequest = new URLRequest();
request.url = _upload_url;
fr.upload(request);
}
/**
* When the OPEN event has dispatched, change the progress bar's label
* and enable the "Cancel" button, which allows the user to abort the
* upload operation.
*/
private function openHandler(event:Event):void {
pb.label = "UPLOADING";
btn.enabled = true;
}
/**
* While the file is uploading, update the progress bar's status and label.
*/
private function progressHandler(event:ProgressEvent):void {
pb.label = "UPLOADING %3%%";
pb.setProgress(event.bytesLoaded, event.bytesTotal);
}
/**
* Once the upload has completed, change the progress bar's label and
* disable the "Cancel" button since the upload is already completed.
*/
private function completeHandler(event:Event):void {
pb.label = "UPLOADING COMPLETE";
pb.setProgress(0, 100);
btn.enabled = false;
}
// Public facing getter
public function set uploadURL(value:String):void {
_upload_url = value;
}
// The setter for the filefilter
public function set fileFilter(filters:Array):void {
// want to receive an array of objects
// where each object has two members
// Member 1: description
// Member 2: extensions
_filters = []; // resetting to ensure that we start from scratch
for (var f:int=0;f<filters.length;f++) {
var tempFilter:FileFilter = new FileFilter(filters[f].description,filters[f].extensions);
_filters.push(tempFilter);
}
}
private function uploadCompleteData(event:DataEvent):void {
// do something useful here
dispatchEvent(new Event(EVENT_DATA_RECEIVED));
}
}
}
So we are catching one event, doing whatever you want to do with it and then dispatching another event to let the outside world know of the updates. The list of changes:
Catch The Event
- Normally add the flash.events.DataEvent.UPLOAD_COMPLETE_DATA event, but it got added with the import flash.events.*
- Add the event listener to the FileReference instance.
- Add the method to catch the event
Throwing The New Event
- Add the meta information for the event to be thrown; this let's Flex Builder find it
- Create a constant for the event
- Throw the event when the UPLOAD_COMPLETE_DATA event is fired
So, voila, a potentially more useful single file uploader.


There are no comments for this entry.
[Add Comment]