Merb on AIR - Drag and Drop Multiple File Upload
Merb was originally created by Ezra Zygmuntowicz to avoid some Rails upload issues.
This is one of the things that Merb was written for. Rails doesn‘t allow multiple concurrent file uploads at once without blocking an entire rails backend for each file upload. Merb allows multiple file uploads at once.
I’ve built ‘multiple’ file uploaders for Rails sites but they always involved some slight of hand, the files appeared to be uploading all at once but they where actually queued up by Flex then handled one by one by the app (which also had the unhappy side effect of blocking any other requests to that process). I’ve been wanting to try out Adobe AIR’s file system drag and drop for a while so this is a two-fer example. You’ll need the beta version of Flex Builder 3 or the Flex 3 SDK beta if you don’t mind getting down with the command line.
In a hurry? Here’s one I made earlier (flex source in ‘).
If you haven’t before install Merb
Then create a new Merb app
and dive on in
We’ll need two folders not in a Merb skeleton, one for Flex, and one for our uploads
Create a local database called ‘merb_air_upload’ and edit dist/conf/merb_init.rb so that the database definition matches your setup
# set your db info here
ActiveRecord::Base.establish_connection(
:adapter => ‘mysql‘,
:username => ‘root‘,
:password => ‘‘,
:database => ‘merb_air_upload‘
)
Our model will be called ‘UserFile’ as ‘Upload’ and ‘File’ are reserved words, create a migration
and edit it (dist/schema/migrations/002_create_user_files.rb) to look like so
create_table :user_files do |t|
t.column :filename, :string, :null => false
end
end
drop_table :user_files
end
end
rake your db
Then create a UserFile model (dist/app/models/user_file.rb)
end
and an upload controller (dist/controllers/upload.rb)
# for testing check jer terminal
puts params.inspect
# new user file object
@upload = UserFile.new
@upload.filename = params[:Filename]
# save
if @upload.save
# create directories
dist_root = Merb::Server.config[:dist_root]
FileUtils.mkdir dist_root + “/public/uploads/“
# move
destination = dist_root + “/public/uploads//“
FileUtils.mv params[:Filedata][:tempfile].path, destination
else
false
end
#render_no_layout
end
end
That’s it for the Merb side of thing on to our AIR app, fire up Flex Builder and create a new AIR project

I like to keep my flex files in my Rails/Merb app directory

The AIR app is three files; the main MXML file (dist/app/fx/merb_air_upload.mxml), a code behind class (dist/app/fx/com/vixiom/merb_air_upload/App.as), and an upload progress component that gets repeated for each file (dist/app/fx/com/vixiom/merb_air_upload/UploadProgressComponent.mxml). Here’s the main MXML file:
It’s hooked up to it’s code behind ‘App.as’ class by the xmlns tag ) App.as extends WindowedApplication which is the base of all AIR apps:
package com.vixiom.merb_air_upload
{
import com.vixiom.merb_air_upload.UploadProgressComponent
import mx.core.WindowedApplication;
import mx.containers.VBox;
import mx.controls.Button;
import mx.events.FlexEvent;
import flash.events.*;
import flash.desktop.*;
import flash.filesystem.File;
import flash.net.*;
public
{
private var filesToUpload :Array
private var UploadProgressComponents :Array;
public var files_vb:VBox;
public var upload_btn:Button;
private var uploadURL:URLRequest;
/*
* Constructor
*/
public :void
{
addEventListener( FlexEvent.CREATION_COMPLETE, creationCompleteHandler );
}
/*
* creationComplete
*
* called when the AIR has finishe loading, sets up drag/drop event listeners reference objects
*
*/
private :void
{
addEventListener( NativeDragEvent.NATIVE_DRAG_ENTER, onDragEnter );
addEventListener( NativeDragEvent.NATIVE_DRAG_DROP, onDragDrop );
upload_btn.enabled = false;
upload_btn.addEventListener( MouseEvent.CLICK, upload );
uploadURL = new URLRequest();
uploadURL.url = “http://localhost:4000/upload“;
uploadURL.method=URLRequestMethod.POST;
filesToUpload = new Array();
UploadProgressComponents = new Array();
}
/*
* onDragEnter
*
* files have been dragged into the app
*/
private :void
{
DragManager.acceptDragDrop(this);
}
/*
* onDragDrop
*
* when files are dropped…
*/
private :void
{
DragManager.dropAction = DragActions.COPY;
var files:Array = event.transferable.dataForFormat( TransferableFormats.FILE_LIST_FORMAT ) as Array;
for each (var f:File in files)
{
addFile( FileReference( f ) );
}
upload_btn.enabled = true;
}
/*
* addFile
*
* …add then to filesToUpload array, and the file upload listeners,
* and create a progress component for each file
*/
private :void
{
filesToUpload.push( f );
var upv:UploadProgressComponent = new UploadProgressComponent();
UploadProgressComponents.push( upv );
files_vb.addChild( upv );
upv.file_lb.text = f.name;
upv.pb.source = f;
f.addEventListener( Event.COMPLETE, completeHandler );
f.addEventListener( IOErrorEvent.IO_ERROR, ioErrorHandler );
}
/*
* completeHandler
*
* a file upload is complete, remove it from filesToUpload
* and remove the upload component
*/
private :void
{
var f:FileReference = FileReference(e.target);
for( var i:uint; i < filesToUpload.length; i++ )
{
if( f.name == filesToUpload[i].name )
{
files_vb.removeChild( UploadProgressComponents[i] );
filesToUpload.splice(i, 1);
UploadProgressComponents.splice(i, 1);
}
}
}
/*
* trace any errors
*/
private :void
{
trace(“ioErrorHandler: “ + event);
}
/*
* upload!
*/
private :void
{
for each (var f:File in filesToUpload)
{
f.upload( uploadURL );
}
}
}
}
The last file is the upload progress component, it’s progress bar listens for events from each file (upv.pb.source = f; above in the addFile method)
That’s it! test your AIR app by dragging some files from the file system, once you drop them the upload progress components show a visual representation of the files, click ‘upload files’ and the files are upload all at once (for real real not for play play this time).

drag!

drop!

upload!




Like this post? subscribe to the feed.






