Neal Davis
Neal Davis

Reputation: 2008

How to write an object name with a variable for iteration in AS3?

Let's say I wanted to make several (or more) buttons with instance names like "button_1" etc. Isn't there a way to loop through all of the instances in a function or an event handler by iterating through the number component of the instance name?

for example:

for (i = 1; i < 5; i++) {
    btn_[i].addEventListener(MouseEvent.CLICK, onBtn_[i]Click);
}

in order to eliminate:

//btn_2.addEventListener(MouseEvent.CLICK, onBtn_2Click);
//btn_3.addEventListener(MouseEvent.CLICK, onBtn_3Click);
//btn_4.addEventListener(MouseEvent.CLICK, onBtn_4Click);
//btn_5.addEventListener(MouseEvent.CLICK, onBtn_5Click);  

Thanks for any help you can offer.

I think there is a better way to do this by making the event listener refer back to the target of the event? But I'm not too worried about that for now, just wondering about iterating through object instances specifically.

Oh, and my code that I posted throws this:

1084: Syntax error: expecting rightparen before Click.

Thanks, all!

Upvotes: 1

Views: 706

Answers (2)

null
null

Reputation: 5255

what you want

I wanted to make several (or more) buttons with instance names like "button_1"

No, you really don't want to do that, it's

  • tedious: What if you need 50 buttons? Do you really want to name them all by hand?
  • prone to error: What if you make a typo somewhere? Or you mess something up while copy & pasting the common part of the name? What if you forget on number, or use one twice?
  • not easy to maintain: What if you want to remove buttons 3 to 7? How do you deal with that gap of numbers? What if you want to include 2 buttons in the middle? Do you rename all the following buttons?

the number component of the instance name?

Names are great to identify specific single objects, but listing individual names is not an ideal solution if the list should be exhaustive.

If your brothers are Jack, Sam and Robin would you refer to them with that list in speech or would you just say my brothers?

what you need

Isn't there a way to loop through all of the instances

This is the actual desirable thing. You have several objects and you need some mechanism to group them.

There are several such mechanisms that can be used in conjunction:

Array

An array is just a list of things. A list of all the buttons is the grouping mechanism you are looking for. Having all buttons and functions in arrays allows you to iterate over the arrays:

var buttons:Array = [btn_1, btn_2];
var functions:Array = [onBtn_1Click, onBtn_2Click];

Which would allow you to do something very similar to what you asked for:

for (var i:int = 1; i < buttons.length; i++) {
    buttons[i].addEventListener(MouseEvent.CLICK, functions[i]);
}

This still has a few problems:

  • both buttons and functions have to be explicitly named
  • filling the arrays manually actually adds another layer of complexity, that seems redundant and is prone to error itself: What if you add a button but forget to add it to the array?

We have to look further.

not an improvement to Array

Wait! There's this cool trick with [] brackets that allows you to assemble names and stuff.

Well, there is indeed the possibility to access properties of objects dynamically with said brackets.

If you have an object with a property:

var object:Object = {property:"hello world"};

You have two possibilities to access this property. Dot syntax:

trace(object.property);

and brackets:

trace(object["property"]);

With the brackets, the property name can be chosen dynamically:

var object:Object = {property:"hello world"};
var propertyName:String = "property";
trace(object[propertyName]);

and thus can be assembled in any way:

var object:Object = {property:"hello world"};
var propertyName:String = "prop";
propertyName += "erty";
trace(object[propertyName]);

Because instance names are turned into property names of the time line, you can access your instances this way:

this["button_" + 1]

Now you can easily fill the arrays, say the one of the buttons for example:

var buttons:Array = []; //creating an empty array

for (var i:int = 1; i < 5; i++) {
    buttons.push(this["button_" + i]);
}

The problem with this is that it doesn't solve the actual problem. It just makes a ridiculous task less tedious.

You still have to type all those individual names for the buttons. Nothing prevents you from typing a wrong name, a name twice or to skip a value.

You will find this "solution" all over the internet, but it is more of a quick and dirty hack that only appears to be clever but doesn't really help with the underlying problem.

DisplayObjectContainer

All the buttons are displayed. That means they are on the display list. You can access and manipulate that list with code.

This sounds dubious, there was this Array thing before which was also a list and it didn't really solve the problem.

The difference is that the buttons are on the display list automatically. You don't have to do anything. You do not have to identify them with names.

The display list is made up of containers like MovieClip. Each container can contain other containers, which in turn can contain containers, etc.

Here's how it goes:

  1. Create an empty MovieClip on the timeline and give it an instance name buttonContainer.
  2. Add all your buttons to that buttonContainer. The buttons do not need to have instance names!

It's kind of like an array, but you assemble it visually and you do not need individual instance names, just one for the container.

The container also knows how many things are inside it. To iterate over them all you can use a for loop, too. Here's an example that makes the buttons half transparent:

for (var i:int = 0; i < buttonContainer.numChildren; i++) {
        buttonContainer.getChildAt(i).alpha = 0.5;
}

This code works for pretty much any number of buttons in that container. All you have to do is make sure you add the buttons to the container.

Ok great, now how do I do this with the functions?

This is where the problem is: you cannot. There's no built in mechanism that groups functions together the way it happens with display objects.

event system

I think there is a better way to do this by making the event listener refer back to the target of the event?

Indeed there is and it's what solves the problem of having individual functions for each button. Instead, have only one function that works with the source of the event: currentTarget. Here's what such a function would look like that goes to half transparency:

function onClick(e:MouseEvent):void
{
    currentTarget.alpha = 0.5;
}

You can now add this function to all buttons:

for (var i:int = 0; i < buttonContainer.numChildren; i++) {
        buttonContainer.getChildAt(i).addEventListener(MouseEvent.CLICK, onClick);
}

That's it! Plain and simple: add this listener to all the buttons in that container. No name wizardry. Just a container.


The superior solution

Dude! You were taking your sweet time with all that gibberish above, introducing one almost-a-solution after the other, with only the last one actually getting the job done. What's the matter with this cheesy subtitle now? How will this one 'not quite work'?

All of the above assumes that having all buttons on a list is the actual goal and indeed it solved the problem. But if you think about it, everything above is just trying to be as clever as possible about how to put the individual buttons on the list. this["button_" + i] has its flaws and the DisplayObjectContainer solutions fixed them, but introduced its own ones: What if you cannot add all buttons to a container for some reason? What if some are in one container and some are in another?

You could combine the approaches and add the buttons from several containers into a single array, like so:

var buttons:Array = [];

for (var i:int = 0; i < containerA.numChildren; i++) {
        buttons(containerA.getChildAt(i));
}

for (i = 0; i < containerB.numChildren; i++) {
        buttons(containerB.getChildAt(i));
}

The array is the super container that's independent from the display list. This still sucks. What if the buttons are scattered around and cannot be grouped by containers?

Do you remember Jack, Sam and Robin? And how it was easier to refer to them as "my brothers" as a kind of list of their names?

What if you wanted to refer to all brothers in the world? It seems to be a ridiculous task, but actually the phrase "all brothers" does exactly that. It doesn't say how many there are or where they are, but sure enough it refers to them. It does so not by creating an explicit list of them all, but them fitting to the definition of what a brother is: a male sibling.

You don't really need an explicit list of all buttons either. You just want to refer to all the buttons in existence implicitly by having a definition of what a button is.

A class is such a definition. Instead of running around identifying objects that are buttons, either by name, container or whatever other way you imagined, define what a button is and derive objects from that definition.

Here's what a file TransparentButton.as could look like:

package
{
    public class TransparentButton extends SimpleButton
    {
        public function TransparentButton()
        {
            addEventListener(MouseEvent.CLICK, onClick);
        }

        private function onClick(e:MouseEvent):void
        {
            this.alpha = 0.5;
        }
    }
}

Essentially, this defines that when a button is created, it adds a listener to itself. That function reduces its transparency to 0.5.

If you now associate that class with your button symbol in the library, it will cause the button instances that you drag & drop onto the time line to behave according to that class definition.

disclaimer: This class does not necessarily work as-is, depending on what type of symbol your buttons are (if they are actually MovieClips this will fail). Maybe it lacks some imports.

Upvotes: 3

Yasuyuki  Uno
Yasuyuki Uno

Reputation: 2547

You can access the property by array syntax like below.

Obj["foo"] = "baz";
trace(Obj.foo);     // baz

However the following code gets build error.

["btn_1"].addEventListener      // NG

You can avoid the error by adding "this".

this["btn_1"].addEventListener  // OK

So this is the answer that fixed your example code.

for (i = 1; i <= 5; i++) {  // Fixed "<" to "<=". Because it seems you want to create 1 to 5.
    this["btn_"+i.toString()].addEventListener(MouseEvent.CLICK, this["onBtn_"+i.toString()+"Click"]);
}

And .toString() is unnecessary. When concatenate String and Number, the number automatically conversion to String type. This code also works.

this["btn_"+i].addEventListener(MouseEvent.CLICK, this["onBtn_"+i+"Click"]);

In addition, if onBtn_1Click and onBtn_2Click and onBtn_3Click ... are similar or common methods, I suggest you to integrate them to one function onBtnClick.
Like this.

for (i = 1; i <= 5; i++) {
    this["btn_"+i].addEventListener(MouseEvent.CLICK, onBtnClick);
}

function onBtnClick(event: MouseEvent): void{

    // Some common process.
    Button(event.target).visible = false;

    // You can get the id by using event.target.name
    if (event.target.name == "btn_1"){

    }
    else if (event.target.name == "btn_2"){

    }
    else{

    }

    // Some common process.
    doSomething();
}

Upvotes: 1

Related Questions