Skip to content

Instantly share code, notes, and snippets.

@davemo
Created January 12, 2010 16:45
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save davemo/275349 to your computer and use it in GitHub Desktop.
Save davemo/275349 to your computer and use it in GitHub Desktop.
package
{
import com.yahoo.yui.YUIAdapter;
import flash.display.Loader;
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Sprite;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import flash.events.DataEvent;
import flash.events.Event;
import flash.events.FocusEvent;
import flash.events.HTTPStatusEvent;
import flash.events.IOErrorEvent;
import flash.events.KeyboardEvent;
import flash.events.MouseEvent;
import flash.events.ProgressEvent;
import flash.events.SecurityErrorEvent;
import flash.external.ExternalInterface;
import flash.net.FileFilter;
import flash.net.FileReference;
import flash.net.FileReferenceList;
import flash.net.URLRequest;
import flash.net.URLVariables;
import flash.net.URLLoader;
import flash.net.URLLoaderDataFormat;
import flash.ui.Keyboard;
import flash.utils.Dictionary;
import flash.utils.ByteArray;
import flash.utils.setTimeout;
import mx.core.Application;
import flash.geom.Matrix;
import JPEGAsyncCompleteEvent;
import JPEGAsyncEncoder;
[SWF(backgroundColor=0xffffff)]
/**
* The base Uploader class for YUI's Flash-based file uploader.
*
* @author Allen Rabinovich
*/
public class Uploader extends YUIAdapter {
//--------------------------------------
// Constructor
//--------------------------------------
public function Uploader()
{
super();
}
//--------------------------------------------------------------------------
//
// Variables
//
//--------------------------------------------------------------------------
private var allowMultiple:Boolean = false;
private var allowLog:Boolean = false;
private var filterArray:Array;
private var fileDataList:Object;
private var fileRefList:Object;
private var fileIDList:Dictionary;
private var fileIDCounter:Number;
private var filesToUpload:Array;
private var singleFile:FileReference;
private var multipleFiles:FileReferenceList;
private var loaded:Boolean = true;
/**
* Determines how many files will be uploaded simultaneously
*
* @see setSimUploadLimit
* @langversion 3.0
* @playerversion Flash 9.0.28.0
*/
public var simultaneousUploadLimit:Number = 2;
// Track the number of current upload threads
private var currentUploadThreads:Number = 0;
// How the uploader is rendered, either "button" or "transparent"
private var renderType:String;
// The Sprite containing the rendered UI.
private var buttonSprite:Sprite = new Sprite();
// The skin for the button, if "button" renderType is used.
private var buttonSkin:Loader = new Loader();
// Height and width for the button
private var buttonHeight:Number;
private var buttonWidth:Number;
//--------------------------------------
// Public Methods
//--------------------------------------
/**
* Sets the number of simultaneous file uploads possible.
* The maximum is 5.
* @param numberOfUploads Number of simultaneous uploads, no fewer than 1
* and no larger than 5.
*/
public function setSimUploadLimit (simUploadLimit:int) : void {
if (simUploadLimit <= 1) {
this.simultaneousUploadLimit = 1;
}
else if (simUploadLimit >= 5) {
this.simultaneousUploadLimit = 5;
}
else {
this.simultaneousUploadLimit = simUploadLimit;
}
}
/**
* Sets a list of file type filters for the "Open File(s)" dialog.
*
* @param newFilterArray An array of sets of key-value pairs of the form
* {extensions: extensionString, description: descriptionString, macType: macTypeString [optional]}
* The extension string is a semicolon-delimited list of elements of the form "*.xxx",
* e.g. "*.jpg;*.gif;*.png".
*/
public function setFileFilters(newFilterArray:Array) : void {
filterArray = processFileFilterObjects(newFilterArray);
if (allowLog) {
var logString:String = "File filters have been set to the following: \n";
for each (var ff:FileFilter in filterArray) {
logString += ff.extension + ": " + ff.description + "\n";
}
logMessage(logString);
}
}
/**
* Sets a flag allowing logging in Flash trace and Yahoo logger.
*
* @param allowLogging Whether to allow log messages.
*
*/
public function setAllowLogging(allowLogging:Boolean) : void {
this.allowLog = allowLogging;
logMessage("Logging has been turned " + (allowLog ? "on." : "off."));
}
/**
* Sets a flag allowing multiple file selection in the "Browse" dialog.
*
* @param allowMultiple Whether to allow multiple file selection.
*
*/
public function setAllowMultipleFiles(allowMultipleFiles:Boolean) : void {
this.allowMultiple = allowMultipleFiles;
logMessage("Multiple file upload has been turned " + (allowMultiple ? "on." : "off."));
}
/**
* Triggers a prompt for the user to browse their file system to select
* files to be uploaded.
*
* @param allowMultiple Whether to allow the user to select more than
* one file
*
* @param filterArray An array of filter objects, each with <code>
* description</code>, and <code>extensions</code> properties which
* determine which files the user is allowed to select
*/
private function browse(allowMultiple:Boolean = false, filterArray:Array = null):void {
if(!allowMultiple) {
logMessage("Browsing for a single file.")
singleFile = new FileReference();
singleFile.addEventListener(Event.SELECT, singleFileSelected);
if(filterArray) {
singleFile.browse(filterArray);
}
else {
singleFile.browse();
}
}
else {
logMessage("Browsing for one or more files.")
multipleFiles = new FileReferenceList();
multipleFiles.addEventListener(Event.SELECT, multipleFilesSelected);
if(filterArray) {
multipleFiles.browse(filterArray);
}
else {
multipleFiles.browse();
}
}
}
/**
* Removes the file from the set to be uploaded
*
* @param fileID The ID of the file to be removed
*/
public function removeFile(fileID:String):Object {
// TODO: Do we need to remove the item from filesToUpload also?
delete fileDataList[fileID];
delete fileRefList[fileID];
return fileDataList;
}
public function enable () : void {
if (renderType == "button") {
this.addEventListener(MouseEvent.ROLL_OVER, buttonMouseOver);
this.addEventListener(MouseEvent.ROLL_OUT, buttonMouseOut);
this.addEventListener(MouseEvent.MOUSE_DOWN, buttonMouseDown);
this.addEventListener(MouseEvent.MOUSE_UP, buttonMouseUp);
this.addEventListener(MouseEvent.CLICK, handleMouseClick);
buttonSkin.y = 0;
}
else {
this.addEventListener(MouseEvent.CLICK, handleMouseClick);
this.addEventListener(MouseEvent.CLICK, transparentClick);
this.addEventListener(MouseEvent.MOUSE_DOWN, transparentDown);
this.addEventListener(MouseEvent.MOUSE_UP, transparentUp);
this.addEventListener(MouseEvent.ROLL_OVER, transparentRollOver);
this.addEventListener(MouseEvent.ROLL_OUT, transparentRollOut);
}
logMessage("Uploader UI has been enabled.");
}
public function disable () : void {
if (renderType == "button") {
this.removeEventListener(MouseEvent.ROLL_OVER, buttonMouseOver);
this.removeEventListener(MouseEvent.ROLL_OUT, buttonMouseOut);
this.removeEventListener(MouseEvent.MOUSE_DOWN, buttonMouseDown);
this.removeEventListener(MouseEvent.MOUSE_UP, buttonMouseUp);
this.removeEventListener(MouseEvent.CLICK, handleMouseClick);
buttonSkin.y = -3*buttonHeight;
}
else {
this.removeEventListener(MouseEvent.CLICK, handleMouseClick);
this.removeEventListener(MouseEvent.CLICK, transparentClick);
this.removeEventListener(MouseEvent.MOUSE_DOWN, transparentDown);
this.removeEventListener(MouseEvent.MOUSE_UP, transparentUp);
this.removeEventListener(MouseEvent.ROLL_OVER, transparentRollOver);
this.removeEventListener(MouseEvent.ROLL_OUT, transparentRollOut);
}
logMessage("Uploader UI has been disabled.");
}
/**
* Clears the set of files that had been selected for upload
*/
public function clearFileList():Boolean {
// TODO: Remove event listeners (or weak references?)
filesToUpload = [];
fileDataList = new Object();
fileRefList = new Object();
fileIDList = new Dictionary();
fileIDCounter = 0;
logMessage("The file list has been cleared.");
return true;
}
/**
* Uploads a file corresponding to a specified ID to a specified path where a script handles writing to the server.
*
* @param fileID The ID of the file to be uploaded
* @param url The path to the serverside script
* @param method The HTTP submission method. Possible values are "GET" and "POST"
* @param vars An object containing variables to be sent along with the request
* @param fieldName The field name that precedes the file data in the upload POST operation.
* The uploadDataFieldName value must be non-null and a non-empty String.
* @param headers An object containing variables that should be set as headers in the POST request. The following header names
* cannot be used:
* <code>
* Accept-Charset, Accept-Encoding, Accept-Ranges, Age, Allow, Allowed, Authorization, Charge-To, Connect, Connection,
* Content-Length, Content-Location, Content-Range, Cookie, Date, Delete, ETag, Expect, Get, Head, Host, Keep-Alive,
* Last-Modified, Location, Max-Forwards, Options, Post, Proxy-Authenticate, Proxy-Authorization, Proxy-Connection,
* Public, Put, Range, Referer, Request-Range, Retry-After, Server, TE, Trace, Trailer, Transfer-Encoding, Upgrade,
* URI, User-Agent, Vary, Via, Warning, WWW-Authenticate, x-flash-version.
* </code>
*/
public function upload(fileID:String, url:String, method:String = "GET", vars:Object = null, fieldName:String = "Filedata"):void {
// null checking in the params is not working correctly
filesToUpload = [];
if(isEmptyString(method)) {
method = "GET";
}
if(isEmptyString(fieldName)) {
fieldName = "Filedata";
}
var request:URLRequest = formURLRequest(url, method, vars);
var fr:FileReference = fileRefList[fileID];
this.currentUploadThreads++;
fr.upload(request,fieldName);
}
/**
* Uploads all files to a specified path where a script handles writing to the server.
*
* @param fileID The ID of the file to be uploaded
* @param url The path to the serverside script
* @param method The HTTP submission method. Possible values are "GET" and "POST"
* @param vars An object containing data to be sent along with the request
* @param fieldName The field name that precedes the file data in the upload POST operation. The uploadDataFieldName value must be non-null and a non-empty String.
* @param headers An object containing variables that should be set as headers in the POST request. The following header names
* cannot be used:
* <code>
* Accept-Charset, Accept-Encoding, Accept-Ranges, Age, Allow, Allowed, Authorization, Charge-To, Connect, Connection,
* Content-Length, Content-Location, Content-Range, Cookie, Date, Delete, ETag, Expect, Get, Head, Host, Keep-Alive,
* Last-Modified, Location, Max-Forwards, Options, Post, Proxy-Authenticate, Proxy-Authorization, Proxy-Connection,
* Public, Put, Range, Referer, Request-Range, Retry-After, Server, TE, Trace, Trailer, Transfer-Encoding, Upgrade,
* URI, User-Agent, Vary, Via, Warning, WWW-Authenticate, x-flash-version.
* </code>
*/
public function uploadAll(url:String, method:String = "GET", vars:Object = null, fieldName:String = "Filedata", headers:Object = null):void {
if(isEmptyString(method)) {
method = "GET";
}
if(isEmptyString(fieldName)) {
fieldName = "Filedata";
}
var request:URLRequest = formURLRequest(url, method, vars);
filesToUpload = [];
for each(var fr:FileReference in fileRefList) {
queueForUpload(fr, request, fieldName);
}
processQueue();
}
/**
* Cancels either an upload of the file corresponding to a given fileID, or in the absence of the specified fileID, all active files being uploaded.
*
* @param fileID The ID of the file to be uploaded
*/
public function cancel(fileID:String = null):void {
logMessage("Canceling upload");
if (fileID == null) { // cancel all files
for each (var item:FileReference in fileRefList) {
item.cancel();
}
this.currentUploadThreads = 0;
filesToUpload = [];
}
else { // cancel specified file
var fr:FileReference = fileRefList[fileID];
if (this.currentUploadThreads > 0)
this.currentUploadThreads--;
fr.cancel();
}
}
/*
Events
-------------------------------
mouseDown - fires when the mouse button is pressed over uploader
mouseUp - fires when the mouse button is released over uploader
rollOver - fires when the mouse rolls over the uploader
rollOut - fires when the mouse rolls out of the uploader
click - fires when the uploader is clicked
fileSelect - fires when the user selects one or more files (after browse is called). Passes the array of currently selected files (if prior browse calls were made and clearFileList hasn't been called, all files the user has ever selected will be returned), along with all information available about them (name, size, type, creationDate, modificationDate, creator).
uploadStart - fires when a file starts uploading. Passes a file id for identifying the file.
uploadProgress - fires when a file upload reports progress. Passes the file id, as well as bytesUploaded and bytesTotal for the given file.
uploadComplete - fires when a file upload is completed successfully and passes the corresponding file id.
uploadCompleteData - fires when data is received from the server after upload and passes the corresponding file id and the said data.
uploadError - fires when an error occurs during download. Passes the id of the file that was being uploaded and an error type.
*/
private function transparentDown (event:MouseEvent) : void {
logMessage("Mouse down on the uploader.");
var newEvent:Object = new Object();
newEvent.type = "mouseDown";
super.dispatchEventToJavaScript(newEvent);
}
private function transparentUp (event:MouseEvent) : void {
logMessage("Mouse up on the uploader.");
var newEvent:Object = new Object();
newEvent.type = "mouseUp";
super.dispatchEventToJavaScript(newEvent);
}
private function transparentRollOver (event:MouseEvent) : void {
logMessage("Mouse rolled over the uploader.");
var newEvent:Object = new Object();
newEvent.type = "rollOver";
super.dispatchEventToJavaScript(newEvent);
}
private function transparentRollOut (event:MouseEvent) : void {
logMessage("Mouse rolled out the uploader.");
var newEvent:Object = new Object();
newEvent.type = "rollOut";
super.dispatchEventToJavaScript(newEvent);
}
private function transparentClick (event:MouseEvent) : void {
logMessage("Mouse clicked on the uploader.");
var newEvent:Object = new Object();
newEvent.type = "click";
super.dispatchEventToJavaScript(newEvent);
}
private function uploadStart (event:Event) : void {
//logMessage("Started upload for " + fileIDList[event.target]);
var newEvent:Object = new Object();
newEvent.id = 4;//fileIDList[event.target];
newEvent.type = "uploadStart";
super.dispatchEventToJavaScript(newEvent);
}
private function uploadProgress (event:ProgressEvent) : void {
//logMessage("Progress for " + fileIDList[event.target] + ": " + event.bytesLoaded.toString() + " / " + event.bytesTotal.toString());
var newEvent:Object = new Object();
newEvent.id = 4;//fileIDList[event.target];
newEvent.bytesLoaded = event.bytesLoaded;
newEvent.bytesTotal = event.bytesTotal;
newEvent.type = "uploadProgress"
super.dispatchEventToJavaScript(newEvent);
}
private function uploadComplete (event:Event) : void {
//logMessage("Upload complete for " + fileIDList[event.target]);
var newEvent:Object = new Object();
newEvent.id = 4;//fileIDList[event.target];
newEvent.type = "uploadComplete"
super.dispatchEventToJavaScript(newEvent);
this.currentUploadThreads--;
// get next off of queue:
if(filesToUpload.length > 0) {
processQueue();
}
}
private function uploadCompleteData (event:DataEvent) : void {
logMessage("Got data back for " + fileIDList[event.target] + ": ");
logMessage(event.data);
var newEvent:Object = new Object();
newEvent.id = fileIDList[event.target];
newEvent.data = event.data;
newEvent.type = "uploadCompleteData"
super.dispatchEventToJavaScript(newEvent);
}
private function uploadCancel (event:Event) : void {
logMessage("Canceled upload for " + fileIDList[event.target]);
var newEvent:Object = new Object();
newEvent.id = fileIDList[event.target];
newEvent.type = "uploadCancel";
super.dispatchEventToJavaScript(newEvent);
}
private function uploadError (event:Event) : void {
var newEvent:Object = {};
if (event is HTTPStatusEvent) {
var myev:HTTPStatusEvent = event as HTTPStatusEvent;
newEvent.status = myev.status;
logMessage("HTTP status error for " + fileIDList[event.target] + ": " + myev.status);
}
else if (event is IOErrorEvent) {
newEvent.status = event.toString();
logMessage("IO error for " + fileIDList[event.target] + ". Likely causes are problems with Internet connection or server misconfiguration.");
}
else if (event is SecurityErrorEvent) {
newEvent.status = event.toString();
logMessage("Security error for " + fileIDList[event.target]);
}
newEvent.type = "uploadError";
newEvent.id = fileIDList[event.target];
super.dispatchEventToJavaScript(newEvent);
// get next off of queue:
if(filesToUpload.length > 0) {
processQueue();
}
}
// Fired when the user selects a single file
private function singleFileSelected(event:Event):void {
this.clearFileList();
addFile(event.target as FileReference);
processSelection();
}
// Fired when the user selects multiple files
private function multipleFilesSelected(event:Event):void {
var currentFRL:FileReferenceList = multipleFiles;
for each (var currentFR:FileReference in currentFRL.fileList) {
addFile(currentFR);
}
processSelection();
}
private function renderAsButton (buttonSkinSprite:String) : void {
buttonSkin.load(new URLRequest(buttonSkinSprite));
var _this:Uploader = this;
var initLoader:Function = function (event:Event) : void {
buttonSprite.addChild(buttonSkin);
buttonHeight = buttonSkin.height/4;
buttonWidth = buttonSkin.width;
var buttonMask:Sprite = new Sprite();
buttonMask.graphics.beginFill(0x000000,1);
buttonMask.graphics.drawRect(0,0,buttonWidth,buttonHeight);
buttonMask.graphics.endFill();
_this.addChild(buttonMask);
buttonSprite.mask = buttonMask;
function buttonStageResize (evt:Event) : void {
buttonSprite.width = buttonSprite.stage.stageWidth;
buttonSprite.height = buttonSprite.stage.stageHeight*4;
buttonMask.width = _this.stage.stageWidth;
buttonMask.height = _this.stage.stageHeight;
};
buttonSprite.width = _this.stage.stageWidth;
buttonSprite.height = _this.stage.stageHeight*4;
buttonMask.width = _this.stage.stageWidth;
buttonMask.height = _this.stage.stageHeight;
_this.stage.scaleMode = StageScaleMode.NO_SCALE;
_this.stage.align = StageAlign.TOP_LEFT;
_this.stage.tabChildren = false;
_this.stage.addEventListener(Event.RESIZE, buttonStageResize);
_this.addEventListener(MouseEvent.ROLL_OVER, buttonMouseOver);
_this.addEventListener(MouseEvent.ROLL_OUT, buttonMouseOut);
_this.addEventListener(MouseEvent.MOUSE_DOWN, buttonMouseDown);
_this.addEventListener(MouseEvent.MOUSE_UP, buttonMouseUp);
_this.addEventListener(MouseEvent.CLICK, handleMouseClick);
_this.stage.addEventListener(KeyboardEvent.KEY_DOWN, handleKeyDown);
_this.stage.addEventListener(KeyboardEvent.KEY_UP, handleKeyUp);
_this.addChild(buttonSprite);
}
var errorLoader:Function = function (event:IOErrorEvent) : void {
renderAsTransparent();
}
buttonSkin.contentLoaderInfo.addEventListener(Event.COMPLETE, initLoader);
buttonSkin.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, errorLoader);
}
private function buttonMouseOver (event:MouseEvent) : void {
buttonSkin.y = -1*buttonHeight;
}
private function buttonMouseOut (event:MouseEvent) : void {
buttonSkin.y = 0;
}
private function buttonMouseDown (event:MouseEvent) : void {
buttonSkin.y = -2*buttonHeight;
}
private function buttonMouseUp (event:MouseEvent) : void {
buttonSkin.y = 0;
}
private function renderAsTransparent () : void {
function transparentStageResize (evt:Event) : void {
buttonSprite.width = buttonSprite.stage.stageWidth;
buttonSprite.height = buttonSprite.stage.stageHeight;
}
buttonSprite.graphics.beginFill(0xffffff, 0);
buttonSprite.graphics.drawRect(0,0,5,5);
buttonSprite.width = this.stage.stageWidth;
buttonSprite.height = this.stage.stageHeight;
buttonSprite.graphics.endFill();
this.stage.scaleMode = StageScaleMode.NO_SCALE;
this.stage.align = StageAlign.TOP_LEFT;
this.stage.tabChildren = false;
this.stage.addEventListener(Event.RESIZE, transparentStageResize);
this.addEventListener(MouseEvent.CLICK, handleMouseClick);
this.addEventListener(MouseEvent.CLICK, transparentClick);
this.addEventListener(MouseEvent.MOUSE_DOWN, transparentDown);
this.addEventListener(MouseEvent.MOUSE_UP, transparentUp);
this.addEventListener(MouseEvent.ROLL_OVER, transparentRollOver);
this.addEventListener(MouseEvent.ROLL_OUT, transparentRollOut);
this.buttonMode = true;
this.useHandCursor = true;
this.addChild(buttonSprite);
}
private function handleKeyDown (evt:KeyboardEvent) : void {
if (evt.keyCode == Keyboard.ENTER || evt.keyCode == Keyboard.SPACE) {
logMessage("Keyboard 'Enter' or 'Space' down.");
buttonSkin.y = -2*buttonHeight;
}
}
private function handleKeyUp (evt:KeyboardEvent) : void {
if (evt.keyCode == Keyboard.ENTER || evt.keyCode == Keyboard.SPACE) {
buttonSkin.y = 0;
logMessage("Keyboard 'Enter' or 'Space' up.");
logMessage("Keyboard 'Enter' or 'Space' detected, launching 'Open File' dialog.");
this.browse(this.allowMultiple, this.filterArray);
}
}
private function handleFocusIn (evt:FocusEvent) : void {
logMessage("Focus is on the Uploader.");
}
private function handleFocusOut (evt:FocusEvent) : void {
logMessage("Focus is out on the Uploader.");
}
private function handleMouseClick (evt:MouseEvent) : void {
logMessage("Mouse click detected, launching 'Open File' dialog.");
this.browse(this.allowMultiple, this.filterArray);
}
//--------------------------------------------------------------------------
//
// Overridden Properties
//
//--------------------------------------------------------------------------
/**
* @private
* Initializes the component and enables communication with JavaScript
*
* @param parent A container that the PopUpManager uses to place the Menu
* control in. The Menu control may not actually be parented by this object.
*
* @param xmlDataProvider The data provider for the Menu control.
* @see #dataProvider
*
* @return An instance of the Menu class.
*
* @see #popUpMenu()
* @see com.yahoo.astra.fl.data.XMLDataProvider
*/
override protected function initializeComponent():void {
super.initializeComponent();
var btnSkinURL:String;
btnSkinURL = this.stage.loaderInfo.parameters["buttonSkin"];
if (btnSkinURL != null) {
this.renderType = "button";
this.renderAsButton(btnSkinURL);
}
else {
this.renderType = "transparent";
this.renderAsTransparent();
}
// removeFile (fileID:String = null)
// Removes one or all files from the upload queue
ExternalInterface.addCallback("removeFile", removeFile);
// clearFileList (): Boolean
// Clears the list of files to be uploaded.
ExternalInterface.addCallback("clearFileList", clearFileList);
// upload(fileID:String, url:String, method:String = "GET", vars:Object = null, fieldName:String = "Filedata")
// Uploads the specified file in a specified POST variable, attaching other variables using the specified method
ExternalInterface.addCallback("upload", upload);
// uploadAll(url:String, method:String = "GET", vars:Object = null, fieldName:String = "Filedata")
// Uploads all files in the queue, using simultaneousUploads.
ExternalInterface.addCallback("uploadAll", uploadAll);
// cancel (fileID:String = null)
// Cancels the specified file upload; or all, if no id is specified
ExternalInterface.addCallback("cancel", cancel);
// setAllowLoging (allowLogging:Boolean = false)
// Allows log outputs to be produced.
ExternalInterface.addCallback("setAllowLogging", setAllowLogging);
// setAllowMultipleFiles (allowMultiple:Boolean = false)
// Allows multiple file selection
ExternalInterface.addCallback("setAllowMultipleFiles", this.setAllowMultipleFiles);
// setSimUploadLimit(simUpload:int = [2,5])
// Sets the number of simultaneous uploads allowed when automatically managing queue.
ExternalInterface.addCallback("setSimUploadLimit", this.setSimUploadLimit);
// setFileFilters(fileFilters:Array)
// Sets file filters for file selection.
ExternalInterface.addCallback("setFileFilters", this.setFileFilters);
// enable()
// Enables Uploader UI
ExternalInterface.addCallback("enable", enable);
// disable()
// Disables Uploader UI
ExternalInterface.addCallback("disable", disable);
// Initialize properties.
fileDataList = new Object();
fileRefList = new Object();
fileIDList = new Dictionary();
singleFile = new FileReference();
multipleFiles = new FileReferenceList();
fileIDCounter = 0;
filesToUpload = [];
}
//--------------------------------------
// Private Methods
//--------------------------------------
/**
* @private
* Formats objects containing extensions of files to be filtered into formal FileFilter objects
*/
private function processFileFilterObjects(filtersArray:Array) : Array {
// TODO: Should we have an 'allowedExtensions' property that the JS user accesses directly? Potential here for typos ('extension' instead of 'extensions') as well as a misunderstanding of the nature of the expected array
// TODO: Description not showing (testing on OS X PPC player)
for (var i:int = 0; i < filtersArray.length; i++) {
filtersArray[i] = new FileFilter(filtersArray[i].description, filtersArray[i].extensions, filtersArray[i].macType);
}
return filtersArray;
}
/**
* @private
* Outputs the files selected to an output panel and triggers a 'fileSelect' event.
*/
private function processSelection():void {
var dstring:String = "";
dstring += "Files Selected: \n";
for each (var item:Object in fileDataList) {
dstring += item.name + "\n ";
}
logMessage(dstring);
var newEvent:Object = new Object();
newEvent.fileList = fileDataList;
newEvent.type = "fileSelect"
super.dispatchEventToJavaScript(newEvent);
}
/**
* @private
* Adds a file reference object to the internal queue and assigns listeners to its events
*/
private function addFile(fr:FileReference):void {
var fileID:String = "file" + fileIDCounter;
var fileName:String = fr.name;
var fileCDate:Date = fr.creationDate;
var fileMDate:Date = fr.modificationDate;
var fileSize:Number = fr.size;
fileIDCounter++;
fileDataList[fileID] = {id: fileID, name: fileName, cDate: fileCDate, mDate: fileMDate, size: fileSize};//, type: fileType, creator: fileCreator};
fr.addEventListener(DataEvent.UPLOAD_COMPLETE_DATA, uploadCompleteData);
fr.addEventListener(HTTPStatusEvent.HTTP_STATUS, uploadError);
fr.addEventListener(IOErrorEvent.IO_ERROR, uploadError);
fr.addEventListener(SecurityErrorEvent.SECURITY_ERROR, uploadError);
fr.addEventListener(Event.CANCEL,uploadCancel);
fileRefList[fileID] = fr;
fileIDList[fr] = fileID;
}
/**
* @private
* Queues a file for upload
*/
private function queueForUpload(fr:FileReference, request:URLRequest, fieldName:String):void {
filesToUpload.push( {fr:fr, request:request, fieldName:fieldName });
}
/**
* @private
* When the Loader ldr is done loading the ByteArray into its content attribute this method
* will be fired as a callback. This is where the actual resizing of the image is done and
* where the request containing the resized image is sent to the server.
*/
private function ldrEventDone(fr:FileReference, request:URLRequest, ldr:Loader):void
{
//create an event announcing the start of the upload process for the current file
var newEvent:Object = new Object();
newEvent.id = fileIDList[fr];
newEvent.type = "uploadStart";
//dispatch the event to the JS layer
dispatchJSEvent(newEvent);
// set the maximum dimensions and the jpeg quality to encode to
var max_width:Number = 800;
var max_height:Number = 800;
var jpeg_quality:Number = 80.0;
//pull the binary data from ldr into a new Bitmap
var image:Bitmap = Bitmap(ldr.content);
//calculate scale values to change the width and height to match max
//values above
var scaleX:Number = max_width / image.width;
var scaleY:Number = max_height / image.height;
//scale factor is which ever gets scaled the most of the width or height
//more scaling will be shown as a smaller scale value
var scale:Number = Math.min(scaleX, scaleY);
//grab a reference to the original bitmapData
var originalBitmapData:BitmapData=image.bitmapData;
//make a new bitmapData object with the scaled height and width
var newWidth:Number=originalBitmapData.width*scale;
var newHeight:Number=originalBitmapData.height*scale;
var scaledBitmapData:BitmapData=new BitmapData(newWidth,newHeight,true,0xFFFFFFFF);
//create a matrix for scaling the data
var scaleMatrix:Matrix=new Matrix();
scaleMatrix.scale(scale,scale);
//"draw" the original bitmapData into the new scaledBitmapData object
//through the scaleMatrix
scaledBitmapData.draw(originalBitmapData,scaleMatrix);
//create a new instance of the asynchronous JPEG encoder
var encoder:JPEGAsyncEncoder = new JPEGAsyncEncoder(jpeg_quality);
//specify how many pixels to encode in each iteration of the asynchronous encoder
encoder.PixelsPerIteration = 800;
//register a callback on the asynchronous encoder for when the JPEGASYNC_COMPLETE event is fired
encoder.addEventListener(JPEGAsyncCompleteEvent.JPEGASYNC_COMPLETE,
(function (fileRef:FileReference, request:URLRequest):Function {
return function(evnt:JPEGAsyncCompleteEvent):void {
// grab the byte array attached to the event
var jpgBytes:ByteArray = evnt.ImageData;
// the call we will make is a standard HTTP POST call
request.method = "POST";
// this enables us to send binary data for the body of the HTTP POST call
request.contentType = "application/octet-stream";
//create a new URLLoader for actually sending the data to the server
var loader:URLLoader = new URLLoader();
// the loader is the data being sent along to the server. Its dataFormat property lets us specify the format for the body, which, in our case, will be BINARY data
loader.dataFormat = URLLoaderDataFormat.BINARY;
// the data property of our URLRequest object is the actual data being sent to the server, which in this case is the photo JPEG data
request.data = jpgBytes;
// even though the upload has not yet happened, we fire off the upload complete
// event to the JS layer and get a new image into the queue here because the
// upload takes less time than the encoding and is non-blocking. This means we can
// proceed to the next image to encode while we fire and forget the http upload
//var newEvent:Object = new Object();
//newEvent.id = fileIDList[fileRef];
//newEvent.type = "uploadComplete"
//dispatchJSEvent(newEvent);
//decrease the number of in-progress files
decrementUploadThreads();
// get next off of queue:
if(filesToUpload.length > 0) {
processQueue();
}
//create an event listener on the URLLoader's complete so we can know when the file is
//done
loader.addEventListener(Event.COMPLETE,
(function (fileRef:FileReference):Function {
return function(evt:Event):void {
var newEvent:Object = new Object();
newEvent.id = fileIDList[fileRef];
newEvent.type = "uploadComplete";
dispatchJSEvent(newEvent);
}
})(fr)
);
loader.addEventListener(Event.COMPLETE,
(function (fileRef:FileReference,ldr:URLLoader):Function {
return function(evt:Event):void {
var newEvent:Object = new Object();
newEvent.id = fileIDList[evt.target];
// NASTY HACK
// URLLoader doesn't have an DataEvent dispatched for DataEvent.UPLOAD_COMPLETE_DATA
// so we need to cast this data as a String (it's coming back from the server as json anyways)
// so that the frontend script can parse it as such and not as a ByteArray object.
newEvent.data = String(ldr.data);
newEvent.type = "uploadCompleteData";
dispatchJSEvent(newEvent);
}
})(fr,loader)
);
// create an event listener on the URLLoader's progress so that we can update
// the JS layer with progress information about the upload
loader.addEventListener(ProgressEvent.PROGRESS,
(function (fileRef:FileReference):Function {
return function(evt:ProgressEvent):void {
var newEvent:Object = new Object();
newEvent.id = fileIDList[fileRef];
newEvent.bytesLoaded = evt.bytesLoaded;
newEvent.bytesTotal = evt.bytesTotal;
newEvent.type = "uploadProgress";
dispatchJSEvent(newEvent);
}
})(fr)
);
//send the request to the server
loader.load(request);
}
})
(fr, request));
// create a callback on the encoder for the ProgressEvent which is fired to inform
// us of how much of the current image has been encoded. We'll pass this back to the
// JS layer as the "encodeProgress" event to update the progress bar
encoder.addEventListener(ProgressEvent.PROGRESS,
(function (fileRef:FileReference):Function {
return function (evt:ProgressEvent):void {
var newEvent:Object = new Object();
newEvent.id = fileIDList[fileRef];
newEvent.bytesLoaded = evt.bytesLoaded;
newEvent.bytesTotal = evt.bytesTotal;
newEvent.type = "encodeProgress";
dispatchJSEvent(newEvent);
};
}
)(fr));
// actually start the encoding process on the scaled image
encoder.encode(scaledBitmapData);
}
/**
* @private
* We cannot access the class variable currentUploadThreads from within anonymous
* functions, so this method allows for the decrement of the variable.
*/
public function decrementUploadThreads():void
{
this.currentUploadThreads--;
}
public function get_currentUploadThreads():Number {
return this.currentUploadThreads;
}
/**
* @private
* We cannot access methods belonging to the super class from within anonymous
* functions, so this method allows us to send new events to the JavaScript.
*/
private function dispatchJSEvent(newEvent:Object):void
{
super.dispatchEventToJavaScript(newEvent);
}
/**
* @private
* When the FileReference fr is done loading data from disk (using fr.load()) into its
* .data property, this method will be fired as a callback. This method sets up a Loader
* which will be used to load the binary data into a Bitmap object which can then have resize
* operations performed on it.
*/
private function frEventDone(fr:FileReference, request:URLRequest):void
{
// create a new Loader which will be used to load the file reference data
// into a Bitmap
var ldr:Loader = new Loader();
//grab the binary data from the FileReference
var bAr:ByteArray = fr.data;
//register a call back for when the Loader is done loading the ByteArray
ldr.contentLoaderInfo.addEventListener(Event.COMPLETE, function(event:Event):void{
ldrEventDone(fr, request, ldr);
});
//Load the ByteArray into the Loader
ldr.loadBytes(bAr);
}
/**
* @private
* Method to process the queue of files to be uploaded.
*/
private function processQueue():void
{
while (this.currentUploadThreads < this.simultaneousUploadLimit)
{
//initialize some variables
var objToUpload:Object = filesToUpload.pop();
var fr:FileReference = objToUpload.fr;
var request:URLRequest;
var fieldName:String = objToUpload.fieldName;
request = objToUpload.request;
// register a callback for the FileReference.load() of data from the disk into
// its .data property
fr.addEventListener(
Event.COMPLETE,
(
function(fileReference:FileReference):Function
{
return function(event:Event):void
{
frEventDone(fileReference, request);
};
}
)(fr)
);
// load the file data into the FileReference object
fr.load();
//increment the count of current upload threads.
this.currentUploadThreads++;
}
}
/**
* @private
* Creates a URLRequest object from a url, and optionally includes an HTTP request method and additional variables to be sent
*/
private function formURLRequest(url:String, method:String = "GET", vars:Object = null):URLRequest {
var request:URLRequest = new URLRequest();
request.url = url;
request.method = method;
request.data = new URLVariables();
for (var itemName:String in vars) {
request.data[itemName] = vars[itemName];
}
return request;
}
/**
* @private
* Determines whether an object is equivalent to an empty string
*/
private function isEmptyString(toCheck:*):Boolean {
if( toCheck == "null" ||
toCheck == "" ||
toCheck == null ) {
return true;
}
else {
return false;
}
}
private function logMessage (message:String) : void {
if (this.allowLog) {
trace(message);
ExternalInterface.call("YAHOO.log", message);
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment