Pages

April 24, 2011

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.

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...

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...

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.

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.

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.

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.

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.