Event bubbling weirdness - apache-flex

I'm seeing some weirdness in event bubbling, which suggests I don't really understand how this is supposed to work.
I have a component which extends DataGroup, and it's item renderer dispatches an event (which bubbles).
// MyRenderer.mxml
<s:ItemRenderer>
<s:Button click='dispatchEvent(new Event('customEvent',true))' />
</s:ItemRenderer>
The DataGroup adds a listener for the event to itself.
// MyDataGroup.mxml
<s:DataGroup itemRenderer="MyRenderer" creationComplete='onCreationComplete()'>
<fx:Metadata>
[Event(name='customEvent',type='flash.events.Event')]
</fx:Metadata>
<fx:Script>
private function onCreationComplete():void
{
addEventListener('customEvent',onCustomEvent);
}
private function onCustomEvent(event:Event):void
{
}
</fx:Script>
</s:DataGroup>
The Parent of the datagroup is also adding a listener for the event.
// MyComponent.mxml
<s:Group>
<MyDataGroup customEvent='onCustomEventHandler()' />
</s:Group>
I'd have expected that the handler registered in MyDataGroup should catch the event first, then the handler in MyComponent.
However, I'm seeing the reverse - ie., caught in MyComponent, then in MyDataGroup. When caught, event.phase == EventPhase.BUBBLING.
What's going on here? Why am I seeing this behaviour?
I'm using Flex 4.0.

I'm pretty sure the problem is that both of your event listeners are listening to the same instance on the MyDataGroup component.
If you add the an event listener to MyComponent instead of MyDataGroup, you'll get expected behavior:
<s:Group xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx" width="400" height="300"
initialize="group1_initializeHandler(event)">
<fx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
import mx.events.FlexEvent;
public function onCustomEventHandler(event:Event):void{
trace('my Component handler');
}
protected function group1_initializeHandler(event:FlexEvent):void
{
// add the event listener to 'this'
this.addEventListener('customEvent',onCustomEventHandler);
}
]]>
</fx:Script>
<martyPitt:MyDataGroup id="dataGroup" />
<!-- The event listener here was listening on the myDataGroup instance, not on the MyComponent instance customEvent="onCustomEventHandler(event)" -->
</s:Group>
I suspect the event listeners--even though not in the same component--were firing based on the order they were added. You'd have to examine the generated ActionScript with the compiler argument '-keep' to figure that out specifically. I suspect your in-line listener (MyComponent) is added in the MyDataGroup constructor. Since the other listener is added in the MyDataGroup a creationComplete handler, the MyComponent listener fires first.

Strange behaviour indeed.
Actually, when you add an event listener inline (in your example :
<MyDataGroup
customEvent='onCustomEventHandler()'
/>
), the framework add an event listener on the capture phase. So it's an expected behaviour it enters in your parent handler first. What I don't undestand is that the eventPhase is equals to EventPhase.BUBBLE_PHASE although it should display EventPhase.CAPTURE_PHASE.

Related

flex event triggered on component re-render?

What's the call analogous to creationComplete that happens every time a component is rendered? I want to rerun a function every time the component gets rendered (it has to make an HTTP request, but the url it calls changes each time) and I can't figure out how to do it.
For context:
<s:Group xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx" width="100%" height="100%"
creationComplete="loadGallery()">
private function loadGallery():void {
var src:String = "http://localhost:3000/car_type/" + UserPreference.getInstance().carType.toString() + ".xml";
Alert.show(src);
httpService.url = src;
httpService.send();
}
Instead of creationComplete use updateComplete.
I would override the updateDisplayList method for the component and add a call to your loadGallery method there.
Hope that helps.
http://docs.huihoo.com/flex/4/flash/display/DisplayObject.html#event:render maybe?

ButtonBarButton custom rollover/out event

What is the best way to add a custom rollover and rollout to a buttonbarbutton? I tried adding the eventlisteners in the custom skin I am using but the mouseevents do not get fired.
Any ideas?
Thanks!
Ok, I won't say this is the best way to do it by any means, but I do have a solution.
In the ButtonBar Skin I made I add a listener for the creationComplete of the DataGroup and then add a listener to each of the elements inside it.
<fx:Script>
<![CDATA[
import com.mattel.styleguide.events.DesignDropDownEvent;
import mx.controls.Alert;
private function init():void
{
for (var i:int = 0; i < dataGroup.numElements; i++)
{
dataGroup.getElementAt(i).addEventListener(MouseEvent.ROLL_OVER, onRollOver);
dataGroup.getElementAt(i).addEventListener(MouseEvent.ROLL_OUT, onRollOut);
}
}
private function onRollOver(e:MouseEvent):void
{
//dispatch custom event
}
private function onRollOut(e:MouseEvent):void
{
//dispatch custom event
}
]]>
</fx:Script>
<s:DataGroup id="dataGroup" width="100%" height="100%" creationComplete="init()">
<s:layout>
<s:ButtonBarHorizontalLayout gap="-1"/>
</s:layout>
</s:DataGroup>
This allows the rollover/rollout events to get hit for each button, I can then check the e.currentTarget and see which button was interacted with and dispatch a custom event inside the functions which bubble up to my view.
I was hoping the was a cleaner way to do it and that could very well be the truth and I just don't know how to do it. Any one else have a different idea?
Thanks!

A question on capturing the button click event in ANOTHER mxml file

this seems to be an interesting question to be discovered in Flex.
I registered a very simple button event listener in A.mxml:
<mx:Script><![CDATA[
import mx.controls.Alert;
public function Handler():void
{
Alert.show('click event occured');
}
]]></mx:Script>
<mx:Button label="{resourceManager.getString('resources', 'button.startLab')}"
id="nextStepButton" click="Handler()" />
It works fine when clicking the button everytime.
Now I want to have something interesting,that is,I want to capture this buttonClick Event in another mxml file,say B.mxml and do something in B.mxml instead of A.
I am bit stuck on this,hope you could give me some hint and help,thanks a lot.
There are a number of approaches to this problem. The simplest (and least object-oriented) is to have A be aware of B, or vice versa. In that case you can just add a listener. In B you could say a.nextStepButton.addEventListener(MouseEvent.CLICK, myHandler), or in A you could do this.nextStepButton.addEventListener(MouseEvent.CLICK, b.myHandler). (When one component is instantiated, you have to set a reference to it on the other component.)
One step better would be to dispatch a custom event that bubbles, with one of the components still aware of the other. In B: a.addEventListener(CustomNavigationEvent.NEXT_CLICK, myHandler), or in A: b.addEventListener(CustomNavigationEvent.NEXT_CLICK, myHandler).
Taking it further, you could just let the event bubble to the top (the SystemManager) and add your listener to the SystemManager. This way B is not aware of A at all. In B: this.systemManager.addEventListener(CustomNavigationEvent.NEXT_CLICK, myHandler).
Taking it even further, you can implement your own version of an event broadcaster, which is just a third object that is accessible by any component, usually implemented as a singleton, that takes listener registrations and accepts event dispatches, then broadcasts that event to registered listeners.
Hope that helps.
EDIT: Here's some code for doing it the first way:
In A.mxml:
<?xml version="1.0" encoding="utf-8"?>
<s:Group xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx" creationComplete="onCreationComplete(event)">
<fx:Script>
<![CDATA[
import mx.events.FlexEvent;
public var b:B;
private function onCreationComplete(e:FlexEvent):void {
// Note that you have to have a public onClick handler in B
this.myButton.addEventListener(MouseEvent.CLICK, b.onClick);
}
]]>
</fx:Script>
<s:Button id="myButton"/>
</s:Group>
You need to make A aware of B in the container that declares instances of both A and B:
MXML:
<mypackage:A id="aComponent" b="bComponent"/>
<mypackage:B id="bComponent"/>
ActionScript equivalent:
var aComponent:A = new A();
var bComponent:B = new B();
aComponent.b = bComponent;

flex custom events bubbling

Dear Richard Szalay,
i go through your answers regarding bubbling, i want explore bubbling more.
Please see my sample below
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" xmlns:view="com.view.*" >
<mx:Script>
<![CDATA[
import com.events.ImgLoaded;
private function loadedd(evt:ImgLoaded):void{
trace("test")
evt.stopImmediatePropagation();
}
private function panelClickHandler(evt:Event):void{
trace("panel");
}
]]>
</mx:Script>
<mx:VBox>
<mx:Panel click="panelClickHandler(event)">
<view:Load imgLoad="loadedd(event)"/>
</mx:Panel>
</mx:VBox>
</mx:Application>
In my custom event class i set bubbling=true, cancelable=true
I can understand from previous answer that bubbling only affects UI components; events fired from custom classes will not bubble, even if the bubbles argument is set to true.
My question is how can i prevent panelClickHandler function got fired when i click button in the "Load" (custom component)??
please explain bubbling with good example ( like to have with custom event classes)?
I assume your first language isn't english and at any rate I am not sure I fully understand you, but what I think you are asking for is how to allow for a click in the view:load from firing the click handler on the panel.
What you need is to set up an event listener for a click on the view:load component, and stopPropagation from there. That will prevent the click handler on the panel from firing. Example:
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" xmlns:view="com.view.*" >
<mx:Script>
<![CDATA[
import com.events.ImgLoaded;
private function loadedd(evt:ImgLoaded):void{
trace("test")
evt.stopImmediatePropagation();
}
private function panelClickHandler(evt:Event):void{
trace("panel");
}
private function load_clickHandler ( e:MouseEvent ) : void
{
e.stopPropagation;
}
]]>
</mx:Script>
<mx:VBox>
<mx:Panel click="panelClickHandler(event)">
<view:Load imgLoad="loadedd(event)" click="load_clickHandler(event)"/>
</mx:Panel>
</mx:VBox>
</mx:Application>

Why wouldn't a flex remoteobject be able to work within a custom component?

Please enlighten this flex noob. I have a remoteobject within my main.mxml. I can call a function on the service from an init() function on my main.mxml, and my java debugger triggers a breakpoint. When I move the remoteobject declaration and function call into a custom component (that is declared within main.mxml), the remote function on java-side no longer gets called, no breakpoints triggered, no errors, silence.
How could this be? No spelling errors, or anything like that. What can I do to figure it out?
mxml code:
&#060 mx:RemoteObject id="myService"
destination="remoteService"
endpoint="${Application.application.home}/messagebroker/amf" &#062
&#060 /mx:RemoteObject &#062
function call is just 'myService.getlist();'
when I move it to a custom component, I import mx.core.Application; so the compiler doesn't yell
my child component: child.mxml
<mx:Panel xmlns:mx="http://www.adobe.com/2006/mxml" creationComplete="init()" >
<mx:Script>
<![CDATA[
import mx.core.Application;
public function init():void {
helloWorld.sayHello();
}
]]>
</mx:Script>
<mx:RemoteObject id="helloWorld" destination="helloService" endpoint="$(Application.application.home}/messagebroker/amf" />
<mx:Label text="{helloWorld.sayHello.lastResult}" />
</mx:Panel>
my main.mxml:
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" creationComplete="init()" xmlns:test="main.flex.*" >
<mx:Script>
<![CDATA[
[Bindable]
public var home:String;
[Bindable]
public var uName:String;
public function init():void {
//passed in by wrapper html
home = Application.application.parameters.appHome;
uName = Application.application.parameters.uName;
}
]]>
</mx:Script>
<test:child />
</mx:Application>
The child components are calling creationComplete before the parent (so home is null). A solution is to throw an event (like InitDataCompleted) from the parent after you read the data, and in the child components listen for this event (so don't rely on creationcomplete in the child).
However more important than that is how can you diagnose in future this kind of problems. A simple tool like a proxy (eg Charles) should help.
For your endpoint value you've got
endpoint="$(Application.application.home}/messagebroker/amf"
Why are you using $( before Application.application... This should be a { as in:
endpoint="{Application.application.home}/messagebroker/amf"

Resources