Saturday, August 23, 2008

Fade Text Without Embedding a Font

Effects like Fade or Rotate won't play nice with text. One way around this is to embed a font in your SWF, but that's just New Bulk City. A coworker reminded me that everything would be hunky-dory if I converted the component to a bit map. Here's an example where I use the ImageSnapshot class to do just that.

I used view states to swap out a Canvas based component with a bitmap image in a SWFLoader. Then I used the SWFLoader's complete event to trigger the final view state, where the Fade effect is actually played.

A little bit of extra work pays off... now the text fades out with the rest of the component. Right click on the app to view the source.

Wednesday, August 20, 2008

Balls, balls, balls!

A prospective employer put forward this little test to screen potential candidates, this is my quick solution. Not quite production ready code, but I don't think they are expecting that for something like this :)

The problem:

Write a Flash application that loads a BML file from an arbitrary URL. A BML file is "ball markup language". It looks something like this:

ball_name:
    color: FF0000
    radius: 40
    appear:
        time: 2
        x: 40
        y: 40
    disappear:
        time: 15
        x: 260
        y: 260

A solution:

My solution was written in Flex, which if you can't tell, I am smitten with. I didn't implement the loading from an arbitrary URL part, but that is trivial using an URLLoader or HTTPService. The code I wrote to parse the BML input is real horror show. But in the real world we'd use XML, JSON, etc. to retrieve this data so I didn't think it was necessary to write something more robust.

I like to write elegant, maintainable code. This is not necessarily the most elegant solution, but it's not bad given that I only spent a few hours doing it. Check out the Ball.as class which does most of the heavy lifting. You can right click on the app to view the source code.

Here is some sample BML markup that you can paste into the app:

brownie_points:
    color: 5B4202
    radius: 30
    appear:
        time: 1
        x: 175
        y: 175
    disappear:
        time: 15
        x: 15
        y: 15
deep_purple:
    color: 380A67
    radius: 35
    appear:
        time: 5
        x: 50
        y: 50
    disappear:
        time: 15
        x: 250
        y: 250
green_monster:
    color:  3F912D
    radius: 99
    appear:
        time: 3
        x: 350
        y: 125
    disappear:
        time: 18
        x: 45
        y: 125
orange_crush:
    color: ECB834
    radius: 77
    appear:
        time: 10
        x: 250
        y: 50
    disappear:
        time: 20
        x: 25
        y: 250

Tuesday, August 19, 2008

Reversing the order of child XML tags

e4x is so powerful, that when I'm working with XML data I'm like Nike: I just do it. It makes everything so easy.

But today I scratched my head for a while on how to achieve a simple task. A BarChart was not rendering my data in descending order. It wasn't the chart's fault, it just rendered the data in the order that the data was being supplied (first item at the bottom). Rather than ask the back end guys to change the order of the XML, I thought I would do a simple reverse() on the data.

Alas, none of the XML, XMLList, or XMLListCollection classes have a reverse() method. That would be a nice enhancement, in my opinion... to be able to reverse the order of an element's child tags.

Here's what I did to get the job done. It's short and sweet, but it took me a few go-rounds to come up w/the solution, so I am documenting it here in case I need it again:


private function reverseChildren( x:XMLList ):XML
{
    var root:XML = <root></root>;
    var children:XMLList = x.children();
   
    for (var i:int = children.length() - 1; i >= 0; i--)
    {
        root.appendChild( children[i] );
    }
   
    return root;
}

This snippet throws away the name of the orginal root tag, but that never seems to matter anyway!

Friday, August 15, 2008

Inverting a Chart Series

You can plot multiple data series on a chart directly on top of each other, or by stacking them. Another way to plot them is by inverting one of the data series so that it is drawn upside down below the horizontal axis.

In the example below I used an AreaChart with two AreaSeries representing the data. You can use the dataFunction property of the series to tell Flex how to render the data. In this case I negated one of the series so it would be drawn below the axis. I also used the labelFunction property of the vertical axis to make sure that labels would be printed with positive values.

You can right click on the example below to view the source.

Wednesday, July 30, 2008

Processing HTML forms with the AIR HTMLLoader class

The Adobe AIR project I'm working on needs to authenticate with an appliance that my company sells. The form on the appliance's login page executes some fancy Javascript prior to submitting the form, which results in an additional session cookie being generated. The simplest approach seemed to be to use an HTMLLoader object to access the login page, fill out the form variables, and submit the form.

After a successful login I could extract the session cookies and tack them onto subsequent requests I was making with Flex's HTTPService. Doing all of this turned out to be pretty easy. Here's an outline of what I did, followed by some code snippets.

In my AIR application I am not rendering any of the HTML retrieved by the HTMLLoader. I'm just using it for authentication, so all of this happens behind the scenes and is not visible to the AIR app's user.

  1. Examine the structure of the login form, paying attention to any form variables and onSubmit methods required by the form.
  2. Instantiate an HTMLLoader, setup an event listener to handle the Event.COMPLETE event, and load the page.
  3. In the event handler, use DOM methods to specify values for the required form variables.
  4. If the form has an onSubmit function, call this function before you submit the form. When you use the DOM to submit a form, the onSubmitfunction is not executed like it normally would by a browser.
  5. Submit the HTML form.
  6. Extract any cookies and store them in a generic object. These cookies will be needed for any HTTPService that you'll be using after authentication.
  7. Use your HTTPService to fetch data as usual.

Note that there is one drawback to this approach. The HTMLLoader doesn't seem to fire an event if your page cannot be loaded. In my AIR app, the user supplies an IP address for the appliance they wish to authenticate with. If the user supplies an invalid IP, the HTMLLoader won't find any page to load and will never fire off an event to indicate that it failed to load the page.

Check out this nice post which describes some options for detecting when the page cannot be loaded.

Here's the code relating to the HTMLLoader, which I've taken from my Caringorm Command class's execute method. Forgive me for not showing the entire class...

import com.adobe.cairngorm.commands.ICommand;
import com.adobe.cairngorm.control.CairngormEvent;
import flash.events.Event;
import flash.html.HTMLLoader;
import flash.net.URLRequest;

// my Cairngorm model locator class which stores my app's variables
import com.sunild.model.AppModelLocator;

private modelLocator:AppModelLocator = AppModelLocator.getInstance();

private var loginAttempted:Boolean = false;

public function execute( event:CairngormEvent ):void {
    var htmlLoader:HTMLLoader = new HTMLLoader();

    // the appliance IP address is stored in my Cairngorm model locator class
    var url:URLRequest = new URLRequest("https://" + modelLocator.applianceIp);
    htmlLoader.addEventListener(Event.COMPLETE, handleHtmlLoaderComplete);
    htmlLoader.load(url);
}

/*
    This method gets called anytime content in the HTMLLoader object changes.
    That can happen if: we submit a form, get redirected, or otherwise navigate
    to some other page.  This function therefore needs logic to detect what
    page it's processing.
*/
private function handleHtmlLoaderComplete( event:Event ):void {

    var ldr:HTMLLoader = event.target as HTMLLoader;

    var doc:Object = ldr.window.document;

    // my login form is found in a frame on the login page
    var frame:Object = ldr.window.frames;

    // a simple method of determining which page we're processing
    trace("Title of page being loaded: " + doc.title);

    // login page, frame[0] has a form named "loginForm"
    if ( !loginAttempted && frame[0] && frame[0].loginForm ) {
        // set the userName and password form variables
        frame[0].loginForm.userName.value = modelLocator.username;
        frame[0].loginForm.password.value = modelLocator.password;

        // execute the form's onSubmit function if necessary
        frame[0].onSubmitFunction();

        // keep track of whether we submitted the form so we only do it once
        loginAttempted = true;

        frame[0].loginForm.submit();
    }
    else if (loginAttempted && frame[0] && frame[0].loginForm) {
        // if we attempted to login but still see the login form, the login attempt failed

        // set loginAttempted to false
        loginAttempted = false;

        // add code here to handle failed login

        // I'm removing the event listener, probably isn't necessary here
        ldr.removeEventListener(Event.COMPLETE, handleHtmlLoaderComplete);

    }
    else {
        // look for something on the page to verify that we were logged in
        // I'm simply using the page's title
        if ( String(doc.title).search("Authentication Succeeded") ) {

            // extract the session cookies and store in an anonymous object
            var tmpCookies:Array = String(doc.cookie).split("; ");
            for each ( var tmpString:String in tmpCookies ) {
                var tmpArray:Array = tmpString.split("=");
                modelLocator.cookieObject[ tmpArray[0] ] = tmpArray[1];
            }

            /* When we get to this point, we are logged in ... now we should clean up
                 and unregister the event handler for the this HTMLLoader object
            */
            ldr.removeEventListener(Event.COMPLETE, handleHtmlLoaderComplete);
        }   
    }
}

And later in my AIR application, I can use an HTTPService to request data from the appliance. Here's an MXML snippet that defines the HTTPService which includes the cookie object we created above. Now when I call the HTTPService's send() method, my session cookies will be included for me!

<mx:HTTPService id="fetchSomeData"
    url="https://{modelLocator.applianceIp}/some_path_to_data.xml"
    resultFormat="e4x"
    headers="{modelLocator.cookieObject}" />

This all seems a little hoaky, and is not the final code that we'll be using in the AIR application. Eventually, authentication with the appliance will all be done via an HTTPService that posts XML data to the appliance. But this functionality has not been implemented on our appliance yet!

Manipulating the DOM with an HTMLLoader is quite easy, so this seemed like a good solution to move the project along in the meantime.

Tuesday, July 1, 2008

Data binding in ActionScript

Most examples you see with Flex do data binding in MXML. This seems to suffice for most cases I can think of, but recently I found the need to change a binding dynamically at run time.

At the time the only way I could think of doing this was to was to do it in ActionScript. I didn't know how to do that, and the documentation seemed a little terse on this topic. So here's my little ditty on how to bind (and "unbind") data in ActionScript.

As a side note, I didn't use this in my application. Instead I used an interim variable to bind my custom control to. I stored my actual data in an array of arrays. Then, in my Cairngorm Command class I copied the appropriate array element into the actual variable that was bound to my custom control. But since I haven't posted anything here for a while, I thought I'd post this article about performing binding operations in ActionScript...

You can use the BindUtils class, which provides two static methods for performing data binding operations: bindProperty() and bindSetter(). I used the bindProperty() method. Here's some example code, and my explanation of what it does:


<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
 layout="absolute"
 creationComplete="init()" viewSourceURL="BindExampleSrc/index.html">
 
 <mx:Form >
  <mx:FormHeading label="ActionScript Binding Example" />
  <mx:FormItem>
   <mx:Label id="labelTextInput1" text="Text Input 1 (original binding source)" />
   <mx:TextInput id="textInput1" />
  </mx:FormItem>
  <mx:FormItem>
   <mx:Label id="labelTextInput2" text="Text Input 2 (original binding destination)" />
   <mx:TextInput id="textInput2" />
  </mx:FormItem>
  <mx:FormItem>
   <mx:Label text="Click this button to reverse the source/dest of the binding" />
   <mx:Button label="Reverse Bindings" click="reverseButtonHandler()" />
  </mx:FormItem>
 </mx:Form>
 
 <mx:Script>
  <![CDATA[
   import mx.binding.utils.ChangeWatcher;
   import mx.binding.utils.BindingUtils;
   
   private var changeWatcher:ChangeWatcher;
   private var bindToggle:Boolean = true;
   
   private function init():void {
    
    changeWatcher = BindingUtils.bindProperty(textInput2, "text", textInput1, "text");
    
   }
   
   private function reverseButtonHandler():void {
    // undo the data binding
    changeWatcher.unwatch();
    
    if (bindToggle) {
     changeWatcher = BindingUtils.bindProperty(textInput1, "text", textInput2, "text");
     bindToggle = false;
     labelTextInput1.text = "Text Input 1 (now the binding destination)";
     labelTextInput2.text = "Text Input 2 (now the binding source)";
    }
    else {
     changeWatcher = BindingUtils.bindProperty(textInput2, "text", textInput1, "text");
     bindToggle = true;
     labelTextInput1.text = "Text Input 1 (now the binding source)";
     labelTextInput2.text = "Text Input 2 (now the binding destination)";
    }
        
   }

  ]]>
 </mx:Script>
</mx:Application>

The function signature for the bindProperty has confusing argument names for the uninitiated:


public static function bindProperty(site:Object, prop:String, host:Object, chain:Object, commitOnly:Boolean = false):ChangeWatcher

But it's not that bad:

  1. The first argument "site" is the destination object, the object that we're binding data to.
  2. The second argument "prop" is a string that contains the name of the property we are binding data to.
  3. The third argument "host" is the object that is the source of the binding.
  4. The fourth argument "chain" is a string that contains the name of the property on the binding source object.
  5. The fifth argument is a boolean, which provides finer grain control over when to update the binding destination, and you probably won't need it for most situations.
  6. The bindProperty() method returns a ChangeWatcher object, we can use this object to undo the data binding.

Note that the BindingUtils class also has a bindSetter method, which should be used when the binding destination object has a setter method to set the value of the desired property.

Here's the simple app listed above, you can also right click on it to the Flex source code. When the app is initialized, the value of the 1st text input field is bound to the second text input. If you click the "Reverse Bindings" button, the data binding is undone and then performed in the opposite direction. Simple, but you should get the point. Cheers!

Friday, May 23, 2008

All Flex developers should know this

I sort of thought I didn't have to worry about memory leaks in Flex, because garbage collection would come to my rescue and sweep away the cruft. However, after what I learned last night (links below), I am reminded of a friend's favorite interview question: is it possible to have memory leaks in Java?

My friend would ask this and the greener candidates would emphatically reply, "No!" He liked asking that question, because he knew there were ways to leak memory in Java despite all the hype about garbage collecting. Sure, in most cases you don't have to worry about cleaning up after yourself. But I guess the smarter folks (not me) just know that it all depends on what you're doing and how you do it.

I stumbled on this post from Dan Schultz, who gives some nice tips on when to use weak or strong references when you add an event listener in Flex. Dan references another article from David Colleta which provides a little more insight.

I am using a Timer in a project that I'm working on, and fortunately I learned this before I had to trouble shoot the associated problems of using the wrong type of reference in my code.

Thanks, guys!