Pages

July 27, 2012

Flattening Hierarchical Data

Posted by Jeremy Mitchell
I'm not a big fan of hierarchical data structures. They can be a pain. For example, I hate having to perform recursive loops to find matching elements.

Unfortunately, many UI controls require hierarchical data (i.e. the Tree component) and sometimes it is unavoidable.

Therefore, if hierarchical data is required, I like to "flatten" the hierarchical data and maintain two data structures - a hierarchical one and a flat one. Flat data structures are much easier to navigate and thanks to the power of object references, each data structure contains references to the same objects so updating an object in the flat data structure also updates it in the hierarchical data structure.

To demonstrate, imagine we have a hierarchical data structure that looks like this:

- Object A
-- Object B
--- Object C

We can use that hierarchical data structure for the Tree component, but, to make our lives easier, let's create a flat data structure (i.e. Array or ArrayCollection) to hold the same objects:

- Object A
- Object B
- Object C

Searching and updating objects in the flat data structure will prove to be much easier.

Here's a example to demonstrate this technique. View source is enabled.

January 3, 2012

Testing for a Flash Builder Pro License

Posted by Jeremy Mitchell
Certain features of the Flex SDK require a valid Flash Builder Pro license to function properly. Examples include charting and automation. For example, if you compile a SWF without a valid Flash Builder Pro license, you'll get the following results:

Charting

Charts compiled without a Flash Builder Pro license contain a watermark.


Automation

Test automation tools (i.e. FlexMonkey) that interact with a SWF compiled without a Flash Builder Pro license will trigger a "License not present. With the trial version only limited records are allowed" error.


A Solution

To avoid these results, simply enter a valid Flash Builder Pro license into Flash Builder and compile your SWF using Flash Builder. Duh.

A Catch

Most enterprise applications are not compiled using Flash Builder. Instead, the build process directly utilizes the command-line compilers, MXMLC and COMPC, in tandem with Ant or Maven. In this scenario, the Flash Builder Pro license key must be entered into the configuration file (flex-config.xml):

    <licenses>
        <license>
            <product>flashbuilder45</product>
            <serial-number>xxxx-xxxx-xxxx-xxxx-xxxx-xxxx</serial-number> <!-- this is my 4.5 license key -->
        </license>
    </licenses>

Another Catch

When using Ant or Maven, you may find out that your Flash Builder license will NOT work for all Flex SDKs. For example, a Flash Builder 4 license will not activate charting or automation for the Flex 4.5 SDK...but it will for the Flex 4.1 SDK. (see related StackOverflow post)

Testing for a valid Flash Builder license

Well, one way to ensure that a valid Flash Builder Pro license was compiled into your SWF is to simply check out your charts or your automation scripts. For charts, if there are no watermarks, you're good. For automation, if you don't get the "license not found" error, you're good. But, for automation, this can potentially get tedious.

A quick way to test that a valid Flash Builder license has been compiled into your SWF involves 2 classes, one super secret metadata tag and a trace statement. Check out this simple app to see how this is done (view source is enabled): http://www.flexdevelopers.com/b/swfs/20120103/License.html

November 14, 2011

AIRKinect Native Extension

Posted by Justin Imhoff
AIRKinect version 1 is out and ready for your NUI needs. AIRKinect is an opensource project to bring the Microsoft Kinect SDK to Adobe AIR. Developers can now leverage depth camera, rgb camera, skeletal tracking, and lots of other great things to expand their interactive applications. AIRKinect is a development of as3NUI with contribution from 15Letters and NUIORITY. The code does not stop there though, we are actively developing specialized UI controls and other plugins for the Kinect to enable unlimited potential. We would enjoy you trying out the extension and providing feedback about feature requests, demos, and any other items related to NUI and AIR. For more information about the extension and a getting starting guide with demos, go to as3NUI.com

August 7, 2011

Finding the Most Popular Flex Framework

Posted by Jeremy Mitchell
Some Flex developers insist an architectural framework (aka framework or microarchitecture) is the fundamental piece of a well-designed and maintainable Flex application architecture. Others insist that frameworks are unnecessary and only contribute to the overall complexity of an application.

Personally, I believe frameworks are a valuable tool and can provide a great starting point for a sound application architecture. If used properly (that's a big IF), frameworks provide structure and consistency, both of which contribute to the predictability and maintainability of a code base. Also, many frameworks provide built-in support for many useful design patterns (i.e. Dependency Injection or Inversion of Control) to help developers solve complex problems.

Regardless of what I think, frameworks exist, and they are being heavily utilized in Flex projects. Let's find out which is currently the most popular, shall we?


April 24, 2011

Creating Custom Context Menu Components

Posted by Jeremy Mitchell
Providing the ability to right-click (or ctrl-click on a Mac) a list item and initiate an action, the context menu is a nice built-in feature of a List control. However, when the contents and behavior of the context menu differ in different parts of your application, your code can get quickly out of hand.

Note: Not only does a List have a context menu but so does any Flex component that extends the InteractiveObject class.
In this blog entry, I'm faced with the challenge of sharing a custom List component across an application while providing other developers with the ability to modify the context menu without generating a pile of unmaintainable spaghetti code.

To customize a List's context menu, the most common approach taken by inexperienced Flex developers violates OOP's open/closed principle and involves adding code directly to the custom List class.

Here is the custom List component (SuperList) before any modification to the context menu:

Make sure you read the comments in the code snippets. They may provide some clarification.
// SuperList.as

package components
{
 import spark.components.List;
 
 /**
  * This isn't your ordinary List control. However, it's not very special
  * outside of the canDeleteItems property. In practice, you may have a 
  * List with a bunch of fancy features and different parts of your 
  * application may want to use this fancy List component while customizing 
  * the context menu.
  */
 
 public class SuperList extends List
 { 
  private var _canDeleteItems:Boolean = false;
  
  public function get canDeleteItems():Boolean
  {
   return _canDeleteItems;
  }
  
  public function set canDeleteItems(value:Boolean):void
  {
   _canDeleteItems = value;
  }
    
  public function SuperList()
  {
   super();
   allowMultipleSelection = true;
   doubleClickEnabled = true;
   labelField = "name";
  }  
 }
}
Here is the SuperList after one developer decided he needed to add Enable, Disable and Delete to the context menu:
package components
{
 import flash.events.ContextMenuEvent;
 import flash.events.Event;
 import flash.ui.ContextMenu;
 import flash.ui.ContextMenuItem;
 
 import spark.components.List;
 
 public class SuperList extends List
 { 
  private var _canDeleteItems:Boolean = false;
  
  public function get canDeleteItems():Boolean
  {
   return _canDeleteItems;
  }
  
  public function set canDeleteItems(value:Boolean):void
  {
   _canDeleteItems = value;
  }
    
  public function SuperList()
  {
   super();
   allowMultipleSelection = true;
   doubleClickEnabled = true;
   labelField = "name";
   createContextMenu();
  }
  
  protected function createContextMenu():void
  {
   contextMenu = new ContextMenu();
   contextMenu.hideBuiltInItems();
   
   var enableMenu:ContextMenuItem = new ContextMenuItem("");
   enableMenu.caption = "Enable item...";
   enableMenu.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, contextMenu_onEnableSelected);
   
   var disableMenu:ContextMenuItem = new ContextMenuItem("");
   disableMenu.caption = "Disable item..."
   disableMenu.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, contextMenu_onDisableSelected);
   
   var deleteMenu:ContextMenuItem = new ContextMenuItem("");
   deleteMenu.caption = "Delete item...";
   deleteMenu.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, contextMenu_onDeleteSelected);
   
   contextMenu.customItems.push(enableMenu);
   contextMenu.customItems.push(disableMenu);
   contextMenu.customItems.push(deleteMenu);    
  }
    
  protected function contextMenu_onEnableSelected(event:ContextMenuEvent):void
  {
   dispatchEvent(new Event("enable"));
  }
  
  protected function contextMenu_onDisableSelected(event:ContextMenuEvent):void
  {
   dispatchEvent(new Event("disable"));
  }
  
  protected function contextMenu_onDeleteSelected(event:ContextMenuEvent):void
  {
   dispatchEvent(new Event("delete"));
  }
 }
}
This is a fine solution IF every instance of SuperList requires an Enable, Disable and Delete context menu item. Unfortunately, these context menu items don't apply in most cases and a bug is filed. Back to work for your inexperienced developer...
package components
{
 import flash.events.ContextMenuEvent;
 import flash.events.Event;
 import flash.ui.ContextMenu;
 import flash.ui.ContextMenuItem;
 
 import mx.events.FlexEvent;
 
 import spark.components.List;
 
 public class SuperList extends List
 { 
  // developer adds a public variable to turn his context menu off by default
  public var showContextMenu:Boolean = false; 
  
  private var _canDeleteItems:Boolean = false;
  
  public function get canDeleteItems():Boolean
  {
   return _canDeleteItems;
  }
  
  public function set canDeleteItems(value:Boolean):void
  {
   _canDeleteItems = value;
  }
  
  public function SuperList()
  {
   super();
   allowMultipleSelection = true;
   doubleClickEnabled = true;
   labelField = "name";
   /* we can't use the constructor anymore to build the context menu and junior developers 
      that don't understand the flex component lifecycle love the creationComplete event */
   addEventListener(FlexEvent.CREATION_COMPLETE, onCreationComplete);
  }
  
  protected function onCreationComplete(event:FlexEvent):void
  {
   // build the context menu based on the value of the showContextMenu variable
   if (showContextMenu)
    createContextMenu();
  }
  
  protected function createContextMenu():void
  {
   contextMenu = new ContextMenu();
   contextMenu.hideBuiltInItems();
   
   var enableMenu:ContextMenuItem = new ContextMenuItem("");
   enableMenu.caption = "Enable item...";
   enableMenu.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, contextMenu_onEnableSelected);
   
   var disableMenu:ContextMenuItem = new ContextMenuItem("");
   disableMenu.caption = "Disable item..."
   disableMenu.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, contextMenu_onDisableSelected);
   
   var deleteMenu:ContextMenuItem = new ContextMenuItem("");
   deleteMenu.caption = "Delete item...";
   deleteMenu.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, contextMenu_onDeleteSelected);
   
   contextMenu.customItems.push(enableMenu);
   contextMenu.customItems.push(disableMenu);
   contextMenu.customItems.push(deleteMenu);    
  }
  
  protected function contextMenu_onEnableSelected(event:ContextMenuEvent):void
  {
   dispatchEvent(new Event("enable"));
  }
  
  protected function contextMenu_onDisableSelected(event:ContextMenuEvent):void
  {
   dispatchEvent(new Event("disable"));
  }
  
  protected function contextMenu_onDeleteSelected(event:ContextMenuEvent):void
  {
   dispatchEvent(new Event("delete"));
  }
 }
}
He can now "activate" his context menu without affecting other instance of SuperList by using the new showContextMenu property.
<components:SuperList dataProvider="{data}"
         canDeleteItems="true"
         showContextMenu="true"/>
This code works so it remains in your code base for quite some time until the next developer wants a custom context menu of his own. Looking inside the SuperList class, he sees the work of the previous developer and wanting to get back on Facebook ASAP, he applies a quick fix. The mess begins...
package components
{
 import flash.events.ContextMenuEvent;
 import flash.events.Event;
 import flash.ui.ContextMenu;
 import flash.ui.ContextMenuItem;
 
 import mx.events.FlexEvent;
 
 import spark.components.List;
 
 public class SuperList extends List
 { 
  /* 2nd developer changes this variable to a String that he can key off of
     to determine which context menu to show. No context menu by default */
  public var contextMenuType:String = "none"; 
  
  private var _canDeleteItems:Boolean = false;
  
  public function get canDeleteItems():Boolean
  {
   return _canDeleteItems;
  }
  
  public function set canDeleteItems(value:Boolean):void
  {
   _canDeleteItems = value;
  }
  
  public function SuperList()
  {
   super();
   allowMultipleSelection = true;
   doubleClickEnabled = true;
   labelField = "name";
   addEventListener(FlexEvent.CREATION_COMPLETE, onCreationComplete);
  }
  
  protected function onCreationComplete(event:FlexEvent):void
  {
   // key off of the value of contextMenuType to determine which context menu to show if any
   if (contextMenuType == "foo")
   {
    createFooContextMenu();
   }
   else if (contextMenuType == "bar")
   {
    createBarContextMenu();
   }
  }
  
  protected function createFooContextMenu():void
  {
   contextMenu = new ContextMenu();
   contextMenu.hideBuiltInItems();
   
   var enableMenu:ContextMenuItem = new ContextMenuItem("");
   enableMenu.caption = "Enable item...";
   enableMenu.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, contextMenu_onEnableSelected);
   
   var disableMenu:ContextMenuItem = new ContextMenuItem("");
   disableMenu.caption = "Disable item..."
   disableMenu.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, contextMenu_onDisableSelected);
   
   var deleteMenu:ContextMenuItem = new ContextMenuItem("");
   deleteMenu.caption = "Delete item...";
   deleteMenu.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, contextMenu_onDeleteSelected);
   
   contextMenu.customItems.push(enableMenu);
   contextMenu.customItems.push(disableMenu);
   contextMenu.customItems.push(deleteMenu);    
  }
  
  protected function createBarContextMenu():void
  {
   contextMenu = new ContextMenu();
   contextMenu.hideBuiltInItems();
   
   var activateMenu:ContextMenuItem = new ContextMenuItem("");
   activateMenu.caption = "Activate item...";
   activateMenu.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, contextMenu_onActivateSelected);
   
   var deactivateMenu:ContextMenuItem = new ContextMenuItem("");
   deactivateMenu.caption = "Deactivate item..."
   deactivateMenu.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, contextMenu_onDeactivateSelected);
   
   var deleteMenu:ContextMenuItem = new ContextMenuItem("");
   deleteMenu.caption = "Delete item...";
   deleteMenu.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, contextMenu_onDeleteSelected);
   
   contextMenu.customItems.push(activateMenu);
   contextMenu.customItems.push(deactivateMenu);
   contextMenu.customItems.push(deleteMenu);    
  }
  
  protected function contextMenu_onEnableSelected(event:ContextMenuEvent):void
  {
   dispatchEvent(new Event("enable"));
  }
  
  protected function contextMenu_onDisableSelected(event:ContextMenuEvent):void
  {
   dispatchEvent(new Event("disable"));
  }
  
  protected function contextMenu_onActivateSelected(event:ContextMenuEvent):void
  {
   dispatchEvent(new Event("activate"));
  }
  
  protected function contextMenu_onDeactivateSelected(event:ContextMenuEvent):void
  {
   dispatchEvent(new Event("deactivate"));
  }
  
  protected function contextMenu_onDeleteSelected(event:ContextMenuEvent):void
  {
   dispatchEvent(new Event("delete"));
  }
 }
}
By setting the value of the SuperList's new contextMenuType property, the appropriate context menu is displayed.
<components:SuperList dataProvider="{data}"
         canDeleteItems="true"
         contextMenuType="foo"/>
Do I really need to show you how this turns out? 2 years, 5 developers and 10 change requests later, SuperList contains 1500 lines of code. Most of this code applies to one small aspect of the SuperList component - the context menu. The true purpose of SuperList has been lost.

A Better Way


Two techniques can be used to isolate the custom behavior of each custom context menu. The first technique is very common and involves subclassing SuperList. The other technique adheres to the OOP principle of favoring composition over inheritance.

Using Inheritance to Create a Custom Context Menu


A slight modification to SuperList is required to utilize inheritance...
package components
{
 import spark.components.List;
 
 /**
  * This isn't your ordinary List control. However, it's not very special
  * outside of the canDeleteItems property. In practice, you may have a 
  * List with a bunch of fancy features and different parts of your 
  * application may want to use this fancy List component while customizing 
  * the context menu.
  */
 
 public class SuperList extends List
 { 
  private var _canDeleteItems:Boolean = false;
  
  public function get canDeleteItems():Boolean
  {
   return _canDeleteItems;
  }
  
  public function set canDeleteItems(value:Boolean):void
  {
   _canDeleteItems = value;
  }
  
  public function SuperList()
  {
   super();
   allowMultipleSelection = true;
   doubleClickEnabled = true;
   labelField = "name";
   createContextMenu();
  }  
  
  protected function createContextMenu():void
  {
   // If you use SuperList, you'll get no context menu which may be OK
   // but if you want a context menu, extend SuperList and create one
   // by overriding this method
   return;
  }
 }
}
Create a custom context menu for SuperList by extending the class and overriding the createContextMenu method.
package components
{
 import flash.events.ContextMenuEvent;
 import flash.events.Event;
 import flash.ui.ContextMenu;
 import flash.ui.ContextMenuItem;
 
 /**
  * This subclass only has one purpose - to create a context menu that
  * dispatches events based on what context menu item is selected
  */

 [Event(name = "enable")]
 [Event(name = "disable")]
 [Event(name = "delete")]
 
 public class MySuperList extends SuperList
 {
  public static const ENABLE:String = "enable";
  public static const DISABLE:String = "disable";
  public static const DELETE:String = "delete";
  
  public function MySuperList()
  {
   super();
  }
  
  override protected function createContextMenu():void
  {
   contextMenu = new ContextMenu();
   contextMenu.hideBuiltInItems();
   
   var enableMenu:ContextMenuItem = new ContextMenuItem("");
   enableMenu.caption = "Enable item...";
   enableMenu.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, contextMenu_onEnableSelected);
   
   var disableMenu:ContextMenuItem = new ContextMenuItem("");
   disableMenu.caption = "Disable item..."
   disableMenu.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, contextMenu_onDisableSelected);
   
   var deleteMenu:ContextMenuItem = new ContextMenuItem("");
   deleteMenu.caption = "Delete item...";
   deleteMenu.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, contextMenu_onDeleteSelected);
   
   contextMenu.customItems.push(enableMenu);
   contextMenu.customItems.push(disableMenu);
   contextMenu.customItems.push(deleteMenu);    
  }
  
  protected function contextMenu_onEnableSelected(event:ContextMenuEvent):void
  {
   dispatchEvent(new Event(ENABLE));
  }
  
  protected function contextMenu_onDisableSelected(event:ContextMenuEvent):void
  {
   dispatchEvent(new Event(DISABLE));
  }
  
  protected function contextMenu_onDeleteSelected(event:ContextMenuEvent):void
  {
   dispatchEvent(new Event(DELETE));
  }
 }
}
Now you can use the plain old SuperList that has no context menu...
<?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"
      xmlns:components="components.*">
 
 <fx:Script>
  <![CDATA[
   import mx.collections.ArrayCollection;
   
   [Bindable]
   private var _data1:ArrayCollection = new ArrayCollection([{name: "foo"}, {name: "bar"}, {name: "moo"}, {name: "cow"}]);   
  ]]>
 </fx:Script>
  
 <components:SuperList id="superList" 
        width="200"
        canDeleteItems="true"
        dataProvider="{_data1}"/>

</s:Application>
...or you can use the subclass with a context menu in line with your needs...
<?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"
      xmlns:components="components.*">
 
 <fx:Script>
  <![CDATA[
   import mx.collections.ArrayCollection;
   import mx.controls.Alert;
   
   [Bindable]
   private var _data1:ArrayCollection = new ArrayCollection([{name: "foo"}, {name: "bar"}, {name: "moo"}, {name: "cow"}]);   

   protected function mySuperList_enableHandler(event:Event):void
   {
    Alert.show("Enable " + mySuperList.selectedItem.name);
   }

  ]]>
 </fx:Script>
    
 <components:MySuperList id="mySuperList"
       width="200"
       canDeleteItems="false"
       dataProvider="{_data1}"
       enable="mySuperList_enableHandler(event)"/> 

</s:Application>
When a different context menu is required with different behavior, SuperList can be extended again...
package components
{
 import flash.events.ContextMenuEvent;
 import flash.ui.ContextMenu;
 import flash.ui.ContextMenuItem;
 
 import mx.controls.Alert;
 
 /**
  * This subclass is built a little different. It has different context menu items
  * than MySuperList and it doesn't dispatch events. Instead it acts on the context
  * menu item selection immediately. 
  */
 
 public class YourSuperList extends SuperList
 {  
  public function YourSuperList()
  {
   super();
  }
  
  override protected function createContextMenu():void
  {
   contextMenu = new ContextMenu();
   contextMenu.hideBuiltInItems();
   
   var activateMenu:ContextMenuItem = new ContextMenuItem("");
   activateMenu.caption = "Activate item...";
   activateMenu.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, contextMenu_onActivateSelected);
   
   var deactivateMenu:ContextMenuItem = new ContextMenuItem("");
   deactivateMenu.caption = "Deactivate item..."
   deactivateMenu.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, contextMenu_onDeactivateSelected);
   
   var deleteMenu:ContextMenuItem = new ContextMenuItem("");
   deleteMenu.caption = "Delete item...";
   deleteMenu.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, contextMenu_onDeleteSelected);
   
   contextMenu.customItems.push(activateMenu);
   contextMenu.customItems.push(deactivateMenu);
   contextMenu.customItems.push(deleteMenu);    
  }
  
  protected function contextMenu_onActivateSelected(event:ContextMenuEvent):void
  {
   Alert.show("Activate " + selectedItem.name);
  }
  
  protected function contextMenu_onDeactivateSelected(event:ContextMenuEvent):void
  {
   Alert.show("Deactivate " + selectedItem.name);
  }
  
  protected function contextMenu_onDeleteSelected(event:ContextMenuEvent):void
  {
   Alert.show("Delete " + selectedItem.name);
  }
 }
}
...and used as such...
<?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"
      xmlns:components="components.*">
 
 <fx:Script>
  <![CDATA[
   import mx.collections.ArrayCollection;
   
   [Bindable]
   private var _data1:ArrayCollection = new ArrayCollection([{name: "foo"}, {name: "bar"}, {name: "moo"}, {name: "cow"}]);   
  ]]>
 </fx:Script>
     
 <components:YourSuperList id="yourSuperList"
       width="200"
       canDeleteItems="false"
       dataProvider="{_data1}"/>
 
</s:Application>

Using Composition to Create a Custom Context Menu


To me, it seems like a waste to extend SuperList only to add a custom context menu. Instead I'd rather "inject" an object into SuperList and delegate the responsibilities of the context menu to this object. Sounds like the Strategy pattern, right?

To accomplish this, I'm going to start with building an interface. Anything that implements this interface will contain a context menu.
package interfaces
{
 import flash.ui.ContextMenu;
 
 /**
  * This interface guarantees that anything that 
  * implements this interface is composed of a contextmenu 
  */
 public interface ICustomContextMenu
 {  
  function get contextMenu():ContextMenu 
 }
}
Again, a slight modification to SuperList is required. A setter is used to inject the custom context menu into SuperList. This setter accepts any object that implements the ICustomContextMenu interface previously defined. This ensures the injected object contains a context menu. SuperList will use this context menu as its own. Let's review the changes to SuperList.
package components
{
 import interfaces.ICustomContextMenu;
 
 import mx.collections.ArrayCollection;
 
 import spark.components.List;
 
 /**
  * This isn't your ordinary List control. However, it's not very special
  * outside of the canDeleteItems property and the ability to accept a custom
  * context menu. However, in practice, you may have a List with a bunch 
  * of fancy features and different parts of your application may want to 
  * use this fancy List component while customizing the context menu.
  */
 
 public class SuperList extends List
 { 
  private var _canDeleteItems:Boolean = false;
  
  public function get canDeleteItems():Boolean
  {
   return _canDeleteItems;
  }
  
  public function set canDeleteItems(value:Boolean):void
  {
   _canDeleteItems = value;
  }
  
  private var _customContextMenu:ICustomContextMenu;
  private var _customContextMenuChanged:Boolean = false;
  
  public function set customContextMenu(value:ICustomContextMenu):void
  {
   // pass in an ICustomContextMenu anytime you like and SuperList will use its context menu
   _customContextMenu = value;
   _customContextMenuChanged = true;
   invalidateProperties();
  }
  
  public function SuperList()
  {
   super();
   allowMultipleSelection = true;
   doubleClickEnabled = true;
   labelField = "name";
  }
  
  override protected function commitProperties():void
  {
   super.commitProperties();
   
   if (_customContextMenuChanged)
   {
    contextMenu = _customContextMenu.contextMenu;
    _customContextMenuChanged = false;
   }
  }
  
 }
}
Next, I'll create a custom class for each custom context menu. This class must implement the ICustomContextMenu interface previously defined to ensure that it contains a context menu and can be used with SuperList.
// MyCustomContextMenu.as

package components.contextMenus
{
 import components.SuperList;
 
 import flash.events.ContextMenuEvent;
 import flash.events.Event;
 import flash.ui.ContextMenu;
 import flash.ui.ContextMenuItem;
 
 import interfaces.ICustomContextMenu;
 
 /**
  * This class implements ICustomContextMenu therefore guaranteeing
  * the existence of a context menu. However, we can customize the 
  * context menu as we please. We can add whatever ContextMenuItems
  * we like and we can handle clicks however we please.
  */
 public class MyCustomContextMenu implements ICustomContextMenu
 {
  public static const ENABLE:String = "enable";
  public static const DISABLE:String = "disable";
  public static const DELETE:String = "delete";

  private var _owner:SuperList; // with this I can dispatch events targeted at the SuperList
  
  private var _enableMenu:ContextMenuItem;
  private var _disableMenu:ContextMenuItem;
  private var _deleteMenu:ContextMenuItem;
 
  private var _contextMenu:ContextMenu;
  
  public function get contextMenu():ContextMenu
  {
   // this satisfies the interface requirements
   return _contextMenu;
  }
  
  public function MyCustomContextMenu(owner:SuperList)
  {
   super();
   
   _owner = owner;
   _contextMenu = new ContextMenu();
   
   _contextMenu.hideBuiltInItems();
   
   _enableMenu = new ContextMenuItem("");
   _enableMenu.caption = "Enable item...";
   _enableMenu.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, contextMenu_onEnableSelected);
   
   _disableMenu = new ContextMenuItem("");
   _disableMenu.caption = "Disable item..."
   _disableMenu.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, contextMenu_onDisableSelected);
   
   _deleteMenu = new ContextMenuItem("");
   _deleteMenu.caption = "Delete item...";
   _deleteMenu.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, contextMenu_onDeleteSelected);
   
   _contextMenu.customItems.push(_enableMenu);
   _contextMenu.customItems.push(_disableMenu);
   _contextMenu.customItems.push(_deleteMenu);
   
   _contextMenu.addEventListener(ContextMenuEvent.MENU_SELECT, contextMenu_onMenuSelect);
  }
  
  public function contextMenu_onMenuSelect(event:ContextMenuEvent):void
  {
   _enableMenu.enabled = true;
   _disableMenu.enabled = true;
   // this property (canDeleteItems) of the SuperList is being used by the context menu 
   _deleteMenu.enabled = _owner.canDeleteItems;
  }
  
  protected function contextMenu_onEnableSelected(event:ContextMenuEvent):void
  {
   _owner.dispatchEvent(new Event(ENABLE));
  }
  
  protected function contextMenu_onDisableSelected(event:ContextMenuEvent):void
  {
   _owner.dispatchEvent(new Event(DISABLE));
  }
  
  protected function contextMenu_onDeleteSelected(event:ContextMenuEvent):void
  {
   _owner.dispatchEvent(new Event(DELETE));
  }
 }
}
We can create another custom context menu with it's own unique behavior as long as it implements ICustomContextMenu.
// YourCustomContextMenu.as 

package components.contextMenus
{
 import flash.events.ContextMenuEvent;
 import flash.ui.ContextMenu;
 import flash.ui.ContextMenuItem;
 
 import interfaces.ICustomListContextMenu;
 
 import mx.controls.Alert;
 
 /**
  * This class implements ICustomContextMenu therefore guaranteeing
  * the existence of a context menu. However, we can customize the 
  * context menu as we please. We can add whatever ContextMenuItems
  * we like and we can handle clicks however we please.
  */
 public class YourCustomContextMenu implements ICustomListContextMenu
 {  
  private var _activateMenu:ContextMenuItem;
  private var _deactivateMenu:ContextMenuItem;
  private var _deleteMenu:ContextMenuItem;
 
  private var _contextMenu:ContextMenu;
  
  public function get contextMenu():ContextMenu
  {
   return _contextMenu;
  }
  
  public function YourCustomContextMenu()
  {
   super();
   
   _contextMenu = new ContextMenu();
   
   _contextMenu.hideBuiltInItems();
   
   _activateMenu = new ContextMenuItem("");
   _activateMenu.caption = "Activate item...";
   _activateMenu.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, contextMenu_onActivateSelected);
   
   _deactivateMenu = new ContextMenuItem("");
   _deactivateMenu.caption = "Deactivate item..."
   _deactivateMenu.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, contextMenu_onDeactivateSelected);
   
   _deleteMenu = new ContextMenuItem("");
   _deleteMenu.caption = "Delete item...";
   _deleteMenu.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, contextMenu_onDeleteSelected);
   
   _contextMenu.customItems.push(_activateMenu);
   _contextMenu.customItems.push(_deactivateMenu);
   _contextMenu.customItems.push(_deleteMenu);
  }
    
  protected function contextMenu_onActivateSelected(event:ContextMenuEvent):void
  {
   Alert.show("Item Activated");
  }
  
  protected function contextMenu_onDeactivateSelected(event:ContextMenuEvent):void
  {
   Alert.show("Item Deactivated");
  }
  
  protected function contextMenu_onDeleteSelected(event:ContextMenuEvent):void
  {
   Alert.show("Item Deleted");
  }
 }
}
Finally, we can inject whichever custom context menu (if any) we like into our new and improved SuperList:
<?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"
      xmlns:components="components.*">
 
 <fx:Script>
  <![CDATA[
   import components.contextMenus.MyCustomContextMenu;
   import components.contextMenus.YourCustomContextMenu;
   
   import mx.collections.ArrayCollection;
   
   [Bindable]
   private var _data:ArrayCollection = new ArrayCollection([{name: "foo"}, {name: "bar"}, {name: "moo"}, {name: "cow"}]);
  ]]>
 </fx:Script>
 
 <s:layout>
  <s:VerticalLayout/>
 </s:layout>
  
 <components:SuperList id="superListWithMyCustomContextMenu" 
        width="200"
        canDeleteItems="true"
        dataProvider="{_data}"
        customContextMenu="{new MyCustomContextMenu(superListWithMyCustomContextMenu)}"/>
  
 <components:SuperList id="superListWithYourCustomContextMenu" 
        width="200"
        canDeleteItems="false"
        dataProvider="{_data}"
        customContextMenu="{new YourCustomContextMenu()}"/>
  
 <components:SuperList id="superListWithNoCustomContextMenu" 
        width="200"
        canDeleteItems="false"
        dataProvider="{_data}"/>
 
</s:Application>

March 5, 2011

Using Flex 4 Skins & States to Disable a Component

Posted by Jeremy Mitchell
In a previous post entitled Temporarily Disable a Flex Component, I utilized a handful of classes to disable and enable a Flex component as needed. In short, the technique involved adding or removing a modal overlay to a container on demand. With the Flex 4 skinning architecture, the process of "disabling" a component is simplified.

I will start by creating an MXML component (Editor.mxml) that extends the Panel class.
<?xml version="1.0" encoding="utf-8"?>
<s:Panel xmlns:fx="http://ns.adobe.com/mxml/2009"
   xmlns:s="library://ns.adobe.com/flex/spark"
   xmlns:mx="library://ns.adobe.com/flex/mx"
   xmlns:comps="comps.*"
   width="500"
   title="Editor">
 
 <mx:Form width="100%" height="100%"
    backgroundColor="#CCCCCC">
  
  <mx:FormHeading label="Field"/>
  
  <mx:FormItem label="Name:"
      required="true">
   <s:TextInput/>
  </mx:FormItem>
  
 </mx:Form>
 
</s:Panel>
Next, I will create an MXML skin for the Editor class by selecting New > MXML Skin, naming the skin "EditorSkin" and selecting "Editor" as the host component. I will leave checked the checkbox entitled "Create as copy of: spark.skins.spark.Panel" and choose "Remove ActionScript styling code". Now, I have a skin class that serves as a starting point for my component.

I need to map the skin class to the host component (Editor) so I will add the skinClass property and the appropriate value to the host component's root tag.
<s:Panel xmlns:fx="http://ns.adobe.com/mxml/2009"
   xmlns:s="library://ns.adobe.com/flex/spark"
   xmlns:mx="library://ns.adobe.com/flex/mx"
   xmlns:comps="comps.*"
   width="500"
   title="Editor"
   skinClass="skins.EditorSkin">
Since I want to "disable" my component while it is in a specific state, I'll add a new state to my skin class. I'll name this new state "loading".
<s:states>
 <s:State name="normal" />
 <s:State name="disabled" />
 <s:State name="loading"/>"
 <s:State name="normalWithControlBar" stateGroups="withControls" />
 <s:State name="disabledWithControlBar" stateGroups="withControls" />
</s:states>
With my new "loading" state defined, I'll create a Group to hold a "modal overlay" that will be turned on when the loading state is active thus "disabling" the component. I'll place this new Group next to the contentGroup but inside of a new Group used to enforce absolute positioning. Notice that the "loading" Group is defined below the contentGroup's MXML declaration allowing it to lay on top of the component and serve as a "modal overlay".
<s:Group width="100%">
 
 <!--- @copy spark.components.SkinnableContainer#contentGroup -->
 <s:Group id="contentGroup" width="100%" height="100%" minWidth="0" minHeight="0">
 </s:Group>
 
 <s:Group id="loadingGroup"
    left="0"
    right="0"
    top="0"
    bottom="0"
    includeIn="loading">
  
  <s:Rect id="modal"
    left="0"
    right="0"
    top="0"
    bottom="0">
   
   <s:fill>
    <s:SolidColor color="#FFFFFF"
         alpha="0.5"/>
   </s:fill>
   
  </s:Rect>
  
  <mx:ProgressBar id="loader" 
      x="{(width / 2) - (loader.width / 2)}"
      y="{(height / 2) - (loader.height / 2)}"
      
 </s:Group>
   
</s:Group>
Notice the "includeIn" property? This limits the inclusion of the "loading Group" to the loading state ONLY.

Next, I will define the skin states that the host component (Editor) will utilize to disable or enable itself. These skin states need to be defined as Metadata in the Editor class.
<fx:Metadata>
 [SkinState("loading")]
 [SkinState("normal")]
</fx:Metadata>
I will also create a couple of public methods designed to toggle a property that "invalidates" the skin state and turns the loading state on and off. Here is my new Editor class:
<?xml version="1.0" encoding="utf-8"?>
<s:Panel xmlns:fx="http://ns.adobe.com/mxml/2009"
   xmlns:s="library://ns.adobe.com/flex/spark"
   xmlns:mx="library://ns.adobe.com/flex/mx"
   xmlns:comps="comps.*"
   width="500"
   title="Editor"
   skinClass="skins.EditorSkin">
 
 <fx:Metadata>
  [SkinState("loading")]
  [SkinState("normal")]
 </fx:Metadata>
 
 <fx:Script>
  <![CDATA[   
   public static const VIEW_MODE_LOADING:String="loading";
   public static const VIEW_MODE_NORMAL:String="normal";
   
   private var _viewMode:String = VIEW_MODE_NORMAL;
   
   protected function set viewMode(value:String):void
   {
    _viewMode = value;
    invalidateSkinState(); // this forces a call to getCurrentSkinState
   }
   
   public function turnOnLoadingIndicator():void
   {
    viewMode = VIEW_MODE_LOADING;
   }
   
   public function turnOffLoadingIndicator():void
   {
    viewMode = VIEW_MODE_NORMAL;
   }
   
   override protected function getCurrentSkinState():String
   {
    // this forces a change in skinstate
    return _viewMode;
   }
  ]]>
 </fx:Script>
 
 <mx:Form width="100%" height="100%"
    backgroundColor="#CCCCCC">
  
  <mx:FormHeading label="Field"/>
  
  <mx:FormItem label="Name:"
      required="true">
   <s:TextInput/>
  </mx:FormItem>
  
 </mx:Form>
 
</s:Panel>
Finally, I'll add a couple of buttons to the main Application file required to toggle the state of the Editor component.
<?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"
      xmlns:comps="comps.*">
 
 <s:layout>
  <s:VerticalLayout/>
 </s:layout>
  
 <comps:Editor id="editor"/>
 
 <s:HGroup width="{editor.width}"
     horizontalAlign="center">
  
  <s:Button id="disableBtn" 
      label="Click to disable component"
      click="editor.turnOnLoadingIndicator()"/>
  
  <s:Button id="enableBtn" 
      label="Click to enable component"
      click="editor.turnOffLoadingIndicator()"/>
  
 </s:HGroup>
 
</s:Application>
The result is a Flex component (Editor.mxml) with a skin (EditorSkin.mxml) that can be disabled or enabled on demand. The following application represents the final component with a few extra features, a fancier loading image and some best practices in place (view source is enabled).

January 7, 2011

Where do Flex developers come from?

Posted by Jeremy Mitchell
The Flash platform's rich feature set paired with the robust Flex Framework has attracted a wide variety of talent over the last few years. Needless to say, most Flex developers did not start their careers using Flex.

Please share. What were you primarily doing before you became a Flex developer?