Jon asked me an interesting Flex based question. Now before I show any code, I'll remind folks - I'm still very much the Flex Padawan. Whatever I show here I expect could be done much better (in fact, be sure to read the final code sample as it was created by a true Flex Jedi). That being said, I'm proud to have at least had an idea of the ballpark of the solution! Ok, enough jibber jabber - let's get to the question. Jon was using Flash Remoting to talk to a ColdFusion component. His code made use of RemoteObject and method, a bit like so:
<mx:RemoteObject destination="ColdFusion" source="flextest" id="doubleService">
<mx:method name="double" result="handleResult(event)" fault="handleFault(event)" />
</mx:RemoteObject>
In this code block I've created a connection to a CFC, "flextest", and given the remote object an id of doubleService. I've defined a method called double (can you guess what it does?) and a result handler. Now I can take a field and set it up so that when you enter a value, it runs this method and sets the result. Something like so:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" minWidth="955" minHeight="600">
<mx:RemoteObject destination="ColdFusion" source="flextest" id="doubleService">
<mx:method name="double" result="handleResult(event)" fault="handleFault(event)" />
</mx:RemoteObject>
<mx:Script>
<![CDATA[
import mx.rpc.events.FaultEvent;
import mx.rpc.events.ResultEvent;
public function handleFault(evt:FaultEvent):void {
mx.controls.Alert.show(evt.toString())
}
public function handleResult(evt:ResultEvent):void {
resultone.text = evt.result.toString()
}
]]>
</mx:Script>
<mx:VBox>
<mx:HBox>
<mx:Text text="Field One" />
<mx:TextInput id="fieldone" change="doubleService.double(fieldone.text)" />
<mx:Text text=" doubled is " />
<mx:TextInput id="resultone" />
</mx:HBox>
</mx:VBox>
</mx:Application>
I've got a simple set of fields here - one being the input - one for the output. When you enter a value, I fire off a request to the CFC to get the value. My result handler simply takes the result and puts it in the field. Simple, right? Well what happens when you add 2 more fields?
<mx:HBox>
<mx:Text text="Field One" />
<mx:TextInput id="fieldone" change="doubleService.double(fieldone.text)" />
<mx:Text text=" doubled is " />
<mx:TextInput id="resultone" />
</mx:HBox>
<mx:HBox>
<mx:Text text="Field Two" />
<mx:TextInput id="fieldtwo" change="doubleService.double(fieldtwo.text)" />
<mx:Text text=" doubled is " />
<mx:TextInput id="resulttwo" />
</mx:HBox>
<mx:HBox>
<mx:Text text="Field Three" />
<mx:TextInput id="fieldthree" change="doubleService.double(fieldthree.text)" />
<mx:Text text=" doubled is " />
<mx:TextInput id="resultthree" />
</mx:HBox>
While this won't generate any errors, the the result handler is hard coded to update resultone. Hence the problem. So here is where a little knowledge can be a dangerous thing. I knew that Flex supported something called an AsyncToken. This is a little magical fairy that creates state for your asynchronous call. Magical may not be exactly right - but I had worked with them a bit in the past so I had a basic idea of how they could work. I whipped up a quick demo that was very jQuery related. Hopefully you will see how:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" minWidth="955" minHeight="600">
<mx:RemoteObject destination="ColdFusion" source="flextest" id="doubleService">
<mx:method name="double" />
</mx:RemoteObject>
<mx:Script>
<![CDATA[
import mx.rpc.AsyncToken;
import mx.rpc.Responder;
import mx.rpc.events.FaultEvent;
import mx.rpc.events.ResultEvent;
public function handleFault(evt:FaultEvent):void {
mx.controls.Alert.show(evt.toString())
}
public function handleResult(evt:ResultEvent):void {
resultthree.text = evt.result.toString()
}
public function getDouble(name:String):void {
var field:mx.controls.TextInput = this["field"+name]
var resfield:mx.controls.TextInput = this["result"+name]
var val:String = field.text
var token:AsyncToken = doubleService.double(val)
token.addResponder(new mx.rpc.Responder(function(evt:ResultEvent):void { resfield.text = evt.result.toString()}, handleFault))
}
]]>
</mx:Script>
<mx:VBox>
<mx:HBox>
<mx:Text text="Field One" />
<mx:TextInput id="fieldone" change="getDouble('one')" />
<mx:Text text=" doubled is " />
<mx:TextInput id="resultone" />
</mx:HBox>
<mx:HBox>
<mx:Text text="Field Two" />
<mx:TextInput id="fieldtwo" change="getDouble('two')" />
<mx:Text text=" doubled is " />
<mx:TextInput id="resulttwo" />
</mx:HBox>
<mx:HBox>
<mx:Text text="Field Three" />
<mx:TextInput id="fieldthree" change="getDouble('three')" />
<mx:Text text=" doubled is " />
<mx:TextInput id="resultthree" />
</mx:HBox>
</mx:VBox>
</mx:Application>
Ok, a lot going on here. Let's tackle it bit by bit. First, I've simplified my mx:method tag to just contain a name. Right now I guess it's pretty useless. I've written a new function, getDouble, that my fields call. Notice that each field passes a name ("one", "two", "three"). Within getDouble I use this name to get the field I want to work with. I then get the result field as well. Both of these lines....
var field:mx.controls.TextInput = this["field"+name]
var resfield:mx.controls.TextInput = this["result"+name]
reminded me of $("#..") calls in jQuery. Or at least a document.getElementById call. I create an AsyncToken and then create an anonymous function to create a dynamic handler for the result. You can see how I passed in the result field to the handler. This then lets any of my three fields run the call and get the result in the right place.
So it "works" - but here is where someone with a little bit more knowledge than I (ok, a lot more) comes along and makes things much more simpler. I ran my code by Simeon Bateman and he pointed out that you can assign any custom data to the token itself. What does that mean? Check out this version:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" minWidth="955" minHeight="600">
<mx:RemoteObject destination="ColdFusion" source="flextest" id="doubleService">
<mx:method name="double" result="handleResult(event)" fault="handleFault(event)" />
</mx:RemoteObject>
<mx:Script>
<![CDATA[
import mx.rpc.AsyncToken;
import mx.rpc.Responder;
import mx.rpc.events.FaultEvent;
import mx.rpc.events.ResultEvent;
public function handleFault(evt:FaultEvent):void {
mx.controls.Alert.show(evt.toString())
}
public function handleResult(evt:ResultEvent):void {
trace(evt.token.fieldName)
var resfield:mx.controls.TextInput = this["result"+evt.token.fieldName]
resfield.text = evt.result.toString()
}
public function getDouble(name:String):void {
var field:mx.controls.TextInput = this["field"+name]
var val:String = field.text
var token:AsyncToken = doubleService.double(val)
token.fieldName = name
}
]]>
</mx:Script>
<mx:VBox>
<mx:HBox>
<mx:Text text="Field One" />
<mx:TextInput id="fieldone" change="getDouble('one')" />
<mx:Text text=" doubled is " />
<mx:TextInput id="resultone" />
</mx:HBox>
<mx:HBox>
<mx:Text text="Field Two" />
<mx:TextInput id="fieldtwo" change="getDouble('two')" />
<mx:Text text=" doubled is " />
<mx:TextInput id="resulttwo" />
</mx:HBox>
<mx:HBox>
<mx:Text text="Field Three" />
<mx:TextInput id="fieldthree" change="getDouble('three')" />
<mx:Text text=" doubled is " />
<mx:TextInput id="resultthree" />
</mx:HBox>
</mx:VBox>
</mx:Application>
I've restored the result/fault handlers back to my method tag. Now when I run getDouble, I just assign the name to the token itself. What's cool then is that in the handler, it's set into a token value of the event object. It just. Plain. Works.
Hopefully this is helpful to others (and if not, I sure as heck learned something).