This question comes up a lot in online Actionscript forums, and for the longest time I have been wanting to write an article to address it. That is, after I had answered the question many times, I started wishing I had an article that I could just point someone to. Well, here it is... with a couple of bonus pages thrown in for good measure.
In AS2, you were able to use the following technique to create a set of buttons (as always, when I say "buttons" I usually mean MovieClips being used as buttons, as is the case here):
for(var i = 0; i < 5; i++) { this.attachMovie("ButtonMC", "btn" + i, i); this["btn" + i]._y = 15; this["btn" + i]._x = i * 100 + 15; } btn0._y += 20; //affect one certain button by name
This block of code not only creates five buttons and adds them to the display, but also gives you five new instance names that you can use in your subsequent code to refer to them by: btn0, btn1, btn2, btn3, and btn4. So, notice that after the for loop, you are able to affect just one of the buttons by directly using the name btn0. You are able to use the name btn0 like any other instance name, as though you had set it in the properties panel.
This is no longer possible in AS3. At least not exactly. So a lot of people who know AS2 are naturally perplexed, when they begin to learn AS3, that the above scenario isn't possible anymore. That is, although you can dynamically add your buttons to the stage by giving the symbol a class name, you can't give each one an instance name that you can use directly, as you could in AS2. However, you can come close. I will outline the "close" techniques, and then show you an even better approach using arrays.
Now let's switch to AS3, and work with a fla file of that type. Open a new Flash Actionscript 3.0 file. Create a similar MovieClip symbol, or just copy the same one over from the AS2 file. Instead of a linkage name of "ButtonMC," now it should have a Class name of "ButtonMC." We would use "new ButtonMC()" to create an instance programmatically, and optionally set that new instance equal to a variable name. But now there is no way in the loop to create a set of variable names like the instance names from the AS2 version. Here is the first way that we are able to closely approximate it, though:
for(var i = 0; i < 5; i++) { this["btn" + i] = new ButtonMC(); this["btn" + i].y = 15; this["btn" + i].x = i * 100 + 15; addChild(this["btn" + i]) } this.btn0.y += 20; //affect one certain button by name
One disadvantage here is that since you create the variable with array access notation, you can't give the variable a type. Another disadvantage is that in the subsequent code, you must use the "this" keyword every time. Using btn0 by itself won't work. The only reason the whole thing works at all is because the main timeline is basically a MovieClip, and MovieClip is a dynamic class (So this whole scenario would not work if you were writing your code in a document class that extended Sprite instead of MovieClip. And in a document class that extended MovieClip, the document class would have to be declared dynamic, also).
Here is the second technique:
for(var i = 0; i < 5; i++) { var btn:ButtonMC = new ButtonMC(); btn.y = 15; btn.x = i * 100 + 15; btn.name = "btn" + i; addChild(btn); } getChildByName("btn0").y += 20; //affect one certain button by name
In this approach, a new instance of ButtonMC is set equal to a variable. However, the same variable is reused in subsequent iterations of the loop. So after the loop runs, this variable points only to the last object created, or btn4. So this variable isn't of any use for our purpose here. However, each button, as it was generated, was given an name. The name property of display objects is just simply a string value. It is not an instance name. But as you can see, we are able to use the method getChildByName(), which is a method of the DisplayObjectContainer class, to refer to a specific button. The disadvantages here are:
The two techniques outlined above, while you can use them, really leave a lot to be desired, at least in my opinion. There just is no longer any way to create a series of variable names in a loop and use the loop counter to increment the variable name. At least, if there is, I haven't found it yet. So, that's the bad news. But the good news is that there's a better way that is so good, you won't even miss the old AS2 way! It's called an array. And here is the array technique:
var btn:Array = new Array(); for(var i = 0; i < 5; i++) { var button:ButtonMC = new ButtonMC(); button.y = 15; button.x = i * 100 + 15; addChild(button); btn.push(button); } btn[0].y += 20; //affect one certain button by name
This creates an array named btn. Now, in the loop, I've chosen another name for the temporary variable: "button." When you create an array, you basically get a series of variable names that you can use by just supplying the index in the brackets. You can use these variable names as though they were instance names of the buttons themselves. Notice that in the line after the loop, that offsets the first button, we can just use "btn[0]." So the only real disadvantage between this and the AS2 version is that you have to type brackets. Big deal!
But putting buttons in an array has other advantages that the AS2 technique can't match. We will explore those advantages in the remainder of this article, as we leave the AS2 technique behind in favor of using arrays.
I should note, though, that even in AS2 it was possible to put buttons in an array, and use the technique outlined above. So this isn't completely an AS2 vs. AS3 thing. It's more an issue of one technique vs. another. In AS2, the "buttons in an array" technique was already superior to the "series of numbered instance names" for those who cared to use it. But now that in AS3 that latter technique is no longer an option, it just becomes that much more important to learn the array technique.
It has always seemed to me that those who want to hang onto names in programming just haven't (yet!) learned the techniques I'm going to outline in this article.
Putting buttons in an array is such a cool technique, that I can kind of guarantee that once you get to using it, you'll want to use it for everything!
Let's start with a simple example so that I can really clue you in to just how useful this is. Suppose that you have a fla file for a website. In your fla file, you've created five buttons. Each button is actually a MovieClip instance. Whether they are all instances of the same MovieClip symbol is irrelevant. What's relevant is that they have all been created in the authoring tool, and that they all exist on the stage. If the code is going to be on frame 1 (we'll assume it is), then the buttons should all exist on frame 1 too, otherwise you will get null object reference errors.
Anyway, let's say that your buttons are named:
The point is that the names could be anything at all (they don't have to be numbered in sequence like the former btn0, btn1, btn2, etc). We will put them in an array, and after we do, it will be easy to treat them as a group instead of a bunch of unrelated buttons. So let's create an array and add our buttons to the array:
var clipArray:Array = [home_mc, about_mc, products_mc, services_mc, contact_mc];
One mistake beginning programmers make is to assume that arrays can only store string values (no offense, I used to think of arrays that way, too!). So they proceed to give all their MC buttons names so that they can store the names in the array, then they use perhaps getChildByName("theName") to get references to their buttons. But arrays can store any and all kinds of values. So why not store the MovieClip instances themselves, rather than string values? In fact, I don't so much think of the array as "storing" the values--rather I think of it as giving all the values a new set of names. Here's what I mean. After that above line of code is run:
So, I want you to get this mental picture: putting all of these buttons into an array didn't cause them to get scooped up and placed somewhere else, as if they had moved or something. Let's take home_mc for example. I can still use the name home_mc even after I have put home_mc in the array:
home_mc.addEventListener(MouseEvent.CLICK, clickHandler); function clickHandler(event:MouseEvent):void { this.gotoAndStop("home"); }
But now I can also use home_mc's new alternate name to do the same thing:
clipArray[0].addEventListener(MouseEvent.CLICK, clickHandler); function clickHandler(event:MouseEvent):void { this.gotoAndStop("home"); }
This is the part I want you to get: clipArray[0], for all practical purposes, is home_mc. Everything that you could do with home_mc, using all of the properties and methods of MovieClips, you can now also do using clipArray[0] instead! So to move home_mc twenty pixels further down, you can do this:
clipArray[0].y += 20;
I could go on and on with more examples of properties and methods you can use (the whole list of MovieClip properties and methods!), but I think by now you get the central idea. Each mc button can now be programmed using the array name and index number instead.
Where this really comes in handy is when you combine arrays and loops. Arrays have a length property that you can use to know how many times to loop. This makes for dynamic code that need only be written once. For example:
var clipArray:Array = [home_mc, about_mc, products_mc, services_mc, contact_mc]; for(var i:int = 0; i < clipArray.length; i++) { clipArray[i].buttonMode = true; clipArray[i].addEventListener(MouseEvent.CLICK, clickHandler); } function clickHandler(event:MouseEvent):void { trace("you clicked " + event.target.name); }
In the above code, the loop is run five times. Each time through the loop, the loop counter "i" is incremented by one. So the first time through the loop, we are dealing with clipArray[0], which corresponds to home_mc. So home_mc's buttonMode property is set to true, giving it a hand cursor, and an event listener for a mouse click (clickHander) is added to home_mc. The next time through the loop, i will equal 1, and the same actions will be performed for about_mc. And so on for the rest of the loop, until all of the buttons have been programmed in the same way.
Suppose that later on, you add another mc button called links_mc. Now all you would have to do is add links_mc to the end of the array. There would be no need to change the for loop at all. Since the loop is based on the array's length, it will now automatically include links_mc, which will get the same programming as all the other buttons (but we won't add links_mc right now).
I am aware that the same event listener has been added to all of the buttons, and you may be wondering how to make individualized actions happen inside the listener, based on which button was clicked. One answer is to either use if statements, or a switch statement, that compares each button to event.currentTarget (you could also use event.target, if you are sure that your buttons don't contain other display objects, but currentTarget will always be safe in this case, because the event listener was added to the button itself. See my event flow tutorial for details):
var clipArray:Array = [home_mc, about_mc, products_mc, services_mc, contact_mc]; for (var i:int = 0; i < clipArray.length; i++) { clipArray[i].buttonMode = true; clipArray[i].addEventListener(MouseEvent.CLICK, clickHandler); } function clickHandler(event:MouseEvent):void { switch (event.currentTarget) { case home_mc : gotoAndStop("home"); break; case about_mc : gotoAndStop("about"); break; case products_mc : gotoAndStop("products"); break; case services_mc : gotoAndStop("services"); break; case contact_mc : gotoAndStop("contact"); break; } }
Notice that this switch statement uses the actual objects themselves, and compares them to event.currentTarget. I realize that you could also use strings, and compare them to event.currentTarget.name. Either way is fine, it's just that this way seems more direct and intuitive to me.
Still, this code is more verbose than it needs to be. Notice that there are two sets of information: the buttons themselves, and the destination frame on the main timeline that each button is intended to take you to. The buttons are already in an array, so why not put the destination frames in an array also? This technique is called using parallel arrays:
stop(); var clipArray:Array = [home_mc, about_mc, products_mc, services_mc, contact_mc]; var destArray:Array = ["home", "about", "products", "services", "contact"]; for (var i:int = 0; i < clipArray.length; i++) { clipArray[i].buttonMode = true; clipArray[i].addEventListener(MouseEvent.CLICK, clickHandler); } function clickHandler(event:MouseEvent):void { for (var i:int = 0; i < clipArray.length; i++) { if (event.currentTarget == clipArray[i]) { this.gotoAndStop(destArray[i]); } } }
Notice that each element of the first array has a corresponding, or parallel element in the other array. Now, when you click, there is another for loop inside the clickHandler that cycles through the clipArray and compares each button to the event.currentTarget. When it finds a match, it has effectively figured out which button was clicked. At that point, while we still have the proper value for "i," we can tell the main timeline to gotoAndStop() at the frame that's labelled with the value from the corresponding array.
Here's this simple website example, followed by a link for you to download the fla file:
Although the website example is simple, it makes a good illustration of the techniques outlined. So even if your buttons don't belong to a website application, the principles are the same. On the next page we'll explore how to give your buttons "disabling" behavior, using the same website example.
On the previous page, we finished out with a simple website example. Clicking on each button would take you to the corresponding "page," or frame on the main timeline. However, you may want to give your buttons "disabling" behavior. Yeah, I know I wrote a tutorial on button disabling that uses classes. This, however, is going to be the quick version, for those cases where you want to program the behavior more directly.
Button disabling behavior means that if you are at the home page, for example, you want to disallow the home button from being clicked again. Whatever page you are at, you want the corresponding button to be disabled so that it cannot be clicked again. This scenario creates what might also be termed "radio buttons." When they are clicked, they are selected, and cannot be selected again until something else has been selected instead.
To do this is very easy. We already have code that uses a for loop to cycle through the array that holds the buttons. Each one is compared to the event.currentTarget to see which button was clicked. All we have to do is add code that disables that button, and perhaps includes some visual clue that the button can't be clicked on again.
To disable the button, we set its mouseEnabled property to false. This will cause the cursor to become an arrow instead of the hand cursor. For a further visual clue that the button is disabled, we will set its alpha to 0.5. At the same time, we need to enable all of the other buttons, and set their alpha back to 1, just in case one of them is the button that was formerly disabled. We can do this using an else block added to our if statement:
stop(); var clipArray:Array = [home_mc, about_mc, products_mc, services_mc, contact_mc]; var destArray:Array = ["home", "about", "products", "services", "contact"]; for (var i:int = 0; i < clipArray.length; i++) { clipArray[i].buttonMode = true; clipArray[i].addEventListener(MouseEvent.CLICK, clickHandler); } function clickHandler(event:MouseEvent):void { for (var i:int = 0; i < clipArray.length; i++) { if (event.currentTarget == clipArray[i]) { this.gotoAndStop(destArray[i]); clipArray[i].mouseEnabled = false; clipArray[i].alpha = 0.5; } else { clipArray[i].mouseEnabled = true; clipArray[i].alpha = 1; } } }
So you can see that when the buttons in the clipArray are cycled through with the for loop, something happens to every one of them. If the button happens to be the one that was clicked, it is disabled. If not, the else block handles it, and the button is enabled. There is one last little thing. When the application first begins, the home page is on the screen, and so the home_mc button should really start out in a disabled state. So add these lines to the end of the code:
home_mc.mouseEnabled = false; home_mc.alpha = 0.5;
With those changes in place, here is how the application behaves now:
website_simple_button_disabling.fla
That concludes this tutorial. I hope you can now see some of the possibilities for using arrays in your programming.
I recently did a project where I needed to set up a system of button disabling like I described in the previous pages of this article. However, in this case, it was requested that each button's over state be used as the indicator that the button is disabled. That's when I discovered something interesting: the code I came up with wouldn't work right! It turns out that it wasn't enough to just set mouseEnabled to false for the disabled button, it was also necessary to set buttonMode to false.
By now I hope you realize that I'm not talking about the Button symbol, but rather a movie clip being used as a button. If you have been reading my other articles, you already know this. (Note: Whenever the word Button appears with a capital letter, it always means the Button symbol. Whenever it appears with an initial lowercase letter: "button," I almost always mean a MovieClip symbol being used as a Button. That's my own convention, anyway.)
Anyway, this seems to be a pretty common request--it would seem that a lot of people would like to know how to make a button "stick" in its over state when clicked. In fact, I believe a couple of visitors to this site have asked about that. Well, with the Button symbol, forget it. I don't know of any way. But if you have read my article on MCB's (movie clip buttons) you know that a MovieClip symbol can be made to behave like a button, and that a MovieClip can do anything that a Button can do, and much more! But nothing inside a Button symbol can be addressed with actionscript from outside the Button. So a Button cannot be told to gotoAndStop on its over frame.
If you didn't read my article on Movie Clip Buttons, you should take the time to do that now. Go read it and come back. If you have read it, then let's move on. Download the fla file at the top of this page. It's a modification of the button disabling file from the previous article. What I have done in this one is deleted the former MovieClip symbol that was being used as a button. Instead, I brought in the MCB symbol from the example file used in that article. Then I proceeded to create five new instances of it. I added code that gives each MCB instance a label on its face. Then I took out the code that alters each button's alpha, replacing it with code that makes each button gotoAndStop on its "_over" frame.
I also added code that sets each MCB instance's buttonMode property to false when it is clicked, and back to true again when it is a button that was not clicked. Everything else is still the same, and you will see what I mean about the buttonMode property if you comment out the lines that set it each time (lines 21, 24, and 32). It just doesn't work right without that additional touch.
stop();
home_mc.myText.text = "Home";
about_mc.myText.text = "About Us";
products_mc.myText.text = "Products";
services_mc.myText.text = "Services";
contact_mc.myText.text = "Contact Us";
var clipArray:Array = [home_mc, about_mc, products_mc, services_mc, contact_mc];
var destArray:Array = ["home", "about", "products", "services", "contact"];
for (var i:int = 0; i < clipArray.length; i++) {
clipArray[i].buttonMode = true;
clipArray[i].addEventListener(MouseEvent.CLICK, clickHandler);
}
function clickHandler(event:MouseEvent):void {
for (var i:int = 0; i < clipArray.length; i++) {
if (event.currentTarget == clipArray[i]) {
this.gotoAndStop(destArray[i]);
clipArray[i].mouseEnabled = false;
clipArray[i].buttonMode = false;
clipArray[i].gotoAndStop("_over");
} else {
clipArray[i].mouseEnabled = true;
clipArray[i].buttonMode = true;
clipArray[i].gotoAndStop("_up");
}
}
}
home_mc.mouseEnabled = false;
home_mc.buttonMode = false;
home_mc.gotoAndStop("_over");
Stay tuned for more interesting tutorials! Happy coding!
Jody Hall