Wednesday, March 16, 2011

Stump the Evangelists

So back at MAX 2010, there was a stump the evangelists game. There was a call for questions. I submitted a whole bunch, but most weren't used. These questions and their answers are useful to share, because they were intended to stump the people in the know. They all came about because there was significant debugging effort involved in understanding them.

Q1) Which kind of COLLECTION_CHANGE Event (as specified by CollectionKindEvent), does the HierarchicalCollectionView ignore, when dispatched from its hierarchicalData source.
A1) CollectionKindEvent.REFRESH
Reference: HierachicalCollectionView.
collectionChangeHandler() method   -> line 1413; if-else statement
Discovered when trying to apply filtering to an array collection that gets mapped to hierachicalData




Q2)  Why do you see columns A&B and not just A in the following example:

<?xml version="1.0" encoding="utf-8"?>
<mx:AdvancedDataGrid xmlns:fx="http://ns.adobe.com/mxml/2009"
                     xmlns:s="library://ns.adobe.com/flex/spark"
                     xmlns:mx="library://ns.adobe.com/flex/mx"
                     currentState="A"
                     >
    <mx:states>
        <s:State name="A" />
        <s:State name="B" />
    </mx:states>
   
    <mx:columns>
        <mx:AdvancedDataGridColumn dataField="A" />
        <mx:AdvancedDataGridColumn dataField="B" />
    </mx:columns>
   
    <mx:columns.A>
        <mx:AdvancedDataGridColumn dataField="A" />
    </mx:columns.A>
   
    <mx:columns.B>
        <mx:AdvancedDataGridColumn dataField="B" />
    </mx:columns.B>
</mx:AdvancedDataGrid>

A2) The problem is with commitProperties. currentState gets set in UIComponent's commitProperties, but  in AdvancedDataGridBaseEx.commitProperties(), createDisplayableColumns is called BEFORE super.commitProperties(). This means that the columns get set before the state, which SHOULD cause them to reset, but since we are currently within the commitProperties method, the invalidateProperties flag does NOT get triggered again (to avoid an infinite loop), even though the columnChanged flag gets set. CommitProperties doesn't run again until an external event which causes invalidateProperties to be set.





Q3) Within a single collection displayed in an editable column of an editable AdvancedDataGrid, you have a property that is sometimes editable and sometimes not (for example, you cannot purchase quantity to an out of stock item, even though it still shows up on the catalog). What tricks can you employ to have the ADG skip over the non-editable cells when using keyboard navigation?

A3) There is nothing you can do on the renderer/editor to prevent this. This cell will still receive focus from keyboard navigation, because of the findNextItemRenderer() method within AdvancedDataGridBase. Even if there are no focusable elements within the cell. Nor can you pass focus to the next element via the focusManager as the grid doesn't respond to non-keyboard or mouse focus events. The ONLY point of entry (ie: public or protected properties or methods) to which you have access to cause focus to skip over this renderer is the  protected function isDataEditable(data:Object):Boolean defined on AdvancedDataGridBase. You MUST extend the ADG and override this method in order to be able to set up your grid to skip particular cells.

Q4.1) Not using Flash Player 10.1, how can you catch a majority of thrown errors within the Flex component lifecycle.
A4.1) UIComponentGlobals.catchCallLaterExceptions = true

Q4.2) With UIComponentGlobals.catchCallLaterExceptions set to true, what errors do NOT get caught?
A4.2) Errors that happen within EventListeners

Q4.3) With Flash Player 10.1, how can you catch all errors
A4.3) loaderInfo["uncaughtErrorEvents"].addEventListener("uncaughtError",globalExeptionCatcher) //at the time being, there is / should be a way with strongly typed values to do this later.


Q5) How do I see a traceLog of all of the Bindings that are firing?
A5) BindingManager.debugBinding()

Q6) What is the value of the updateCount from the following code?
<?xml version="1.0" encoding="utf-8"?>
<s:myComp xmlns:fx="http://ns.adobe.com/mxml/2009"
               xmlns:s="library://ns.adobe.com/flex/spark"
               xmlns:mx="library://ns.adobe.com/flex/mx"
               creationComplete="onCreationComplete()"
               >
    <fx:Script>
        <![CDATA[
            private var _myProp:Number = 1;
            private var updateCount:Number = 0;
           
            [Bindable]
            public function get myProp():Number {return _myProp;}
            public function set myProp(v:Number):void
            {
                _myProp = v;
                updateCount++;
            }
           
            private function onCreationComplete():void
            {
                myProp = 1;
            }
        ]]>
    </fx:Script>
   
</s:myComp>
A6) 0. The binding doesn't fire if the getter returns the same value as what you are setting. You would need to manually dispatch an event within the setter and set the event metadata within the binding to get the updateCount value to change to 1

Q7) How must you create Bindings on implict getters and setters within an interface?
package
{
    interface IExample
    {
        function get propA():String;
        function set propA(v:String):void;
    }
}

A7) You must use explict events within the bindings tag, otherwise bindings will never fire in the implementations
package
{
    interface IExample
    {
        [Bindable(event="propAChanged")]
        function get propA():String;
        function set propA(v:String):void;
    }
}


Q8) What are any of the non-standard practices (related to the flex framework as a whole) regarding the hover state in the MxAdvancedDataGridItemRenderer that make it extremely difficult to extend this class and modify the hover functionality?

A8)        * hovered property is being set in the "invalidateProperties" method instead of the "commitProperties" method.
             * actually it is super.hovered that is being set, elminating the ability to override it
             * when hovered IS being set, the state change happends via "setCurrentState()" method instead of "currentState" property
             * hovered is determined from "listBase.isItemHighlighted(data);"... ie the renderer asks the grid. this supercedes any value that is passed in manually
             * hovered property doesn't actually get set (in the super clases) if the currentState is already "hovered".

Thursday, March 10, 2011

Getting below the minimum height for the spark combo box

Did you know that the spark combobox has a built in minimum height of 16? Trying to change that is a little tricky.

<?xml version="1.0" encoding="utf-8"?>
<s:ComboBox xmlns:fx="http://ns.adobe.com/mxml/2009" 
   xmlns:s="library://ns.adobe.com/flex/spark" 
   xmlns:mx="library://ns.adobe.com/flex/mx"
   >
 <fx:Declarations>
  <!-- Place non-visual elements (e.g., services, value objects) here -->
 </fx:Declarations>
 <fx:Script>
  <![CDATA[
   import mx.core.IUIComponent;
   import mx.core.UIComponent;
   import mx.events.FlexEvent;
   
   private var partArray:Array = new Array();




   protected override function childrenCreated():void
   {
    //TODO Auto-generated method stub
    super.childrenCreated();
    var len:int = partArray.length;
    var comp:UIComponent;
    //reset those pesky minimum heights
    for (var i:int = 0 ; i< len; i++)
    {
     comp = UIComponent(partArray[i])
     comp.explicitMinHeight = Math.min(height,comp.minHeight);
     comp.explicitMinWidth = Math.min(width, comp.minWidth);
    }
   }

   protected override function partAdded(partName:String, instance:Object):void
   {
    //TODO Auto-generated method stub
    super.partAdded(partName,instance);
    partArray.push(instance);
   }

   
  ]]>
 </fx:Script>
 
</s:ComboBox>

Generating fake data

Fake data? Why would you ever need fake data?

Well the backend isn't ready? You're doing a POC? You're offline (airplane) while trying to develop.

I've found myself creating collections of vo's just to fill a list or combo box to test my interactions and events. I use to write for loops to do this and found that I could simplify the process somewhat.

So I created a little generator script, that you input the class (VO), the number that you want, and optionally any properties that you don't want to generate and it will return an Array of sequentially described VOs.

Basically what this means is that all of your vo string properties will be A,B,C,etc. Your vo number properties will be the index that it is, ie first vo = 0, 2nd vo = 1, etc. boolean vo properties will be random t/f. Date properties will be new Date().

This is not set up for complex nested datatypes


package com.squaredi.core.data.generators
{
 /**
  *
  * Context:
  *  During development (unit testing, POC's, and deployment before the backend)
  *  We may want to quickly create a series of temporary objects to fill a grid or to play with
  * 
  * Therefore:
  *   We can generate a series of objects where string properties will be "A,B,C..."  
  *   Number properties will be the number of the item that is generated
  *   Boolean properties will be random. 
  * 
  * Forces:
  *   The intent is to create a filled VO quickly... the data is not ment to be accurate
  * 
  * But:
  *   This doesn't include any additional datatypes then the intrinsic ones listed
  */
 
 import flash.utils.describeType;
 
 import mx.utils.ObjectUtil;

 public class MockGenerator
 {
  private static function createStub(cls:Class,index:int = 0, ignoreProperties:Array /*String*/ = null):Object // NO PMD
  {
   if (ignoreProperties == null) {ignoreProperties = [];}
   
   // First grab all of our writeable getter/setters and variables
   var info:XML =  describeType(cls);
   var setters:XMLList = info..accessor.(@access == "readwrite");
   var vars:XMLList = info..variable;
   var merge:XMLList = setters + vars;
   
   //Figure out what letter in the sequence this is going to be
   //If we go above 26, then start repeating letters (AA, BB) (AAA,BBB)
   var lettersInAlphabet:Number = 26; //0 based
   var repeatCount:int = Math.max(1,Math.ceil(index / lettersInAlphabet));
   if (index % lettersInAlphabet ==0 && index >0) {repeatCount++;}
   var asciiNum:Number = 65 + (index % lettersInAlphabet)
   var asciiChar:String = String.fromCharCode(asciiNum);
   var stringVal:String = ""
   for (var i:int = 0 ; i < repeatCount; i++)
   {
    stringVal += asciiChar
   }
   
   //Figure out what Numbers and Booleans are going to be
   var numVal:Number = index;
   var boolVal:Boolean = Boolean(Math.round(Math.random()));
   
   //Create the new Object, loop over each property and apply value
   var obj:Object = new cls();
   var prop:String;
   var type:String;
   for each (var propXml:XML in merge)
   {
    prop = propXml.@name;
    type= propXml.@type;
    
    //skip over namespaced properties (internal managed data for example)
    if (propXml.attribute("uri").length() >0)
    {
     break;
    }
    
    //Skip over uid
    if (prop == "uid")
    {
     continue;
    }
    
    //Allow for the developer to ignore setting some of the properties (as that might be the thing that they are testing)
    if (ignoreProperties.indexOf(prop) == -1)
    {
     switch(type)
     {
      case "String": obj[prop] = stringVal;break;
      case "Number":
      case "int":
       obj[prop] = numVal;
       break;
      case "Boolean":
       obj[prop] = boolVal;
       break;
      case "Date":
       obj[prop] = new Date();
       break;
     }
    }
   }
   
   return obj;
  }
  
  /**
   * Generate a given number of mock objects with some unique data
   * */
  public static function createStubs(cls:Class, total:int, ignoreProperties:Array /*String*/ = null ):Array /*Objects of type Class*/
  {
   var rtn:Array = new Array();
   for (var i:int = 0; i < total; i++)
   {
    rtn.push(createStub(cls,i,ignoreProperties));
   }
   return rtn;
  }
  
 }
}

Conversion to TitleCase

So I discovered in livedocs (Using Regular Expressions ) that you can pass a function as the 2nd parameter to a regular expression replace.

When a function name is used as the second parameter of the replace() method, the following are passed as parameters to the called function:
  • The matching portion of the string.
  • Any captured parenthetical group matches. The number of arguments passed this way varies depending on the number of captured parenthetical group matches. You can determine the number of captured parenthetical group matches by checking arguments.length - 3 within the function code.
  • The index position in the string where the match begins.
  • The complete string. 
therefore, to make a really easy convert to TitleCase method:


public static function toTitleCase(value:String):String
  {
   if (value)
   {
    //Convert to title case
    var titleCaseStr:String = value.toLowerCase();
    
    //http://livedocs.adobe.com/flex/3/html/help.html?content=12_Using_Regular_Expressions_10.html
    
    var toUpperFxn:Function = function (...parameters):String 
    {
     return parameters[1].toUpperCase()
    }

    //Find all word boundarys and then the first letter of the word... then call the function
    titleCaseStr = titleCaseStr.replace(/\b(\w)/g, toUpperFxn);
    
    return titleCaseStr;
   }

   return ""; 
  }

Copying Related Properties into Objects

Lets say that you have a hierarchy of VO's that share some properties....
for example:
  • OrderedItemVO extends ProductItemVO
  • PriceExtendedVO extends PriceVO
and the issue is that you did a search (or whatever) and you have a collection of products, that you then want to add to an order (in which they need to be upgraded to an ordered item vo).

The brute force solution would be to create a new OrderedItemVo() and copy a property one by one into it like:
var copy: OrderedItemVO = new OrderedItemVO();
copy.id = source.id
copy.description = source.description
...

but here is an alternative method that will do this a little more elegantly:

public static function copy(source:Object, destination:Object):void // NO PMD
  {
   // First grab all of our writeable getter/setters and variables
   var info:XML =  describeType(source);
   var setters:XMLList = info..accessor.(@access == "readwrite");
   var vars:XMLList = info..variable;
   var merge:XMLList = setters + vars;
   
   //loop over everything adding the properties
   var prop:String;
   var type:String;
   var isDynamic:Boolean = info.@isDynamic == "true"
   for each (var propXml:XML in merge)
   {
    prop = propXml.@name;
    
    //skip over uid... a good idea to exclude this one.
    if (prop == "uid")
    {
     continue;
    }
    
    if (destination.hasOwnProperty(prop) || isDynamic )
    {
     destination[prop] = source[prop]
    }
    
   } 
  }

Wednesday, March 9, 2011

Cancelling inflight remote calls

So here is a couple of scenarios:
  1. User clicks search, then immediately clicks logout, a couple of moments later the search results come back and the user is taken back into search results screen, resulting in an unstable app now that the user is null
  2. Sales Rep is working on a quote for customer A, in the middle of an operation (search, save, quote), they get a phone call from customer B and swithes accounts to handle the phone call. Want to make sure that customer A result events don't corrupt or popuplate customer B's data model.
So how to cancel a remote operation?

mx.rpc.Operation does have a cancel method, but that won't work if you are using a Responder pattern based on a token. Besides, it requires knowledge of all of the operations that are inflight.

Here is another approach. Add a static variable to AsyncToken. When a new AsyncToken is created, copy that static variable into a local variable. Before calling our responders compare the local variable to the static variable before proceeding. If you need to cancel a response, change the static variable on the AsyncToken.

So that is the solution... now how to implement it.
You need to extend 3 classes to get this work. AsyncToken, Operation, RemoteObject.
AsyncToken is extended as described above.
Operation is extended to return the new AsyncToken,
RemoteObject is extended to use the new Operation.

AsyncTokenWResponderValidation.as
package com.squaredi.remoting.cancelable
{
 import mx.core.mx_internal;
 import mx.messaging.messages.IMessage;
 import mx.rpc.AsyncToken;
 import mx.rpc.IResponder;
 import mx.rpc.events.FaultEvent;
 import mx.rpc.events.ResultEvent;
 
 use namespace mx_internal;
 /**
  * Context: You need to cancel an inflight message
  * Issue: While you can cancel an mx.rpc.Operation, it doesn't actually cancel responders assigned to the AsyncToken
  *     Since many of the frameworks relay on the Responder pattern, which applies the responder to the token,
  *     this is how we can not respond to an inflight message
  * 
  * Usage: if needing to cancel an inflight message -> AsyncTokenWResponderValidation.resetValidationToken();
  *       
  * 
  * 
  * */
 public dynamic class AsyncTokenWResponderValidation extends AsyncToken
 {
  
  private static var globalValidationToken:String;
  protected var validationToken:String;
  
  public static function resetValidationToken():void
  {
   globalValidationToken = String(new Date().time);
  }
  
  public static function getGlobalValidationToken():String
  {
   return globalValidationToken;
  }
  
  public function AsyncTokenWResponderValidation(message:IMessage=null)
  {
   super(message);
   if (getGlobalValidationToken() == null)
   {
    resetValidationToken()
   }
   validationToken = getGlobalValidationToken();
  }
  
  
  mx_internal override function applyFault(event:FaultEvent):void
  {
   if (isTokenValid())
   {
    super.applyFault(event);
   }
  }

  mx_internal override function applyResult(event:ResultEvent):void
  {
   if (isTokenValid())
   {
    super.applyResult(event);
   }
  }

  protected function isTokenValid():Boolean
  {
   return this.validationToken == getGlobalValidationToken() 
  }

 }
}

CancelableOperation.as
package com.squaredi.remoting.cancelable
{
 import mx.core.mx_internal;
 import mx.messaging.messages.IMessage;
 import mx.rpc.AbstractService;
 import mx.rpc.AsyncToken;
 import mx.rpc.remoting.Operation;
 
 use namespace mx_internal;
 
 public class CancelableOperation extends Operation
 {
  public function CancelableOperation(remoteObject:AbstractService=null, name:String=null)
  {
   super(remoteObject, name);
  }
  
  mx_internal override function invoke(message:IMessage, token:AsyncToken=null):AsyncToken
  {
   if (token == null)
   {
    token = new AsyncTokenWResponderValidation(null);
   }
   return super.invoke(message,token);
  }
 }
}

CancelableRemoteObject.as
package com.squaredi.remoting.cancelable
{
 import mx.core.mx_internal;
 import mx.rpc.AbstractOperation;
 import mx.rpc.remoting.RemoteObject;
 
 use namespace mx_internal
 public class CancelableRemoteObject extends RemoteObject
 {
  public function CancelableRemoteObject(destination:String=null)
  {
   super(destination);
  }
  
  override public function getOperation(name:String):AbstractOperation
  {
   var op:AbstractOperation = super.getOperation(name);
   if (!( op is CancelableOperation))
   {
    op = new CancelableOperation(this, name);
    this._operations[name] = op;
    op.asyncRequest = this.asyncRequest;
   }
   return op;
  }
 }
}

Tuesday, March 8, 2011

Caingorm as a plugin

Context: When developing a large app, we often want to make modules, or even fully self contained subsections of the app. The implemention of  classic cairngorm (< version 3.0) doesn't really allow for that.
I wanted to be able to create a userPreference package, a product package, and order package, etc that I could then easily merge together into the main app.

so this is what I want.

package examples.cairngorm
{
 import com.squaredi.frameworks.cairngorm.FrontControllerPlugin;

 public class MainFC extends FrontControllerPlugin
 {
  public function MainFC()
  {
   super("main");
   addController(new PlugInFC());
  }
 }
}
package examples.cairngorm
{
 import com.squaredi.frameworks.cairngorm.FrontControllerPlugin;
 
 public class PlugInFC extends FrontControllerPlugin
 {
  public function PlugInFC(id:String=null)
  {
   super("plugin");
   addCommand(MyCoolThingEvent.EVENT, MyCoolThingCommand);
  }
 }
}

So here is the class that I created to do that.

FrontControllerPlugIn.as
package com.squaredi.frameworks.cairngorm
{
 import com.adobe.cairngorm.CairngormError;
 import com.adobe.cairngorm.CairngormMessageCodes;
 import com.adobe.cairngorm.commands.ICommand;
 import com.adobe.cairngorm.control.CairngormEvent;
 import com.adobe.cairngorm.control.CairngormEventDispatcher;
 import com.adobe.cairngorm.control.FrontController;
 
 import flash.events.EventDispatcher;
 import flash.utils.Dictionary;
 
 import mx.logging.ILogger;
 import mx.logging.Log;
 import mx.utils.ObjectUtil;

 /**
  * This file is not likely to be edited. Please extend it for the various projects
  * 

  * It also has the ability to add other Controllers into itself to make a plugin type architecture via the addController method
  * 
  * As a plugin type architecture, there are use cases when a developer of a subcomponent or subproject might have a default implementation for and event 
  * (like show an alert for isolation testing), and know that the real implementation needs to be different. 
  * 
  * They should add the command via the addCommandToOverride method
  * 
  * */
 public class FrontControllerPlugin extends FrontController
 {
  protected static var logger:ILogger = Log.getLogger("com.squaredi.frameworks.cairngorm.FrontControllerPlugin")
  private var pluginControllers:Array /*FrontControllerPlugin*/ = new Array();
  
  private var cmdToPluginMap:Dictionary = new Dictionary(true);
  
  protected var overrideCommands:Dictionary = new Dictionary(true);
  
  private var id:String
  
  public function FrontControllerPlugin(id:String = null)
  {
   super();
   this.id = id;
   if (Log.isInfo())
   {
    logger.info("FrontControllerPlugin constructor: " + this.toString());
   }
  }
  
  
  
  /**
   * Adds all of the event command pairs already assocated with another frontController
   * This will throw an error if there are duplicated event names.
   * The expection to this is that if the duplicated event name is intended to be override
   * 
   * @param p
   * 
   */
  public function addController(p:FrontControllerPlugin):void
  {
   /*
   1) Recurse through all subcontrollers
   2) Add only new controllers
   3) Registger commands and deregister from added controllers
   */
   
   //Do subcontrollers
   if (p.pluginControllers.length >0)
   {
    //Register sub controllers first
    var len:int = pluginControllers.length;
    for (var i:int = 0; i < len; i++)
    {
     addController(p.pluginControllers[i]);
    }
    
   }
   
   //Find new Controllers
   if (findPlugin(p) < 0)
   {
    //Have not registered the controller yet
    pluginControllers.push(p);
    
    //Copy Override Items
    for (var clz:String in p.overrideCommands)
    {
     overrideCommands[clz] = p.overrideCommands[clz]; 
    }
    
    //Copy the commands in and remove them from the plugin
    var localcommands:Dictionary = p.commands;
    for (var cmd:String in localcommands)
    {
     //Make sure not to add commands twice either
     cmdToPluginMap[cmd] = p //Make sure to map the command before it gets added
     addCommand(cmd,localcommands[cmd],true);
     p.removeCommand(cmd);
    }
   }
   else
   {
    if (Log.isInfo()){logger.info(p.toString() + " has already been added");}
   }
  }
  
  protected function findPlugin(p:FrontControllerPlugin):int
  {
   var rtn:int = -1;
   var len:int = pluginControllers.length;
   for (var i:int = 0; i < len; i++)
   {
    if (pluginControllers[i].toString() == p.toString())
    {
     rtn = i;
     break;
    }
   }
   return rtn;
  }
  
  /**
   * 
   * Overrides the super class to allow for an error message that includes which plugin the conflict already exists in
   * 
   * @param commandName
   * @param commandRef
   * @param useWeakReference
   * 
   */
  public override function addCommand(commandName:String, commandRef:Class, useWeakReference:Boolean=true):void
  {
   //Add usefull infomation for the alert
   var pluginRef:FrontControllerPlugin = cmdToPluginMap[commandName];
   var pluginName:String = this.toString();
   if (pluginRef != null) { 
    pluginName = pluginRef.toString();
    cmdToPluginMap[commandName] = this;
   }
   
   var commandReference:Class = commands[ commandName ]
   if( commandReference != null)
   {
    //Verify that the command is not in the override list
    var overrideCommandReference:Class = overrideCommands[ commandName ]
    if (overrideCommandReference == null)
    {  
     throw new Error( "Command already registered for " + commandName +" in " + pluginName);
    }
    else
    {
     //We are indeed overriding a command
     if (Log.isInfo()) { logger.info(this.toString + " overriding a command")};
     
     //lets remove the old reference and add the new
     overrideCommands[ commandName ] = null;
     delete overrideCommands[ commandName ];
     
     cmdToPluginMap[commandName] = this;
     
    }
   }
   commands[ commandName ] = commandRef;
   CairngormEventDispatcher.getInstance().addEventListener( commandName, executeCommand, false, 0, useWeakReference );
  }
  
  /**
   * * As a plugin type architecture, there are use cases when a developer of a subcomponent or subproject might have a default implementation for and event 
   * (like show an alert for isolation testing), and know that the real implementation needs to be different. 
   * 
   * Adding command via this method will throw an error if this frontController gets added to another one AND the command is not redefined in the new one
   * 
   * */
  public function addCommandToOverride(commandName:String, commandRef:Class, useWeakReference:Boolean=true):void
  {
   var commandReference:Class = overrideCommands[ commandName ]
   if( commandReference != null)
   {
    var pluginRef:FrontControllerPlugin = cmdToPluginMap[commandName];
    var pluginName:String = this.toString();
    if (pluginRef != null) { 
     pluginName = pluginRef.toString();
     cmdToPluginMap[commandName] = this;
    }
    
    throw new Error( "Command already registered for override " + commandName +" in " + pluginName);
   }
   
   overrideCommands[ commandName ] = commandRef;
   
   addCommand(commandName, commandRef, useWeakReference);
   
  }
  
  protected function checkForOverrideCommands():void
  {
   //Copy Override Items
   var needsToBeOverridden:Array = new Array();
   for  (var clz:String in overrideCommands)
   {
    needsToBeOverridden.push(clz);
   }
   
   if (needsToBeOverridden.length >0)
   {
    throw new Error (this.toString() + " needs to override the following events: " + needsToBeOverridden.toString());
   }
   
  }
  
  internal function forceCheckForOverrideCommands():void
  {
   checkForOverrideCommands();
  }
  /**
   * 
   * Creates the command and temporarily adds it to the context so that we get dependancy injection
   * 
   * 
   */
  override protected function executeCommand( event : CairngormEvent ) : void
  {
   var commandToInitialise : Class = getCommand( event.type );
   var commandToExecute : ICommand = new commandToInitialise();
   
   if (Log.isInfo()) {logger.info("EVENT:" + event.type);}
   commandToExecute.execute( event );
   
  }
  
  internal function getCommandForTesting(commandName:String):Class
  {
   try
   {
    var cmd:Class = getCommand(commandName)
   }
   catch (e:Error)
   {
    //Ignore Cairngorm Errors for testing
   }
   return cmd;
  }
  
  /**
   * 
   * Returns the className for debugging purposes
   * 
   * @return 
   * 
   */
  public function toString():String
  {
   var classInfo:Object = ObjectUtil.getClassInfo(this);
   return classInfo.name + " (" + id + ")"
  }
  
  
 }
}

Using Palettes with CSS

Context: Often times when working on a project, I find that there is a limited number of colors, or a palette, that we are going to be using. But in the CSS each color is specified independently. I would love to be able to say use "corporateGreen" or "corporateBrown" in my css rather then litter the entire css or skins with hex values. This will improve readability, consistency and maintenance.

So here is the solution that I have:

  1. Define a Palette Class based on Proxy that can translate names (String) to colors(uint)
  2. Have this Palette Class read in an embeded text file that defines the colors
  3. Pass the palettes to a stylesheetMixin class that parses the css to replace the names in the css with the hexdecimal values before applying the styles.

Palette.as
package com.squaredi.styles
{
 /**
  * 
  * @author Drew Shefman 
  * dshefman at squaredi dot com
  * 
  */
 import flash.utils.Dictionary;
 import flash.utils.Proxy;
 import flash.utils.flash_proxy;
 
 import mx.core.ByteArrayAsset;
 
 /**
  * Read name value pairs of color names from an "external file."
  * "external file" is quoted because to get it to sync with CSS we actually embed the external files.
  * Here is an example of the external file 
* 
  * _companyRed=0xFF0000
  * _companyGreen=0x00FF00
  * _companyBlue=0x0000FF
  * _columnGreen=_companyGreen
  * 
  * Note: Watch out for spaces in the file
*/
 dynamic public class Palette extends Proxy
 {
  public static var throwErrorsForMissingColors:Boolean = true;
  public static const MISSING_COLOR:int = -1
  
  private  var dict:Dictionary;
  public var paletteName:String;
  
  public function Palette(p_name:String)
  {
   dict = new Dictionary();
  }
  
  /**
   * Create an instance of the embedded text file, 
   * Process the file and split it into name value pairs
   * Save off the names into the palette
   **/ 
  public  function addColorsViaEmbededTextClass(theClass:Class):void
  {
   //Convert the embeded text file to a string
   var byteArray:ByteArrayAsset = ByteArrayAsset(new theClass());
   var str:String = byteArray.readUTFBytes(byteArray.length);
   
   //Clean up extra characters
   str = str.split(" ").join(""); //remove white spaces
   
   
   //Loop over name/value pairs
   var split:Array = str.split("\r\n");
   var obj:Object = this;
   var key:String;
   var value:String;
   var pair:Array;
   var pairStr:String;
   var len:int = split.length;
   for ( var i:int =0 ; i < len ; i++ )
   {
    pairStr = split[i];
    pair = pairStr.split("=");
    key = pair[0];
    value = pair[1];
    obj[key] = value;
   }
  }
  
  
  flash_proxy override function getProperty(name:*):*
  {
   if (isValid(name))
   {
    if (flash_proxy::hasProperty(name))
    {
     return Number(dict[name]);
    }
    else
    {
     if (throwErrorsForMissingColors)
     {
      throw new Error(name + " is an invalid color. Please look in the external colors file to figure out the name.");
     }
     else
     {
      return MISSING_COLOR;
     }
    }
   }
   return null;
  }
  
  flash_proxy override function hasProperty(name:*):Boolean
  {
   return dict[name] != null;
  }
  
  flash_proxy override function setProperty(name:*, value:*):void
  {
   
   //Make sure the name/values are not blank
   if (name=="" || value == "") {return;}
   
   //name comes in as a QName, so we need to getString to get the value out of it.
   name = name.toString();
   
   //Allow for colors to reference previously defined colors
   if (isValid(value)) { value = flash_proxy::getProperty(value)};
   
   //Verify that the name is correct and save it 
   if (isValid(name))
   {
    dict[name] = value;
   }
   else
   {
    throw new Error("Palette colors should start with a '_' to allow for easy distinction from other css variables");
   }
  }
  
  /**
   * Force that color variables start with and "_" 
   * */
  private function isValid(value:String):Boolean
  {
   if (value.indexOf("_") == 0)
   {
    return true;
   }
   
   return false;
  }
  
 }
}

StyleSheetMixin.as:
StyleSheeMixin.as
usage: http://stackoverflow.com/questions/2292127/how-to-have-constants-in-flex-css-files
// =================================================================
/* 
*  http://stackoverflow.com/questions/2292127/how-to-have-constants-in-flex-css-files** *  Copyright (c) 2010 viatropos http://www.viatropos.com/
 *  Lance Pollard
 *  lancejpollard at gmail dot com
 *  
 *  Permission is hereby granted, free of charge, to any person
 *  obtaining a copy of this software and associated documentation
 *  files (the "Software"), to deal in the Software without
 *  restriction, including without limitation the rights to use,
 *  copy, modify, merge, publish, distribute, sublicense, and/or sell
 *  copies of the Software, and to permit persons to whom the
 *  Software is furnished to do so, subject to the following
 *  conditions:
 * 
 *  The above copyright notice and this permission notice shall be
 *  included in all copies or substantial portions of the Software.
 * 
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 *  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 *  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 *  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 *  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 *  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 *  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 *  OTHER DEALINGS IN THE SOFTWARE.
 */
// =================================================================

package com.squaredi.styles
{
 import flash.display.Sprite;
 import flash.events.Event;
 import flash.utils.getDefinitionByName;
 
 import mx.core.IMXMLObject;
 import mx.core.Singleton;
 import mx.styles.CSSStyleDeclaration;
 import mx.styles.IStyleManager2;
 import mx.styles.StyleManager;
 
 public class StylesheetMixin implements IMXMLObject
 {
  private var _palettes:Array;
  /**
   *  Classes of static constants storing values for css
   */
  public function get palettes():Array
  {
   return _palettes;
  }
  public function set palettes(value:Array):void
  {
   _palettes = value;
  }
  
  public function StylesheetMixin()
  {
  }
  
  public function setStyles():void
  {
   // get all selectors in the application
   var styleManager:IStyleManager2 = getStyleManager();
   var selectors:Array = styleManager.selectors;
   var declaration:CSSStyleDeclaration;
   var i:int = 0;
   var n:int = selectors.length;
   for (i; i < n; i++)
   {
    declaration = styleManager.getStyleDeclaration(selectors[i]);
    // set palette properties to each declaration
    setProperties(declaration);
   }
  }
  
  protected function getStyleManager():IStyleManager2
  {
   var application:*;
   try {
    application = flash.utils.getDefinitionByName("mx.core::FlexGlobals")["topLevelApplication"];
    if (application)
     return application.styleManager as IStyleManager2;
   } catch (error:Error) {
    application = flash.utils.getDefinitionByName("mx.core::Application")["application"];
    if (application)
     return IStyleManager2(Singleton.getInstance("mx.styles::IStyleManager2"));
   } 
   return null;
  }
  
  protected function setProperties(declaration:CSSStyleDeclaration):void
  {
   var selector:Object = getDeclarationToken(declaration);
   var property:String;
   for (property in selector)
   {
    setProperty(declaration, property, selector[property]);
   }
  }
  
  public function getDeclarationToken(declaration:CSSStyleDeclaration):Object
  {
   var selector:Object = {factory:declaration.factory};
   // maybe your selector has a "factory" property which we should avoid?
   if (!(typeof(selector.factory) == "function") || selector.factory == null)
    return null;
   selector.factory();
   delete selector.factory;
   return selector;
  }
  
  public function setProperty(declaration:CSSStyleDeclaration, property:String, value:*):*
  {
   var paletteValue:*;
   var changed:Boolean = false;
   if (value is Array)
   {
    var i:int = 0;
    var n:int = value.length;
    for (i; i < n; i++)
    {
     paletteValue = getPaletteItem(value[i]);
     if (paletteValue)
     {
      changed = true;
      value[i] = paletteValue;
     } 
    }
    
   }
   else if (value is String)
   {
    paletteValue = getPaletteItem(value);
    if (paletteValue)
    {
     value = paletteValue;
     changed = true;
    }
   }
   if (changed)
   {
    declaration.setStyle(property, value);
   }
  }
  
  public function getPaletteItem(targetId:String):*
  {
   var i:int = 0;
   var n:int = palettes.length;
   var PaletteClass:Object;
   for (i; i < n; i++)
   {
    PaletteClass = palettes[i];
    if (PaletteClass[targetId])
     return PaletteClass[targetId];
   }
   return null;
  }
  
  private var timer:Sprite = new Sprite();
  // have to wait a frame for styles to be initialized
  public function initialized(document:Object, id:String):void
  {
   var handler:Function = function(event:Event):void
   {
    timer.removeEventListener(Event.ENTER_FRAME, handler);
    timer = null;
    setStyles();
   }
   timer.addEventListener(Event.ENTER_FRAME, handler);
  }
 } 
}
So then in your Application, the way that you would use it would be:
Application.mxml
<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
      xmlns:s="library://ns.adobe.com/flex/spark" 
      xmlns:mx="library://ns.adobe.com/flex/mx" minWidth="955" minHeight="600"
      xmlns:styles="com.squaredi.styles.*"
      
      initialize="onInitialize(event)" 
      >
 <fx:Script>
  <![CDATA[
   import com.squaredi.styles.Palette;
   
   import mx.events.FlexEvent;
   [Embed(source="examples/styles/palette.txt", mimeType="application/octet-stream")]
   public  var paletteClass:Class
   
   [Bindable] private var allPalettes:Array
   
   protected function onInitialize(event:FlexEvent):void
   {
    var pal:Palette = new Palette("main");
    pal.addColorsViaEmbededTextClass(paletteClass);
    
    allPalettes = new Array(pal);
    
   }


  ]]>
 </fx:Script>
 
 <fx:Style>
  @namespace s "library://ns.adobe.com/flex/spark";
  @namespace mx "library://ns.adobe.com/flex/mx";
  
  
  
  .example
  {
   backgroundColor: _corpRed;
   backgroundAlpha:1.0;
   
  }
  </fx:Style>
 
 <fx:Declarations>
  <!-- Place non-visual elements (e.g., services, value objects) here -->
  <styles:StylesheetMixin palettes="{allPalettes}" />
 </fx:Declarations>
 <s:SkinnableContainer styleName="example" width="200" height="200" >
  
  <s:Label text="Hello world" />
 </s:SkinnableContainer>
</s:Application>

and your embeded text file would look like:
_corpRed=0xFF0000

Spark ComboBox and selectedIndex issues

FYI, there is a barely documented "feature" in the spark ComboBox. The property is allowCustomSelectedItem = true (which BTW is mx_internal namespace, so you can't change it unless you override the component or include the namespace)
What this will do, is allow the user to type in a value that is NOT in the dataprovider. If this happens, you get a selectedIndex of -3 and a valid selectedItem...

Here is your pitfall

if (comboBox.selectedIndex != -1)
{
myImportantValue = comboBox.selectedItem
}
You could end up with non-valid user inputed values here.... the better solution is.
if (comboBox.selectedIndex >=0)
{
myImportantValue = comboBox.selectedItem
}

Turning over a new leaf

So I have a little bit of time and a lot of information to share.... so hopefully I can get all of the recent things that I've learned out to the world.

Also, I'm departing from my blog at http://blog.squaredi.com, so I can better format code snippets, which my other blog, I strangely don't have access to.