-
Get a monthly update on best practices for delivering successful software.
I recently had an issue in flex where I needed the click event for a component in an ItemRenderer to not bubble up to the parent list. Since the click event was being dispatched by Flex SDK code, I could not directly do this by passing false into the event's constructor. I had hoped to find a property I could set on the object, something like "clickBubbles", which I could set which would accomplish this.
Either that property doesn't exist or I could not find it. My solution was to basically hijack the event and send an impersonator in its place.
What I ended up doing was writing an event listener function which would stop propagation on the event. It would then dispatch another event with the same properties as the original, except it would set bubbles to false. This will prevent the event from bubbling up to parent components, but still allow event listeners to be added to the component itself.
The following is the function I wrote:
private function removeMouseEventBubble(event:MouseEvent):void { // if the event already does not bubble, we don't // need to do anything. // also, this will prevent any infinite loops from occurring if (event.bubbles) { // stop the event from being handled by other code event.stopImmediatePropagation(); // dispatch a new event which does not bubble event.target.dispatchEvent( new MouseEvent( event.type, false, event.cancelable, event.localX, event.localY, event.relatedObject, event.ctrlKey, event.altKey, event.shiftKey, event.buttonDown, event.delta ) ); } }
Now, all you need to do is set the click handler on the component:
<components:Foo click="removeMouseEventBubble(event)"/>
In order to make sure that this code prevented the original event from propagating and to ensure that it dispatched a similar event which does not bubble, I wrote the following FlexUnit test. N.B. This code is adapted from an actual test, I wouldn't recommend having the code under test defined in the test case itself, nor do I like the fact that the removeMouseEventBubble function is being added in the test.
public function testShouldRemoveBubbleOnClickButAllowEventListenersToStillFire():void { var component:UIComponent = new UIComponent(); component.addEventListener( MouseEvent.CLICK, removeMouseEventBubble ); // if we cannot add other event listeners, then the // async function will not get fired and the test will fail. component.addEventListener(MouseEvent.CLICK, addAsync( function(event:MouseEvent):void { assertFalse(event.bubbles); // assert that the event was translated properly assertEquals(MouseEvent.CLICK, event.type); assertTrue(event.cancelable); assertEquals(8, event.localX); assertEquals(9, event.localY); assertEquals(renderer, event.relatedObject); assertTrue(event.ctrlKey); assertTrue(event.altKey); assertTrue(event.shiftKey); assertTrue(event.buttonDown); assertEquals(5, event.delta); }, 1000) ); component.dispatchEvent( new MouseEvent( MouseEvent.CLICK, true, true, 8, 9, renderer, true, true, true, true, 5 ) ); }
I then wrote a test to verify that the event did not bubble up to the parent:
public function testShouldNotBubbleClickEventToParent():void { var parent:UIComponent = new UIComponent(); parent.addEventListener(MouseEvent.CLICK, function(event:MouseEvent):void { // this won't cause the test to fail per say, // but it will cause an exception to be thrown // and this will show up when executing the // test. This will fail the build, especially // if run on a CI server like hudson or cruise. fail('did not expect event to bubble'); } ); var child:UIComponent = new UIComponent(); child.addEventListener( MouseEvent.CLICK, removeMouseEventBubble ); parent.addChild(child); child.dispatchEvent(new MouseEvent(MouseEvent.CLICK)); }
This solution works, but I was hoping there was something more elegant and less hacky which accomplished the same thing.
Related posts:
Hey Anthony,
thanks a lot for this code! I had the exact same problem and now it works.
Greets from Germany
Comment by Sebastian, Saturday, August 22, 2009 @ 2:55 am