May
27

1

Cairngorm - 4 Delegates and the ServiceLocator

In the previous tutorial we covered how the FrontController and its mappings to Command  classes allow applications to encapsulate discreet tasks in one place. Building upon that theory we are going to explore two major concepts in this tutorial. The first is the use of a delegate which employs a ServiceLocator and a dataservice. The second major concept to be covered is the extension of our Command classes to implement Iresponder.

Unlike the previous examples, this application is going to get its data from an external source. There are three XML files on the server which we are going to download at runtime to populate our application. The 1st step in accessing remote data is setting up your ServiceLocator. Flex SDK provides two services out of the box; HTTPService and . Flex’s ability to consume remote data is not limited to those two classes. By using BlazeDS or Livecycle Data Services (LCDS) your application can have some amazing data connectivity. This example is going to be using HTTPService though it can just as easily use any or all of the other methods.

The file below is our ServiceLocator; there are two services declared inside.  The first is a generic HTTPServich which this example uses to dynamically change the URL before making requests. The second already has the URL declared. The reason why I have these two service declarations is because I want to show how flexible we are with where we are getting data. If you want your data layer to be the only layer in your application that knows where to get data, so be it and use a strategy like “getData1”. On the other hand, if you need to get data from some dynamic source at runtime you would be using something more like “getData”.

// AngryService.mxml

    
<cairngorm:ServiceLocator 
        xmlns
:mx="http://www.adobe.com/2006/mxml" 
        
xmlns:cairngorm="com.adobe.cairngorm.business.*">
        
        <
mx:HTTPService id="getData"
            
showBusyCursor="true"
            
method="POST"
            
resultFormat="e4x"
            
/>
        
        <
mx:HTTPService id="getData1"
            
url="http://codingAngry.com/demos/cairngorm4/data/Tutorial_4_Data_1.xml"
            
showBusyCursor="true"
            
method="POST"
            
resultFormat="e4x"
            
/>
    
    </
cairngorm:ServiceLocator


In the last tutorial we included the controller in our main application. This example still uses the controller but it needs to know about the service too. We are adding one additional line to our application to include this service.

// index.mxml
    
    
<!-- Cairngorm Initialization -->
    <
control:AngryController id="angryController" />
    <
control:AngryService id="angryService" /> 


Now your new data service is ready to use. Since we are trying to keep our application clean and layered, the only class that will touch this service is our delegate. The delegate provides us a single point of entry to the remote data. Aside from the constructor, each function declared in the delegate is a remote service call. Starting with the constructor, we are binding to a data service and setting up the responder. The responder is the class that is going to listen to the service and handle its response or fault. Typically I like to have one delegate per data service, since this is an example, I have overloaded this one a bit.

// AngryDelegate.as
    
    
public class AngryDelegate
    {
        
protected var responder     IResponder;
        protected var 
service         HTTPService;
        
        public function 
AngryDelegateresponder IResponder null )
        
{
            this
.service 
               
ServiceLocator.getInstance().getHTTPService"getData" ) as HTTPService;
            
            
this.responder responder;
        
}
    } 


The delegate knows which service to connect to “getData” and it assigns a responder if provided to listen to the service call. Since there are three data files up on the server, I am going to declare three functions, one per file. These functions in a real application could be more meaningful units of work, login, logout, forgotPassword…

// AngryDelegate.as
    
    
public function getData1() : void
    {
        service 

        
ServiceLocator.getInstance().getHTTPService"getData1" ) as HTTPService;
        
        var 
token:AsyncToken service.send();
        
token.addResponder(responder);
    
}
    
    
public function getData2() : void
    {
        service
.url "http://codingAngry.com/demos/cairngorm4/data/Tutorial_4_Data_2.xml";
        
        var 
token:AsyncToken service.send();
        
token.addResponder(responder);
    
}
    
    
public function getData3url String ) : void
    {
        service
.url url;
        
        var 
token:AsyncToken service.send();
        
token.addResponder(responder);
    


The functions above are slightly different from one another. Their differences illustrate how data can be encapsulated in the various layers of your application. The first function calls the dataService “getData1” this is the service that had the URL hard coded in the declaration. The second service uses the “getData” service and passes it a URL. The third function receives a URL as a parameter and then passes it to the “getData” service.

As I mentioned before we are going to extend our command classes to implement the iResponder interface.  This is a simple interface that declares two methods; fault and result. As you might imagine, the result is where you process the data you were looking for and the fault is where you deal with things gone awry. The execute function does two things. It sets itself as the responder to the delegate and using the delegate it calls getData1.  The result and fault functions are the same for all 3 of our new commands with one exception, the events thrown.

// GetData1Command.as

    
public class GetData1Command implements CommandIResponder
    {
        
public function executeevent CairngormEvent ):void 
        {    
            
var delegate:AngryDelegate = new AngryDelegate(this);
            
delegate.getData1();
        
}  
        
        
public function resultinfo Object) : void
        {
            
var result    XML XML(info.result);
            var 
model     SampleModel SampleModel.getInstance();
            
            for 
each( var item XML in result.item )
            
{
                model
.delegateListData.addItem
                    
{item:item.attribute("id"), title:item.valueOf());
            
}
            
            
var response CairngormEvent 
            new 
CairngormEventTutorialEvent.GET_DATA_1_COMPLETE );
            
            
response.data info.result;
            
CairngormEventDispatcher.getInstance().dispatchEventresponse );
        
}
        
        
public function faultinfo Object ) : void 
        {
            
var request CairngormEvent 
            new 
CairngormEventTutorialEvent.GET_DATA_1_FAIL );
            
            
request.data info;
            
CairngormEventDispatcher.getInstance().dispatchEventrequest );
        
}
    } 


The result function in this example parses the return into our model. After that it takes the result and sets it to the data field on a CairngormEvent and dispatches it. This example doesn’t listen for the thrown events, that code is there to illustrate that if you didn’t want to update the model in the result handler you could listen for the GET_DATA_1_COMPLETE event and deal with it later. The fault handler sets the fault info object as the data field on a CairngormEvent and dispatches it as GET_DATA_1_FAIL .

We need to set up the mappings between the events and the commands. Like before, we are going to add more commands to our Controller.

// AngryController.as

    
addCommandTutorialEvent.GET_DATA_1,    GetData1Command );
    
addCommandTutorialEvent.GET_DATA_2,    GetData2Command );
    
addCommandTutorialEvent.GET_DATA_3,    GetData3Command ); 


Now all we have to do is generate the events to trigger our commands. This example uses a ComboBox to generate change events which in turn builds and dispatches Cair events. Below is the event handler for the ComboBox.

// AngryPanelT4L2.mxml
    
    
public function getData() : void
    {
        
var request TutorialEvent;
        
        switch( 
dataMenu.selectedIndex )
        
{
            
case request =    
                new 
TutorialEventTutorialEvent.GET_DATA_1 ); 
                break;
            
            case 
request =     
                new 
TutorialEventTutorialEvent.GET_DATA_2 ); 
                break;
            
            case 
request =     
                new 
TutorialEventTutorialEvent.GET_DATA_3 ); 
                
request.data "http://codingAngry.com/demos/cairngorm4/data/Tutorial_4_Data_3.xml";
                break;
        
}
        
        CairngormEventDispatcher
.getInstance().dispatchEventrequest );
    


The delegate has its three functions for getting our data. The getData1 function uses the data service that had the URL hard coded in it. The getData2 function introduced the URL in the delegate. As you can see above, we are passing getData3 the Url from our Flex app.

So what just happened? We declared two data services, one pointed to a file on the server and the other had no URL specified. Then added a delegate with three function to use those data services. We wrote three commands one each to call the functions declared in the deleage. Mappings were added to the controller so that the new event types would map to our new commands. Lastly we fired off events that got us new remote data. Happy coding.

 

Comments

Your tutorial is really brilliant!
It helps me understand Cairngorm at less than 2 hours! Thank you very much for your efforts to write this tutorial.
Best regards, Eugene.

Posted by Eugene on Oct 12, 2009

Enter a comment - moderated

Commenting is not available in this weblog entry.