Ever since I’ve been using Rails I’ve missed one important tool from my webdev toolbox, Flash Remoting. I’ve been getting by with XML but it’s just not the same and it changes the way you approach things ("If all you have is a hammer everything looks like a nail"). Remoting can pass objects, arrays, strings etc. straight to Flash as native data types which avoids XML sit-ups (as the Rails folk would say). Remoting uses AMF (Active Message Format) which comes across as bytes so it’s faster than XML. Yesterday my prayers where answered as the Midnight Coders released WebORB for Ruby on Rails.
Their examples use Flex 2.0 which is the future of of Rich Internet Apps, however requiring Flash player 9.0 in all situations isn’t too user friendly (like on a hybrid HTML page) and since Remoting has been available since Flash player 6 (pre Ajax craziness) you should try to publish for only the features you need. That said I’m a design Nazi, and can’t get enough of anti-alias for readability, so I publish for Flash 8 as much as possible (Adobe claims 86.0% user penetration for player 8).
If you’ve never used Flash Remoting before you need to install the Remoting Components.
We’ll be hooking a Flash MP3 player up to a Rails back-end (which in real life would have some kind of CMS for adding MP3s). For this tutorial I ported a MP3 player I made for Buchanan a local Orange County band. The original uses XML as the data is static. I’ve replaced the Buchanan playlist with a “Midnight” themed one in honor of the hard work Midnight Coders put in to deliver Remoting on Rails for free!
On to the tutorial… a zip file of all the source code is here. All the flash files are in mp3app/fla.
Create a new rails app called ‘mp3app’
> rails mp3app
> cd mp3app
Install Weborb for Rails
> ruby script/plugin install http://themidnightcoders.net:8089/svn/weborb
Create a MySQL development database ‘mp3app_development’ with a table to hold your tracks
CREATE TABLE `tracks` (
`id` int(11) NOT NULL auto_increment,
`title` varchar(50) NOT NULL default ”,
`artist` varchar(50) NOT NULL default ”,
`album_art` varchar(50) NOT NULL default ”,
`filename` varchar(50) NOT NULL default ”,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
insert into `tracks` values(‘1′,‘After Midnight’,‘Eric Clapton’,‘clapton.jpg’,‘AfterMidnight.mp3′),
(‘2′,‘Midnight Train to Georgia’,‘Gladys Knight’,‘gladys.jpg’,‘MidnightTrainToGeorgia.mp3′),
(‘3′,‘Midnight In A Perfect World’,‘DJ Shadow’,’shadow.jpg’,‘MidnightInAPerfectWorld.mp3′),
(‘4′,‘Two Minutes to Midnight’,‘Iron Maiden’,‘maiden.jpg’,‘TwoMinutesToMidnight.mp3′);
Create a ‘Track’ model
> ruby script/generate model Track
Create a remoting service inside of the app/services directory called ‘TrackService.rb’
require ‘weborb/context’
require ‘rbconfig’
class TrackService
def getTracks
tracks = Track.find(:all)
end
end
If you’ve ever used AMFPHP or one of the .NET flavors of remoting you probably said "That’s it!?!". This is by far the shortest remoting service I’ve ever written. You should now be able to start the local lighttpd/WebBrick server and test the mp3Player.fla file locally. I’ve left all my traces in so you can see what’s going on.
Next on to Flash. There’s a few frameworks out there for Flash apps (ARP, Cairngorn) but for relatively simple things like our MP3 Player they’re overkill. I have my own lightweight framework (if it even qualifies as a framework, it’s only two files) that is a kind of MVC where the Model is a Remoting class that hooks up to the back-end (PHP, .NET, or now Rails) a Controller that is actually the .fla file, and a View class. The base Remoting and View classes have event dispatching for communicating with each other through the controller and that’s about it, any other functionality is extended with other classes.
The base Remoting class looks like this
import mx.remoting.Service;
import mx.services.Log;
import mx.remoting.PendingCall;;
import mx.rpc.RelayResponder;
import mx.rpc.FaultEvent;
import mx.rpc.ResultEvent;
import mx.remoting.debug.NetDebug;
import mx.utils.Delegate;
class com.vixiom.remoting.Remoting
{
private var gatewayURL:String;
private var servicePath:String;
private var svc:Service;
function dispatchEvent() {};
function addEventListener() {};
function removeEventListener() {};
public function Remoting(gURL, sp, u, p)
{
gatewayURL = gURL;
servicePath = sp;
mx.events.EventDispatcher.initialize(this);
svc = new Service (gatewayURL, null, servicePath, null, null);
if (u != undefined && p != undefined) {
svc.connection.setCredentials(u, p);
}
}
function handleRemotingError(fault:FaultEvent):Void
{
mx.remoting.debug.NetDebug.trace({level:“None”, message:“Error: “ + fault.fault.faultstring });
}
function dispatch(d, et)
{
var eventObj:Object={target:this,type:et}
eventObj.data = d;
dispatchEvent(eventObj);
}
}
This is the base View class
import mx.utils.Delegate;
class com.vixiom.view.View
{
private var target:MovieClip;
function dispatchEvent() {};
function addEventListener() {};
function removeEventListener() {};
public function View (t:MovieClip)
{
target = t;
mx.events.EventDispatcher.initialize(this);
}
function dispatch(d, et)
{
var eventObj:Object={target:this,type:et}
eventObj.data = d;
dispatchEvent(eventObj);
}
}
For the MP3 Player I extended the Remoting class as shown below. You don’t have to keep your classes in packages (com.vixiom.remoting…) but it helps to keep them organized. ‘var pc:PendingCall = this.svc.getTracks();‘ is the line that makes the remoting call to Rails (it corresponds to ‘getTracks‘ in our TrackService.rb class). My class has a ‘tracks’ object and it receives the tracks object straight from ruby ‘tracks = re.result;‘. I’ve commented out the line ‘‘ as that’s for creating secured remoting calls. The last line ‘dispatch(tracks, "onGetTracks");‘ uses even dispatching to pass data to the view.
import mx.remoting.*;
import mx.rpc.*;
class com.vixiom.remoting.RemotingMp3 extends com.vixiom.remoting.Remoting
{
private var tracks:Object;
public function RemotingMp3 (gURL, sp, u, p)
{
super(gURL, sp);
}
public function getTracks()
{
trace(“// getting tracks”)
var pc:PendingCall = this.svc.getTracks();
pc.responder = new RelayResponder(this, “onGetTracks”, “handleRemotingError”);
}
public function onGetTracks (re:ResultEvent)
{
if (re != undefined)
{
trace(“// onGetTracks broadcaster - Word!”)
tracks = re.result;
for (var i = 0; i < tracks.length; i++) {
trace(tracks[i].title);
}
dispatch(tracks, “onGetTracks”);
}
}
}
This is a little bit out of order but I’m going to show the ‘controller’ code next. The thing to notice is ‘Rmp3′ is an instance of my Extended Remoting class and it has two parameters the weborb gateway URL ‘http://localhost:3000/weborb‘ and the ruby class that’s in the app/services folder ‘TrackService‘. I haven’t shown the Extended View class yet bu the controller code is also creating a ‘Vmp3′ instance of it with the _root of the Flash file as it’s parameter (it uses that as a target). The next four lines are delegating button functions that are in the Extended View, the last line ‘Rmp3.getTracks();‘ calls the remoting method and is the entry point for the app (as it’s pretty useless sans data).
import mx.remoting.debug.NetDebug;
import mx.utils.Delegate;
import com.vixiom.remoting.RemotingMp3;
import com.vixiom.view.ViewMp3;
NetDebug.initialize ();
iniApp();
function iniApp()
{
var Rmp3:RemotingMp3 = new RemotingMp3 (“http://localhost:3000/weborb”, “TrackService”);
var Vmp3:ViewMp3 = new ViewMp3 (_root);
Rmp3.addEventListener (“onGetTracks”, Delegate.create (Vmp3, Vmp3.onGetTracks));
pause_btn.onRelease = Delegate.create(Vmp3, Vmp3.pauseTrack);
play_btn.onRelease = Delegate.create(Vmp3, Vmp3.playTrack);
prev_btn.onRelease = Delegate.create(Vmp3, Vmp3.previousTrack);
next_btn.onRelease = Delegate.create(Vmp3, Vmp3.nextTrack);
Rmp3.getTracks();
}
Here’s the Extended View. It’s pretty complicated and unrelated to Rails or Remoting so I’ll just wave my hands and say "Ta da!", hopefully my comments explain what’s going on. Our tracks object from the remoting side hitches a ride on an event object in the listener ‘onGetTracks’ (tracks = eventObj.data;)
class com.vixiom.view.ViewMp3 extends com.vixiom.view.View
{
private var tracks :Object;
private var currentTrack :MovieClip;
private var albumArt :MovieClip;
private var currentTrackNumber :Number;
private var totalTracks :Number;
private var pausePos :Number;
private var songPosInterval :Number
public function ViewMp3 (t:MovieClip)
{
super(t);
}
public function onGetTracks(eventObj:Object)
{
trace (“// onGetTracks listener - Word heard!”)
tracks = eventObj.data;
totalTracks = tracks.length;
iniPlayer();
}
public function iniPlayer ()
{
var vObj:ViewMp3 = this;