Pages

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>