Flex 2 FMS Explorer

The Flex 2 FMS Explorer started off in response to a post by Bill Sanders on Stefan Richter's FlashMedia mailing list. Bill noted that he had to write twice as much code in AS 3 as in AS 2 to create the simplest of FMS applications. As a result he started to create basic templates he could use to build quick tests and sample FMS applications. After moving most of my work to Flex I found I was building fewer and fewer small test applications and that's not good. Like Bill, I needed a simple way to try things out and quickly solve problems. So, I thought it might be interesting to extend the idea of reusing a template to creating a basic application shell that I could add FMS sample code to as needed. The idea was that each sample could live on its own as a separate component within a larger test-bed application instead of creating a new project or application for each test. Thinking of how useful I've found Adobe's Flex 2 Style Explorer, I built the Flex 2 FMS Explorer with it in mind.

Try out the Flex 2 FMS Explorer here.

The source code is available by right clicking in a non-text area and selecting View Source in the pop-up menu. The server-side code is available in the applications/flex2FMSExplorer folder within the View Source frameset.

Samples

Aside from the code to connect the Flex 2 client to FMS, several sample panels are provided:

Each sample is designed to provide source code that demonstrates how to do things like work with streams, shared objects, and remote method calls. All of them include a TextArea at the top of the panel where test output appears as various events occur. The components are designed to show how things work and not how to build production grade communication components.

Making Use of the Flex 2 FMS Explorer

One way to use the explorer is to just try it out and look at the source code. But a better way to use it is to download it and experiment with the code, then start building your own test samples. Each sample actually has two parts: a subclass of a Flex Panel that appears in the main ViewStack and a server-side class that it works with. Since I wanted multiple sample panels to work in the same application I had to build a tiny server-side framework to load and pass events to each server-side sample object.

Downloading and Installing the Flex 2 FMS Explorer

Before creating your own samples you should download and import into Flex 2 the flex2FMSExplorer archive file. To import it create a new project named Flex2FMSExplorer in Flex 2 and then chose File > Import > Archive File. Importing it into the root folder "/" should work. Once the file is imported you should locate the flex2FMSExplorer folder in the applications directory and copy it into your FMS applications folder. With those steps complete you should be able to compile and run flex2FMSExplorer locally. You should also change the connection string to rtmp://localhost/flex2FMSExplorer in the pfms/fms/components/connect/SimpleConnectForm.mxml file.

Creating Your Own Samples

Once you import the Flex2FMSExplorer project into Flex 2 and copy the applications/flex2FMSExplorer folder you should be ready to create a new sample panel. Here's how you could create a simple test sample panel to test out calling server-side functions and client-side functions:

To start with we need a name for the sample panel component. So, we'll call it SimpleRemoteCalls.

Write the Server-Side Sample Code

To create the server side SimpleRemoteCalls class do the following:

  1. create a new folder in the .../applications/flex2FMSExplorer/source/samples directory named simpleRemoteCalls
  2. create a new file in the .../applications/flex2FMSExplorer/source/samplessimpleRemoteCalls/ directory named SimpleRemoteCalls.asc
  3. In the SimpleRemoteCalls.asc file add the following code:
    /**
     * Declare the JavaScript 1.5 SimpleRemoteCalls function so we can add methods to its prototype
     */
    function SimpleRemoteCalls(){
    
    	// Keep a reference to this object so we can call it's methods within a Client method.
    
    	var simpleRemoteCalls = this;
    
    	// Whenever the client object's SimpleRemoteCalls|getServerTime method is called
    	// call the simpleRemoteCalls object's getServerTime method and return the result.
    	Client.prototype["SimpleRemoteCalls|getServerTime"] = function(){
    		return simpleRemoteCalls.getServerTime();
    	}
    }
    
    /** Remember the class name for this object **/
    SimpleRemoteCalls.prototype.className = "SimpleRemoteCalls";
    
    /**
     * Return a new Date object so the client knows what time/date it is
     * on the server.
     */
    SimpleRemoteCalls.prototype.getServerTime = function(){
    	return new Date();
    }
  4. Finally open the main.asc file and add to the list of sample class files to load:
    sampleFramework.loadSampleClasses("source/samples/minimalSample/MinimalSample.asc",
    	"source/samples/peopleList/PeopleList.asc",
    	"source/samples/simpleLiveStream/SimpleLiveStream.asc",
    	"source/samples/controlledLiveStream/ControlledLiveStream.asc",
    	"source/samples/simpleVideoConference/SimpleVideoConference.asc",
    	"source/samples/plainTextChat/PlainTextChat.asc",
    	"source/samples/sampleTemplate/SampleTemplate.asc",
    	"source/samples/sharedBall/SharedBall.asc",
    	"source/samples/scratchPad/ScratchPad.asc",
    	"source/samples/sharedText/SharedText.asc",
    	"source/samples/simpleRemoteCalls/SimpleRemoteCalls.asc",
    	"source/samples/simpleSharedObjectEditor/SimpleSharedObjectEditor.asc");
    
    and then save the file. The added line is in bold.

Write the Client-Side Sample Code

Now let's create the client side of the simple remote calls sample:

Try It Out

At this point you should be able to compile, run, and connect the application to FMS. Clicking on the "Simple Remote Calls" button should show a simple TextArea with the message: set netConnection> netConnection: [object FMSConnection]

Now, let's go back to the SimpleRemoteCalls.mxml code and:

Here's the completed code for you with the new code in bold:

<?xml version="1.0" encoding="utf-8"?>
<mx:Panel implements="pfms.samples.ISample"
	xmlns:mx="http://www.adobe.com/2006/mxml"
	width="100%" height="100%"
	title="Simple Remote Calls" label="Simple Remote Calls"
	backgroundAlpha="0.69"
	paddingTop="6" paddingBottom="6" paddingLeft="6" paddingRight="6" verticalGap="6">
	<mx:TextArea id="traceArea" width="100%" height="100%"/>
	<mx:Button id="getServerTimeButton" label="Get Server Time" click="getServerTime()"/>

	<mx:Script>
		<![CDATA[

			import flash.net.NetConnection;
			import pfms.fms.User;
			import flash.net.Responder;

			private var _netConnection:NetConnection;

			public function set user(user:User):void{
				// Not used in this sample.
			}

			/**
			 * set netConnection is the place to initialize the sample:
			 */
			public function set netConnection(netConnection:NetConnection):void{
				_netConnection = netConnection;
				writeln("set netConnection> netConnection: " + netConnection);
			}

			/**
			 * disconnect is called when the sample is no longer in use
			 * or when the NetConnection is lost.
			 */
			public function disconnect():void{
				writeln("disconnect>");
			}
			
			private function getServerTime():void{
				// Note: className will evaluate to SimpleRemoteCalls
				// so the call will be to "SimpleRemoteCalls|getServerTime"
				_netConnection.call(className + "|getServerTime", new Responder(getServerTimeResult, getServerTimeStatus));
			}

			public function getServerTimeResult(dateTime:Date):void{
				writeln("getServerTimeResult> dateTime: " + dateTime);
			}

			public function getServerTimeStatus(status:Object):void{
				writeln("getServerTimeStatus> status: " + status);
				for (var p:String in status){
					writeln("   " + p + ": " + status[p]);
				}
			}
			
			/**
			 * writeln is used to write information about how the sample is
			 * working to the traceArea.
			 */
			public function writeln(msg:String):void{
				traceArea.text += msg + "\n";
				traceArea.validateNow();
				traceArea.verticalScrollPosition = traceArea.maxVerticalScrollPosition;
			}


		]]>
	</mx:Script>
</mx:Panel>

Now, pressing the button should display the server time in the text area. If you've followed on this far, try it out!

Calling A Client-Side Method from the Server

Now let's have the server ask for the time of the client. An obvious thing to do would be to have the server call the client as soon as it connects. But since the SimpleRemoteCalls panel might not be connected at the time, we'll just call the server and tell it to call us back. Here's the updated code that does this:

Server-Side Code

/**
 * Declare the JavaScript 1.5 SimpleRemoteCalls function so we can add methods to its prototype
 */
function SimpleRemoteCalls(){

	// Keep a reference to this object so we can call it within a Client method.
	var simpleRemoteCalls = this;

	// Whenever the client object's SimpleRemoteCalls|getServerTime method is called
	// call the simpleRemoteCalls object's getServerTime method and return the result.
	Client.prototype["SimpleRemoteCalls|getServerTime"] = function(){
		return simpleRemoteCalls.getServerTime();
	}

	// As above, pass on the callMeBack function call to the simpleRemoteCalls object.
	// But this time use the this keyword to also pass a reference to the client
	// to the simpleRemoteCalls.callMeBack method.
	Client.prototype["SimpleRemoteCalls|serverGetClientTime"] = function(){
		simpleRemoteCalls.serverGetClientTime(this);
	}

	// Setup a responder object for the client call:
	this.getServerTimeResponder = {
		onResult: function(result){
			trace("SimpleRemoteCalls.getServerTimeResponder.onResult> result: " + result);
		},
		onStatus: function(status){
			trace("SimpleRemoteCalls.getServerTimeResponder.onStatus> status: " + status);
			for (var p in status) trace("  " + p + ": " + status[p]);
		}
	}
}

/** Remember the class name for this object **/
SimpleRemoteCalls.prototype.className = "SimpleRemoteCalls";

/**
 * Return a new Date object so the client knows what time/date it is
 * on the server.
 */
SimpleRemoteCalls.prototype.getServerTime = function(){
	return new Date();
}

/**
 * Return a new Date object so the client knows what time/date it is
 * on the server.
 */
SimpleRemoteCalls.prototype.serverGetClientTime = function(client){
	// Now call a remote method on the client:
	client.call(this.className + "|getClientTime", this.getServerTimeResponder);
}

Client-Side Code

<?xml version="1.0" encoding="utf-8"?>
<mx:Panel implements="pfms.samples.ISample"
	xmlns:mx="http://www.adobe.com/2006/mxml"
	width="100%" height="100%"
	title="Simple Remote Calls" label="Simple Remote Calls"
	backgroundAlpha="0.69"
	paddingTop="6" paddingBottom="6" paddingLeft="6" paddingRight="6" verticalGap="6">
	<mx:TextArea id="traceArea" width="100%" height="100%"/>
	<mx:Button id="getServerTimeButton" label="Get Server Time" click="getServerTime()"/>
	<mx:Button id="getClientTimeButton" label="Server, Get Client Time" click="serverGetClientTime()"/>

	<mx:Script>
		<![CDATA[

			import flash.net.NetConnection;
			import pfms.fms.User;
			import flash.net.Responder;

			private var _netConnection:NetConnection;

			public function set user(user:User):void{
				// Not used in this sample.
			}

			/**
			 * set netConnection is the place to initialize the sample:
			 */
			public function set netConnection(netConnection:NetConnection):void{
				_netConnection = netConnection;
				writeln("set netConnection> netConnection: " + netConnection);

				// Add a method to the netConnection.client object that calls a method
				// on this sample panel.
				var client:Object = _netConnection.client;
				var simpleRemoteCalls:SimpleRemoteCalls = this;

				client[className + "|getClientTime"] = function():Date{
					return simpleRemoteCalls.getClientTime();
				}

			}
			/** function that will be called by the server.*/
			public function getClientTime():Date{
				var now:Date = new Date();
				writeln("getClientTime> Returning date: " + now + " to the server.");
				return now;
			}

			/**
			 * disconnect is called when the sample is no longer in use
			 * or when the NetConnection is lost.
			 */
			public function disconnect():void{
				writeln("disconnect>");
			}

			private function getServerTime():void{
				_netConnection.call(className + "|getServerTime", new Responder(getServerTimeResult, getServerTimeStatus));
			}

			public function getServerTimeResult(dateTime:Date):void{
				writeln("getServerTimeResult> dateTime: " + dateTime);
			}

			public function getServerTimeStatus(status:Object):void{
				writeln("getServerTimeStatus> status: " + status);
				for (var p:String in status){
					writeln("   " + p + ": " + status[p]);
				}
			}

			/** function to tell the server to ask the client what time it is.*/
			private function serverGetClientTime():void{
				_netConnection.call(className + "|serverGetClientTime", null);
			}
				
			/**
			 * writeln is used to write information about how the sample is
			 * working to the traceArea.
			 */
			public function writeln(msg:String):void{
				traceArea.text += msg + "\n";
				traceArea.validateNow();
				traceArea.verticalScrollPosition = traceArea.maxVerticalScrollPosition;
			}


		]]>
	</mx:Script>
</mx:Panel>

Summary

The important points to note about panels is that they must conform to the ISample interface. Each must implment the following four functions:

And each should have a TextArea with id traceArea.

See the sample panels for examples and comments on each function.

On the server-side you must create an object for each sample. See the previous example for how to do that. As an additional option you can also create the following methods on each server-side sample object:

Corrections and Updates

November 17, 2007

In a number of places I originally used netStream.addEventListener to add listeners for onPlayStatus and onMetaData. Stefan Richter was kind enough to point out that using addEventListener does not work for those callbacks. Adobe points that out here as well: http://livedocs.adobe.com/flex/2/langref/flash/net/NetStream.html. I've corrected the code so that each stream's client object has onPlayStatus or onMetaData methods.


Post or read comments for this page.


Document posted February 14, 2005 by Brian Lesser