removing event listener - actionscript-3

I have learned this method of passing values with addEventLister. Here is the code:
for (var i:uint = 0; i < asteroids.length; i++)
{
asteroids[i].x = Math.random() * 450;
asteroids[i].y = Math.random() * 450;
asteroids[i].addEventListener(MouseEvent.MOUSE_UP, function(e:MouseEvent){
changeValue(e, otherArguments);
});
}
public function changeValue(event:MouseEvent, otherArguments:Object):void
{
playSound(anote);
trace(event.currentTarget);
}
but there is no explanation about how to remove the event listener from
asteroids[i].addEventListener(MouseEvent.MOUSE_UP, function(e:MouseEvent){
changeValue(e, otherArguments);
});

That is because you cannot unhook an anonymous function if you don't have a reference to it. If you'd like, you can always keep a reference around:
asteroidsMouseUpHandler = function(e:MouseEvent){
changeValue(e, otherArguments);
};
asteroids[i].addEventListener(MouseEvent.MOUSE_UP, asteroidsMouseUpHandler);
asteroids[i].removeEventListener(MouseEvent.MOUSE_UP, asteroidsMouseUpHandler);
Of course, if these things are happening in separate member functions, then you need to define a member variable (var asteroidsMouseUpHandler:Function). At that point, you might just want to create it as a named function instead. You'd also only want to do this once (not in a loop) to avoid clobbering your reference. Really, I am showing this code for illustrative purposes only.
Thinking about this further, you might be doing this because the otherArguments is specific to the particular asteroids[i] instance so that you are actually creating a new anonymous function every time you hook it. In that case, if that is what you really want to do, you can keep track of your anonymous functions in a dictionary:
var asteriodsCallbacks:Dictionary = new Dictionary();
Then, you can keep track of the functions there:
asteroidsCallbacks[asteroids[i]] = asteroidsMouseUpHandler;
asteroids[i].addEventListener(MouseEvent.MOUSE_UP, asteroidsMouseUpHandler);
And then when you want to unhook, you can look it up:
asteroids[i].removeEventListener(MouseEvent.MOUSE_UP, asteroidsCallbacks[asteroids[i]]);
Of course, I am ignoring existential checks for simplicity. You'd have to add that as well.

Why don't you declare the function like normal, rather than in the parameter? You can do the following:
for (var i:uint = 0; i < asteroids.length; i++)
{
asteroids[i].x = Math.random() * 450;
asteroids[i].y = Math.random() * 450;
asteroids[i].addEventListener(MouseEvent.MOUSE_UP, onMouseUp);
});
}
public function onMouseUp(e:MouseEvent) {
changeValue(e, otherArguments);
}
public function changeValue(event:MouseEvent, otherArguments:Object):void
{
playSound(anote);
trace(event.currentTarget);
}
You can remove it as follows:
asteroids[index].removeEventListener(MouseEvent.MOUSE_UP, onMouseUp)

When you add an anonymous function as an event listener, the only time you can reasonably remove that event listener is inside the callback.
public function changeValue(event:MouseEvent, otherArguments:Object):void
{
playSound(anote);
trace(event.currentTarget);
event.currentTarget.removeEventListener(MouseEvent.MOUSE_UP, arguments.callee);
}
Otherwise, you need to have a non-anonymous function in order to remove it.

Related

AS3: Adding function to population loop

Basically I have 2 movieclip objects with some code, currently just to trace them. The blue circles when clicked will say 'Blue' and the red ones when clicked will say 'Red'. This works fine in theory until I add a population loop, which adds more of them. Then only 1 of each colour correctly works, the rest are just 'mock' circles. I wish for each circle to tell me their colour.
This is my code for the .fla:
import flash.events.MouseEvent;
BlueBall.addEventListener(MouseEvent.CLICK, fun1)
function fun1(e:MouseEvent){
trace("Blue!");
}
RedBall.addEventListener(MouseEvent.CLICK, fun2)
function fun2(e:MouseEvent){
trace("Red!");
}
and this is the population loop in an .as file:
private function PopulateCircles():void
{
for (var i:int=0; i < 10; i++)
{
var blueCircle:BlueCircle = new BlueCircle();
this.addChild(blueCircle);
var redCircle:RedCircle = new RedCircle();
this.addChild(redCircle);
}
}
tldr; how do I get the on-click events to occur on every newly populated circle?
Pretty easy, actually. Just as you subscribe method to listen the predesigned instances' events, you can subscribe via temporary variable references. As long, as the variable holds the reference (or a pointer in C++ terms), you can address the instance and do anything you could do to a predesigned MovieClip:
private function PopulateCircles():void
{
var aRed:RedCircle;
var aBlu:BlueCircle;
for (var i:int = 0; i < 10; i++)
{
// If there are no mandatory constructor arguments,
// you can omit the () brackets.
aRed = new RedCircle;
aBlu = new BlueCircle;
// Disperse clips to random places.
aBlu.x = 500 * Math.random();
aBlu.y = 500 * Math.random();
aRed.x = 500 * Math.random();
aRed.y = 500 * Math.random();
// Subscribe methods to newly created instances.
aRed.addEventListener(MouseEvent.CLICK, fun2);
aBlu.addEventListener(MouseEvent.CLICK, fun1);
// You're operating inside 'this' object,
// no need to explicitly point it out.
addChild(aRed);
addChild(aBlu);
}
}

Accessing event.target or event.currentTarget beyond the listener Function

Originally I had two functions, the first function being dataLoaded which had an eventListener in it that spawned a new function called itemClicked. But if I wanted to add a delayedFunction and have itemClicked as an intermittent stage between dataLoaded and delayedFunction, how can I access the event.target or event.currentTarget of the original listener when I come to reference it in my delayedFunction?
private function dataLoaded(event:Event):void {
//items properties call - add other calls to master properties later on
items = data.item;
// parsing of each ingredient
for (var i = 0; i < items.length(); i++) {
// instantiation of mcItem (the stage for each item)
_item = new Item();
// sets //over// layer to invisible / transparent
_item.item_btn_over.alpha = 0;
// creates the var itemTextField
_itemTextField = new TextField();
_itemTextField.text = items[i].toString();
// adds textfield to displaylist
_item.addChild(_itemTextField);
//adds items to container displaylist
_container.addChild(_item);
}
_item.parent.addEventListener( MouseEvent.CLICK, itemClicked );
}
function itemClicked(event:MouseEvent):void {
if (event.target is Item) {
var item:Item = Item(event.target);
TweenLite.to(item.btn_delete, 1,{rotation:180});
setTimeout(delayedFunction,5000);
item.parent.removeChild(item);
parseXML(data);
}
}
function delayedFunction():void {
var item:Item = Item(event.target);
item.parent.removeChild(item);
}
(I've mocked up the functions above, so I may be missing some closing parenthesis, but this doesn't matter in relation to my question)
This question has been on my mind for days and I can't think of a way of making the event.target of the parent _item accessible to functions other than the listening function.
You can pass arguments to your setTimeout call like this:
setTimeout(delayedFunction, 5000, event.currentTarget);
Then get the argument from the arguments array from your delayedFunction method:
function delayedFunction():void {
var item:Item = arguments[0] as Item;
item.parent.removeChild(item);
}
Have a look at the setTimeout docs if you need information.

assign mouse event listener with loop to 10 movieclips

I am trying to loop through my 10 "bomb" movieclips and assign an eventlistener that calls the same function but passes the current movieclips name. the "bomb" movieclip names are incremented.
Below is my try
var i:number;
i=0;
while (i <= 10){
var current_bomb:Movieclip = (movingbomb_+i);
current_bomb.addEventListener(MouseEvent.ROLL_OVER, function updateBomb(current_bomb));
i++
}
function updateBomb(currentBomb):void{
currentBomb.gotoAndPlay(2);
}
Close, but not quite. Also, using a for loop is a much better idea here. With those changes, the code should look something like this:
for (var i:int = 0; i < 10; i++) {
var currentBomb:MovieClip = this["movingbomb_" + i];
currentBomb.addEventListener(MouseEvent.ROLL_OVER, function (evt:MouseEvent):void { updateBomb(currentBomb); });
}
function updateBomb(currentBomb:MovieClip):void {
currentBomb.gotoAndPlay(2);
}
Here's how this works.
The for loop simplifies all your while looping code into a single statement for efficiency.
We then select the current bomb using array-bracket selection syntax. To do this, you use this[name], where name is a string. The string we use will be "movingbomb_" with i tacked on to the end.
Finally, we create a unique anonymous function for each new event listener which redirects the mouse event to your updateBomb function and passes the currentBomb object.

AS3, for loop create button inside movieclip and how to call it?

Here is my full code
import fl.controls.*;
var test:MovieClip = new MovieClip();
var btn:Button;
for(var i:int = 1; i<=7; i++){
btn = new Button();
btn.name = "btn" + i;
btn.x = i * 100;
test.addChild(btn);
btn.addEventListener(MouseEvent.CLICK, function(evt:MouseEvent) { nothing(i); });
}
addChild(test);
function nothing(bla:int):void {
trace("You clicked " + bla);
}
Result:
You clicked 8
You clicked 8
You clicked 8...
Is there anyway, such that, I can use for loop to create different name button, and add it to an event listener?
Your problem is the function(evt:MouseEvent){} closure (JavaScript info applies to ActionScript too, as they're both ECMAScript). Here's what you can do:
function makeClickListener(i:int) {
return function(evt:MouseEvent) {
nothing(i);
};
}
...
for(...) {
...
btn.addEventListener(MouseEvent.CLICK, makeClickListener(i));
}
in your example your i is not doing what you think it's doing. You've declared it as a global variable and passing into the function as you're doing is meaningless. In the end your buttons are simply reporting the current value of i (which after the loop is always 8).
I like the other solution offered, but here's a more object oriented solution, which may be of some use depending on what you're doing with your button (or other objects in the future).
public class MyButton extends Button{
public var myIndex:Number;
}
Now you use MyButton instead of Button and in your loop throw in
btn.myIndex = i;
then attach generic event handler
btn.addEventListener(MouseEvent.CLICK, myHandler);
which would look like this:
function myHandler(evt:MouseEvent){
trace(evt.target.myIndex);
}
See... the target of the event will always be the object to which you attached the event. It is to that object that you should attach whatever values you wish it to retain. Personally I prefer this approach because then the information is with the object (and can be used by other elements that might need it) rather than in the handler and only in the handler.
var test:MovieClip = new MovieClip();
var btn:Button;
for(var i:int = 1; i<=7; i++){
btn = new Button();
btn.name = i.toString();
btn.x = i * 100;
test.addChild(btn);
btn.addEventListener(MouseEvent.CLICK, nothing);
}
function nothing(event:MouseEvent):void {
//trace("You clicked " + int( event.currentTarget.name ) );
trace("You clicked " + event.currentTarget.name );
}

Add multiple movieclips, not replacing the old ones

So, in short, my problem is this. I am using a variable which is a movieclip loaded from an external swf. I want to "spawn" multiple instances of the movieclip that all react to the same code, so for example if I say var1.x = 100, they all are at 100x. But my problem is when I run addChild(var1) multiple times(I'm not actually typing in addChild(var1) over and over, I just have it set to add them at random times), the new child just replaces the old one, instead of making multiple movieclips. Should I do something like
var var1:MovieClip
var var2:MovieClip = new var1 ?(which doesnt work for me btw, gives me errors)
Oh, heres the code, and also, I am pretty new to as3 fyi, still don't even know how arrays work, which was my second guess to the problem.
var zombieExt:MovieClip;
var ldr2:Loader = new Loader();
ldr2.contentLoaderInfo.addEventListener(Event.COMPLETE, swfLoaded2);
ldr2.load(new URLRequest("ZombieSource.swf"));
function swfLoaded2(event:Event):void
{
zombieExt = MovieClip(ldr2.contentLoaderInfo.content);
ldr2.contentLoaderInfo.removeEventListener(Event.COMPLETE, swfLoaded2);
//zombieExt.addEventListener(Event.ENTER_FRAME, moveZombie)
zombieExt.addEventListener(Event.ENTER_FRAME,rotate2);
function rotate2 (event:Event)
{
var the2X:int = playerExt.x - zombieExt.x;
var the2Y:int = (playerExt.y - zombieExt.y) * 1;
var angle = Math.atan(the2Y/the2X)/(Math.PI/180);
if (the2X<0) {
angle += 180;
}
if (the2X>=0 && the2Y<0) {
angle += 360;
}
//angletext.text = angle;
zombieExt.rotation = (angle*1) + 90;
}
playerExt.addEventListener(Event.ENTER_FRAME,spawn1);
function spawn1 (event:Event)
{
if(playerExt.y < 417)
{
var someNum:Number = Math.round(Math.random()*20);
if(someNum == 20)
{
addChild(zombieExt)
zombieExt.x = Math.round(Math.random()*100)
zombieExt.y = Math.round(Math.random()*100)
}
}
}
}
addChild() does not create new instances. It is used to add an already created instance to the display list. If you call addChild() multiple times on the same instance then you are just readding itself.
Also each instance is unique, you can not globally change the x position of an instance by changing another one of them. What you would do is as Henry suggests and add each new instance of a MovieClip into an array, then whenever you change something you can loop through the array and apply the changes to each instance.
You can not go var2:MovieClip = new var1 either since var1 is an instance and not a class.
Here's a different method of receiving loaded MovieClips, which i use when i need many copies of the item.
in the swf you are loading, give the target movieclip a linkage name in the library, for this example i will use "foo"
private var loadedSwfClass:Class
private var newZombie:MovieClip;
private var zombieArray:Array = new Array();
function swfLoaded2(event:Event):void
{
loadedSwfClass = event.target.applicationDomain.getDefinition("foo");
for(var n:int = 0; n<100; n++){
newZombie = new loadedSwfClass()
zombieArray.push(newZombie);
addChild(newZombie);
}
}
as per this tutorial
http://darylteo.com/blog/2007/11/16/abstracting-assets-from-actionscript-in-as30-asset-libraries/
although the comments say that
var dClip:MovieClip = this;
var new_mc = new dClip.constructor();
this.addChild(new_mc);
will also work.
It sounds like you might be accessing the same instance some how in your code. It would be helpful to see your code to figure this one out.
If I wanted to load in one swf files and add a MovieClip multiple times I would place it in the library of that SWF file. And then instantiate it and store it into an object pool or a hash or some list.
// after the library were finished loading
var list:Array = [];
for(var i:int=0; i<10; i++) {
var myCreation:MySpecialThing = new MySpecialThing();
addChild(myCreation);
list.push(myCreation);
}
where my library would contain a linkage to the class MySpecialThing.
Calling addChild(var1) multiple times on the same parent doesn't have any effect (unless you have added another child to the same parent in between, in which case it will change the child index and bring var1 to the top). If you call it on different parents, it will just change the parent of var1, doesn't duplicate. Call addChild(new MovieClassName()) at random times instead to add new copies of it. Use an array as suggested here to access them later.
Wow, thanks there henry, just using an array did exactly what I needed, and made things alot simpler.
when you load in using a loader you only get 1 instance, however you can do some funky reflection to determine what class type the given loader.content is, and then instantiate them using that. For Example:
var loader:Loader = new Loader();
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, loader_completeHandler);
loader.load(new URLRequest("ZombieSource.swf"));
var classType:Class;
function loader_completeHandler(evt:Event):void
{
var loadInfo:LoaderInfo = (evt.target as LoaderInfo);
var loadedInstance:DisplayObject = loadInfo.content;
// getQualifiedClassName() is a top-level function, like trace()
var nameStr:String = getQualifiedClassName(loadedInstance);
if( loadInfo.applicationDomain.hasDefinition(nameStr) )
{
classType = loadInfo.applicationDomain.getDefinition(nameStr) as Class;
init();
}
else
{
//could not extract the class
}
}
function init():void
{
// to make a new instance of the ZombieMovie object, you create it
// directly from the classType variable
var i:int = 0;
while(i < 10)
{
var newZombie:DisplayObject = new classType();
// your code here
newZombie.x = stage.stageWidth * Math.random();
newZombie.x = stage.stageHeight * Math.random();
i++;
}
}
Any problems let me know, hope this helps.