views:

59

answers:

2

I have a class which exposes literally dozens of events(before you get of on a tangent about whether that's good/bad design, just know that I didn't make that class). The event object of each event(eventParam in the below code) always has a toDebugString function, that basically creates a string containing all of the event object's property values:

propertyName1: propertyValue1
propertyName2: propertyValue2
propertyName3: propertyValue3

It works so far as creating all of the panels, with the title of each panel being the name of the event. However, the big problem is that all of events end up in the TextArea of the last panel. So there is something I don't understand about the anonymous method. It's as if each iteration of the loop uses the same function, and on the last iteration of the loop it decides that the debugPanel that was just created will be the one that all instances of that function will reference. In other words, a new unique debugSubPanel and TextArea is created in each iteration of the loop, but there is only one debugResponseListener event handler shared by all iterations of the loop. So my question is, how can I dynamically create the event handler function dynamically so that it stays associated with the debugSubPanel that I want it to?

public function debugPanelCreated(event:FlexEvent)
{
    //iterate through all of the events exposed by mClient.ResponsesDispatcher
    //where key is the name of the event
    for (var key:String in mClient.ResponsesDispatcher.respMap)
    {     
     //for each event, create a panel containing a text box
     var debugSubPanel:Panel = new Panel();
     debugSubPanel.title = debugSubPanel.label = key;
     var debugSubPanelTextArea:TextArea = new TextArea();
     debugSubPanel.addChild(debugSubPanelTextArea);    

     var debugResponseListener:Function =
      function (eventParam :Object) : void
      {       
       //use debugString function to write the properties
       //of eventParam to the text box
       debugSubPanelTextArea.text = eventParam .toDebugString();     

      };

     //listen to this event:
     mClient.ResponsesDispatcher.addEventListener(key,debugResponseListener);

     //add the panel for this event     
     debugPanel.addChild(debugSubPanel);
    }    
}
A: 

This is the hack I came up with. I really don't like it, but it'll work for now. Open to suggestions still.

public class ResponseDispatcherToDebugStringHelper
{
    public var textArea:TextArea;  

    public function responseToDebugStringHandler(eventParam:Object) : void
    {       
     //use debugString function to write the properties
     //of eventParam to the text box
     textArea.text = eventParam.toDebugString();   

    }

}


public function debugPanelCreated(event:FlexEvent)
{
    //iterate through all of the events exposed by mClient.ResponsesDispatcher
    //where key is the name of the event
    for (var key:String in mClient.ResponsesDispatcher.respMap)
    {     
     //for each event, create a panel containing a text box
     var debugSubPanel:Panel = new Panel();
     debugSubPanel.title = debugSubPanel.label = key;
     var debugSubPanelTextArea:TextArea = new TextArea();
     debugSubPanel.addChild(debugSubPanelTextArea);

     var helper:ResponseDispatcherToDebugStringHelper = 
      new ResponseDispatcherToDebugStringHelper();
     helper.textArea = debugSubPanelTextArea; 

     //listen to this event:
     mClient.ResponsesDispatcher.addEventListener(key,helper.responseToDebugStringHandler);

     //add the panel for this event     
     debugPanel.addChild(debugSubPanel);
    }    
}
AaronLS
+1  A: 

Actionscript includes a feature called closures, which means that when you create an inner function and call it, the variables of its parent function are still available. (This is how debugResponseListener = function() ... works at all.) The issue is that a closure is only created when that function is called, and it uses the variable values from their last setting.

You can get around this by making a function that returns the listener function you want.

function makePanelListener(debugSubPanelTextArea:TextArea) : Function
{   
 return function(eventParam :Object) : void {
  //use debugString function to write the properties
  //of eventParam to the text box
  debugSubPanelTextArea.text = eventParam .toDebugString();
 }

}

and in your original code:

var debugResponseListener:Function = makePanelListener(debugSubPanelTextArea);

(There's a little explanation of what's going on in Explaining JavaScript scope and closures, look for the section called "The Infamous Loop Problem". More on closures at jibbering.)

Selene
+1 Thanks for explaining closers. I ended up just creating an entire mxml component that represented the debugSubPanel, which contained the textArea and the event handler. In my previous loop I instantiated one of these and passed to it a reference to the ResponsesDispatcher and the name of the event. Then inside the DebugSubPanel class I wired up to the event.
AaronLS