Flex: Expose component's existing events with new name
I created a flex component that hosts several built in (mx) components such as a listbox and combo box. My component relies on external data, and I need to expose events such as ComboBox.enter and List.click to get certain pieces of data.
I was wondering if there is any easy way to do this without having to create my own custom event handlers. I'm simply trying to expose these events with different names so that when my component is used, I can do things such as:
<?xml version="1.0" encoding="utf-8"?>
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml" width="100%" h开发者_如何学运维eight="100%" xmlns:com="com.*">
<mx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
[Bindable]
public var dp:ArrayCollection = new ArrayCollection(["Apple","B","C","D","E","F","G","H","I","J","K"]);
]]>
</mx:Script>
<mx:ComboBox dataProvider="{dp}"/>
<mx:List/>
</mx:Canvas>
And I want to be able to use it as follows:
<com:MyComponent listBoxChanged="getExternalData(event)" comboBoxClick="comboBoxClicked(event)"/>
I suppose what I want to do is propagate events in a component to a parent component with the event being renamed.
You can do this with the least amount of code by creating a class to redirect the events. What you want is a class that accepts a source event dispatcher, source event name, target event name, and target event dispatcher. Say you call this EventRedispatcher. Here's a complete example.
EventRedispatcherTest.mxml:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application
xmlns:mx="http://www.adobe.com/2006/mxml"
xmlns:local="*"
layout="horizontal"
width="100%"
height="100%">
<mx:Script>
<![CDATA[
import mx.events.ListEvent;
private function doLog(event:Event):void {
var extraInfo:String = "";
var listEvent:ListEvent = event as ListEvent;
if (listEvent != null && listEvent.itemRenderer != null) {
extraInfo = String(listEvent.itemRenderer.data);
}
var mouseEvent:MouseEvent = event as MouseEvent;
if (mouseEvent != null) {
extraInfo = mouseEvent.stageX + "," + mouseEvent.stageY;
}
log.text += event.target.id + "." + event.type + ":" + extraInfo + "\n";
}
]]>
</mx:Script>
<mx:TextArea width="300" height="100%" id="log" />
<local:EventRedispatcherComponent
id="component1"
listboxChange="doLog(event)"
comboboxChange="doLog(event)"
buttonClick="doLog(event)"
/>
<local:EventRedispatcherComponent
id="component2"
listboxChange="doLog(event)"
comboboxChange="doLog(event)"
buttonClick="doLog(event)"
/>
</mx:Application>
EventRedispatcherComponent.mxml:
<?xml version="1.0" encoding="utf-8"?>
<mx:HBox
xmlns:mx="http://www.adobe.com/2006/mxml"
creationComplete="init()"
borderStyle="solid"
borderColor="#FF0000"
width="300"
height="200">
<mx:Metadata>
[Event(name="comboboxChange", type="mx.events.ListEvent")]
[Event(name="listboxChange", type="mx.events.ListEvent")]
[Event(name="buttonClick", type="flash.events.MouseEvent")]
</mx:Metadata>
<mx:Script>
<![CDATA[
private function init():void
{
// TODO: Create EventRedispatcher class :-)
new EventRedispatcher(combobox, "change", this, "comboboxChange");
new EventRedispatcher(listbox, "change", this, "listboxChange");
new EventRedispatcher(button, "click", this, "buttonClick");
}
]]>
</mx:Script>
<mx:ComboBox id="combobox" dataProvider="{[1, 2, 3]}" />
<mx:List id="listbox" dataProvider="{[1, 2, 3]}" />
<mx:Button id="button" label="Text" />
</mx:HBox>
EventRedispatcher.as
package
{
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.utils.describeType;
import flash.utils.getDefinitionByName;
import flash.utils.getQualifiedClassName;
public class EventRedispatcher
{
private var targetDispatcher:EventDispatcher;
private var targetName:String;
private static var propertiesByEventType:Object = new Object();
public function EventRedispatcher(sourceDispatcher:EventDispatcher, sourceName:String, targetDispatcher:EventDispatcher, targetName:String)
{
this.targetDispatcher = targetDispatcher;
this.targetName = targetName;
sourceDispatcher.addEventListener(sourceName, redispatch);
}
private function redispatch(event:Event):void {
var newEvent:Event = copyEvent(event);
targetDispatcher.dispatchEvent(newEvent);
}
private function copyEvent(event:Event):Event {
var className:String = getQualifiedClassName(event);
var newEvent:Event = new (getDefinitionByName(className))(targetName);
var properties:Array = getPropertiesForClass(event, className);
for each(var propertyName:String in properties) {
newEvent[propertyName] = event[propertyName];
}
return newEvent;
}
private function getPropertiesForClass(event:Event, className:String):Array {
var properties:Array = propertiesByEventType[className];
if (properties != null) {
return properties;
}
var description:XML = describeType(event);
properties = new Array();
for each(var accessor:XML in description.accessor.(@access == 'readwrite')) {
properties.push(accessor.@name);
}
for each(var variable:XML in description.variable) {
properties.push(variable.@name);
}
propertiesByEventType[className] = properties;
return properties;
}
}
}
This should'nt be too hard.
First you need to decide if your events (listBoxChanged, comboBoxClick) need to store data with them. If it's the case, then create 2 custom events (one of each) with a variable that stores the data to pass.
In MyComponent, you need to add listeners to ComboBox click event and List change event. In your method handlers , dispatch an event of type you just created before.
Don't forget to add 2 Event metatags on top of your component to get an autocompletion on listBoxChanged & comboBoxClick
You can do this through the component metadata:
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml" width="100%" height="100%" xmlns:com="com.*">
<mx:Metadata>
[Event(name="myListChange", type="mx.event.ListChange")]
</mx:Metadata>
<mx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
[Bindable]
public var dp:ArrayCollection = new ArrayCollection(["Apple","B","C","D","E","F","G","H","I","J","K"]);
private function onListEventChange(event:ListEvent):void
{
//create your own event if you need to pass data with the event.
dispatchEvent(new Event("myListChange"));
}
]]>
</mx:Script>
<mx:ComboBox dataProvider="{dp}"/>
<mx:List change="onListEventChange(event)/>
</mx:Canvas>
Or you can just bubble the events and listen for them on the parent component, though I do not recommend this.
Event.type
is read-only, so you can't dispatch the same exact event with a new name. So you'll need to wrap or copy the data in your newly dispatched event. (Usually you would use clone
, but that would keep the event type the same.)
The bear is copying the individual Event types, but if you've got a limited set you can do it.
public function EventRedispatcher(src:EventDispatcher, srcType:String, dest:EventDispatcher, destType:String) {
src.addEventListener(srcType, function(e:Event):void {
var clone:Event = cloneEvent(e, destType);
dest.dispatchEvent(clone);
});
}
// pick your favorite factoryish idiom here
private function cloneEvent(e:Event, newType:String):Event {
if (e is MouseEvent) {
var me:MouseEvent = MouseEvent(e);
return new MouseEvent(newType, me.bubbles, me.cancelable, me.localX, ...);
}
if (e is ...) {}
// default
return new Event(newType, e.bubbles, e.cancelable);
}
精彩评论