19
Aug/09
3

Flex Configurations made easy

If you have worked on a project of any size using Flex and Cairngorm one of the issues that you might have encountered is how to allow the application to connect to the necessary services in different environments. For example with your local development you might connect to a Tomcat server running on your machine, but in the test environment it would connect to a different server. To solve this I used to have code that looked like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.softwarejesus.configuration.model
{
   import com.adobe.cairngorm.model.ModelLocator;

   public class OldModelLocator implements ModelLocator
   {
      private static var modelLocator : OldModelLocator;

      public var SERVICE_URL:String = "http://localhost:8080/sampleServices/messagebroker/amf";
//    public var SERVICE_URL:String = "http://devServer:8080/sampleServices/messagebroker/amf";

      public static function getInstance() : OldModelLocator
      {
         if ( modelLocator == null ) {
            modelLocator = new OldModelLocator;
         }

         return modelLocator;
      }
   
      public function OldModelLocator()
      {
         if ( modelLocator != null )
            throw new Error( "Only one ModelLocator instance should be instantiated" );
      }

   }
}

You’ll notice that I have different endpoints commented out in my ModelLocator. In order to create a Test build, I would un-comment the second line and rebuild the application, but I was never entirely happy with that solution.
What some coworkers and I came up with, was creating a simple configuration XML that would reside in the same directory as the generated SWF. At startup, the application would load any environment specific constants before any real work began.
Now, how did this impact my code? The only major deviation from the standard Cairngorm pattern is that we had to wait to add the Services to our application until after the application had received confirmation that the configuration has been loaded.
As you’ll see here my Services.mxml code looks exceptionally ordinary.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="utf-8"?>
<ServiceLocator xmlns="com.adobe.cairngorm.business.*" xmlns:mx="http://www.adobe.com/2006/mxml">
   <mx:Script>
      <![CDATA[
         import com.softwarejesus.configuration.model.ConfigModelLocator;

         [Bindable]
         private var serviceUrl : String = ConfigModelLocator.getInstance().SERVICE_URL;
      ]]>
   </mx:Script>
   <mx:RemoteObject
      id="sampleService"
      destination="sampleService"
      showBusyCursor="true"
      endpoint="{serviceUrl}"/>
   
</ServiceLocator>

Here is how I’ve written my ModelLocator, notice that when I instantiate the model, I retrieve the configuration:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package com.softwarejesus.configuration.model
{
   import com.adobe.cairngorm.control.CairngormEvent;
   import com.adobe.cairngorm.control.CairngormEventDispatcher;
   import com.adobe.cairngorm.model.ModelLocator;

   import flash.events.Event;
   import flash.events.IOErrorEvent;
   import flash.net.URLLoader;
   import flash.net.URLRequest;

   import mx.controls.Alert;

   public class ConfigModelLocator implements ModelLocator
   {
      private static var modelLocator : ConfigModelLocator;
      private static const CONFIG_FILE_NAME:String = "config.xml";

      public var SERVICE_URL:String;

      public static const CONFIG_LOAD_COMPLETE:String = "ConfigLoaded";

      public static function getInstance() : ConfigModelLocator
      {
         if ( modelLocator == null ) {
            modelLocator = new ConfigModelLocator;
            modelLocator.loadXML();
         }

         return modelLocator;
      }
   
      public function ConfigModelLocator()
      {
         if ( modelLocator != null )
            throw new Error( "Only one ModelLocator instance should be instantiated" );
      }

      public function loadXML():void {
         var configPath:String = CONFIG_FILE_NAME;        
         var loader:URLLoader = new URLLoader(new URLRequest(configPath));
         loader.addEventListener(IOErrorEvent.IO_ERROR, handleIOError);
         loader.addEventListener(Event.COMPLETE, handleXML);    
 
      }

      private function handleXML(event:Event):void
      {
         var xml:XML = new XML((event.currentTarget as URLLoader).data);
         SERVICE_URL  = xml.system.serviceUrl;

         CairngormEventDispatcher.getInstance().dispatchEvent(new CairngormEvent(CONFIG_LOAD_COMPLETE));
      }
       
      private function handleIOError(event:Event):void {
         Alert.show("Unable to open config.xml. Please contact the your IT representative");
      }        

   }
}

Tying it all together, in my Application file, we I initialize the app, I retrieve the model, and add a listener for when it has completed the configuration process. Once the configuration complete event has been dispatched, I know I can now add the services and get to work making Service Calls

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" initialize="doInit()">
   <mx:Script>
      <![CDATA[
         import com.adobe.cairngorm.control.CairngormEventDispatcher;
         import com.softwarejesus.configuration.services.Services;
         import com.adobe.cairngorm.control.CairngormEvent;
         import com.softwarejesus.configuration.model.ConfigModelLocator;

         private function doInit():void {
            ConfigModelLocator.getInstance();    
            CairngormEventDispatcher.getInstance().addEventListener(ConfigModelLocator.CONFIG_LOAD_COMPLETE, addServices);
         }

         private function addServices(event:CairngormEvent):void {
            var serv:Services = new Services();
            addChild(serv);
         }
      ]]>
   </mx:Script>
</mx:Application>

I found this solution to be perfectly suited to address an issue I ran into on another project. In that project, the client was a manufacturer of goods, and had distributors that sold his products. Initially it was set up so that when someone logged in they could browse through the entire catalog of products. A couple of months in, he told me that each of his distributors had a finite set of products they could sell, and that none of them could sell all of the products. As a result, the distributors could not show their customer’s the app, for fear that the customer would decide to purchase something they were not authorized to sell.

The solution I came up with was to host the web services, and all the assets (and believe me, the number of assets was disgusting) centrally, and develop a swf that could be given to the distributors to host on their respective websites. Along with the swf, the customer was also given a config.xml that would determine which products were available through the application. This solution allowed me to introduce brand new functionality and all I had to do was implement one new service call.

Source

1
Jul/09
0

Cairngorm Super Classes

I’m a big fan of the Cairngorm micro-architecture for Flex, I am unapologetic and vocal about my support of it. I’m also incredibly lazy, and hate to rewrite the same two or three lines of code over and over, but I found that because of some of the design choices that were made on two of my recent projects, I was having to do just that. The biggest example of my rewriting the same code was in my commands. If you use Cairngorm, and you’re anything like me, you probably have commands that look roughly like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package com.softwarejesus.cairngorm.command
{
    import com.adobe.cairngorm.commands.Command;
    import com.adobe.cairngorm.control.CairngormEvent;
   
    import mx.controls.Alert;
    import mx.rpc.IResponder;
    import mx.rpc.events.FaultEvent;

    public class SimpleCommand implements Command, IResponder
    {
        public function execute(event:CairngormEvent):void
        {
            /*Do something - call a web service*/
        }
       
        public function result(data:Object):void
        {
            /*Handle the response*/
        }
       
        public function fault(info:Object):void
        {
            var event:FaultEvent = info as FaultEvent;
            Alert.show(event.fault.faultString);
            /*trace(event.fault.faultString);*/
        }
       
    }
}

So I decided I was going to run with this lazy thing and create a simple class called MyResponder which would at least remove the need to re-implement the fault method again and again. But then I noticed another pattern, but before I can show you I need to explain a little bit about how applications were structured. These applications allowed the user to open up 1 to n datagrids or charts. So I created a simple multiton model which contained data that controlled how the data was retrieved, how the data was rendered, as well as the actual result data. Here’s what models basically looked like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.softwarejesus.cairngorm.model
{
    import mx.collections.ArrayCollection;
   
    [Bindable]
    public class GenericModel
    {
        /*Criteria variables*/
        public var string1:String;
        public var date1:Date;
       
        /*Display variables*/
        public var string2:String;
        public var number1:Number;
       
        /*List of some type of objects*/
        public var resultData:ArrayCollection;
       
       
    }
}

When somebody launched a new chart or datagrid a new GenericModel was created. It was then populated with the data my app needed to render it properly, search criteria, and then a new Cairngorm Event was created to retrieve the applicable data. Depending on how the data was structured, I would have a number of events, like this one:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.softwarejesus.cairngorm.event
{
    import com.adobe.cairngorm.control.CairngormEvent;
    import com.softwarejesus.cairngorm.model.GenericModel;

    public class SpecificEvent extends CairngormEvent
    {
        public var obj:GenericModel;
        public static const EVENT_TYPE:String = "SpecificEventType";
        public function SpecificEvent(obj:GenericModel)
        {
            super(EVENT_TYPE);
            this.obj = obj;
        }
       
    }
}

I would also have a number of commands that were structured like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package com.softwarejesus.cairngorm.command
{
    import com.adobe.cairngorm.commands.Command;
    import com.adobe.cairngorm.control.CairngormEvent;
    import com.softwarejesus.cairngorm.event.SpecificEvent;
    import com.softwarejesus.cairngorm.model.GerenicModel;
   
    import mx.collections.ArrayCollection;
    import mx.controls.Alert;
    import mx.rpc.IResponder;
    import mx.rpc.events.FaultEvent;
    import mx.rpc.events.ResultEvent;

    public class SpecificCommand implements Command, IResponder
    {
        var obj:GerenicModel;
        public function execute(event:CairngormEvent):void
        {
            var newEvent:SpecificEvent = event as SpecificEvent;
            this.obj = newEvent.obj;
           
            //get my delegate
            //make some service call
        }
       
        public function result(data:Object):void
        {
            var event:ResultEvent = data as ResultEvent;
            obj.resultData = event.result as ArrayCollection;
        }
       
        public function fault(info:Object):void
        {
            var event:FaultEvent = info as FaultEvent;
            Alert.show(event.fault.faultString);
        }
       
    }
}

A repeated pattern is an opportunity to be lazy, which I’m a huge fan of. What I came up with were 2 super classes that I would use to cut a great deal of superfluous, repetitive code out of my project. The first super class I’ve created is my SuperEvent, you’ll notice that it implements EventConstants. EventConstants contains all the event type strings that I use in my project:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.softwarejesus.cairngorm.event
{
    import com.adobe.cairngorm.control.CairngormEvent;
    import com.softwarejesus.cairngorm.model.GerenicModel;

    public class SuperEvent extends CairngormEvent implements EventConstants
    {
        public var object:GerenicModel;
        public function SuperEvent(object:GerenicModel, type:String) {
            this.object = object;
            super(type);
        }
    }
}

Next came my SuperCommand,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package com.softwarejesus.cairngorm.command
{
    import com.adobe.cairngorm.commands.Command;
    import com.adobe.cairngorm.control.CairngormEvent;
    import com.softwarejesus.cairngorm.event.SuperEvent;
    import com.softwarejesus.cairngorm.model.GenericModel;
   
    import mx.collections.ArrayCollection;
    import mx.controls.Alert;
    import mx.rpc.IResponder;
    import mx.rpc.events.FaultEvent;
    import mx.rpc.events.ResultEvent;

    public class SuperCommand implements Command, IResponder
    {
        private var object:GerenicModel;
        public function execute(event:CairngormEvent):void
        {
            var newEvent:SuperEvent = event as SuperEvent;
            this.object = newEvent.object;
        }
       
        public function result(data:Object):void
        {
            var event:ResultEvent = data as ResultEvent;
            object.resultData = event.result as ArrayCollection;
           
        }
       
        public function fault(info:Object):void
        {
            var event:FaultEvent = info as FaultEvent;
            Alert.show(event.fault.faultString);
        }
       
    }
}

With good middle-tier design, you can eliminate the need to implement 80-90% of your web service result handlers. Thereby saving you valuable time, which you can turn around and dedicate to World of Warcraft or whatever it is you choose to do with your spare time.