views:

1043

answers:

3

A little (!) bit of background before I can get to the question :

I have an accordion control loaded with an array of grids, each of which is lazy loaded with arrays of things. I'm using an auto-generated web service proxy to retrieve these lists. I'd like for the user to be able to change the selected child in the accordion without having to wait for the web service to respond. I was originally using the same proxy instance for all requests and keeping track of the requests in the order they were made, but the problem with this is that shorter arrays return more quickly from the server, so the order the requests were made in becomes irrelevant.

I couldn't find an obvious way to determine the original request when handling a proxy result event so what I ended up with is a function which handles the change event on the accordion, instantiates a new webservice proxy, shoves that into a hashtable with the index of the selected child and then adds a closure as an event handler. i.e. something a bit like this :

private proxyTable:Object = new Object();
private function PopulateThingGrid(index:Number):void
{
    var grid:ThingGrid = myAccordion.getChildAt(index) as ThingGrid;
    grid.things = ArrayCollection(proxyTable[index].getThings_lastResult);
}

private function SendThingRequest(index:int):void
{
    var grid:ThingGrid= myAccordion.getChildAt(index) as ThingGrid;
    if (grid.things.length == 0)
    {
     if (proxyTable[index] == null)
     {
      proxyTable[index] = new MyWebServiceProxy();
     }
     var proxy:MyWebServiceProxy= proxyTable[index];
     proxy.addgetThingsEventListener(function ():void { PopulateThingGrid(index); });

     var list:ThingList = thingLists.getItemAt(index) as ThingList;
     proxy.getThings("thinglist", list.ListID);
    }
}

private function myAccordion_Change(event:IndexChangedEvent):void
{
    SendThingRequest(event.newIndex);
}

(I've tried to anonymise this a bit, so I may have missed something, but hopefully you get the idea)

So, to the question(s) : is there an easier way to match up proxy results with the original requests that I'm just missing?

If not, is what I've done reasonable? I'm a little concerned about the number of proxy instances that I could end up generating and then disposing of them correctly (when that becomes necessary) - are there any pitfalls I might not be aware of?

Update : I think the problem may arise because the generated proxy code subclasses the ResultEvents from flash.events.Event, rather than mx.rpc.events.ResultEvent. I'm not entirely sure why it does this - the only way to access the AsyncToken is when it's initially returned by the method call.

+1  A: 

Ok, I think I understand: since the requests are made at selection-time, and you're calling through a local proxy, and the responses come back at random, you can't tell which response maps to which UI element. Makes sense.

Hmm. It's sort of hard to say without knowing what kind of "thing" your proxy object is, but assuming it's something concrete that maps appropriately to a UI object -- say, ListOfWidgets to a DataGrid -- then it's probably better to have each grid map to its own instance of a ListOfWidgets proxy, anyway. In that case, when you instantiate a proxy, you can pass it the index of the grid its results should populate (similar to what you're doing above, but with the intention of storing the index as a public member on the proxy object, as opposed to in a separate object). Then, since each proxy's result-event handler should provide you with access to the proxy through the target property of the event, you can retrieve the proxy's target index (e.g., event.target.index) and populate the appropriate grid.

public class MyProxy
{
    public var targetIndex:uint;

    public function MyProxy()
    {
     // ...
    }
}

private function handleSelection():void
{
    var proxy:MyProxy = new MyProxy();
    proxy.addGetThingsEventListener(Event.YOUR_RESULT_EVENT, yourHandler); 
    proxy.targetIndex = 1;
}

private function yourHandler(event:Event):void
{
    fillGridWithResults(event.target.targetIndex);
}

The coding model is a little simpler if you were using data binding, in which case you could do something more like this, provided either your proxy class or its results collection were marked Bindable:

<mx:DataGrid dataProvider="{proxy.results}" />

... and have things "just work" (since each proxy's instance would be unique). Does this make sense? Hopefully I've understood the problem correctly. ;)

Christian Nunciato
It makes sense, yes, but I don't think it's a solution I can easily use without rearchitecting the proxy class (which is auto-generated anyway). Also, the proxy is used by lots of other controls and has many other methods. i.e. it doesn't just get Things, it also gets Foos, Bars, Bears, Rhinos....
inferis
+1  A: 

I am afraid I don't fully understand all of what you are trying to do, but If I am understanding correctly, you are making many requests and getting multiple responses back asynchronously and you are trying to match up the response with the request.

I have done this in the past for a similar problem, I was calling a webservice on keyup in a text field to do a "search as you type" sort of feature, but what that meant was that I always only cared about the response for the last request. So I changed the service to also take a parameter of a timestamp (down to the millisecond) and return it back with the rest of the response. Then I could either look for the latest timestamp in the responses or on the flex side, keep track of the timestamp of the last request and look for that specific response.

You could also do this with any kind of UUID or anything unique. It takes a little more work on the service side, but it scales well and is easily understood.

The other thing you could possibly look at, and I apologize for not researching this more myself is to look in debug at the request and response objects that are generated. You might find some ancillary information with those objects that you could look at to do the matching. But I am afraid it will depend greatly on exactly how you are calling and receiving the response.

HTH and hope I have understood the problem well enough.

Ryan Guill
I did consider something like this, but I'd rather not have to change the webservice itself (it's a .NET project I want to leave alone for the moment - other things rely on it).
inferis
+8  A: 

Not sure if this helps, but I had a similar situation, where I had a RemoteObject on which I call the 4 CRUD methods, but only one resultHandler. I solved this using the AsyncToken.

My RemoteObject calls looked like this:

public function list() {
    var token:AsyncToken = myService.list();
    token.operation = "list";
}

public function update() {
    var token:AsyncToken = myService.update(selectedItem);
    token.operation = "update";
}

public function create() {
    var token:AsyncToken = myService.create(selectedItem);
    token.operation = "create";
}

public function delete() {
    var token:AsyncToken = myService.delete(selectedItem);
    token.operation = "delete";
}

Then, the resultHandler looks like this:

public function resultHandler(event:ResultEvent):void {
    if( event.token.operation == "list" ) {
      // do something
    }   else if( event.token.operation == "update" ) {
    // do something
    }   
    // etc...

operation is a dynamic property, not a member of AsyncToken, so you can call it whatever you like. There's a Cookbook article describing it here:

Mike Sickler
This is excellent and should do exactly what the poster is asking for. I wish I could favorite this answer. It is exactly what I was thinking of in the last paragraph of my answer.
Ryan Guill
Ah, of course! I also forgot about AsyncToken. Nice.
Christian Nunciato
I did look at the AsyncTokens returned by the getThings method, but it's only really useful if I write the webservice proxy class myself - the GetThingsResultEvent doesn't have a token propery.
inferis
Actually, strike that, I think I can add my event handlers to the token itself rather than binding to the (fake/wrong) events that the webservice has. Next time I may write the webservice code by hand!
inferis
Yeah, AsyncToken should do the trick for you. But you're right -- using the WebService proxies can become a pain real quick. I've messed with them numerous times, trying to make them work as nicely as it seems they ought to (with .NET and WCF), but ended up rolling my own, too.
Christian Nunciato