views:

263

answers:

4

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%" height="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.

A: 

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

PeZ
Yeah, but I want to dispatch the events with a different name since multiple components have a "click" or "creationComplete" event for example. I want something clearer such as "listBoxClicked" or "comboBoxCreationComplete".I already know how to dispatch events.Perhaps you already answered my question, but I don't quite see how.
FlashFlex
@FlashFlex - your comment here clarifies your problem, you should edit your question and add these details to it.
invertedSpear
A: 

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.

unscene
+3  A: 

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;
        }
    }
}
Sam
Not to sound smart, but if I knew how to do that then I wouldn't have asked this question.
FlashFlex
@FlashFlex: I updated the example so it's complete now with full component, full test app, and implementation of EventRedispatcher.
Sam
Thanks, THIS is what I was trying to do. I had my own event EventRedispatcher class, but it wrapped the old event inside a custom event. This is much better.
FlashFlex
A: 

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);
}
Michael Brewer-Davis