Multiple uploads with jQuery and Flex or Flash

Update 01/11/2008: There is now a new Flash only version that works properly with version 10 of the player.


Update 30/10/2008: The below approach doesn’t work anymore as per Flash player 10, they turned off the ability to automatically open a file choosing dialog. The flex/flash component below needs to be recreated to have a button so that user interaction can initiate the whole process. This defeats the whole point of the way we do things in this article, this thing is supposed to be design agnostic without any graphical elements. Adobe really doesn’t want this stuff to work, they would really love to see competing tech/approaches to win over Flash every time, if you’re a stock holder, get out of there!

As you might know I’m currently working on a custom cms/community project and the specs state that each user should have a simple gallery. However, I’m really, really tired of uploading files the traditional way through file inputs. There is for instance no way of telling how the upload is going or preventing too big files before they are already on the server, just to name two problems. To make a long story short, it’s kinda primitive.

So I started looking around for an alternative and quickly realized that currently there are two ways of doing what I wanted:

1.) Ajax + Perl. I was really not in the mood to wrestle with my admin yet again to setup Perl to work with this. Besides, I have never coded a single line of Perl in my whole life, and I might have had to do that if there was something I wanted to change in whatever solution I chose. I’ve gotten the impression that if Perl was great then why the need for PHP? And since PHP is obviously an extremely popular language… All in all, this way of solving the problem had little appeal to me.

2.) Flash through the FileReferenceList class. This solution seemed more interesting, especially since there are ways of loading shockwaves that will work in any browser plus the fact that I’m a flash guy. If flash loading works for youtube it should work for me too. Another, more elegant solution is the jQuery flash loader which I have not tried yet.

The big problem with using Flash is that the shockwave is a compiled piece of logic. If you want to change something you have to locate the source, make your changes and recompile it. You also have to be able to do actionscripting of course. Not all designers can do this. Therefore, the requirements for the shockwave is that it should be able to communicate with various Javascript functions which in turn will be responsible for displaying progressbars and more. The ultimate goal is to never have to recompile the shockwave again. This will allow designers with a working knowledge of CSS and some Javascript to do the rest in the form of progress bars and so on.

However, I already knew that the Flex SDK is free to download and use, it’s only their Flex IDE that costs money. Could it be possible to create the uploader with Flex instead of Flash to enable a free solution? Locating the Flex FileReferenceList description answered the question with a YES!

However the example code that the above link links to would need some adjustments in order for it to handle my requirements:

package upload{
	import flash.external.*;
	import flash.events.*;
	import flash.net.FileReference;
	import flash.net.FileReferenceList;
	import flash.net.FileFilter;
	import flash.net.URLRequest;

	class CustomFileReferenceList extends FileReferenceList {
		private var uploadURL:URLRequest;
		private var pendingFiles:Array;
		private var uploadScript:String;
		private var maxSize:Number;
		private var msgCallback:String;
		private var progressCallback:String;

		public function CustomFileReferenceList() {
			var info:String 		    = String(ExternalInterface.call("forFlash"));
			var infoArr:Array 		  = info.split(',');
			this.uploadScript 		  = infoArr[0];
			this.maxSize 			      = Number(infoArr[1]);
			this.msgCallback		    = infoArr[2];
			this.progressCallback 	= infoArr[3];

			uploadURL = new URLRequest();
			uploadURL.url = this.uploadScript;
			initializeListListeners();
		}
    .
    .
    .

As you can see I’m using the same technique here that I use in my flash image loading example. We basically have our information already setup in the markup so we can easily pass it to the shockwave. In this case we pass the upload script URL, the max file size and the names of two Javascript functions that will be used to keep track of misc events and the file upload progress respectively. Let’s take a look at where these variables are used:

private function addPendingFile(file:FileReference):void {
  if(file.size > this.maxSize){
    ExternalInterface.call(this.msgCallback, "err_big", file.name);
  }else{
    pendingFiles.push(file);
    file.addEventListener(Event.OPEN, openHandler);
    file.addEventListener(Event.COMPLETE, completeHandler);
    file.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler);
    file.addEventListener(ProgressEvent.PROGRESS, progressHandler);
    file.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler);
    file.upload(uploadURL);
  }
}

private function doOnComplete():void {
  ExternalInterface.call(this.msgCallback, "finished", '');
  var event:Event = new Event(FileReferenceListExample.LIST_COMPLETE);
  dispatchEvent(event);
}

private function progressHandler(event:ProgressEvent):void {
  var file:FileReference = FileReference(event.target);
  ExternalInterface.call(this.progressCallback, file.name, event.bytesLoaded, event.bytesTotal);
}

private function completeHandler(event:Event):void {
  var file:FileReference = FileReference(event.target);
  removePendingFile(file);
  ExternalInterface.call(this.msgCallback, "complete", file.name);
}

In addPendingFile() we use maxSize to check the file size and if it is too big we send a message back to a Javascript function with the simple string “err_big” and the file name. Here we solve the problem of having to upload too big files before we can check if they are too big. DoOnComplete() simply sends “finished” back so that the Javascript will know when all files have been uploaded. ProgressHandler() will call our special Javascript function we have designated for this, it will pass the file name, bytes uploaded and the total file size. That is all the information we need to create a Javascript controlled progressbar for instance.

Flex project source download.

The zip file above contains a Flash example as well for those who feel more comfortable with Flash. It has not been tested though. The project can be compiled by putting the folder in the flex_sdk\samples folder and running build.bat in the command line interpreter.

Let’s take a look at the markup (rendered with PHP/Smarty):

{config_load file="$config_file"}
<script src="{$baseUrl}/js/jquery.js" language="JavaScript"></script>
<script src="{$baseUrl}/js/swfobject.js" language="JavaScript"></script>
<div id="thumbs">
	{include file="gallery_thumbs.tpl"}
</div>
<div id="msgBox"></div>
<div id="prgrsMsg"></div>

<script type="text/javascript">
	  // <![CDATA[
	  var messages            = new Array();
	  messages['err_big'] 	  = "{#big_err#}";
	  messages['complete'] 	= "{#complete#}";
	  var getThumbsUrl       = "{$get_thumb_url}";
	  
	  {literal}
	 
	  function uploadMsg(msg, file_name){
	  	if(msg != 'finished')
	  		$("#msgBox").append("<br/>"+file_name+" "+messages[msg]);
                else{
                        $("#prgrsMsg").html(' ');
                        $("#thumbs").load(getThumbsUrl);
                }
	  }
	  
	  function uploadProgress(file_name, bytes_loaded, bytes_total){
	  	var percentage = (bytes_loaded / bytes_total) * 100;
                $("#prgrsMsg").html(file_name+": "+percentage+"%");
	  }
	  
	  function forFlash(){
	  {/literal}
	    var to_flash = new Array();
	    to_flash[0] = "{$upload_script}";
	    to_flash[1] = "{$max_size}";
            to_flash[2] = "uploadMsg";
            to_flash[3] = "uploadProgress";
	    return to_flash;
	  {literal}
	  }
	  {/literal}
	  // ]]>
</script>

We start with filling the messages array with data from PHP to populate some error messages. As you can see uploadMsg() is our msgCallback in the Shockwave and uploadProgress() is the progressCallback. Max size is set from PHP, the same goes for the PHP script we want the Shockwave to communicate with during the upload.

During upload, all messages except “finished” will be displayed in the msgBox div. If “finished” is sent from the Shockwave we instead call the PHP script getThumbsUrl and display the result in the thumbs div. This is an Ajax request that will basically fetch the thumbs for the current gallery and display them after they have all been uploaded. Note that I’ve been too lazy to actually implement a proper progress bar here. I hope that the percentage display will be enough, we’ll see if that will be the case. Anyhow, it should not be too difficult for some designer with a bit of Javascript knowledge to fix something fairly easy. But really, can Ajax become much more simple than this? jQuery is definitely my new pet library.

The rest of the markup looks like this:

<div id="flashcontent_div" name="flashcontent_div" style="display: block;">
<embed type="application/x-shockwave-flash" src="{$baseUrl}/swf/file_upload.swf" id="flashcontent" name="flashcontent" wmode="transparent" height="1" width="1">
</div>

<script type="text/javascript">
// <![CDATA[
   var fo = new SWFObject("{$baseUrl}/swf/file_upload.swf", "flashcontent", "1", "1", 9, "#fff");
   fo.addParam("quality", "high");
   fo.addParam('wmode', 'transparent');
   fo.useExpressInstall('{$baseUrl}/swf/expressinstall.swf');
   fo.write("flashcontent_div");
  // ]]>
</script>

Of note here is the Shockwave size of 1×1 pixel. I tried setting it to 0×0 but then the logic would not load. Anyway, the point is to make the thing completely invisible. But what will happen if we need to run the express install? Will it too be 1×1, thus making it useless? I’ve got no idea, this is something I have to test before this project goes live.

The original plan with the Ajax function was to display each thumb after the image had been uploaded. This did not work, at least not by calling the Ajax function after each “complete”. I ended up displaying the whole gallery after all uploads were finished anyway, my current theory is that the Ajax will not complete until the Shockwave has completely finished it’s execution. If you know a solution to this problem then don’t hesitate to post it here (or a link to it).

Are you interested in taking a look at the PHP behind all this? Then check out: Writing a CMS/Community with Smarty and the Zend Framework: Part 6

Related Posts

Tags: , , , , , , ,