Vixiom Axioms

June 29, 2007

Merb on AIR - Drag and Drop Multiple File Upload

Filed under: AIR, ActionScript, Flex, Merb, Ruby Alastair @ 12:07 am

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!

Digg! submit Merb on AIR - Drag and Drop Multiple File Upload to stumbleupon.com submit Merb on AIR - Drag and Drop Multiple File Upload to del.icio.us submit Merb on AIR - Drag and Drop Multiple File Upload to reddit.com Like this post? subscribe to the feed.

June 27, 2007

I heart Merb

Filed under: Flash, Flex, Merb, Ruby Alastair @ 10:53 pm

Yes every new framework sets my heart a flutter, but this time it’s for real.

Developing Flex/Flash on top of Rails? What if you could get ActiveRecord and RESTful routing at almost eight times the speed with 75% less RAM? How about all that and real concurrency, including true multiple file upload? All of the above without the Rails attitude? You need to get you some Merb.

Models look the same:

class Photo < ActiveRecord::Base
  belongs_to :photo_roll
  acts_as_list :scope => :photo_roll

  def to_json
    self.attributes.to_json
  end
end

Controllers look the same:

class PhotoRolls < Application

  def index
    @photo_rolls = PhotoRoll.find(:all)
    render :js => @photo_rolls.to_json
  end

end

Then consume away in Flex…

public function find() : void
        {
            var svc:JSONRESTService = new JSONRESTService();
            svc.url = /photo_rolls
            svc.sendRESTfully( index, null, onResult, onFault );
        }

That’s a custom JSON REST Service which will be showing up in a future tutorial.

or use HAML as a built in templating engine for all your HTML needs.

Merb!
Merb Docs!
Merb Tutorial!

Digg! submit I heart Merb to stumbleupon.com submit I heart Merb to del.icio.us submit I heart Merb to reddit.com Like this post? subscribe to the feed.

June 14, 2007

My Dock Overfloweth

Filed under: OS X Alastair @ 7:19 pm

This might be obsolete when Apple releases Leopard and it’s desktop stacks, but Overflow is a great utility app for organizing your shise. Overflow let’s you group applications, folders and files into categories that you can quickly access by hitting ‘F1′. Below is my ‘Dev’ category, I also make a category for all my current projects with links to the more frequently accessed (but hard to find) files like stylesheets.

Overflow

Digg! submit My Dock Overfloweth to stumbleupon.com submit My Dock Overfloweth to del.icio.us submit My Dock Overfloweth to reddit.com Like this post? subscribe to the feed.

June 12, 2007

Get on the bus

Filed under: AIR, Flex Alastair @ 11:33 am

I just registered for the LA stop of the Adobe AIR Bus Tour. It’ll be cool to meet some other Flex/AIR developers in SoCal.

Digg! submit Get on the bus to stumbleupon.com submit Get on the bus to del.icio.us submit Get on the bus to reddit.com Like this post? subscribe to the feed.

June 7, 2007

Remove a project from subversion control

Filed under: svn Alastair @ 9:48 am

About twice a year I need to completely remove a project from svn, I can never find/remember the snippet so I’m posting it on the internets.

# to remove svn from an existing app
find . -name .svn -print0 | xargs -0 rm -rf
Digg! submit Remove a project from subversion control to stumbleupon.com submit Remove a project from subversion control to del.icio.us submit Remove a project from subversion control to reddit.com Like this post? subscribe to the feed.

June 5, 2007

Rails helper for SWFObject

Filed under: ActionScript, Flash, Flex, JavaScript, Ruby on Rails Alastair @ 2:57 pm

SWFObject (formally know as FlashObject) is a javascript file that unobtrusively embeds flash in a web page. It works by replacing a holder div with Flash content, if the end user doesn’t have Flash or the targeted Flash player SWFObject will ‘fail’ silently and keep the original div. Here’s a Rails helper that let’s you use SWFObject in your views.

The steps are:

1) download SWFObject, and place it in ‘public/javascripts’

2) include it in your layout file

<%= javascript_include_tag "swfobject" %>

3) put the helper in ‘app/helpers/application_helper.rb’:

# swf_object
  def swf_object(swf, id, width, height, flash_version, background_color, params = {}, vars = {}, create_div = false)
    # create div ?
    create_div ? output = "<div id=’#{id}‘>This website requires <a href=’http://www.adobe.com/shockwave/download/download.cgi?P1_Prod_Version=ShockwaveFlash&promoid=BIOW’ target=’_blank’>Flash player</a> #{flash_version} or higher.</div><script type=’text/javascript’>" : output = "<script type=’text/javascript’>"
    output << "var so = new SWFObject(’#{swf}‘, ‘#{id}‘, ‘#{width}‘, ‘#{height}‘, ‘#{flash_version}‘, ‘#{background_color}‘);"
    params.each  {|key, value| output << "so.addParam(’#{key}‘, ‘#{value}‘);"}
    vars.each    {|key, value| output << "so.addVariable(’#{key}‘, ‘#{value}‘);"}
    output << "so.write(’#{id}‘);"
    output << "</script>"
  end

4) Then in any rhtml page you can use the helper like so (note: place your SWFs in ‘public/swfs’):

<%= swf_object("/swfs/myswf.swf", "flash_id", "550", "400", "9", "#000000",
        {:wmode => "transparent", :quality => "high"},
        {:myvar1 => "foo1", :myvar2 => "foo2"}) %>

In the above code the flash content will replace a div with the id ‘flash_id’, the required arguments are (SWF File location, holder div to replace, width, height, flash version, background color) it also shows two parameters being set ‘wmode’ to transparent (for those annoying flash overlay ads) and ‘quality’ to high, it also passes two variables to the SWF ‘myvar1′ and ‘myvar2′ which are then available at root level of your SWF (’_level0.myvar’ in AS2, a bit trickier in AS3, and ‘Application.application.parameters.myvar’ with Flex).

4b) I, being a lazy/absent-minded programmer, included an option to programmatically create the holder div, just add true as the last argument:

<%= swf_object("/swfs/myswf.swf", "flash_id", "550", "400", "9", "#000000",
        {:wmode => "transparent", :quality => "high"},
        {:myvar1 => "foo1", :myvar2 => "foo2"},
        true) %>
Digg! submit Rails helper for SWFObject to stumbleupon.com submit Rails helper for SWFObject to del.icio.us submit Rails helper for SWFObject to reddit.com Like this post? subscribe to the feed.

June 4, 2007

Flex 3 Preview

Filed under: Flex Alastair @ 8:27 am

Ted on Flex is previewing enhancements in the next version of Flex. First up is a look at better tools for designing/laying out your Flex app. I currently end up with a chain of HBox VBox components embedded together to get my layout ‘just’ right so the new advanced constraints sound great:

Advanced Constraints:
There are 2 new components for constraint based layout, mx:ConstraintRow and mx:ConstraintColumn. These new components allow you to subdivide a containers supporting absolute positioning. Where Flex 2 provided parent-child constraints, Advanced constraints allow definition of sibling relative constraints.

Digg! submit Flex 3 Preview to stumbleupon.com submit Flex 3 Preview to del.icio.us submit Flex 3 Preview to reddit.com Like this post? subscribe to the feed.