Book review: ActionScript Graphic Cookbook by Peter Backx and Dominic Gélineau

book cover
ActionScript Graphic Cookbook
Backx, Peter and Gélineau, Dominic
2012, Packt Publishing
978-1-84969-320-2

This is a cookbook, a book of recipes; it is not a reference or teaching book; you will not have a comprehensive library at the end. This book give specific solutions to specific needs. A teaching book might show a finished chart with call-outs explaining how to create or configure the pieces, but a recipe is not an analysis: it starts with the least foundation it can and works towards a finished graphic. This book will show you how to create graphic representations of data; it will not tell you when to use a particular technique.

It is also not a users’ manual for the Data Visualization chart library from Adobe, although the recipes in the middle of the book use that library. For simple line charts (e.g. sparklines), the recipe describes how to draw the lines oneself using the ActionScript graphic primitives. For advanced rotatable 3-D charts (which Data Visualization does not support) and 3-D landscapes, it uses advanced geometry and low-level polygon fills. While these are not Flex components (the code is pure ActionScript with mostly Sprite-based classes, many recipes are connected to data, sometimes dynamically.

Each recipe presents as a set of sections: the title, Getting ready, How to do it…, How it works…, There’s more…, and See also. Besides the inconsistent use of ellipsis, the title section (which describes the need) is often perfunctory and sometimes omits a picture of the finished graph or chart (i.e. the tempting photo of the finished dish). While some How it works… sections show additional (but too few) images, a graphics book should err on their side of too many images, and certainly should always have one as part of the recipe’s description.

Within sections, some of the recipes do built on the previous one a bit: graphic enhancements, adding different data sources, or using a more complex representations. The sections cover

  • Drawing with the core ActionScript objects
  • Representing sets of data in graphs
  • Bar charts (using Adobe’s Data Visualization library)
  • Advanced charts without the Data Visualization library
  • Charting Data on Maps
  • Animation and Zooming in on Data
  • Showing Network Data Relationships Graphically
  • Rotatable 3-D Charts and Data Landscapes

The book is available in Kindle and paperback editions as well as PDF, mobi, and epub direct from the publisher. The PDF seems to be images of the pages, not text (no copy-and-paste, no external links, and no links from the table of contents). It is also available on-line from the publisher’s site once purchased. The HTML presentation, often being very wide, actually makes the chapters easier to read than the book format: the code does not need to wrap; the images are visible on the same “page” as the rest of the recipe, and the links work.

Some recipes need to import data from files or web services and include code and discussion to do so; these pedestrian routines (and some entire recipes!) distract from the topic of graphic functionality; perhaps they could have been relegated to an appendix. Some recipes, perhaps acknowledging that supporting code is large and tedious, direct the user to download files from the Packt Publishing website and “… follow along”.  The index is 12 pages long; very respectable for a 280 page book.

For its niche, it is likely the definitive reference. It does not address the relative merits of using a well-known charting library vs. using trigonometric functions and low-level drawing functions; clearly these include abstraction and performance. Cookbooks don’t offer a questionnaire; they just tell you how to make a great souffle  Joe Bob says: check it out.

The Only Good Comment Is a Useful Comment

I’ve talked about comments in The Zen of Comments, but the rising tide of ASDocs (and JDoc, etc.) brings this home: requiring comments is as pointless as requiring lines-of-code or a minimum number of check-ins. The existence of comments adds nothing to source code; they make it harder to see the code itself and are often out of date.

/**
* this is the current record id
*/
public var currentRecordId : String ="";

/**
* handle search button click
*
* @param event the mouse event from the click
*/
private function searchButtonClickHandler(event : MouseEvent) : void
{
   ...
}

/**
* @return Foo
*/
public function get foo() : Foo
{
    return _foo;
}
/** TODO: fix this
*/
public function clearGlobalList() : void
{
    this.globalList.removeAll();
}
/**
* @param control int
* @param clearAll delete extra items
*
* @return the number of deleted items
*/
public function truncateData(maximumCount : Number) : void
{
    ...
}

A bad comment is worse than no comment. The goal is to add information to the source code, nor obsolete noise, and the best way is to name the variables, objects, and methods so they are self-descriptive. As you can see from the examples above, adding ASDoc headers makes the code less readable, and the names of the variables and functions already completely describe their behavior.

Good Nomenclature is the Best Comment

or

“The proper use of comments is to compensate for our failure to express ourselves in code.” — Robert C. Martin

“If the code and the comments disagree, then both are probably wrong” –attributed to Norm Schryer

“Good code is its own best documentation. As you’re about to add a comment, ask yourself, ‘How can I improve the code so that this comment isn’t needed? ‘ Improve the code and then document it to make it even clearer” –Steve McConnell Code Complete

I’m not advocating Hungarian notation. The need to use short variable names has long past, and abbreviating types is more than silly. Perhaps it is a dogged remnant of my sordid past in Pascal: I like complete, explicit names.

  • Class names should be specific nouns and avoid words that have meanings in the context of programming (we are running out of terms for “object”!). For example,
  • Method names should start with a verb (“open”, “delete”, “calculate”) and contain the names of the classes they are going to act on, e.g. importDetail(aDetail : Detail) : void
  • Property and field names should not include their class name (no Client.clientName) unless necessary to differentiate it within the context of the class (Client.clientName and Client.accountName).

Sample Naming Patterns

  • function findFoo(criteria) : Foo returns a Foo or null/-1/empty string if not found. I use an “a” prefix on my parameters to differentiate them from local variables and class fields/properties
  • function getFoo(criteria) : Foo returns a Foo or throws an exception if not found
  • function createFoo(initial data) : Foo returns a new Foo and adds it to the list/collection/model. Throws an exception if the Foo cannot be created (e.g. the key data already exists)
  • function openFoo(initial data) : Foo returns an existing Foo that matches the data or returns a new Foo and adds it to the list/collection/model
  • function removeFoo(aFoo) : Boolean returns true if aFoo was remove and false if aFoo was not present to remove. throws an exception is aFoo was there but the method could, for some reason, not remove it
  • function calculateTotal(fooArray : Array) : Number returns a value based on the parameter. The name indicates that the result is a dynamic value that is not stored persistently and is not based on anything else
  • function composeErrorMessage(error : Error, data : Foo, user : User) : String returns text value based on the parameters. The name indicates that the result is a dynamic value that is not stored persistently and is not based on anything else
  • class DeletionConfirmationView (not DeleteView or event DeleteConfirmationView). “Delete” is a verb and indicates a function (which is going to delete something). The view is going to present a confirmation dialog for the deletion (the noun form of “delete”).
  • function foo(param : type, pleaseDoSomething : Boolean) : void The second parameter is asking the method to do something like delete child objects, suppress notifications. The please makes it clear that it is a parameter, not a field or property of the object. Calling it doSomething or even isDoSomething does not make it clear that it is a parameter

Some bad examples from the Flex framework itself:

  • property includeInLayout, visible, includeInLayout - it should be “isIncludedInLayout”.
  • properties visible and enabled are at least adjectives, not verbs.
Other bad examples
  • property countFavorites - it should be favoritesCount. The function that calculates the value might be called “countFavorites()”

An excellent book referencing the code aesthetics of various luminaries is Clean Code, Robert C. Martin, Prentice Hall 2009, ISBN 0-13-235088-2

There, There, and Everywhere

While Flash is a full participant in the world and idioms of the web, Flex is more attuned to applications (e.g.  full-screen) rather than as part of a larger web page. I come from a traditional applications development (sometimes I say that I don’t do dancing kitties”). When Adobe made the AIR runtime for the major desktops, I felt like I was home. Just as Flex and Flash allow one to Write Once, Browse Everywhere, AIR allows us to write applications that behave the same (mostly) whether running off a browser or on a desktop1.

AIR is a desktop runtime container that executes swf applications2 stored on a local drive rather than receiving the swf as part of a web page in a browser. It’s runtime library supports several classes that the Flash Player runtime library does not, specifically ones dealing with local file selection and management, HTML display, windowing, and native chrome (menus, window widgets, etc). AIR also includes installation and update protocols useful for desktop applications, which include an application profile specifying who distributed  it (i.e. who digitally signed it) and if it will modify local files, access local resources like web cams, etc. The AIR runtime will enforce these limitations (just as the Flash Player limits its swf’s’ access to servers based on the server’s crossdomain.xml file).

Even with the new ability to package, install, and execute native code modules, AIR is not designed to build low-level system tools for the desktop. It is designed to present the same sort of rich application that the Flash Player presents on the browser. These application present data in interesting ways. They connect different services together (e.g. Twitter and Google Maps). They connect to private servers and databases.

Why AIR?

We want to offer an AIR version of our applications because of the things they will notice: They will notice if your web application does not have an internet connection. The will notice if they have to browse to every file instead of dragging it. They will notice that your application reminder makes a new tab in their browser instead of a toaster message onto their desktop.

The key to offering the same application on both the Flash Player and the AIR desktop is more than reuse the same pieces; the key is to “reuse” the whole thing.

To do this, create an AIR project and a Web (Flash Player) project (e.g. DoSomthingAIR and DoSomethingWeb). Both will have just the top-level application MXML file and an implementation of IFilesManager appropriate to that environment. The majority of the application is in a core component in a DoSomethingLibrary project. It  has all the data connections and UI of the application. It can use local storage3 to keep non-confidential default settings (and even share them between the Flash Player and AIR applications on the same machine).

The only parts of the application specific to AIR or the Flash Player are:

  • Native drag and drop messages are ignored in the player (but they generate standard drag & drop messages)
  • System connectivity messages (AIR only)
  • User inactivity messages (AIR only)
  • File access operations (AIR only)

If the core application component is listening for AIR-only events, it will still run well in the Flash Player (although it will never hear those events). To handle the differences with file access operations, for example, one can create an Interface that hides the details and have an implementation class for each platform (see AIRFileManager and FlashFilesManager in Figure 1). Each application can instantiate the appropriate instance and assign it to a property of the core application component or of some common static class. The core application calls methods of that property (which is typed to the Instance) oblivious to which platform’s class is doing the work.

Converting an Existing Application

One might have an application complete or near complete before one considers a dual platform deployment.It’s easy to break the core application out so both runtime platforms can use it.

  1. Open the existing project
  2. Create a new Library project
  3. Add any swc libraries (e.g. flexlib, a3corelib) that the existing project to the new Library project. You can move any swc’s from the original project’s lib/ to the new Library project’s lib/ as well
  4. Drag the src/com/company/stuff directories from the original project down to the Libraryproject src/. You want it to move, not copy.
  5. Open the project’s main MXML file and copy everything to the clipboard
  6. In the new Library project, create a new MXML component based on Canvas in the new library project. Call it “CoreApplication”
  7. In the new CoreApplication component, paste the clipboard under the <Canvas> element of the new element and remove the <Application> elements and any attributes that apply directly to the application itself.
  8. Save and build the new library project and fix any references.
  9. Add the library project to the existingproject’s Build Path. If you add the whole project instead of just the swc reference, debugging is easy
  10. In the existing project’s main MXML file, remove everything except the <Application> elements and those properties that apply only to the application itself (e.g. viewSourceURL)
  11. In Design Mode, add an instance of the new CoreApplication component from the Custom branch of the component tree. Set the bounds to: 0, 0, 100%, 100%
  12. Build the original application again. It should work exactly the same.

Now you can create a new project for the other runtime platform (e.g. AIR) and put a CoreApplication component in it as well.

Two-For-One Is Enough

This technique allows us to deliver the same application via a browser or a desktop; it does not magically change the application. Mobile applications are fundamentally different in their presentation and flows; although the delivery mechanisms (AIR and the Flash Player) might be the same, do not try to leverage any non-trivial application’s UI into a very different environment. For this, we do need to design the parts of the application for individual reuse in a new application designed for one or more mobile environments.


If you are new to AIR, a good intermediate book is:

Adobe AIR in Action, Lott, Joseph; Rotondo, Kathryn; Ahn, Samuel; Atkins, Ashley, Manning 2009


1Windows, Mac, and Linux desktops
2As well as HTML and JavaScript
3using the SharedObject class

Telescope to Hell, or An ActionScript Builder Pattern

One of the evils that OOP freed us from was long parameter lists like this:

wm_op(hdl, 0, 0 , 230, false, false, "false", true, ptr.value)

Even with default parameters and the occasional enumeration, telescoping parameter lists like these are chronic bugs waiting to happen and to regress. The problem was that call was not self-explanatory. Using object properties allows us to see each property’s name as we assign it:

var processor : Processor = new Processor(this);
processor.x = 0;
processor.y = 0;
processor.minHeight = 230;
processor.buttonText = "false";
processor.initialValue = pointer.value;

Instead of entering dummy arguments to allow access to later parameters, the object sets its own defaults.

Every Value a Parameter

The Every-Value-A-Parameter (EVAP) style of constructor design seems to forgo much of the strength of objects. It treats objects as data structures and does not take advantage of their properties’ ability to be internally consistent. The EVAP style passes all the internal values of the object in the constructor’s parameters, and all properties are read-only from that point forward (i.e. an immutable object).

If several parameters in a long list interact with each other or are mutually exclusive, either the constructor fails to create the object (often an opaque exception) or we do not check the interaction until needed (e.g. until a query object was activated). We cannot validate the parameters one-by-one because they all come in at once.

The calling code must also have all the information about the object at once.

var foo : Foo;
if (ref.gah >= 14)
   foo = new Foo(this, ref.gah, null, (container == null) ? null : container.bar);
else
    foo = new Foo(this, 2, null, (container == null) ? null : container.bar);

As shown, a symptom of  EVAP is a mass of trinary expressions (exp ? true : false)  in call parameters, or branches with alternate initializers for the same object. If one could set the new object’s attributes explicitly and individually (i.e. after it was instantiated), then the code branches would be specific to the attributes they affect.

var foo : Foo = new Foo (this);  

if (ref.gah > 14)
   foo.gah = ref.gah;
else
   foo.gah = 2;

if (container != null)
   foo.bar = container.bar;

A subtler aspect is the ability to allow descendant classes, containers, or helpers to override an object’s configuration. For example, a base query object might have a base query string, but a descendant class or a wrapper class, OracleSQLQuery, might take advantage of Oracle-specific syntax or settings. If the query string must be a read-only constructor parameter, the descendant cannot override an contained object created by its ancestor; at best, it can create a new object, copy every other attribute value, and assign it to a protected reference that the ancestor will (we hope) use.

Special Mutators for Mutually Dependent Properties

Some parameters’ validity depends on other parameters (e.g. height and minHeight). An object can adjust one to fit another, but this can violate a  object’s agreement with the user: a writable parameter value will be what the user set it to.

form.height = 95;
form.minHeight = 80;
...
form.height = 75;
form.minHeight = 70;

If the object unilaterally and silently changes the second value for height to 80 (the minimum), the coder will expect it to be 75. If the object rejected the assignment because it (at the time) violated the minHeight value, then the object will remain internally consistent.
Some properties are mutually dependent, so it is more than a matter of the sequence of assignment. If the object cannot defer the dependency validation (see below), then it can offer a special mutator-like function that accepts both or all of the mutually dependent values at once. Two or three parameters are not too obtuse, especially if the function names them explicitly.

foo.setNameAndPassword(...)
foo.setPosition(x, y);

Deferred or Explicit Validation

A SQLQuery object can allow any value for its queryText and parameters properties until the moment its active property turns true. At that moment, all of the properties must be consistent with each other (i.e. the items in the parameters collection must match the references in the queryText). If something tries to change one of these values in an active query, the object must decide whether to set active to false or to reject the new value (e.g. by throwing an exception).

Another example might be a class URIString, which does not check for validity until something calls function isValid(). The user can set and clear different, mutually-exclusive parts of a URI without raising an exception because their interrelations don’t matter until the validation method runs.


Jeffrey Stylos and Steven Clarke: “Usability Implications of Requiring Parameters in Objects’ Constructors.”ICSE 2007.

Immutable Objects and Internal Consistency

Nevertheless, some objects really want to be immutable. It saves a lot of checking and binding events, allows static caching, and removes the need for a .copy() or .clone() method.

The best of both wolds is an immutable object with a (mutable) builder. The builder object takes all of the initial values as interactive, public properties, optionally validates those values when they are set or just before the builder becomes the immutable constructor’s sole parameter. One might use it like this:

    var builder : Object = ImmutableObject.createBuilder();
    builder.name = "Fred";
    builder.birthMonth = 4;
    builder.birthYear = 1960;

    var imm : ImmutableObject = builder.create();

The immutable class and the builder class work together:

  1. The class’ static function returns an instance of the builder class
  2. The consumer plays with the builder’s properties
  3. The consumer has the builder create an instance of the class
  4. The builder validates its properties
  5. The builder passes itself to the class’ constructor
  6. The class initializes its immutable properties (i.e. the private fields) from the builder’s properties
  7. The builder passes the new class instance to the consumer

Instead of creating a public builder class for each immutable class and ending up with lots of little class files, we can use an internal class with some casting. Note that while the client code does not have access to the ImmutableObjectBuilder class, it gets an instance of that class (into its Object-type variable), and it cannot treat it like a generic object (e.g. builder.height = 44; gives an error).

public class ImmutableObject
{
    public function ImmutableObject(builder : Object)
    {
        if (builder is ImmutableObjectBuilder)
        {
            builder.validate();

            initialize(ImmutableObjectBuilder(builder));
        }
        else
            throw new Error("use ImmutableObject.createBuilder().create()");
    }

    private function initialize(builder : ImmutableObjectBuilder) : void
    {
        _name = builder.name;
        _address = builder.address;
        _birthDate = new Date(builder.birthYear, builder.birthMonth);
        _photoUrl = builder.photoUrl;
    }

    public static function createBuilder() : ImmutableObjectBuilder
    {
        return new ImmutableObjectBuilder();
    }

    public function get name() : String
    {
        return _name;
    }
    private var _name : String = "";

    public function get address() : String
    {
        return _address;
    }
    private var _address : String = "";

    public function get birthDate() : Date
    {
        return _birthDate;
    }
    private var _birthDate : Date;

    public function get photoUrl() : String
    {
        return _photoUrl;
    }
    private var _photoUrl : String = null;
}

class ImmutableObjectBuilder
{
    public var name : String = "";
    public var address : String = "";
    public var birthMonth : int;
    public var birthYear : int;
    public var photoUrl : String = null;

    public function validate() : void
    {
        if ((name == null) || (StringUtil.trim(name) == ""))
            throw new Error(""name" is required");

        if ((birthMonth == 0) != (birthYear == 0))
            throw new Error("Both birthMonth and birthYear must be set");
    }

    public function create() : ImmutableObject
    {
        return new ImmutableObject(this);
    }
}

The downside to this pattern is that it is a little tricky to subclass.[EXPAND Click to see how]
Using the ImmutableObject shown above, the descendant class has to get an instance of the ancestor’s builder and give that reference to its builder. The descendant builder cannot descend from the ancestor build because they are both internal classes.

The descendant builder has to proxy or shadow all of the ancestor builder’s properties so the descendant builder can use its reference to the ancestor builder to initialize the ancestor part of the descendant instance. I said it was a little tricky.

In this example, the descendant builder proxies properties (e.g. name, address) to the ancestor builder’s properties, and adds one new one: role.

public class Descendant extends ImmutableObject
{
    public function Descendant(builder : Object)
    {
       if (builder is DescendantBuilder)
       {
           builder.validate();

                   // this calls the ancestor's initialize()
           super(builder.ancestorBuilder); 

                 // we have to cast because the parameter 
                 // cannot be the internal class DescendantBuilder
           initialize(DescendantBuilder(builder));  
        }
        else
            throw new Error("use Descendant.createBuilder().create()");
    }

    public function get role() : String
    {
        return _role;
    }
    private var _role : String;

    //  we can't virtualize this method because
    //     we won't have access to the AncestorBuilder
    private function initialize(builder : DescendantBuilder) : void
    {
        _role = builder.role;
    }

    //      static methods are not inherited, so we do not override
    public static function createBuilder() : DescendantBuilder
    {
        var ancestorClassName : String = getQualifiedSuperclassName(Descendant);
        var ancestorClass : Class = Class(getDefinitionByName(ancestorClassName));
        var ancestorBuilder : Object = ancestorClass.createBuilder();

        return new DescendantBuilder(ancestorBuilder);
    }
}

class DescendantBuilder
{
    public function DescendantBuilder(ancestorBuilder : Object)
    {
        _ancestorBuilder = ancestorBuilder;
    }

    public function get ancestorBuilder() : Object
    {
        return _ancestorBuilder;
    }
    private var _ancestorBuilder : Object;

    public function get name() : String
    {
        return _ancestorBuilder.name;    //    proxy
    }

    public function set name(value : String) : void
    {
        _ancestorBuilder.name = value;
    }

    public function get address() : String
    {
        return _ancestorBuilder.address;    //    proxy
    }

    public function set address(value : String) : void
    {
        _ancestorBuilder.name = value;
    }

    public function get birthMonth() : int
    {
        return _ancestorBuilder.birthMonth;    //    proxy
    }

    public function set birthMonth(value : int) : void
    {
        _ancestorBuilder.birthMonth = value;
    }

    public function get birthYear() : int
    {
        return _ancestorBuilder.birthYear;    //    proxy
    }

    public function set birthYear(value : int) : void
    {
        _ancestorBuilder.birthYear = value;
    }

    public var role : String = null;    //    NOT a proxy

    public function validate() : void
    {
        _ancestorBuilder.validate();

        if ((role == null) || (StringUtil.trim(role) == ""))
            throw new Error(""role" is required");
    }

    public function create() : Descendant
    {
        return new Descendant(this);
    }
}

And the obligatory sequence diagram: 

[/EXPAND]

Lazy, Asynchronous Binding, or Call Once, Return Twice

The best part of binding is that it just works: your visible control (the target) gets the data (the source) to show without worrying about synchronizing it with the various steps of data retrieval.

“Getting the value” means retrieving a field, function, or accessor of an object. The target will always get the value when it is initialized in the Configuration phase (so const values work as binding sources even without the [Bindable] metatag).

The Bindable Mantra

“Hear the event, get the value”

The important and subtle implication of this is that the notification dispatcher and the (presumably changed) data are not necessarily the same thing. The normal and default behavior is that changing a value will dispatch an event to the targets bound to that value. This is the built-in behavior when properties use the [Bindable] metatag without specifying an event type; the compiler creates hidden accessors and mutators that checks for changed values (optimizing by ignoring new values that are the same as the old value) and dispatches a PROPERTY_CHANGED event.

The things that bind to the bindable property get a hidden event listener that listens for the PROPERTY_CHANGED event and assigns the source to the target whenever the listener gets an event.

Simple Binding

Target Object: a form

<Application />
    <s:List dataProvider="{foo.listData}" />
</Application>

Source Object: a data class (e.g. a model)

class Foo extends EventDispatcher
{
    [Bindable]
    public var listData : ICollection;
}

Specified Event Binding:[Bindable("foo")]

If the [Bindable] metatag specifies an event type (e.g [Bindable("event type")], then the compiler does not create hidden accessors and mutators for the property; the class must dispatch a notification event itself. One can use this to make non-accessor functions bindable: the hidden event listeners created for the targets will call the function whenever they hear the event and assign the result to the target.

A class can have many properties marked as [Bindable("foo")], and whenever it dispatches new Event("foo"), everything bound to any of those properties will retrieve the data value immediately.

Target Object: a form

<Application />
<!--   call get listData() on initialization and whenever "listDataChange" heard  -->
    <s:List dataProvider="{foo.listData}" />

<!--   call get listCount() on initialization and whenever "listDataChange" heard  -->
    <s:Label text="{foo.getListCount().toString()}" />   
</Application>

Source Object: a data class (e.g. a model)

class Foo extends EventDispatcher
{
    public static const LIST_DATA_CHANGE : String = "listDataChange";
    public static const TOTAL_COUNT_CHANGE : String = "totalCountChange";

    [Bindable("listDataChange")]
    public function get listData() : ICollection
    {
        return _listData;
    }

    public function setListData(value : ICollection) : void
    {
        if (_listData != value)
        {
            _listData = value;
            dispatchEvent(new Event(LIST_DATA_CHANGE));
        }
    }

    [Bindable("listDataChange")]
    public function getListCount() : int
    {
        if (_listData == null)
            return 0;
        else
            return _listData.length;
    }

    [Bindable("listDataChange")]
    [Bindable("totalCountChange")]    // something else dispatches this event
    public function totalCount() : int
    {
        return getListCount() + someOtherNumber;
    }
}

The Cool Bit

Because the notification can be separate from the actual data, one can return result asynchronously to a bound listener. This might be called the Data Accessor Object Pattern.

1. The target calls the source accessor
2. If the underlying object is uninitialized or stale

a. The accessor immediately returns null(which is a perfectly reasonable return value)

b. The accessor sends a request to a server

c. When the server returns data, the responder dispatches the binding event

d. The target hears the binding event and calls the accessor

3. The accessor returns the new data

Target Object: a form

<Application />
 <!--  call get listData() on initialization and whenever "listDataChange" heard -->
    <s:List dataProvider="{foo.listData}" />  
</Application>

Source Object: a data class (e.g. a model)

class Foo extends EventDispatcher
{
    public static const LIST_DATA_CHANGE : String = "listDataChange";

    [Bindable("listDataChange")]
    public function get listData() : ICollection
    {
        if (_listData == null)
        {
            var serverCall = new ServerCall(receiveListData);

            //  return null immediately; don't wait for the server
        }

        return _listData;
    }

    public function receiveListData(data : ICollection) : void
    {
        if (_listData != data)
        {
            _listData = data;

                     // make the bound targets call listData() AGAIN
            dispatchEvent(new Event(LIST_DATA_CHANGE));
        }
    }
}

… Only one, but the Collection has to want to change

Using an ArrayCollection as the data provider for a list (or any display object) is easy and powerful. Somehow, collection variables fire change events and update their binding targets not only when the whole list variable changes, but also when any element in that list changes. Lists and Grids (which love ArrayCollections) explicitly listen for COLLECTION_CHANGE events from inside their dataProvider as well as wholesale changes.

Note that only the dataProvider seems to do this: binding an ArrayCollection to any other property will not automatically get notifications when a collection item changes (although binding to arrayCollection.length will). If you use BindingUtils or variable injection to a mutator, it will only get updated when the whole value changes, not when an item changes.

Given that the elements know nothing about the Collection containing them (indeed, an object can be contained by several Collections and Arrays simultaneously), and the Collection cannot require that elements descend from some sort of CollectionItem ancestor, or implement a specific ICollectionItem interface, or even call a method in the Collection when they change, the only way the Collection can monitor elements is if it takes advantage of a protocol that most objects do anyway: the Bindable/PROPERTY_CHANGE event.

The ArrayCollection does not, despite appearances, wrap an Array; it wraps an ArrayList that wraps an Array. The ArrayList monitors every element in its array by adding a listener for PropertyChangeEvent.PROPERTY_CHANGE events on every array element that happens to implement the IEventDispatcher interface. A listener can listen for any type of event it wants to without being dependent on the event dispatcher; they are loosely coupled. As it happens, any class with a [Bindable] property is automatically an IEventDispatcher, and it dispatches the PROPERTY_CHANGE event every time a [Bindable] property changes. The class can also dispatch this event explicitly.

The ArrayList listens for these events and dispatches a COLLECTION_CHANGE event summarizing and describing the changes to the Array’s items. The ArrayCollection uses this event from the ArrayList to gather information for its own COLLECTION_CHANGE event, and the List or Grid uses the COLLECTION_CHANGE event to refresh or rebuild their ItemRenderers.

To be clear: array element objects fire PROPERTY_CHANGE events automatically for every change to their [Bindable] properties. If you want properties that are not bound or are bound to explicit event ([Bindable("readOnlyPropertyChange")]) to trigger COLLECTION_CHANGE events in the collections that hold them, those property mutators have to explicitly fire the PROPERTY_CHANGE event (in addition to any other events they dispatch).

[Bindable]
public function get foo() : String
{
    return _foo;
}
public function set foo(value : String) : void
{
    _foo = value;
}

[Bindable("readOnlyPropertyChange")]
public function get readOnlyProperty() : int
{
    return _readOnlyProperty;
}

public function clearBar() : void
{
    var oldValue : String = _readOnlyProperty;
    _readOnlyProperty = 0;

//    signal for binding/listeners directly on this object
    this.dispatchEvent(new Event("readOnlyPropertyChange"));

//    signal for binding/listeners on this object's Collection
    if (hasEventListener(PropertyChangeEvent.PROPERTY_CHANGE ))
    {
        var event : PropertyChangeEvent =
                PropertyChangeEvent.createUpdateEvent( this, "property", oldValue, 0);
        dispatchEvent( event );
    }
}

Arrays do not detect or fire events; they are just a collection of items.

Sequence diagram of the events between Lists, ArrayCollections, ArrayLists, Arrays, and the items in Arrays

Sequence diagram of the events between Lists, ArrayCollections, ArrayLists, Arrays, and the items in Arrays

The Dark World of IExternizable

I never worked with Remote Objects much; I stuck with SOAP, REST., and AMF. What I did learn was how custom objects can represent themselves to an AMF stream, and to local-storage SharedObjects and fileStreams too.

ActionScript has built-in serialization (compatible with java.io.Externalizable interface). While this is very obvious streaming data over AMF pipelines (to LCDS, AMFPHP, Zend, etc.), it quietly makes saving and restoring data pretty painless. These operations use the streaming protocol:

  • RemoteObjects
  • Streaming objects to ByteArray.writeObject()
  • SharedObject (local storage)
  • EncryptedSharedObject

Simple Streaming

Most values and object will stream to and from AMF just fine. To effect this simple protocol, put the [RemoteClass("com.company.application.section.ClassName")] metadata tag (Flex projects only) above the class declaration so the parser knows what class to create. The class constructor must have defaults for all its arguments (or no arguments), and all public properties must writable (i.e. they must be vars or have mutators — aka “setters”).

The streamed properties that are the ones that are public var or have both a public accessor and a mutator. If a class has public properties one does not wish to stream, mark those properies with the [Transient] metadata tag.

[RemoteClass("com.company.application.section.Foo")]
public class Foo 

    public var name : String = "";
    public var id : String = "";

    [Transient]
    public var contact : Contact = null;
}

The streaming system streams and object in by creating a new instance and then setting each property in turn (it will not use the constructor’s parameters).

Streaming Read-Only and Private Properties

If some properties don’t have public mutators (i.e. they are publicly read-only), or one wants to stream non-public attributes, one can write custom serializing and de-serializing routines by implementing the interface IExternizable. It requires two methods:

[RemoteClass("com.company.application.section.Foo")]
public class Foo implements IExternizable
{
    public var name : String  = "";

    public function get id() : String
    {
        return _id;
    }
    private var _id : String = "";

    public function readExternal(input : IDataInput) : void
    {
        name = input.readObject() as String;
        _id = input.readUnsignedInt();

                // this object might need to be IExternizable too
        _contact = input.readObject() as Contact;
    }
    public function writeExternal(output : IDataOutput) : void
    {
        output.writeObject(name);   //  same sequence as readExternal()
        output.writeUnsignedInt(_id);
        output.writeObject(_contact);
    }
}

The IDataInput has no information about its data, so one cannot, for example, test to see if an integer value is less than 127 and safely call writeByte() instead of writeInt() (to save space) because readExternal() has no way of knowing if it should call readByte() or readInt(). One can stream format and version information out first, and use that information to determine the format of the rest of the data when streaming it in (although this smacks of variable record types from Days Long Past). It’s much easier to always stream the same object types in the same sequence, even writing out some empty strings and nulls as placeholders when necessary.

Any class implementing IExternizable must explicitly stream all its data because the built-in streaming is disabled . Note that the example above will stream the entire nested _contact object out as part of the Foo object stream. If  Contact implemented IExternizable, it’s readExternal() and writeExternal() would handle its streaming too.

Streaming References

If a class contains a references to another object, one can stream out an id and resolve the id after it streams in. One can resolve that id into an object reference either explicitly when both the referer and the cross-reference (e.g. the list of all Contacts) are ready, or do it as a lazy-loading accessor:

[RemoteClass("com.company.application.section.Foo")]
public class Foo implements IExternizable
{
    public var name : String  = "";

    public function get id() : String
    {
        return _id;
    }
    private var _id : String = "";

    public function get contact() : Contact
    {
        if (_contact == null)
        {
            if (_contactId != "")
            {
//
// some mechanism to find the contact by Id; it can return null
//
                _contact = Contact.lookupId(_contactId);
                _contactId = "";
            }
        }

        return _contact;
    }
    private var _contact : Contact = null;
    private var _contactId : String = "";

    public function readExternal(input : IDataInput) : void
    {
        name = input.readObject() as String;
        _id = input.readInt();
        _contactId = input.readObject() as String;
    }

    public function writeExternal(output : IDataOutput) : void
    {
         output.writeObject(name); // same sequence as readExternal()
         output.writeInt(_id);

         if (_contact == null)
            output.writeObject(""); // placeholder
        else
            output.writeObject(_contact.Id);   // just the Id
    }
}

Adding IExternizable to Existing Objects

If you add IExternizable to an object that has been saved and will be retrieved, you need to read the stream in the same sequence (and don’t fail if some new property is not in the stream). You can determine the sequence of properties before adding the writeExternal() by creating mutators for all the public properties, and then debugging while an instance comes in.

See Adobe Documentation on IExternizable

All AIR Applications Are Single-Instance

This is not an option: if one tries to launch an AIR application twice, the first instance remains and no other instances start. The first application, however, does get notification and the command-line arguments of the subsequent application executions. It’s a subtle way of communicating with a running AIR application.

If an application has to handle multiple instanciation (like a registered file reader), it has to be able to present multiple instances of some part of its user interface (or be willing to replace the current data at any time). This hearkens back to the days of Multi-Document Interface (MDI) applications. One can encapsulate the main UI as a component, and the application can create one for each “instance” the application needs to present.

Multiple Invocations of an AIR ApplicationThe NativeWindow.invoke Event Fires On Every Application Launch

The application will get an event every time the OS launches an instance of the application. It gets an event on startup, and it gets one each time the OS executes the AIR application file; subsequent executions do not start additional  instances. These events contains the command-line parameters specific to that invocation.

The Invoke event fits into the startup cycle here:

  1. FlexEvent.ADD for the application object
  2. FlexEvent.PREINITIALIZE
  3. Event.ADDED for the descendents of the application object
    (These events happen sporadically intermixed with the following events)
  4. FlexEvent.INITIALIZE
  5. FlexEvent.CREATION_COMPLETE
  6. Event.ADDED_TO_STAGE
  7. FlexEvent.APPLICATION_COMPLETE
  8. InvokeEvent.INVOKE
  9. Event.ACTIVATE

Command-line Parameters in AIR Applications

First, the simple behavior:

<?xml version="1.0" encoding="utf-8"?>
<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009"
    xmlns:s="library://ns.adobe.com/flex/spark" 
    xmlns:mx="library://ns.adobe.com/flex/mx"
    invoke="onInvoke(event)">
    <fx:Script>
        <![CDATA[
            private function onInvoke(event : InvokeEvent) : void
            {
                logText.text += "invoke: event.arguments = " + 
                    event.arguments.toString();

                if (event.currentDirectory != null) {
                    logText.text += "; event.currentDirectory = " + 
                        event.currentDirectory.nativePath;
                }
                else
                    logText.text += "; event.currentDirectory = null";


                if (event.reason != null)
                    logText.text += "; event.reason = " + event.reason;
                else
                    logText.text += "; event.reason =  null";
            }
        ]]>
    </fx:Script>

    <s:TextArea id="logText" left="10" right="10" top="105" bottom="10" />
</s:WindowedApplication>

 

Things My Event Told Me

  • InvokeEvent.arguments is an array (never null) of strings. See your operating system for the rules about special characters and quoting.
  • InvokeEvent.currentDirectory is a File instance set to the directory of the executable. Note that running from the IDE will point to the FlashBuilder.exe directory; running from a shortcut/alias will indicate the location of the shortcut, not the .air file.
  • InvokeEvent.reason is either “standard” or “login” if the  OS starts it automatically (see InvokeEventReason for constants)

Things My Event Never Told Me

  • The event does not indicate if this event is part of the application startup (i.e. the first event) or a subsequent invocation. Use a global counter.
  • The event does not indicate if this event is because the OS registered this application for a file type and the user “opened” a file of that type. The sole argument is the complete (native) path including file name, but shortcuts and the command-line can start the application can have a single argument that is a file path as well.

 

It’s an Interface; It’s a Code-Behind; It’s a … Skin?

I managed to avoid skinning components in Flex 3 (I called it “dancing kitties”). I just made the components work. I tried to ignore the changes in Flex 4 skins, but Spark components are very different than MX components, although Adobe has succeeded in making the changes apparently minor.

In Flex 2 and 3, “skins” were CSS styles and images and, rarely, bits of code that created graphics.

What Flex 4 Skins Are

Flex 4 skins are ActionScript classes that are completely responsible for presenting the data from another ActionScript class.  Custom components can inherent behavior from their class ancestors (UIComponent, Group, etc.), but builds their appearance by choosing or creating a custom skin class. The component has the properties and methods to manage the component’s behavior and data, and the skin class presents it to the user. A skin class usually descends from the ancestor class Skin (itself a child of Group) and contains other components (e.g. Labels, Images, RichTextEdit) to display and gather data, (somewhat like the way a custom item-renderer contains other components). The nesting can seem a little recursive, but it has the same advantages as any other object oriented protocol: each object does its thing with loose linkages to other objects.

It’s Code-Behind

The relationship between a component and its skin is like a code-behind pattern: specified component class variables refer to the skin class’ sub-components by name. As the skin class constructs, it passes each of its sub-compents (e.g. Buttons) to the host component. The host component does two things: first, if it finds a variable annotated with a [SkinPart], it assigns the instance to the value from the skin. Second, it calls the addPart() method so the component can add event listeners, etc. For example, if a skin class used with a Button has a sub-component with the id “labelDisplay”, that sub-component’s “text” property will get the Button.label value via the ButtonBase.setContent() method:

The Component The Skin
[SkinPart(required="false")]
public var labelDisplay : IDisplayText;   // must have a .text property
<s:Label id="labelDisplay" />
protected function setContent(value : String) : void
{
    _content = value;  //    this stores the value in the component

    if (labelDisplay != null)  // it's not required, so it could be null
        labelDisplay.text = value;
}
The Label component displays the new value

It’s an interface

And I mean with a lower-case “i”: it’s is not an ActionScript Interface It is an agreement between the component and the skin so they can work together (actually, the component requires and the skin fulfills):

  • [SkinPart(required="true")] or [SkinPart]on the component requires the skin to have a sub-component with a specific name; the class will throw an exception during creation without it
  • [SkinPart(required="false")] on the component will connect to a skin’s sub-component with a specific name if it’s present

It’s a View

So skins have evolved from being styles and bitmaps to more of a MVC-type view, and the component acts as a controller and model. The component keeps a copy of all the visible data (like label texts) so it can change and initialize skins at any time. When a skin initializes, the component gets a notification for each sub-component of the skin object, and the component can add an event listener or set its value. While the component can query the skin about constraint-based position and sizes, the less the component knows about the view, the better.

Likewise, the less the skin knows about the component, the better. The component handles all the interaction with the outside world: methods to set and filter the data, validation of property values, etc. The skin gets data values and occasionally styles and top-level dimensions. The component uses its code-behind references directly to the skin’s sub-components to set property values and get events.

Binding Optional

While binding is very cool, this protocol does not usually use events and watchers and binding stuff: the code in the component explicitly assigns values to its references of the skin’s sub-components (e.g. SkinnableTextBase.setPrompt()). The skin can bind to the component’s properties (e.g. hostComponent.title), but it’s unusual.

The Component’s View of the Skin

These are the states, properties, and methods of the component that give it access to and control over the skin.

skin class
(will inherit from ancestor classes)
.skinClass
required skin states [SkinState("xxx")]
skin sub-components
[SkinPart(required="true")]
public var closeIcon : Button;

[SkinPart(required="false")]
public var labelDisplay : IDisplayText;   // must have a .text property
add/remove event listeners to skin objects
override protected function partAdded(partName : String, instance : Object) : void
{
    super.partAdded(partName,instance);
    if (instance == closeIcon)
    {
        closeIcon.addEventListener(MouseEvent.CLICK, onCloseIcon);
        closeIcon.source = getStyle(“icon”);
    }
}

override protected function partRemoved(partName : String, instance:Object) : void
{
    super.partRemoved(partName, instance);

    if (instance == closeIcon)
        closeIcon.removeEventListener(MouseEvent.CLICK, onCloseIcon);
}
skin instance
(rarely used)
.skin
supporting methods
SkinnableComponent.invalidateSkinState();
// this forces a call to getCurrentSkinState()
other miscellaneous overrides
override protected function getCurrentSkinState() : String { 

//    translate component states into skin states
//    note: the base class' method, 
//            SkinnableComponent.getCurrentSkinState(), returns null
}

override public function styleChanged(styleProp : String) : void
{
    super.styleChanged(styleProp);

    if (styleProp == “icon”)
        closeIcon.source = getStyle(“icon”);
}
data values push values into the skin using code-behind
currentState the component sets the skin’s state to reflect some aspect (e.g. disabled)

The Skin’s View of the Component

These are the properties the skin used to receive and communicate with the host. In general, the skin assumes that the synchronization with the component will push values into the sub-components and change the skin’s state; it does not reference the .hostComponent directly.

host class [HostComponent("spark.components.Button")]
host instance
(rarely used)
.hostComponent
data values pushed into values by the component
currentState the component sets the skin’s state to reflect some aspect (e.g. disabled)

Skin State

The component can require specific skin states (e.g. “normal”, “disabled”). It uses those states to instruct the skin to change appearance, but leaves the specifics up to the skin. Adobe could have used an event, an expected method, or some other way to signal the skin; the advantage of using states is that it’s easy to set up an MXML component to respond to states. The base component’s invalidateSkinState() method (called when the component’s state change, for example) triggers a call to getCurrentSkinState(). That method examines the component’s properties and returns the skin state that fits.

For example, SkinnableTextBase requires the skin to have states “normalWithPrompt” and “disabledWithPrompt”, and uses them as appropriate in getCurrentSkinState() to have the skin show the .prompt text (using whatever sub-component the skin chooses to show it).

Object-Oriented Skins … Or Not

Adobe has been uncharacteristically forceful in their declaration that  we should not create inheritance hierarchies of skin classes; Adobe wants us to copy and paste when creating new skin classes. Part of that is political: they have lots of tools for designers to work with developers, and designers do not sub-class anything. Although I am a OOP-freak, I see their point: skins are so thin (i.e. they have very little behavior implemented in code) that the logistical overhead of having some parts in the ancestor and some in a descendant does not have much benefit to balance it. A well-designed skin class can use CSS as a replaceable properties file, and that can bundle a lot of behavior in a thin class without sub-classes.