Flex/BlazeDS - resultHandler per function call not per RemoteObject? - apache-flex

I've followed this tutorial to get some Flex code to call Java code hosted on a Tomcat server.
This is how my RemoteObject and the Button to call the remote function is declared:
<mx:RemoteObject id="productService" destination="productJavaService" result="resultHandler(event)" fault="faultHandler(event)"/>
<mx:Button label="Get all Products" click="productService.getAllProducts()" />
These are the definitions of the resultHandler and faultHandler functions:
private function resultHandler(event:ResultEvent):void
{
products = event.result as ArrayCollection;
}
private function faultHandler(event:FaultEvent):void
{
Alert.show(event.fault.faultString);
}
The obvious problem with this for me is that the resultHandler is associated with the RemoteObject as a whole rather than the individual function. If I add a new function such as "getSingleProduct" then obviously a different resultHandler will need to be used. How do I specify the resultHandler at function level?

You can define a method property under a RemoteObject, in your case, it would be getAllProducts(); You can do so like this:
<mx:RemoteObject id="Server" destination="ServerDestination" fault="faultHandler(event)">
<mx:method name="getAllProducts" result="getAllProductsHandler(event)"/>
<mx:method name="getOneProduct" result="getOneProductHandler(event)"/>
</mx:RemoteObject>

Just wanted to add: in case anyone wants to achieve this with actionscript, you can do this with actionscript by adding a Responder to the AsyncToken returned from the service call:
var responder:Responder = new Responder(onGetOneProductResult, onGetOneProductFault);
var token:AsyncToken = Server.getOneProduct();
token.addResponder(responder);
private function onGetOneProductResult(event:ResultEvent):void {
// event.result is the data you sent back from the server
var result:Object = event.result;
}
private function onGetOneProductFault(event:FaultEvent):void {
trace("onGetOneProductFault : "+event.fault.faultString);
}

Related

Check to see if CallResponder is processing

I'm using Flash Builder 4.6. As a simple example, say I have the following application:
<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:sdk="services.sdk.*">
<fx:Script>
<![CDATA[
private function btnGetValue_clickHandler():void
{
getValueResult.token = sdk.getValue();
}
private function getValueResultHandler():void
{
// ...
}
]]>
</fx:Script>
<fx:Declarations>
<sdk:SDK id="sdk" fault="{Alert.show(event.fault.faultString +'\n\n'+ event.fault.faultDetail, 'SDK ERROR');}" showBusyCursor="false"/>
<s:CallResponder id="getValueResult" result="getValueResultHandler()"/>
</fx:Declarations>
<s:Button id="btnGetValue" click="btnGetValue_clickHandler()" label="Get Value" />
</s:Application>
So when you click on the button, it calls a PHP file and when it gets a result, it calls getValueResultHandler(). Easy enough.
But what if the response from the PHP file takes a second or two and the user clicks the button rapidly? Then the result handler function may not get called every time, since the call responder gets a new token before it received the last response.
Is there a standard way of resolving this issue? I came up with the following workaround, and it works fine, but it seems like this issue would be common enough to have a more built-in solution.
My workaround is:
var getValueResultProcessing:Boolean = false;
private function btnGetValue_clickHandler():void
{
var interval:uint = setInterval(function():void
{
if (!getValueResultProcessing)
{
getValueResultProcessing = true;
getValueResult.token = sdk.getValue();
clearInterval(interval);
}
}, 100);
getValueResult.token = sdk.getValue();
}
private function getValueResultHandler():void
{
getValueResultProcessing = false;
// ...
}
Any better way of resolving this issue?
EDIT:
One of my actual scenarios for which I need a solution is when looping:
private function btnGetValue_clickHandler():void
{
var arr:Array = new Array("value1", "value2", "value3");
for each (var value:String in arr)
getValueResult.token = sdk.getValue(value);
}
So to solve this problem I make arr a global variable and use the setInterval method I described before to shift off the first element and then do the same exact thing in the result function until arr is empty.
Again, looking for a more "standard" solution, if such a thing exists.
Instead of using CallResponder, you might want to manage the responses to service calls yourself. HTTPService (or similar Flex services) return an AsyncToken when you call the HTTPService.send() method. You can then add 'result' and 'fault' handlers by using the AsyncToken.addResponder() method.
If you do it this way, each responder will trigger it's result or fault handler as appropriate. Then use the last approach as suggest by Sam DeHaan, where you store each token in an array when the call is made, and remove the token from the array when the 'result' or 'fault' handler is executed. When removing a token from the array, you can re-enable the button if the array is empty.
You do it something like this (typing code from memory):
var service:HTTPService = new HTTPService();
// configure the service...
var token:AsyncToken = service.send();
// resultHandlerMethod and faultHandler method are functions that you should define
// their signatures look like this:
// resultHandlerMethod(event:ResultEvent)
// faultHandlerMethod(event:FaultEvent)
token.addResponder(new Responder(resultHandlerMethod, faultHandlerMethod);
If the call is button based, disable the button on click. Enable the button at the end of the callback. Simple.

Is there init() method in flex like Servlet init() only one time execute

Flex having any init(), destroy() method. Like Servlet init()method will run at Application initialize and never call it again if Refresh the Page also.
I would suggest not using initialize event, and instead use creationComplete. All UIComponent dispatch that event when they are finished constructing themselves AND their children. This event is executed once after the component has been initialized, had a chance to measure itself, perform layout, and added to the stage.
<mx:Application ... creationComplete="init()"/>
<mx:Script>
private function init() : void {
... // put your initialization routine here
}
</mx:Script>
</mx:Application>
All flex components, including the root "application" component have an "initlize" event that you can listen to and handle.
If you'd like it to only run ONCE, regardless of refresh, you'd need to store a variable somehow, such as with a local shared object. That's pretty easy to do:
private function onInit():void{
var appSO:SharedObject = SharedObject.getLocal("yourappdata");
if(appSO.size < 0){
//do your init code
appSO.data.initialized = true;
appSO.flush();
}
}

Having trouble with binding

I'm not sure if I'm misunderstanding the binding in Flex. I'm using Cairngorm framework. I have the following component with code like:
[Bindable]
var _model:LalModelLocator = LalModelLocator.getInstance();
....
<s:DataGroup dataProvider="{_model.friendsSearchResults}"
includeIn="find"
itemRenderer="com.lal.renderers.SingleFriendDisplayRenderer">
<s:layout>
<s:TileLayout orientation="columns" requestedColumnCount="2" />
</s:layout> </s:DataGroup>
in the model locator:
[Bindable]
public var friendsSearchResults:ArrayCollection = new ArrayCollection();
Inside the item renderer there is a button that calls a command and inside the command results there is a line like this:
model.friendsSearchResults = friendsSearchResults;
Putting break points and stepping through the code I confirmed that this like gets called and the friendsSearchResults gets updated.
To my understanding if I update a bindable variable it should automatically re-render the s:DataGroup which has a dataProvider of that variable.
There's nothing obviously wrong in the code sample. It should work so I think there's a problem elsewhere.
I would recommend setting a breakpoint where the dataProvider is assigned and also where model.friendsSearchResults is assigned. Make sure they're both pointing to the same object instance. Then step through the property assignment and corresponding event.
To make debugging easier you can switch to using a named event instead of the default. With a named event, only event listeners interested in your particular property are triggered instead of any listeners listening for any property change. This is easier to debug and will run faster. For example, change:
[Bindable]
public var results:ArrayCollection;
to
[Bindable("resultsChanged")]
private var _results:ArrayCollection;
public function get results():ArrayCollection {
return _results;
}
public function set results(value:ArrayCollection):Void {
_results = value;
dispatchEvent(new Event("resultsChanged"));
}
Another thing to keep in mind is that bindings hide certain errors like null reference exceptions. They assume the value simply isn't available yet and suppress the error. Stepping through the assignment and related bindings will help find a problem like this.

combobox dataprovider

I have the following:
<mx:RemoteObject id="myCFC" destination="ColdFusion" source="components.myCFC" showBusyCursor="true">
<mx:method name="getStuff" result="UserHandler(event);"/>
</mx:RemoteObject>
...
<mx:ComboBox id="propertyCode" dataProvider="{qry_stuff}" labelField="name" />
Index.as has:
[Bindable] public var qry_stuff:ArrayCollection = new ArrayCollection;
private function UserHandler(event:ResultEvent):void {
qry_stuff= event.result as ArrayCollection;
}
public function init():void {
/* call my remote Object to get my data */
myCFC.getStuff();
}
my problem is the combobox displays [object Object]
I know there is nothing wrong with the cfc and there is a field called "name" in getStuff. Why does it not display the value of the object?
thanks in advance.
There is a property on the ComboBox class called labelField. Go ahead and set that to the name field on the data that is returned. If that doesn't work - you need to debug your returned values from CF - to be sure that the name property is actually being populated on the client side as well.
In addition, you data is probably being returned as an array (not an ArrayCollection) - in which case, you would need to set:
qryStuff = ArrayCollection( event.result as Array );
Note: You probably also want to 'strong-type' your response data by creating an ActionScript value object - so that it is not just a generic 'object' that is being returned from CF. You then can use the [RemoteClass(alias="com.sample.MyCFC")] metadata tag to map that value object to your server-side VO.
In my cfc, I had to explicitly set data/label.

flex doesn't seem to bind with custom actionscript object

I have a custom actionscript object defined as bindable with a number of public properties.
[Bindable]
public class MyObject extends Object {
public var mobileNumber:String;
...
In my mxml I have:
<mx:Script><![CDATA[
import mx.binding.utils.BindingUtils;
import org.test.MyObject;
[Bindable]
private var obj: MyObject = new MyObject();
]]></mx:Script>
<mx:Label text="Mobile Number" id="mobileNumberLabel"/>
<mx:TextInput id="mobileNumberText" text="{obj.mobileNumber}" />
<mx:LinkButton label="Load" id="loadButton" enabled="true" click="obj = obj.load();"/>
<mx:LinkButton label="Save" id="saveButton" enabled="true" click="obj.write();"/>
My issue is that when I enter a new value in the field for mobile number and then click the save button, the value typed is not logged out... i.e.:
public function write():void {
var bytes:ByteArray = new ByteArray();
trace("write - mobile:" + this.mobileNumber);
bytes.writeObject(this);
EncryptedLocalStore.setItem(KEY, bytes);
}
I also tried adding in:
private function init():void {
BindingUtils.bindProperty(mobileNumberText, "text", obj, "mobileNumber");
}
but no luck with that either.
I'm probably missing something simple here, but not sure what it is. Hope you can help, thanks.
tst's answer is correct - bindings are one-way. I'm guessing you already knew that though, since you tried to setup the reverse binding in your init() method.
However, there's two problems with your init() method.
First, it's not clear where you put that init() method, or what calls it.
Second, you got the method parameters backwards.
What I typically do in situations like this is either use the mxml tag as the first responder suggested, or if I'm in AS3 code, I do something like this:
private function onCreationComplete(event:Event):void
{
BindingUtils.bindProperty(obj, "mobileNumber", mobileNumberText, ["text"]);
}
Note a couple of points here:
1/ BindingUtils.bindProperty() is "left hand side = right hand side". Thus, it's kinda like writing
obj.mobileNumber = mobileNumberText.text;
Or, closer to what is "actually going on" inside the binding classes:
obj["mobileNumber"] = mobileNumberText["text"];
2/ BindingUtils.bindProperty() actually wants an array as the last param. This is so that you can do "chained properties" logically like:
obj.mobileNumber = mobileNumbersGrid.selectedItem.text;
which would be
BindingUtils.bindProperty(obj, "mobileNumber", mobileNumbersGrid,
["selectedItem", "text"]);
3/ Best practice tip: if you're binding a property chain whose initial member is a property of yourself (this), then write it as:
BindingUtils.bindProperty(obj, "mobileNumber", this,
["mobileNumbersGrid", "selectedItem", "text"]);
This neatly handles the case where your "right hand side object" this.mobileNumbersGrid instance itself is replaced with a new instance object.
4/ If you ever reassign obj ("the left hand side"), you need to create a new binding to the new obj instance. Typically, you do this by turning the local obj property into a getter/setter pair like this:
public function get obj():MyObject
{
return _obj;
}
private var _obj:MyObject = new MyObject();
public function set obj(value:MyObject):void
{
_obj = value;
BindingUtils.bindProperty(_obj, "mobileNumber", mobileNumberText, ["text"]);
}
(Note: to be really careful, you'd stash the returned value from BindingUtils.bindProperty() which is a ChangeWatcher instance, and you'd unwatch() to prevent the old object from receiving property changes)
Hope this helps. Bindings are among the most powerful tools in the Flex framework, but can cause a lot of headaches when used poorly. In particular, be aware of memory leaks and "unexpected updates" when bindings are left "hanging around".
This code:
<mx:TextInput id="mobileNumberText" text="{obj.mobileNumber}" />
makes a one-directional binding between obj.mobileNumber and mobileNumberText.text. What you need is an additional reverse binding - add the following line to your code and it will work:
<mx:Binding source="mobileNumberText.text" destination="obj.mobileNumber"/>
Did you try implementing IEventDispatcher, or extending EventDispatcher?
Bindings work through dispatching of PropertyChangeEvents, so maybe this is the problem?
also, I am not sure, whether bindings work on variables, or whether they require properties (getters/setters).
You should try compiling with -keep-generated-actionscript and see if the resulting code makes any sense, i.e. dispatches any events, if the variable is accessed ...

Resources