I’m going to prefix this tutorial by declaring my love for AJAX to preempt a Flash vs Ajax war. I use AJAX daily, it’s a great tool, the sun shines out it’s arse, etc. But sometimes Flash is just the better tool for the job. I’m going off on a tangent here but many problems with AJAX, e.g. breaks the back button, were the exact same arguments people made about Flash years ago.
ALAX is cool but can you play MP3’s with it? No.
Can you Play Video with AJAX? No.
Upload files with AJAX? No*.
And most importantly can you make a 3D spinning LED thingy in AJAX? No :P
AJAX file upload is impossible for 99.99% of web users, but Flash file upload is available to 90% of users (the rest are using Gopher on Linux so funk ‘em). As an extra special bonus this example will upload multiple files through Flash, but wait there’s more… it will also include the upload status/progress for each file.

files awaiting upload

uploading with progress

done!
This server side will be Rails but .NET or PHP would work as well. The source for the tutorial is here. Flash files are in the ‘fla’ directory.
As usual start off with a new Rails app
> rails upload
Since we’ll be uploading files we’ll need somewhere to store them preferably in a unique folder so as not to mess anything else up.
> mkdir upload/public/uploads
Change into the ‘upload’ app directory (not the public/uploads directory) so we can generate models and controllers.
> cd upload
Create a ‘File’ model, ‘File’ is a reserved word for Rails so we’ll call it ‘DataFile’
> ruby script/generate model DataFile
In the model file (app/model/data_file.rb) we’re going to create a custom save method. It will have three arguments ‘data’ the file data, ‘name’ what to name the file, and ‘directory’ where to put the file.
class DataFile < ActiveRecord::Base
def self.save(data, name, directory)
path = File.join(directory, name)
File.open(path,‘wb’) do |file|
file.puts data.read
end
end
end
Next create an ‘Upload’ controller
> ruby script/generate controller Upload
In the controller file (app/controllers/upload_controller.rb) create an index method that takes file information from Flash and passes it to the ‘DataFile’ model. Because there’s no view associated with this controller we add ‘render :nothing => true‘ to avoid any errors. We set the upload directory to the ‘uploads’ directory we created earlier "directory = ‘uploads’".
class UploadController < ApplicationController
def index
data = params[:Filedata]
name = params[:Filename]
directory = ‘public/uploads’
@data_file = DataFile.save(data, name, directory)
render :nothing => true
end
end
Almost done, after bashing AJAX earlier it’s time to bash Flash a bit. Flash file upload is broken when used with Rails, from the Bubble Share folk "the Windows version of Flash Player 8 sends a multipart request with content-length set to zero(0), before sending the real request, if the file you are uploading is bigger than 10K". Long story short it no worky with Rails. Big ups to the bubblers they figured out how to fix it, download the fix here and place it in the Rails plug-in directory (vendor/plugins).
That’s it for the Rails side of things (easy peasy lemon squeezy).
Using the source files you can start a webbrick/lighttpd server and test the multipleUpload.fla file in the Flash IDE.
Now on to Flash…
The multipleUpload.fla file has three components on stage, a DataGrid (files_dg), and two Buttons one for browsing the file system (browse_btn), and one for uploading (upload_btn).

The actions for the .fla are pretty short, just creating an instance of a ‘MultipleUpload‘ class (described below) and hooking up the components to it.
import com.vixiom.utils.MultipleUpload
System.security.allowDomain (“*.localhost.com”);
var MU:MultipleUpload = new MultipleUpload (this.files_dg, this.browse_btn, this.upload_btn);
Below is the ‘MultipleUpload.as’ file. Here’s a quick run down of what’s going on: a FileReferenceList object is created with a listener that ‘listens’ for various user events dealing with files (onSelect, onCancel, onOpen, onProgress, onComplete, onHTTPError, onIOError, onSecurityError). The major ones are onSelect (when a user selects files with the file browser), onProgress (an interval that runs in reference to a particular file being uploaded), and onComplete (when a file has finished uploading). To show the user the status of our file uploads we’re using the DataGrid’s built in ‘editField‘ method to change what the status cells display.
import mx.utils.Delegate;
import mx.controls.DataGrid
import mx.controls.Button
import flash.net.FileReferenceList;
import flash.net.FileReference;
class com.vixiom.utils.MultipleUpload
{
private var fileRef:FileReferenceList;
private var fileRefListener:Object;
private var list:Array;
private var files_dg:DataGrid;
private var browse_btn:Button;
private var upload_btn:Button;
public function MultipleUpload(fdg:DataGrid, bb:Button, ub:Button)
{
files_dg = fdg;
browse_btn = bb;
upload_btn = ub;
fileRef = new FileReferenceList();
fileRefListener = new Object();
fileRef.addListener(fileRefListener);
iniUI();
inifileRefListener();
}
private function iniUI()
{
browse_btn.onRelease = Delegate.create(this, this.browse);
upload_btn.onRelease = Delegate.create(this, this.upload);
files_dg.addColumn(“name”);
files_dg.addColumn(“size”);
files_dg.addColumn(“status”);
}
private function browse()
{
trace(“// browse”);
fileRef.browse();
}
private function upload()
{
trace(“// upload”);
for(var i:Number = 0; i < list.length; i++) {
var file = list[i];
trace(“name: “ + file.name);
trace(file.addListener(this));
file.upload(“http://0.0.0.0:3000/upload”);
}
}
private function inifileRefListener()
{
fileRefListener.onSelect = Delegate.create(this, this.onSelect);
fileRefListener.onCancel = Delegate.create(this, this.onCancel);
fileRefListener.onOpen = Delegate.create(this, this.onOpen);
fileRefListener.onProgress = Delegate.create(this, this.onProgress);
fileRefListener.onComplete = Delegate.create(this, this.onComplete);
fileRefListener.onHTTPError = Delegate.create(this, this.onHTTPError);
fileRefListener.onIOError = Delegate.create(this, this.onIOError);
fileRefListener.onSecurityError = Delegate.create(this, this.onSecurityError);
}
private function onSelect(fileRefList:FileReferenceList)
{
trace(“// onSelect”);
list = fileRefList.fileList;
var list_dp = new Array();
for(var i:Number = 0; i < list.length; i++)
{
list_dp.push({name:list[i].name, size:Math.round(list[i].size / 1000) + ” kb”, status:“ready for upload”});
}
files_dg.dataProvider = list_dp;
files_dg.spaceColumnsEqually();
}
private function onCancel()
{
trace(“// onCancel”);
}
private function onOpen(file:FileReference)
{
trace(“// onOpenName: “ + file.name);
}
private function onProgress(file:FileReference, bytesLoaded:Number, bytesTotal:Number)
{
trace(“// onProgress with bytesLoaded: “ + bytesLoaded + ” bytesTotal: “ + bytesTotal);
for(var i:Number = 0; i < list.length; i++)
{
if (list[i].name == file.name) {
var percentDone = Math.round((bytesLoaded / bytesTotal) * 100)
files_dg.editField(i, “status”, “uploading: “ + percentDone + “%”);
}
}
}
private function onComplete(file:FileReference)
{
trace(“// onComplete: “ + file.name);
for(var i:Number = 0; i < list.length; i++)
{
if (list[i].name == file.name) {
files_dg.editField(i, “status”, “complete”);
}
}
}
private function onHTTPError(file:FileReference, httpError:Number)
{
trace(“// onHTTPError: “ + file.name + ” httpError: “ + httpError);
}
private function onIOError(file:FileReference)
{
trace(“// onIOError: “ + file.name);
}
private function onSecurityError(file:FileReference, errorString:String)
{
trace(“onSecurityError: “ + file.name + ” errorString: “ + errorString);
}
}
That’s it! I’d love to be proven wrong but that’s impossible to do with AJAX. And if you could do it with AJAX who would want to deal with all the cross browser testing anyways?
*I’ve actually seen a couple of examples of attempts at AJAX file uploading but there’s always some caveat like "only works with FireFox if the user does A, B, and C".