The direct children of a ViewStack receive a FlexEvent.SHOW once the respective child view is shown. Unfortunately, the event information is lost at deeper child levels so that the grandchildren of the ViewStack do not receive the SHOW event.
Which approach would be advisable to tap into the SHOW events received by a ViewStack's direct children from a grandchild or grand-grand-child?
Using parent.parent.[...].addEventListener(FlexEvent.SHOW, [...]) would be possible, of course, but is kind of ugly as it will break as soon as the number of hierarchy levels between the two components changes.
Are there other events that are better suited?
The background of my question is that I would like to reload the grandchildren's content when it becomes visible again.
You could listen for the ADDED_TO_STAGE event in the grandchild view and find out if you're part of a view stack. If yes, then just listen to the view stack child's show/hide events.
... addedToStage="setMeUpForViewStack()" ...
Which is:
private var vsView:UIComponent = null;
private function setMeUpForViewStack():void
{
if (vsView) {
vsView.removeEventListener("show", vsViewShowHideHandler);
vsView.removeEventListener("hide", vsViewShowHideHandler);
vsView = null;
}
var obj:DisplayObject = this;
while (obj.parent != obj) {
if (obj.parent is ViewStack) {
vsView = obj;
break;
}
obj = obj.parent;
}
if (vsView) {
vsView.addEventListener("show", vsViewShowHideHandler, false, 0, true);
vsView.addEventListener("hide", vsViewShowHideHandler, false, 0, true);
}
}
And in your vsViewShowHideHandler you would reload the content (if the view is visible).
Basically this frees your from worrying about the level of nesting. It doesn't work with multiple nested view stacks though.
In the REMOVED_FROM_STAGE event handler you would forget vsView.
Although burying down into the viewstack would work I agree this is ugly and potentially can lead to headaches as soon as something changes.
A different approach could be to implement a global event dispatcher.
By having a static event dispatcher in a class, the grandchildren could subscribe to events from the static dispatcher from anwywhere within the application.
When the parent hears the FlexEvent.Show the handler could dispatch a custom event using the global dispatcher?
Related
I searched quite a bit to find out the correct way to remove event listeners in Cesium. I believe the confusion I have is around whether to treat Cesium events as regular dom events (due to a lack of knowledge about events in general in javascript). I am creating a screen space event like below:
var handler = new Cesium.ScreenSpaceEventHandler(canvas);
handler.setInputAction(function (movement) {
var picked = scene.pick(movement.endPosition);
if (Cesium.defined(picked) && picked.id === someEntity) {
labelEntity.position = someEntity.position;
labelEntity.label.show = true;
} else {
labelEntity.label.show = false;
}
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
My question is, how can I remove this event? Is handler.destroy() removes all the event listeners associated with handler, or do I specifically have to remove event listeners by pointing to the cesium map dom element and calling removeEventListener on it? If that's the case, what parameters should be passed to removeEventListener?
The parameters for removeInputAction are just the type and optionally the modifer, and it looks like you're not using the modifier (SHIFT key, ALT key etc.)
So for the code you posted above, the removal would be:
handler.removeInputAction(Cesium.ScreenSpaceEventType.MOUSE_MOVE);
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);
}
I have a UIComponent that sits on top of a grid visually, which is also a UIComponent. The grid and the UIComponent that sits on top are on different branches. The gird has children which are Sprites. When the MousEvent.CLICK is dispatched it goes to the UIComponent on top. What I want it to do is go the element of the grid underneath. Is this possible?
Yes you can,
if your element id is "ui_component"
then in ActionScript you should add two properties
ui_component.mouseEnabled = true;
ui_component.mouseChildren = true;
This will tell your component to pass click event on MovieClip behind it.
I hope this helps.
What I had to do was just make the UIComponent on top top.mousEnabled = false
Another option, if your implementation does not allow for you to disable the mouse events for the top UIComponent, is to manually redispatch the event.
Here's a rather generic example. Attach the following handler for a MouseEvent.CLICK to either the container that holds the other UIComponents or the one on the UIComponent on top:
private function propagateClickEvent(event:MouseEvent):void
{
// find all objects under the click location
var uicOver:Array = getObjectsUnderPoint(new Point(event.stageX, event.stageY));
// filter out what you don't want; in this case, I'm keeping only UIComponents
uicOver = uicOver.filter(isUIComponent);
// resolve skins to parent components
uicOver = uicOver.map(resolveSkinsToParents);
// remove the current item (otherwise this function will exec twice)
if (event.currentTarget != event.target) { // otherwise let the following line do the removal
uicOver.splice(uicOver.indexOf(event.currentTarget),1);
}
// remove the dispatching item (don't want to click it twice)
uicOver.splice(uicOver.indexOf(event.target),1);
// dispatch a click for everything else
for each (var uic:UIComponent in uicOver) {
uic.dispatchEvent(new MouseEvent(MouseEvent.CLICK, false)); // no bubbling!
}
}
// test if array item is a UIComponent
private function isUIComponent(item:*, index:int, arr:Array):Boolean {
return item is UIComponent;
}
// resolve Skins to parent components
private function resolveSkinsToParents(item:*, index:int, arr:Array):* {
if (item is Skin) {
return item.parent;
}
return item;
}
This code would work as follows in MXML:
<s:Group click="propagateClickEvent(event)">
<s:Button click="trace('button1')"/>
<s:Button click="trace('button2')"/>
<s:Button click="trace('button3')"/>
</s:Group>
<!-- trace output on click: "button3", "button1", "button2" -->
This is very generic. I'd recommend using something more specific than UIComponent, otherwise it would probably dispatch more clicks than are necessary. That all depends on what you're actually doing.
Also, the MXML example is rather poor, since, if you knew all your components beforehand you could definitely handle it differently. However, if you had a bunch of components whose positions were determined at runtime, this would approach would make more sense.
I want to run some code whenever a DisplayObject is added as a child to a DisplayObjectContainer.
Or to put in other words, to catch the addedToStage event of all DisplayObjects, even ones I don't know about.
Is it possible? and if not, any ideas on how to do something similar?
An 'added' event is dispatched whenever a child display object is added to the display list via addChild() or addChildAt(). In the DisplayObjectContainer class add the listener:
addEventListener(Event.ADDED, onAdded);
and the handler:
private function onAdded(e:Event):void
{
trace('number of children is now ' + numChildren);
}
Using Event.ADDED_TO_STAGE on stage Object and setting useCapture to true.
More info on event here
Example:
function onAdded(e:Event):void{
trace(e.target.toString()); //use target to get the Object added
}
stage.addEventListener(Event.ADDED_TO_STAGE, onAdded, true); // set capture to true
I don't know if there is a built in way to do this.
Alternatives include the obvious,
private var _num_children:Number = 0;
addEventListener(Event.ENTER_FRAME, _checkChildren, false, 0, true);
private function _checkChildren($evt:Event):void {
if (this.numChildren != _num_children) {
_num_children = this.numChildren;
// There was a child (or more) added in the last frame execution
}
}
However, this seems like a more elegant solution...
public function _addChild($do:DisplayObject) {
$do .addEventListener(Event.ADDED_TO_STAGE, _childAdded);
addChild($do );
}
private function _childAdded($evt:Event) {
// do whatever with $evt.target
}
The difference here, is the _childAdded will get fired for each and every child added via _addChild method. This means if you are doing some costly code execution you will be doing it once for each child instance.
If you use the first method, you are only calling the method once per frame, and if 10 images are added on a single frame, then it will only run once.
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.