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 ‘dist/app/fx‘).

If you haven’t before install Merb

$ sudo gem install merb

Then create a new Merb app

$ merb -g merb_air_upload

and dive on in

$ cd merb_air_upload

We’ll need two folders not in a Merb skeleton, one for Flex, and one for our uploads

$ mkdir dist/app/fx
$ mkdir dist/public/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

$ ./script/new_migration CreateUserFiles

and edit it (dist/schema/migrations/002_create_user_files.rb) to look like so

class CreateUserFiles < ActiveRecord::Migration
  def self.up
    create_table :user_files do |t|
      t.column :filename, :string, :null => false
    end
  end

  def self.down
    drop_table :user_files
  end
end

rake your db

$ rake db:migrate

Then create a UserFile model (dist/app/models/user_file.rb)

class UserFile < ActiveRecord::Base
end

and an upload controller (dist/controllers/upload.rb)

class Upload < Application

  def index
    # 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/#{@upload.id}"
      # move
      destination = dist_root + "/public/uploads/#{@upload.id}/#{params[:Filename]}"
      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:

<?xml version="1.0" encoding="utf-8"?>
<app:App
    xmlns:mx="http://www.adobe.com/2006/mxml"
    xmlns:app="com.vixiom.merb_air_upload.*"
    layout="vertical"
    width="300" height="375"
    backgroundGradientAlphas="[1.0, 1.0]"
    backgroundGradientColors="[#F4F4F4, #E0E0E0]"
    paddingBottom="10" paddingLeft="10" paddingRight="10" paddingTop="10"
    verticalScrollPolicy="off" horizontalScrollPolicy="off">

    <mx:VBox id="files_vb"
        width="100%" height="300"
        backgroundColor="#FFFFFF"
        horizontalScrollPolicy="off"
        paddingBottom="5" paddingLeft="5" paddingRight="5" paddingTop="5" borderColor="#ABABAB" borderStyle="inset">
    </mx:VBox>

    <mx:Button id="upload_btn" label="Upload Files"/>

</app:App>

It’s hooked up to it’s code behind ‘App.as’ class by the xmlns tag (xmlns:app=com.vixiom.merb_air_upload.*) 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 class App extends WindowedApplication
    {
        private var filesToUpload :Array
        private var UploadProgressComponents :Array;
        public var files_vb:VBox;
        public var upload_btn:Button;
        private var uploadURL:URLRequest;

        /*
         * Constructor
         */
        public function App() :void
        {
            addEventListener( FlexEvent.CREATION_COMPLETE, creationCompleteHandler );
        }

        /*
         * creationComplete
         *
         * called when the AIR has finishe loading, sets up drag/drop event listeners reference objects
         *
         */
        private function creationCompleteHandler( event:FlexEvent ) :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 function onDragEnter( event:NativeDragEvent ) :void
        {
           DragManager.acceptDragDrop(this);
        }

        /*
         * onDragDrop
         *
         * when files are dropped...
         */
        private function onDragDrop( event:NativeDragEvent ) :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 function addFile( f:FileReference ) :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 function completeHandler( e:Event ) :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 function ioErrorHandler( event:IOErrorEvent ) :void
        {
            trace("ioErrorHandler: " + event);
        }

        /*
         * upload!
         */
        private function upload( e:MouseEvent ) :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)

<?xml version="1.0" encoding="utf-8"?>
<mx:VBox
    xmlns:mx="http://www.adobe.com/2006/mxml"
    paddingBottom="5" paddingLeft="5" paddingRight="5" paddingTop="5"
    borderColor="#BABABA" borderStyle="solid" borderThickness="2"
    width="100%"
    backgroundColor="#F7F7F7">

    <mx:Label id="file_lb" text="Label" fontWeight="bold" color="#000000"/>
    <mx:ProgressBar id="pb" labelPlacement="right" label="" width="100%"/>

</mx:VBox>

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!

29 Comments

  1. Posted June 29, 2007 at 5:37 am | Permalink

    Awesome. I have been wanting to play with Flex and Merb and now you have given me some gound to work on. Cheers

  2. Posted June 29, 2007 at 7:12 am | Permalink

    Very cool. It’s awesome to see Merb getting more use. :)

  3. tomo_atlacatl
    Posted July 9, 2007 at 3:43 pm | Permalink

    Hey, u once fancy jerself w/PHP…….would love to see this on php, or on the same realm CAKEPHP? :) any chance?

  4. Posted July 9, 2007 at 7:09 pm | Permalink

    Tomo I know you’re smart enough to make a PHP version, the upload script is ‘cake’ so it could be done in Cake. How about a Strata version :P

  5. tomo_atlacatl
    Posted July 10, 2007 at 2:16 pm | Permalink

    Yeah, I know…. I just wanted to tempt ya back to the php side :P . Strata, heh, haven’t touch dat in a while.

  6. Posted August 9, 2007 at 2:50 pm | Permalink

    Great example, much appreciated and well done.

  7. Posted August 9, 2007 at 4:26 pm | Permalink

    thanks Evan,

    Let me know when flexheads.com is launched, I use Cairngorm on some larger projects but still haven’t wrapped my brain completely around it.

  8. paul
    Posted October 2, 2007 at 11:43 am | Permalink

    Update for flex3 beta 2, change the line in onDrop from:

    var files:Array = event.transferable.dataForFormat( TransferableFormats.FILE_LIST_FORMAT ) as Array;

    to:

    var dropfiles:Array = event.clipboard.dataForFormat(flash.desktop.ClipboardFormats.FILE_LIST_FORMAT) as Array;

    and add

    import flash.desktop.ClipboardFormats;

  9. paul
    Posted October 2, 2007 at 11:50 am | Permalink

    And if I double checked before posting I could fix my code to match the code here.

    var files:Array = event.transferable.dataForFormat( TransferableFormats.FILE_LIST_FORMAT ) as Array;

    to:

    var files:Array = event.clipboard.dataForFormat(flash.desktop.ClipboardFormats.FILE_LIST_FORMAT) as Array;

  10. Nico
    Posted October 10, 2007 at 1:26 am | Permalink

    Hi! Looks like great work! ;-) Is it possible to combine this with a rails app? I’d like to just use merb for uploading, so I’d like to be able to open the uploader from my rails app. Can anybody give me some hints on how to do that? Another thing I’m curious about is how the merb and the air application communicate with each other – I’m relatively new to web development. Also does anybody know about how to add a button to the AIR app for adding files for uploading, using a “traditional” file dialogue? And are there any problems with deploying a rails app that also has a MERB part?

  11. Gerry McLarnon
    Posted October 24, 2007 at 2:05 am | Permalink

    Very nice.

    Can this work with an HTTPS upload URL?

    Thanks, Gerry

  12. Posted October 24, 2007 at 9:16 am | Permalink

    Gerry,

    I haven’t tried it with HTTPS, I know with Flex the Flash player there are special security requirements when using HTTPS http://livedocs.adobe.com/flex/2/langref/flash/system/Security.html#allowDomain() not sure if that also applies to AIR.

  13. Posted October 24, 2007 at 9:19 am | Permalink

    doh that link got cut off the HTML anchor should include the parenthesis after ‘allowDomain’ http://livedocs.adobe.com/flex/2/langref/flash/system/Security.html#allowDomain()

  14. Michael Guterl
    Posted December 15, 2007 at 1:58 pm | Permalink

    Ruby is the programming language under which the Ruby on Rails framework runs. Merb is not a Ruby on Rails gem but a Ruby gem. Merb is a MVC web framework similar to Rails. Please do not confuse the two, Ruby is the language, Rails is the framework.

  15. Posted December 16, 2007 at 10:37 am | Permalink

    Thank you fricking captain of the programming police.

  16. Dipen Kumar
    Posted January 4, 2008 at 8:46 am | Permalink

    Hi,

    I’m having a little trouble when importing the ‘In a hurry’ version into Flex as an Existing Project.

    I instantly receive these errors having made no changes to the code:

    1119: Access of possibly undefined property transferable through a reference with static type flash.events:NativeDragEvent.
    1120: Access of undefined property DragActions.
    1120: Access of undefined property DragManager.
    1120: Access of undefined property DragManager.
    1120: Access of undefined property TransferableFormats.

    I feel as though I am missing something very simple here, any help would be greatly appreciated!
    D.Kumar

  17. Posted January 4, 2008 at 10:48 am | Permalink

    Hey,

    It looks like the Flex Drag classes have changed in Flex Beta 3 (this was made with Beta 2), Merb has also changed since this post, such is the life of living on the egde!

    I’ve posted a fix here http://actionsnip.com/snippets/vixiom/merb-on-air-fix-for-flex-beta-3
    BTW your the first to see actionsnip.com as I just released it yesterday :)

    - Alastair

  18. Dipen Kumar
    Posted January 8, 2008 at 2:30 am | Permalink

    Hi Alastair,

    Thank you for the swift response, my Flex project with the drag and drop feature now works very nicely :)

    However… the problem I have now is that MERB is not allowing me to perform a db:migrate. I’ve checked the methods inside the MERB app and the db:migrate method does not exist. I have tried various things to run db:migrate but have not been successful.

    Again, any help would be greatly appreciated. This is the last step that is needed to complete the project for me.

    Oh, also, I’m impressed with your website, looks like a useful resource for me.

    D.Kumar

  19. Dipen Kumar
    Posted January 8, 2008 at 7:31 am | Permalink

    Okay, last one I promise!

    Here we go…I figured out why the rake db:migrate was not working. The reason was because I had forgotten about the configuring of a database with MERB as MERB does not come with database support. Anyway, MERB connects fine with the database but the rake db:migrate does not work because it cannot find the schema.rb file even though it exists in dist/schema/schema.rb.
    Thoughts, suggestions, plain old pointing out the obvious would help me so much,

    D.Kumar

  20. Posted January 28, 2008 at 4:37 am | Permalink

    Hey, does this solution work with Files bigger than 100MB?

    Thanks,
    Marcel

  21. Posted January 28, 2008 at 8:50 am | Permalink

    Hi Marcel,

    I’m not sure if this applies to AIR apps but I know Flash file upload only handles files less than 100mb.

  22. abhishek
    Posted January 28, 2008 at 11:34 pm | Permalink

    could u send code for upload videos files in flex and send its data to rails ?

  23. Nuno
    Posted April 22, 2008 at 3:39 am | Permalink

    hi
    how can i intregate the air aplication with php to upload files to a ftp server

  24. archie
    Posted April 25, 2008 at 8:30 am | Permalink

    Hello,

    Thank you very much for posting the code for this app. Would it be tough to modify this air-application to be a regular flex application? Thanks.

  25. Posted May 13, 2009 at 2:15 am | Permalink

    Nice tutorial. Can i get the source files please??

  26. Posted July 21, 2009 at 3:49 pm | Permalink

    am i missing something really obvious, the merb -g appName, just gives errors, if i use merb-gen i dont get the same folders and some of the scripts are missing, can you point me in the right direction?

    thanks

  27. Levi
    Posted March 22, 2010 at 10:11 am | Permalink

    Is it possible to have the files upload one at a time instead of simultaneously? If so, how can I do that?

  28. hithere
    Posted July 13, 2010 at 7:04 pm | Permalink

    Can you post a new link to the Flex 3 version, the posted link is dead.
    thanks

  29. Posted January 21, 2011 at 9:57 pm | Permalink

    Hi the drag and drop code works fine….How to do this in the case of mp3 files instead of images.and also i want to drag an mp3 file from AIR to desktop.

11 Trackbacks

  1. [...] I also ran through the Merb/AIR tutorial here: Merb on AIR – Drag and Drop Multiple File Upload. [...]

  2. [...] Check out this AIR tutorial for Drag and Drop Multiple File Upload using Merb and AIR.You need Flex builder 3 Beta for this. more @ http://blog.vixiom.com/2007/06/29/merb-on-air-drag-and-drop-multiple-file-upload/ [...]

  3. [...] and Drop Multiple File UploadHere is the tutorials about Drag and Drop Multiple File Upload.It’s actually using Flex 3 and Adobe AIR.The server side is using the Ruby on Rails gem Merb to [...]

  4. By 21 Merb Links, Tutorials and Other Resources on February 4, 2008 at 8:20 pm

    [...] Merb on AIR – Drag and Drop Multiple File Upload – A slightly old (June 2007) tutorial demonstrating how to use Merb alongside an Adobe AIR powered client to handle file uploads. [...]

  5. By Philip Arkcoll » Flex AIR Drop Box Prototype on April 1, 2008 at 1:49 pm

    [...] http://blog.vixiom.com/2007/06/29/merb-on-air-drag-and-drop-multiple-file-upload/ [...]

  6. By Adobe AIR is… | Psyked on April 13, 2008 at 3:02 am

    [...] And raw byte access is enabling everything from image and video encoding, to email clients to ftp clients, just weeks after AIR is launched. And heck, I nearly forgot about the online / offline [...]

  7. By 9 Flex File Upload Examples Visited on May 8, 2008 at 6:05 am

    [...] Drag and Drop Flex File Upload with Ruby on Rails [...]

  8. By Top 3 Flex File Upload Components | Flex Tech on June 9, 2008 at 10:38 am

    [...] and Drop Multiple File Upload Here is the tutorials about Drag and Drop Multiple File Upload.It’s actually using Flex 3 and Adobe AIR.The server side is using the Ruby on Rails gem Merb to [...]

  9. [...] Whilst writing an Adobe Lightroom plugin to upload library items to a rails app, i spotted something i definitely need to play with! Adobe Air and Merb file uploader. Looks rather handy! [...]

  10. [...] Next, I’ll be adding Ruby-Processing and setting up BlazeDS, and finally I’m going to be making a Merb back-end to accept multiple simultaneous file uploads to a CouchDB store via an Air client app similar to the the one the Vixiom guys described a year ago here. [...]

  11. By private air on October 13, 2009 at 4:05 pm

    private air…

    DojoCampus ” Blog Archive ” Dojo and Air, a fancy file uploader is an excellent site and I have to say that I am really impressed….

Post a Comment

Your email is never shared. Required fields are marked *

*
*

*
To prove you're a person (not a spam script), type the security word shown in the picture. Click on the picture to hear an audio file of the word.
Click to hear an audio file of the anti-spam word