Flash 10 and jQuery multi-file uploader


Update: I’m not using flash for uploads anymore, XHR is better and more stable if you can require users to use at least Chrome 6 or Firefox 3.6 for instance.

Update: There is now a real world project using this multi file up loader, with automatic watermarking of pictures to boot: Image manipulation and watermarking in PHP with GD2.

So Flash Player version 10 won’t stand for any automatically opened file select windows, there goes our flash uploader down the drain, the article is still good as a reference though if something here is unclear, it’s much more verbose. It’s just that the old logic needs a button and I’m not going to learn how to do that in Flex at the moment. And the whole point of that scheme was to make the uploader invisible and therefore design agnostic. Oh dear Adobe people, what on earth are you thinking?

As it happens a new project of ours is using swfUpload, broken too! We need a button, yesterday… The quickest and easiest way of managing this mess is simply creating an all out Flash version where the designers can work with the necessary button to make sure it fits in nicely. We will put the code in the .fla itself this time, quick and dirty is the name of the game unfortunately.

Update: It seems Josip Lazic tested this thing with bigger files than me, he ran into the “slow script” error/warning. After that he proceeded with Googling for a solution and found it in a similar project to this, the jqUploader. Anyway if you just add this at the end of the ActionScript you’re good to go (thanks Josip!):

// fix for "script running slowly.." prompt
setInterval(this,"dummy",1000);
var dummy_counter:Number = 0;
function dummy() {
       if (this.dummy_counter == 1000000) {
               this.dummy_counter = 0;
       }

       this.dummy_counter++;
}

Let’s get on with the rest of the code, we start with the JavaScript/HTML:

<html>
<head>
<script src="jquery-1.2.6.min.js" language="JavaScript"></script> 
<script src="jquery.flash.js" language="JavaScript"></script>
<script type="text/javascript">

Including the necessary jQuery stuff.

// <![CDATA[
      
var messages            = new Array(); 
var completeMsg         = " was uploaded successfully.";
var tooBigMsg           = " was too big.";

function eachComplete(file_name){
  $("#msgBox").append("<br/>"+file_name + completeMsg);
} 
 
function tooBig(file_name){
  $("#msgBox").append("<br/>" + tooBigMsg + file_name);
}
 
function uploadProgress(file_name, bytes_loaded, bytes_total){ 
  var percentage = (bytes_loaded / bytes_total) * 100; 
  $("#prgrsMsg").html(file_name+": "+percentage+"%"); 
}

function uploadComplete(){
  $("#prgrsMsg").html('');
  $("#pics").load("showpics.php"); 
} 
 
function forFlash(){ 
  var to_flash = new Array(); 
  to_flash[0] = "upload.php";
  to_flash[1] = "1048576";
  return to_flash;
}

$(document).ready(function(){  
  $("#flashbutton").flash({  
    src: "upload.swf",  
    width: 250,  
    height: 250
  });
});

// ]]>

Most of the above functions will be called from within the Shockwave.

EachComplete will be called each time a file completes to output this to the designated DIV.

TooBig, is a file too big? In our case 1048576 bytes. Then we output the error message.

UploadProgress, called continuously when a file uploads to display its progress.

UploadComplete, runs when all files have been uploaded, it will use an Ajax call to load the output of showpics.php to display all the nice pictures we have managed to upload.

ForFlash, sends some data the Shockwave needs, to it.

Finally we load the Shockwave with the jQuery Flash plugin.

</script>
</head>
<body>
<div id="flashbutton"></div>
<div id="msgBox"></div> 
<div id="prgrsMsg"></div>
<div id="pics"></div>
</body>

The DIVs we use in the Javascript above.

Let’s move on to the ActionScript:

import flash.external.*;
import flash.net.FileReferenceList;
import flash.net.FileReference;

var info:String 				= String(ExternalInterface.call("forFlash"));
var infoArr:Array 				= info.split(',');
var uploadScript:String 		= infoArr[0];
var maxSize:Number 				= Number(infoArr[1]);

var allTypes:Array 		= new Array();
var imageTypes:Object 	= new Object();
imageTypes.description 	= "Images (*.jpg, *.jpeg, *.gif, *.png)";
imageTypes.extension 	= "*.jpg; *.jpeg; *.gif; *.png";
allTypes.push(imageTypes);
var toUploadCount = 0;
var uploadedCount = 0;

Some code needed to setup what we want to do, most of it is just ripped out of the Flash help describing the FileReferenceList class. Note the uploadScript and maxSize info we pass from Javascript. Other than that toUploadCount and uploadedCount will play important parts of keeping track of the number of uploaded files so we know when they’re all finished.

var listener:Object = new Object(); 
listener.onSelect = function(fileRefList:FileReferenceList){
    var list:Array = fileRefList.fileList;
    var item:FileReference;
    _root.toUploadCount = list.length;
    for(var i:Number = 0; i < list.length; i++) {
        item = list[i];
        if(item.size > maxSize)
          ExternalInterface.call("tooBig", item.name);
        else{
          item.addListener(this)
          item.upload(_root.uploadScript);
        }
    }
}

This is basically where everything happens, note _root.toUploadCount = list.length; and ExternalInterface.call(“tooBig”, item.name);.

listener.onProgress = function(file:FileReference, bytesLoaded:Number, bytesTotal:Number):Void{
	ExternalInterface.call("uploadProgress", file.name, bytesLoaded, bytesTotal);
}

listener.onComplete = function(file:FileReference):Void{
	ExternalInterface.call("eachComplete", file.name);
}

listener.onUploadCompleteData = function(file:FileReference, data:String){
	_root.uploadedCount++;
	if(_root.uploadedCount == _root.toUploadCount){
		_root.uploadedCount = 0;
		ExternalInterface.call("uploadComplete");
	}
}

submit_btn.onRelease = function(){
	var fileRef:FileReferenceList = new FileReferenceList();
	fileRef.addListener(listener);
	fileRef.browse(allTypes);
}

The callbacks, same as in the documentation except for the addition of onUploadCompleteData which we need to keep track of the number of uploaded files, each time the upload script (in our case upload.php) manages to get and handle an image it will echo something back and this is the target of that echo. However we don’t act upon the info (data:String), we just need to capture the event.

And finally the new necessary and disgusting addition of a button with the instance name of submit_btn. When pressed it will display the multiple file chooser.

Let’s move on to the PHP upload script (upload.php):

echo move_uploaded_file($_FILES["Filedata"]["tmp_name"], "images/".uniqid().".jpg");

Just one simple line in our test, we move all images to the images folder and give them a new name.

Showpics.php:

include('FileDirExt.php');
foreach(FileDirExt::list_files_in_dir("images/") as $image){
  $str .= "<img src=\"images/{$image}\"/><br>";
}
echo $str;

We use the file and directory class to walk through our images directory. Each image then gets displayed in the HTML output, just a simple test that the Ajax part works properly.

And that’s that, the Flash 10 problem is solved, but it doesn’t feel right…

The source and test files.

Related Posts

Tags: , , ,