Working with the Flex Framework


This blog entry is about the Flex Framework. I just recently received a project from a vendor. It is written completely in Flex, except for a web service to get data to the application (unnecessary, I have found out, but I do like the SOA patterns, so this one gets kudos).
 
When I got the application, it was configured incorrectly, and everything was hardcoded. I had three things I pushed back on the team:
  1. Pulling from QueryString had to work in all environments
  2. I need to have the maps pulled from a configuration file
  3. I need to be able to set the web service location in configuration

Due to the other projects we have this team on, I ended up doing it myself.

NOTE: This application has to get tings off of the querystring, at least for now, due to constraints in the specifications. It will likely morph into a cleaner FORM post in the future.

Pulling from QueryString in all environments

The original code had the following line:

var params:String = Application.application.parameters.params;

Here is is in context:

public function initComp():void {
       
      var params:String = Application.application.parameters.params;
     
      if (params.length > 0) {
            readParams(params);
            currentState = "showMap";
            mapCont.addMapSource("1" , "lot1.swf", "Lot – 1", "Description for lot 1",
                  new mapPoint(33.934932, -84.174615), new mapPoint(33.930329, -84.16698),
                  980, 634);
           
            mapCont.addMapSource("2" , "lot2.swf", "Lot – 2", "Description for lot 2",
                  new mapPoint(32.847887, -96.865415), new mapPoint(32.84229, -96.857924),
                  863, 767);
                 
      }
      zoomBar.value = 1;
}

This routine is called from the mx:Application tag, which is fairly straightforward Flex. I am fairly certain I have some Flex Framework readers out here that can help me with this one. The line var params:String = Application.application.parameters.params; was not working on my machine. I kept getting a null instead of the value. When I install the application they compiled it works. I did not have a project file, if FlexBuilder creates one, so I did not have any compilation properties, if there are any. Enough of this aside; just ping me if you have an answer.

I went out and found that you can pull from JavaScript by working through a few hoops. First, I needed the routine to grab the querystring, which I found here. Here is the code in the JavaScript:

<script language="JavaScript" type="text/javascript">
      var jsReady = false;
     
function isReady() {
         
return jsReady;
      }
     
function pageInit() {
          jsReady =
true;
      }
     
     
function GetQueryString()
      {
           
var s = window.location.search.substring(1);
           
return s;
      } 
</script>

<
body scroll="no" onLoad="pageInit();">

Easy enough. I am not sure I like search.substring(1), as it is not explicit, but it works fine. Now to my Flex file. First I need an import to use the libraries that pull from JavaScript in the page.

import flash.external.ExternalInterface;

I now needed some code to call the JavaScript routine in action script. The initComp() routine was refactored to avoid having all of the load code. The original is shown above.

public function initComp():void
{
      //Pull the parameters from the querystring
      pullParameters();
      InitConfigLoader();
}

And here is the refactored routines to pull parameters:

private function pullParameters()
{
      params = Application.application.parameters.params;
     
      if(params == null)
      {
            params = EnsureParamsPulledFromQueryString(params);
      }
}

private function EnsureParamsPulledFromQueryString(params:String):String
{
    trace("params are null");
    //Have to run JavaScript for QueryString
    //BABELFISH: Tienen que funcionar el Javascript para QueryString
    var isAvailable:Boolean = ExternalInterface.available;

    //TODO: This should use a timer?
    if(isAvailable)
    {
        if(checkJavaScriptReady())
        {
            params = ExternalInterface.call("GetQueryString");
        }
    }

}

The magic is in the second routine, which fires off the GetQueryString method in JavaScript. Pretty slick, eh? If you want JavaScript to call into Flex, you also need to set up a callback method, but that is beyond what we are doing right now. Here is what happens.

  1. Try to pull parameters from the application object (old code)
  2. If fails, run the pullParameters method and pull from JavaScript GetQueryString
  3. This returns the querystring as a string

Now, this was not good enough for me, as the querystring was in this format:

index.aspx?000000001^vehicle1|000000002^vehicle2|000000003^vehicle3|000000004^vehicle4|000000005^vehicle5|000000006^vehicle6

NOTE: The index.aspx is merely a container at this time. That will change in future iterations.

I am not fond of non-delimited querystring values, as it leaves me no way to add addtional values. As this is not deprecated in the code, I had to leave this alone. The first condition I coded was the normal multi-parameter method of pulling:

    if(params.search("&") > 0)
    {
        //split the string
        //BABELFISH: parta la secuencia
        var splitQueryString:Array = params.split("&");
       
        //Find the correct variable and get rid of ‘vehicles’
        //BABELFISH: Encuentre el variable correcto y líbrese de ‘ vehicles’
        var i:uint;
        for(i=0;1<splitQueryString.length;i++)
        {
            if(splitQueryString[i].substr(0,8) == "vehicles")
            {
                params = splitQueryString[i].toString().replace("vehicles=", "");
                break;
            }
        }
    }

I am fairly certain there is a better syntax here (I could write it shorter in C#, at least), so I am going to learn more Flex to see if I can shorten this up a bit. I am asking here if the string has an ampersand (&), which indicates more than one querystring argument. I then look for the string with vehicles, split it and grab the right hand part of the string. The replace is fairly crude, but it works. This covers this type of string:

index.aspx?debug=true&vehicles=000000001^vehicle1|000000002^vehicle2|000000003^vehicle3|000000004^vehicle4|000000005^vehicle5|000000006^vehicle6

Tested and it works. But what if there is only one argument. It will not find the ampersand, but it will find an equals sign (=). So, I add this condition.

    else if(params.search("=") > 0)
    {
        //See if this is vehicles or something else
        //BABELFISH: Vea si éste es vehículos o algo más
        if(params.substr(0,8) == "vehicles")
        {
            params=params.replace("vehicles=", "");
        }
        else
        {
            params = "";
        }
    }

I have now got something that will pull the string when it is correctly set up as key/value. I also avoid the wrong key, if the single key does not contain the information I want. Any of these URLs yield the correct answer:

index.aspx?000000001^vehicle1|000000002^vehicle2|000000003^vehicle3|000000004^vehicle4|000000005^vehicle5|000000006^vehicle6

index.aspx?vehicles=000000001^vehicle1|000000002^vehicle2|000000003^vehicle3|000000004^vehicle4|000000005^vehicle5|000000006^vehicle6

index.aspx?debug=true&vehicles=000000001^vehicle1|000000002^vehicle2|000000003^vehicle3|000000004^vehicle4|000000005^vehicle5|000000006^vehicle6

And, I have been pessimistic enough that these URLs do not crash anything:

index.aspx
index.aspx?debug=true

One problem solved.

NOTE: As I went to "press" on this, I found that this type of URL could crash the application, so I have to refactor again.

index.aspx?debug=true&

Working with a configuration file

Coming from an ASP.NET world, I use configuration files all of the time. While you cannot change elements on the fly and have the application continue to run without rebooting (not completely true, as you can have your own "config" files), you can customize installs on different machines. What this means, to me, is I can run a variety of like web applications with different skins (configured) and even turn on an off features in config. This is critical to my work, as it stands today, as we produce customized websites with identical funcationality (at least most of it). One engine, multiple sites is a much better option than multiple engines, multiple sites — at least from a maintenance standpoint. Surprised But I digress, as we all know how important configuration is.

As far as I can tell config.xml, the Flex config file, compiles into the application. It is also copied to the output directory, which I do not understand, as I see no way to pull from it at run time. If somebody knows a built in way, let me know. Here is the solution I employed.

I found a blog entry from Ryan Guill which details a component called configLoader (you can find the blog entry here). I included the componet inside my Flex project under the com/util package (create folders and you create package names). Pulling with this is fairly simple. First, you have to pull in import statements to include the package.

import com.util.ConfigLoader;
import com.util.ConfigLoaderEvent;

Next, I have my Init for the config loader.

private function InitConfigLoader()
{
      configLoader = new ConfigLoader("config.xml");
      configLoader.addEventListener(ConfigLoaderEvent.COMPLETE,configLoader_complete_handler);
      configLoader.addEventListener(ConfigLoaderEvent.FAULT,configLoader_fault_handler);
      configLoader.load();
}

Pretty standard. Load the file and careate events to listen to events. This is also straight from the blog page. When it finishes loading, I end up in the routine configLoader_complete_handler, which looks like this:

public function configLoader_complete_handler ( e:ConfigLoaderEvent ) : void
{          
      configXML = e.data; 
      useConfig(configXML);     
}

And, I procede to tear out the XML in the useConfig routine.

public function useConfig( xml:XML ) : void
{
    var i:uint;
    for(i=0;i<xml.maps.map.length();i++)
    {               
        var mapId:String = xml.maps.map[i].ID;
        var source:String = xml.maps.map[i].mapSource;
        var mapName:String = xml.maps.map[i].name;
        var mapDescription:String = xml.maps.map[i].description;
        var ulMapPoint:mapPoint = new mapPoint(xml.maps.map[i].upperLeftLat, xml.maps.map[i].upperLeftLong);
        var lrMapPoint:mapPoint = new mapPoint(xml.maps.map[i].lowerRightLat, xml.maps.map[i].lowerRightLong);
        var x:Number = xml.maps.map[i].imageX;
        var y:Number = xml.maps.map[i].imageY;
       
        mapCont.addMapSource(mapId, source, mapName, mapDescription, ulMapPoint, lrMapPoint, x, y);       
    }   
}

The XML for this routine is here:

<config>
    <webServicePath>http://localhost/MyWebService/webServiceModule.aspx</webServicePath&gt;
    <maps>
        <map>
            <ID>1</ID>
            <name>Lot – 1</name>
            <imageX>600</imageX>
            <imageY>400</imageY>
            <description>Lot 1 Description</description>
            <mapSource>lot1.swf</mapSource>
            <upperLeftLat>33.934932</upperLeftLat>
            <upperLeftLong>-84.174615</upperLeftLong>
            <lowerRightLat>33.930329</lowerRightLat>
            <lowerRightLong>-84.16698</lowerRightLong>
        </map>
        <map>
            <ID>2</ID>
            <name>Lot – 2</name>
            <imageX>600</imageX>
            <imageY>400</imageY>
            <description>Lot 3 Description</description>
            <mapSource>lot2.swf</mapSource>
            <upperLeftLat>32.847887</upperLeftLat>
            <upperLeftLong>-96.865415</upperLeftLong>
            <lowerRightLat>32.84229</lowerRightLat>
            <lowerRightLong>-96.857924</lowerRightLong>
        </map>
    </maps>
    <screen width="500" height="350" />
</config>

Pulling the web service to get data is also easy, so I now have an application that can run on any system, with multiple maps configured. There are still a few things I need to do, but it is much better than it was a few hours ago. I also refactored the web service into testable libraries, which I briefly covered in my last blog entry.

Peace and Grace,
Greg

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: