Tutorial details:
Author: Neil Webb, neil AT nwebb DOT co DOT uk, http://www.nwebb.co.uk
Difficulty Level: Intermediate
Requirements: Flash MX 2004
Assumed Knowledge: Familiarity with basic actionscript (v1)
File(s) to Download: contextmenufiles.zip
Online Example: All examples are embedded within these tutorial pages

Flash MX 2004 introduces the ContextMenu class

We're going to start this tutorial by having a play around with the example below. You will need the Flash 7 plugin in order for this file to work properly. Right-click the blue square and/or the pink square, then choose any one of the new options from the right-click menus (you can have multiple options running simultaneously if you wish). A brief explanation of each option's functionality has been written below the two squares.

As we've just seen, it's now possible to assign custom context menus to our Flash movies. Back in the 'dark ages' (prior Flash MX) right-clicking on a flash movie would bring up a rather unwieldy pop-up menu that looked a little like this:

...and by default this is what we get with Flash MX and Flash MX 2004 too. Flash MX gave us the ability to shorten this menu somewhat by disabling a few of the options, but FMX2 ushers in a new level of control with the ContextMenu class. The Flash dictionary has this to say about the new class:

"The ContextMenu class provides runtime control over the items in the Flash Player context menu, which appears when a user right-clicks (Windows) or Control-clicks (Macintosh) the Flash Player. You can use the methods and properties of the ContextMenu class to add custom menu items, control the display of the built-in context menu items (for example, Zoom In and Print), or create copies of menus"

What may not be instantly apparent from the above description is that we're not just talking about a menu for the main movie. Every instance of a MovieClip, Button and TextField on the main timelinecan now have its very own context menu too. The reason I referred to "main timeline" in itialics is because it isn't possible to assign an instance of the ContextMenu class to any MovieClip, Button or TextField instance that does not [at least effectively] reside on the main timeline. This means that TextFields and Buttons nested inside a MovieClip on the root can be targeted and given their own menu, but a MovieClip nested inside another MovieClip on the root timeline cannot. The following code should help make it a bit clearer:

firstMovieClip.someButton.menu       = someContextMenu; //works
firstMovieClip.someTextField.menu    = someContextMenu; //works
firstMovieClip.secondMovieClip.menu  = someContextMenu; //doesn't work

Why does this happen? Well, neither TextField objects nor Button objects have timelines of their own, so their parent timeline is that of firstMovieClip, which resides on the root timeline, so you can say that effectively, they have been placed on the main timeline. MovieClips however do have their own timeline, and as I convey in the above example, trying to assign a custom context menu to the nested MovieClip (secondMovieClip) will not work. It's an unfortunate handycap to be honest. The current version of the Flash dictionary doesn't mention this limitation at all, so just be aware of it for the moment and thus avoid any hair-pulling scenarios.

Another thing to be aware of is that if we assign a custom menu to a MovieClip that contains [child] MovieClips, TextFields or Buttons then those children automatically inherit the menu of the parent MovieClip (unless those Buttons or TextFields are assigned menus of their own). This is a familiar cascading behaviour that we might expect, but what is interesting is how Flash deals with the hitArea for the parent clip. Each of the children now acts as a separate hit area for the parent clip, but click in an area inbetween these children and the root menu will appear instead. Again, this is easier to clarify with an example...

If you right-click on one of the letters below you will see a custom menu with the brand new option "Dance Baby!!!" Go on, right-click and choose the new option.

Okay, so the letter M should be merrily dancing away at this point. This is all well and good, but how do we stop it from dancing? We will discuss a proper method to do this shortly, but for now just refresh this webpage page in the browser (press F5). Go ahead and refresh it now, because we need the letter "M" to be stationary for this next part...

Both letters in the above swf are contained within a MovieClip called mcLetters. When you right-clicked on either letter, Flash displayed the context Menu that had been assigned to mcLetter. With both letters now stationary you can see that clicking inbetween(or outside) of the letters means that the default flash menu is displayed instead of the custom one. By clicking inbetween the "M" and the "X" we can assume that we are actually clicking on mcLetters but the custom menu for mcLettersis not displayed because only the contents of the clip are acting as its hitAreas. If this would be a problem for your application in terms of functionality, it can easily be solved by creating a background graphic for the mcLetters MovieClip, and giving the background an alpha of 0.

Before we take a look at the code there is at least one more thing that we need to be aware of. Custom context menus will only work for swfs that are embedded in HTML (or tested in the Flash authoring environment). They will not work for standalone files (exe and swf without the accompanying HTML file).

We've covered a lot of ground so far and still haven't seen any real code yet. Here is the minimal amount of code needed to produce our "dance baby!!!" example as seen above:

var lettersCM = new ContextMenu();
lettersCM.hideBuiltInItems();
var dance = new ContextMenuItem("Dance Baby!!!", doDance);
lettersCM.customItems.push(dance);
function doDance() {
        mcLetters.dancer.gotoAndPlay(2);
}
mcLetters.menu = lettersCM;

We start by declaring a new instance of our ContextMenu class called lettersCM. We want to hide all the built in items in our new menu, which can be done with the method hideBuiltInItems(). This effectively does the same job as Stage.showMenu=false did in Flash MX. Next we want to declare a new instance of the ContextMenuItem class (note: ContextMenuItem notContextMenu). A single instance of this class is needed for each seperate item we wish to be displayed in our new menu. As our example only has one item, we only need to create one instance. Here we pass in the minimum possible number of arguments (the linkname to be displayed [Dance Baby!!!] and the function to be called when that link is clicked [doDance]).
All instances of the ContextMenu begin life with a copy of an array called customItems, in which we store our menu links. Using the Array.push() method of the Array object we now need to push our newly created menu item into the customItems array. Following that we create the "doDance" function which, when called simply instructs the "M" movieclip to play onwards from frame 2, and finally we associate our ContextMenu instance with our movie clip, using the keyword menu. Viola, the MovieClip "mcLetters" now has a new menu!

Something to note about this code before we move on; In my code I used a standard function. You may or may not be aware that there are two types of functions - standard and literal:

//standard function
function funcName(){
        trace("I always get processed before other code");
}

//literal function
someVarName = function(){
        trace("I get processed as I'm encountered");
}

If you wish to use a literal function in place of a standard one it must be declared on a line above your new contextMenuIteminstantiation. If it isn't then your link will not appear in your menu. The menu items will also not appear in your menu if you don't declare your function - (whilst writing this tutorial I created a test file but didn't bother to write the doDance function, figuring that I would do it later. I expected the link to appear but not to possess any functionality, but instead the link didn't appear at all and I spent a short while scratching my head and looking for typos in other parts of the code before I realised the cause of the problem). This type of behaviour can throw you, but it does make some degree of sense. You have now been informed =)

We have the ability to do quite a bit more than we have done so far in our example, and we will start to take a look at these extra options now. In both the examples so far I chose to hide all of the built in menu items using the hideBuiltInItems() method of our ContextMenu object, but we actually have more control than that, and can choose to hide/display built-in items on an individual basis. I could just have removed the ability to 'print' by replacing lettersCM.hideBuiltInItems() with the line lettersCM.builtInItems.print = false;

Also, our function doDance was actually being sent two arguments, although we didn't use either of them. The first argument is a reference to the object that was under the mouse when it was clicked, and the second is a reference to the ContextMenu item. Let's re-write our function to make use of these:

function doDance(obj, item) {
        trace(obj); //traces _level0.mcLetters
        trace(item.caption); //traces Dance Baby!!!
}

As you can see, tracing our obj is straight forward enough. However, tracing itemwill just return [object object]. To get anything useful out of the second argument we need to trace one of its properties or use its method or event handler. It has several properties, and all are explained well in the dictionary, so I will only mention their names here. They are: visible, enabled, separatorBefore, and caption. It also has a method called copy() and an event handler, onSelect. We will be using some of these in our code soon, so you will be able to see what they do. The most obvious is probably caption, which dispays the link text we chose - namely "Dance Baby!!!".

Now just to confuse you, I mentioned above that the ContextMenuItem has an onSelect event Handler, but so does ContextMenu! It is the latter which we will be using in the next example.

ContextMenu.onSelect() is very useful to us because it is invoked before the menu is actually displayed. When you right-click, onSelect is invoked, and just milliseconds later the menu appears. To the naked eye it looks as if the menu appears immediately. This means that we can make important decisions about what the menu displays based on the current state of our movie or other criteria. For example, along with the ContextMenuItem.enabled property which we mentioned above, we could do something like this:

var myContextMenu = new ContextMenu();
var item1 = new ContextMenuItem("about this button", aboutBtnFunc);
myContextMenu.customItems.push(item1);
//cmObj is a reference to the ContextMenu object
myContextMenu.onSelect = function(obj, cmObj){
        if(obj instanceof MovieClip){
                item1.enabled = false;
        }
}
someMovieClip.menu = myContextMenu;
function aboutBtnFunc(obj){trace("you press all the right buttons!")}

As you may have been able to fathom from that code, if the object under the mouse when it was clicked was a MovieClip, we would disable the menu item labelled "about this button". ContextMenu.onSelect also accepts two arguments, but it is only the first that we are using here,which is a reference to the object under the mouse pointer at the time the mouse button was clicked. If you like you can coy that code into a new fla, create a MovieClip with an instance name of "someMovieClip", and see the results for yourself - the option will be there, but greyed out; Now change the MovieClip's behaviour to that of a Button, test again and the option will be enabled and clickable!

Okay, so do you remember our letter "M", dancing non-stop like some amphetamine-fuelled loonatic ... what was missing was a way to make him stop using the context menu, and we now almost have enough information to do that. It must be a 'he', because when was the last time you saw a girl dance that badly? Mind you, I'm not such a bad mover myself.

We could have put both options on the menu simultaneously, but that wouldn't be very professional, so instead we'll swap the options around. When he's dancing we'll swap the "Dance Baby!!!" option to say "Stop!!!". Look at the example below to see what I mean:


The last thing needed in order to achieve this is knowing how to make our links visible or invisible. The most appropriate way for us here is to do so when we instantiate a new instance of the ContextMenuItem class. We have already seen how to pass in the minimum amount of arguments needed, caption (ie link name) and callbackFunction. Here are the rest:

//code has been line-wrapped for formatting purposes
new ContextMenuItem
(caption,callbackFunc,[separatorBefore,[enabled,[visible]]])

separatorBefore creates those subtle horizontal bars that separate the links on the menu. We have already used enabled, and finally we have visible- no prizes for guessing what that does. So what we need to do is create both our links and set them to be visible or invisible depending upon the current state of the movie. In order to tell whether the letter is dancing or not I simply created a variable called "playing", which is set by the dancing letter mc on the main timeline. When the user clicks the mouse all we need to do is check whether playing is true or false, and using our trusty eventHandler onSelect we can change the item displayed in the menu before the menu appears.

So, without further ado, here is a screenshot of the code needed to make it all work ("Hurray" I hear you cheer). The fla is available for download (and also contains the "blue & pink squares" example that we saw way back at the beginning of this tutorial.
Note: Some code may be line-wrapped for tutorial formatting purposes. This does not affect performance of the swf.:

screenshot of code

That's it for yet another tute. The code has been commented and shouldn't pose any problems for you given your newfound knowledge. I recommend that you look at the code for the blue & pink squares fla (along with the explanations in the Actionscript dictionary for reference), as there are things in it that have not been possible to cover in this tutorial. Bye for now.


If you have any suggestions or comments about this or any of my tutorials you can email me at and I will do my best to answer them. Please note that due to the volume of Flash-related emails I get, I now prioritize emails related directly to the tutorials themselves. You may find answers to your questions already posted on Flash forums such as actionscript.org

This, and other tutorials can be found on nwebb.co.uk