How to make a parent movieclip listen to a customevent, dispatched by a child of child movieclip ? - actionscript-3

There are 3 Movieclips on stage
A => B => C ( B is the child of A, C is the child of B )
When i use a builtin event like MouseEvent.CLICK, on movieclip "C", then it automatically propogates to movieclip "A". I don't need any type of "dispatch" function at any level. This is understandable, since events propogate from child to parent automatically.
But when i use a customevent say "onMyCustomEvent", and use dispatch function inside "C". I cannot get it propogated ? How can i make the propgation of a customevent, same as how it happens for a built-in event ?

When you register event handler set third parameter useCapture to true.

What you are calling propagation is actually called "bubbling".
An Event bubbles up the display list.
This is also the reason why one should addEventListener() for KeyboardEvent and MouseEvent at the stage in order to capture them regardless of focus:
From wherever the Event starts bubbling up, it will always end up at the stage, which is the top most object in the display list hierarchy.
In order to create a bubbling Event, pass true as the second parameter to the constructor of the Event class:
see here:
http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/events/Event.html#Event%28%29
public function Event(type:String, bubbles:Boolean = false, cancelable:Boolean = false)
bubbles:Boolean (default = false) — Determines whether the Event object participates in the bubbling stage of the event flow. The default value is false.
You do this when calling super() in your CustomEvent's constructor, e.g.:
super("whatever", true);

Related

AS3: Best way to register event listeners

My question today is about the way we can register an event listener.
Let's say we have a Group element and inside it a custom Handler element. We want the Handler element to do something when Group triggers a custom event. Now, what is the best way to do it?
var group:Group = new Group();
var handler:Handler = new Handler();
group.addElement(handler);
Now, what is the best way to register the event listener?
1. Go on and do it from the file where we initialized the objects
group.addEventListener("CustomEvent", handler.handlerFunction);
2. Register the event listener from the Handler's class:
parent.addEventListener("CustomEvent", handlerFunction);
3. Any other way?
You can let Group class instance dispatch custom event directly on Handler class instance. Handler class would have an internal listener registered for example in constructor.
public function Handler() {
addEventListener("CustomEvent", handlerFunction);
}
Group class would dispatch event following way:
handler.dispatchEvent(new CustomEvent());

How to access the variable "abc" in the following code structure

1) First of all I don't wanna use CustomEvent class. Some solution I am looking without using CustomEvent.
2) One of the solution can be having abc variable in ClassA. And then dispatching directly via ClassA ( rathar than saying classB.dispatchEvent() ). But still looking if there is some better solution than this.
//Frame1 code :
import flash.events.Event;
var classA:ClassA = new ClassA() ;
classA.addEventListener("hello", hello);
classA.init();
function hello(e:Event)
{
trace(e.currentTarget.abc); //<<<< NEVER EXECUTED
}
//classA
package
{
import flash.display.MovieClip;
import flash.events.Event;
public class ClassA extends MovieClip
{
var classB:ClassB ;
public function ClassA()
{
classB = new ClassB();
}
public function init()
{
classB.dispatchEvent( new Event("hello"));
}
}
}
//classB
package
{
import flash.display.MovieClip;
public class ClassB extends MovieClip
{
public var abc:Number =123;
public function ClassB()
{
}
}
}
You are missing a couple key concepts before you can get your example to work. First you are dispatching the event on an instance of ClassB, however you are listening on an instance of ClassA. So, they have to be related in some way, in order for event to be properly orchestrated when it gets dispatched. One way to do that is to use event bubbling. One caveat to that is that native event bubbling only really works for DisplayObjects, but both of your classes inherit from MovieClip so thats not a big deal.
So the first thing, you have to understand how bubbling events work. A simplified explanation is that events start at the top of the display hierarchy and capture down the display tree towards the element, they are finally dispatched on the target, then they turn around and bubble back out in the opposite direction.
This means that your instance of ClassB has to be a child of ClassA. So the first thing you'll have to change is in your ClassA constructor:
public function ClassA()
{
classB = new ClassB();
addChild(classB);
}
Next, when you dispatch the event, you'll need to explictly say that its a bubbling event, otherwise it'll be triggered on the target, and neither capture nor bubble through the display stack.
public function init()
{
classB.dispatchEvent( new Event("hello", true));
}
The second argument of true sets the event to a bubbling event.
Finally you'll need to change your handler. Right now, it's using e.currentTarget, which isn't going to be what you expect in this case (usually it is, thought).
You have to understand the difference between e.target and e.currentTarget. e.target is the actual target of the event, independent of how its bubbling or capturing. e.currentTarget on the other hand is the element which is presently handling the event. So in your case e.currentTarget is an instance of ClassA (the instance that the event handler is actually attached to) and e.target is an instance of ClassB (the instance that the event was dispatched on). During the event lifecycle, e.currentTarget will change as the event moves around, but e.target should always be the same.
In this case, you want to reference the actual target of the event, not the element that is currently processing the event. So you need to change your handler to:
function hello(e:Event)
{
trace(e.target.abc);
}
And then it should work. You can find a working example here that encapsulates the changes I've described.
If these classes weren't DisplayObjects then you would have to take a different approach -- either by using a signal pattern or to manually listen for an retrigger the event inside ClassA.
First of all you are adding an event listener to classA but your classA init method is asking classB to dispatch an event and this is the reason why your code does not get executed. If you want to catch the hello event you should be doing something like
public function init()
{
this.dispatchEvent( new Event("hello"));
}
Or you should be registering the listener on classB (which is not in scope so no code suggestion).
In ActionScript the best approach to transfer information is to use custom events so my suggestion is to re evaluate your decision on custom events.

Why isn't dispatchEvent firing?

okay so here is my problem in my main project I'm trying to fire an event using dispatchEvent I've made a simple test class to test this and yet it still isn't working...
Here is the test class
package
{
import flash.display.Sprite;
import flash.events.Event;
public class Main extends Sprite
{
public function Main() {
stage.addEventListener("pOver", rake);
dispatchEvent(new Event("pOver"));
}
public function rake(e:Event):void {
trace("working");
}
}
Why isn't it firing? or why is the listener not capturing that event?
You are dispatching the event on the Main, which is a child of Stage. If you want to specifically dispatch an event on the Stage then use:
stage.dispatchEvent(new Event("pOver"));
Now you may be wondering, "If it's a child, then my event handler should still be getting triggered!"
Well, yes and no.
Lets take a look at a simple diagram of the event life-cycle:
First, the event that you are dispatching is not a bubbling Event. Examining the Event constructor, its signature looks like this:
public function Event(type:String, bubbles:Boolean = false, cancelable:Boolean = false)
Notice that the second argument is by default false, which means this event does not perform the bubbling part of the event life-cycle.
Second, you have attached the event dispatcher on the bubbling side of the event life-cycle. If you look at the signature for .addEventListener() it looks like this:
public function addEventListener(type:String, listener:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false):void
Notice the third argument. Which is by default false again. This means that you are attaching on the "bubbling" side of the event.
This means that this event is getting to the targeted element, the instance of Main, and then stopping and not going anywhere else.
TL;DR: So what does that all mean?
So to trigger your event handler, and not change where the event gets dispatched, you need to change your event that you are triggering to:
this.dispatchEvent(new Event("pOver", true)); // this event will bubble
Then your event handler, since it is a child, will be triggered by this event.
Conversely, I think that non-bubbling events will also progress through the capturing side of the event life-cycle so you could also change your event listener to attach to that side of the event as well.
stage.addEventListener("pOver", rake, true); // attach to a capturing side
I believe that event will always flow through the capturing phase, even if they are marked as not bubbling. But I could be wrong on that. I just can't remember if "non-bubbling" events skip both capturing and bubbling phases and just trigger the target event phase and I don't have time to check it right now.
Edit
So, I wrote up a quick test on wonderfl:
package {
import flash.events.Event;
import flash.display.Sprite;
public class FlashTest extends Sprite {
private var debug:TextField;
public function FlashTest() {
stage.addEventListener("Foo", bubbleFooHandler);
stage.addEventListener("Foo", captureFooHandler, true);
trace("Ready");
trace("---------------");
trace("Trying a non-bubbling event");
this.dispatchEvent(new Event("Foo"));
trace("---------------");
trace("Trying a bubbling event");
this.dispatchEvent(new Event("Foo", true));
}
private function captureFooHandler(e:Event):void {
trace("Triggered \"Foo\" from capturing phase\n");
}
private function bubbleFooHandler(e:Event):void {
trace("Triggered \"Foo\" from bubbling phase");
}
}
}
The output from this is
Ready
---------------
Trying a non-bubbling event
Triggered "Foo" from capturing phase
---------------
Trying a bubbling event
Triggered "Foo" from capturing phase
Triggered "Foo" from bubbling phase
Notice that events will always progress through the capturing phase. However, if they are not marked as a "bubbling event", see before, they will descent through the tree they stop when they arrive at target of the event (the EventDispatcher the event was dispatched on).
Bubbling events, on the other hand, will turn around and head back up the tree.
Hopefully, this clears things up.
First of all, you are listening to stage events. This means that as long as stage is not dispatching any Events you will not get any callbacks.
try
stage.dispatchEvent(new Event("pOver"));

I need help bubbling an event in AS3

I want to have the parent of my class handle the event first, then I want to have the child handle the event. Is there a way to explicitly bubble the event up? I want to do something like this:
...
this.addEventListener(MouseEvent.CLICK, characterClicked);
...
private function characterClicked(e:Event):void{
// pass event to parent to be handled first
...
}
Is this possible, and if so how?
There are three "phases" of an event; Capture, At target and Bubble. They occur in this order, meaning that if you set an event listener to be in the capture phase it will always fire before one set regularly (which would mean either at target or bubble).
Like so:
// in parent, third argument is "use capture"
child.addEventListener(MouseEvent.CLICK, handleClickInParent, true);
// in child, add listener as usual
addEventListener(MouseEvent.CLICK, handleClick);
Now, your parent event listener will always fire first!
I figured out a way to do this. Is seems hackish, but it works. If there is a better way of doing this please let me know.
...
this.addEventListener(MouseEvent.CLICK, characterClicked);
...
private function characterClicked(e:Event):void{
// pass event to parent to be handled first
this.removeEventListener(MouseEvent.CLICK, characterClicked); //prevent infinite loop
dispatchEvent(e); // send event to parent object
this.addEventListener(MouseEvent.CLICK, characterClicked);
e.stopImmediatePropagation();
...
}
If you were to handle the listener in the parent instead of the child it might be easier. Then you could just pass the event to the child when you're done:
// inside parent class:
childObj.addEventListener(MouseEvent.CLICK, onCharacterClicked);
private function onCharacterClicked(e:Event):void {
// do parent stuff first
// ...
// then pass event to child
childObj.onCharacterClicked(e);
}

as3 - dispatchEvent from a parent swf to a child swf

I have one main "parent" swf that loads several other swfs. If something happens in the main swf I need to tell one of the child swfs about it.
This seems to work well the other way around. Any of the children can simply dispatchEvent(), and I can set up the main swf to listen for the event. However, I can't get the child swf to catch any events dispatched by the parent. How is it done?
OK, so if you know most of this already, my apologies... but it seems a pretty common issue and isn't immediately obvious.
In AS3 events dispatched by objects on the display list can be listened for as they bubble up the display list hierarchy without needing to specify the originating object. (Assuming of course that the event has its bubbling property set to true). Hence the Document Class (the old concept of _root) can respond to mouse clicks from any display object, no matter how deeply nested, with addEventListener(MouseEvent.CLICK, _onMouseClick)
In any other situation - e.g. bubbling is set to false, the broadcaster is not an InteractiveObject on the display list or, (as in your case) the listener is lower than the broadcaster in the display list hierarchy - the object broadcasting the event must be specifically listened to: fooInstance.addEventListener(Event.BAR, _bazFunc) as opposed to just addEventListener(Event.BAR, _bazFunc)
Basically you need to pass a reference to the parent object to your child swf so that it can then attach event handlers to it.
One method is to dispatch an event from the child to the parent class via the display list (once the child has loaded and fully initialised). The parent uses the event.target property of this event to reference the child and set a parentClass variable on it. This can then be used to attach listeners:
package {
class ChildClass
{
private var __parentClass:ParentClass;
// EventID to listen for in ParentClass
public static const INITIALISED:String = "childInitialised";
// Constructor
function ChildClass()
{
// Do initialising stuff, then call:
_onInitialised();
}
private function _onInitialised():void
{
// Dispatch event via display hierarchy
// ParentClass instance listens for generic events of this type
// e.g. in ParentClass:
// addEventListener(ChildClass.INITIALISED, _onChildInitialised);
// function _onChildInitialised(event:Event):void {
// event.target.parentClass = this;
// }
// #see mutator method "set parentClass" below
dispatchEvent(new Event(ChildClass.INITIALISED, true);
}
public function set parentClass(value:ParentClass):void
{
__parentClass = value;
// Listen for the events you need to respond to
__parentClass.addEventListener(ParentClass.FOO, _onParentFoo, false, 0, true);
__parentClass.addEventListener(ParentClass.BAR, _onParentBar, false, 0, true);
}
private function _onParentFoo(event:Event):void
{
...
}
}
}
Dispatching a custom ChildSWFEvent - i.e. instead of using a class-defined constant as above - will make this a more flexible solution since the ParentClass instance can listen for a common ChildSWFEvent.INITIALISED event broadcast by any child swf with contextually useful information passed as an additional parameter.
When you load a child swf (Main.swf) in an parent swf (Index.swf), keep a reference in a field variable or class variable
fldMain = BulkLoader.getLoader("Index").getContent("Main.swf") as DisplayObject;
this.addChild(fldMain);
(i'm using BulkLoader to load any content)
It's a good practice to wait with dispatching events until the child is added (ADDED_TO_STAGE event)
When I want to dispatch an event to my child I just say:
fldMain.dispatchEvent(new CustomEvent(CustomEvent.INIT_CHILD,data));
What I did was add a listener on the parent for changes after the child is added to the stage. Now anytime you want to have children deal with updating themselves, just dispatch the Event.CHANGE from the parent. Bubbling can be true or false.
I would think that if you attach the child;s listener to the Stage (stage.addEventListener...) any object that throws a Event.CHANGE could trigger the child to handle the event.
package
{
import flash.display.*
import flash.events.*
public class Child extends Sprite
{
public function Child():void
{
this.addEventListener(Event.ADDED_TO_STAGE, addedToStageHandler, false, 0, true);
}
private function addedToStageHandler(pEvent:Event):void
{
trace("CHILDADDED");
this.removeEventListener(Event.ADDED_TO_STAGE, addedToStageHandler);
this.parent.addEventListener(Event.CHANGE, parent_changeEventHandler, false, 0, true);
}
private function parent_changeEventHandler(pEvent:Event):void
{
trace("PARENT_CHANGED");
}
}
}
IMO, it is almost never appropriate for a child to know or care about its parent. On the other hand, parents nearly always know everything about their children (since they have a direct reference to them). So, in this case, I would simply create a property or method on the child Class that could be set/called by the parent when needed.
This has the advantage of better performance, since creation and handling of an Event is more expensive than simply calling a method or setting a value.
HTH;
Amy
I would listen in each child for
Event.ADDED_TO_STAGE
once it has been added to the stage, you can then reference/listen to the stage for events.
Example
//Child
if(stage) _init(); //Already added
else addEventListener(Event.ADDED_TO_STAGE, _init); //waiting to be added
private function _init(e:Event = null):void
{
stage.addEventListener(CustomEvent.CHANGED, _onStageChanged);
{
I didn't test this, but as long as you dispatch the events from the stage, it should work.
//stage
dispatchEvent(new CustomEvent(CustomEvent.CHANGED));
if you setup your custom event class correctly you can also pass information accross.