Custom Right Click or Context Menu in Flex/Flash AS3
Posted: May 31st, 2009 | Author: danny | Filed under: Flex | 9 Comments »UPDATE: Please see comments for a bug, and a work around.
I've created a small little library for creating a right click menu in flash:
This works not only on the top level components, but also on nested components. In the example you can right click on each button and see a custom menu.
Demo:
Some Links:
AS3 Right Click Context Menu Right, Click To View Source
Context Menu Source In Zip File
package { import flash.events.ContextMenuEvent; import flash.events.MouseEvent; import flash.ui.ContextMenuItem; import mx.core.Application; import mx.core.UIComponent; public class RightClickMenu { public var MenuContents:Array = new Array (); public function RightClickMenu(){} public function AddItem (name:String, func:Function):void { MenuContents.push({Name:name, Func:func}); } public function AssignRightClick (uiComponent:UIComponent):void { uiComponent.addEventListener(MouseEvent.MOUSE_OVER, genEnableMenu (uiComponent)); uiComponent.addEventListener(MouseEvent.MOUSE_MOVE, disableMenu); } /* Assignment */ private function ResetContextMenu (event:MouseEvent):void { //remove menu Application.application.contextMenu.customItems = new Array (); //remove this function Application.application.removeEventListener(MouseEvent.MOUSE_MOVE, ResetContextMenu); } private function disableMenu(event:MouseEvent):void { //Stop the mouse move event from propagating to the application level, where we remove the menu event.stopImmediatePropagation(); } private function genEnableMenu (uiComponent:UIComponent):Function { return function (event:MouseEvent):void { //add event listener to remove the menu on mouse move Application.application.addEventListener(MouseEvent.MOUSE_MOVE, ResetContextMenu); //hide current menu Application.application.contextMenu.hideBuiltInItems(); //remove menu (ifyou right click and then move, this may not be killed. Application.application.contextMenu.customItems = new Array (); //create new menu for (var i:Number in MenuContents) { var menuItem:ContextMenuItem = new ContextMenuItem(MenuContents[i].Name); menuItem.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, MenuContents[i].Func); Application.application.contextMenu.customItems.push(menuItem); } } } private function genClickCall (func:Function):Function { return function (event:ContextMenuEvent):void { func() ResetContextMenu(null); } } } }
To use the class 1) Create an instance of it, 2) Populate it, 3) bind it to the component.
<![CDATA[ import mx.controls.Alert; private function appComplete ():void { var menu1:RightClickMenu = new RightClickMenu (); menu1.AddItem("B1", function ():void{Alert.show("B1");}); menu1.AddItem("B1-A", function ():void{Alert.show("B1-A");}); menu1.AssignRightClick(b1); var menu2:RightClickMenu = new RightClickMenu (); menu2.AddItem("B2", function ():void{Alert.show("B2");}); menu2.AssignRightClick(b2); var menu3:RightClickMenu = new RightClickMenu (); menu3.AddItem("B3", function ():void{Alert.show("B3");}); menu3.AssignRightClick(b3); } ]]>
Bloody brilliant mate...cheers
Danny,
This is a great little library - thanks for putting it together and making it available to us.
One limitation that we've come across is that it prevents the propagation of MOUSE_MOVE events to other parts of the application. In our case, we have drag-and-drop behavior that relies on these events and so for the moment we're looking into a new way to implement the contextual menu that will allow the two pieces of functionality to co-exist peacefully together.
I'll let you know if we make a modification that would usefully be added to the library.
Thanks again,
Dave
Thanks,
Thanks for the heads up on that issue, if you manage to work around it let me know and I'll bring in the updates.
danny
[WORDPRESS HASHCASH] The poster sent us '0 which is not a hashcash value.
Hi Danny,
The code including our workaround is below. Note that this is a bit of a hack on two counts:
1) _uiComponent should be an array (and the corresponding if clause within ResetContextMenu should check all members of the array), but since we only ever AssignRightClick to a single UIComponent for each ContextualMenu, this is isn't necessary for us.
2) The condition (event.target.toString().indexOf("." + _uiComponent.name) == -1) is somewhat naive. Really, we should write a recursive function that can check a component (i.e. _uiComponent) for equality against another component (event.target) and all its view ancestors. Maybe this already exists; if so I'm not aware of it.
The updated code, pre-cleanup to make the changes clearer:
package
{
import flash.events.ContextMenuEvent;
import flash.events.MouseEvent;
import flash.ui.ContextMenuItem;
import mx.core.Application;
import mx.core.UIComponent;
public class ContextualMenu
{
public var MenuContents:Array = new Array ();
private var _uiComponent:UIComponent; // the UIComponent to which this menu applies
// (change to an array if you need menus shared between multiple components)
public function ContextualMenu(){}
public function AddItem (name:String, func:Function):void
{
MenuContents.push({Name:name, Func:func});
}
public function AssignRightClick (uiComponent:UIComponent):void
{
_uiComponent = uiComponent;
uiComponent.addEventListener(MouseEvent.MOUSE_OVER, genEnableMenu (uiComponent));
//uiComponent.addEventListener(MouseEvent.MOUSE_MOVE, disableMenuReset);
}
/* Assignment */
private function ResetContextMenu (event:MouseEvent):void
{
trace (event.target.toString());
trace (_uiComponent.name);
// Only reset the context menu if this event was fired from outside of the uiComponent that the menu belongs to
if (event.target.toString().indexOf("." + _uiComponent.name) == -1)
{
//remove menu
Application.application.contextMenu.customItems = new Array ();
//remove this function
Application.application.removeEventListener(MouseEvent.MOUSE_MOVE, ResetContextMenu);
}
}
private function disableMenuReset(event:MouseEvent):void
{
//Stop the mouse move event from propagating to the application level, where we remove the menu
//event.stopImmediatePropagation();
}
private function genEnableMenu (uiComponent:UIComponent):Function
{
return function (event:MouseEvent):void
{
//add event listener to remove the menu on mouse move
Application.application.addEventListener(MouseEvent.MOUSE_MOVE, ResetContextMenu);
//hide current menu
Application.application.contextMenu.hideBuiltInItems();
//remove menu (if you right click and then move, this may not be killed.
Application.application.contextMenu.customItems = new Array ();
//create new menu
for (var i:Number in MenuContents)
{
var menuItem:ContextMenuItem = new ContextMenuItem(MenuContents[i].Name);
menuItem.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, MenuContents[i].Func);
Application.application.contextMenu.customItems.push(menuItem);
}
}
}
private function genClickCall (func:Function):Function
{
return function (event:ContextMenuEvent):void
{
func()
ResetContextMenu(null);
}
}
}
}
Danny,
What does this method represent: genClickCall
It doesn't seem to be called anywhere, including indirectly by the eventing framework. At least it isn't for me. My app is using MATE for most eventing, though context menus are not part of it.
Lance,
It looks like genClickCall () is a dead piece of code that I forgot to remove before I published this originally. I'm going to be releasing a new version soon. I'm unfortunately a little swamped right now, but as soon as my time frees up there will be a new cleanedup version.
BTW, what is MATE?
Thanks for your help,
danny
That was inspiring,
This is some great tips for as3,
Keep up the good work,
Thanks for writing, most people don't bother.
how could this be used to add a context menu entry to an arbitrary flash object from firefox -- such that adblocking extensions, adblock plus and karma blocker, could add a "block this annoying junk" entry?
Seth,
I don't think it could be used in that way, it's primarily for those developing flash content. Since flash is compiled you cannot dynamically add new behavior. I think if you're interested in doing an adblock extension you would just use standard html/javascript to add a right click to the page.