Chunky Chunk
Chunky Chunk

Reputation: 17217

ActionScript - Global Custom Events?

up until now, the way i've been needing to handle my own custom events is by adding an event listener to the object that was dispatching the custom event. while this method of event handling works just fine, i've come to the point where i would like my custom events to be globally accessible, where the listening object does not need to be the same object that is dispatching the event.

in this example, my main Controller class is instantiating and adding to the display list 2 sprite classes: Square and Triangle. the 4th and final class is a custom event called ColorChangeEvent.

i'm attempting to dispatch a new ColorChangeEvent from the Square class, which uses a timer to dispatch a new random color once every second, while Triangle will listen for the dispatched event and change its fill color to the color that was dispatched by Square.

Controller.as:

package
{
import flash.display.Sprite;

public class Controller extends Sprite
    {
    public function Controller()
        {
        var sq:Square = new Square();
        sq.x = sq.y = 100;

        var tr:Triangle = new Triangle();
        tr.x = tr.y = 250;

        addChild(sq);
        addChild(tr);
        }
    }
}

Square.as:

package
{
import flash.display.Sprite;
import flash.events.TimerEvent;
import flash.utils.Timer;

    public class Square extends Sprite
        {
        public function Square()
            {
            graphics.beginFill(0x999999);
            graphics.drawRect(0, 0, 100, 100);
            graphics.endFill();

            var myTimer:Timer = new Timer(1000);
            myTimer.addEventListener(TimerEvent.TIMER, dispatchNewColor);
            myTimer.start();
            }

        private function dispatchNewColor(evt:TimerEvent):void
            {
            var randomColor:Number = Math.random() * 0xFFFFFF;
            trace("Square Class Dispatched: " + randomColor);

            dispatchEvent(new ColorChangeEvent(ColorChangeEvent.CHANGE, randomColor));
            }
        }
    }

Triangle.as:

package
{
import flash.display.Sprite;
import flash.geom.ColorTransform;

public class Triangle extends Sprite
    {
    public function Triangle()
        {
        graphics.beginFill(0x999999);
        graphics.moveTo(0, 0);
        graphics.lineTo(100, 50);
        graphics.lineTo(-50, 150);
        graphics.endFill();

        addEventListener(ColorChangeEvent.CHANGE, changeColor);
        }

    private function changeColor(evt:ColorChangeEvent):void
        {
        var ct:ColorTransform = new ColorTransform;
        ct.color = evt.color;

        transform.colorTransform = ct;

        trace("Triangle Class Received: " + evt.color);
        }
    }
}

ColorChangeEvent.as:

package
{
import flash.events.Event;

public class ColorChangeEvent extends Event
    {
    public static const CHANGE:String = "change";
    public var color:Number;

    public function ColorChangeEvent(type:String, color:Number) 
        {
        super(type);
        this.color = color;
        }

    override public function clone():Event
        {
        return new ColorChangeEvent(type, color);
        }
    }
}

needless to say, this isn't working.

of course, i could add the event listener to the Square instance in the Controller class, who's event handler could pass that value to Triangle via a public function to change the color, but this is exactly the kind of limitation i'm trying to avoid.

it's not always easy to access and pass a value to a class from where the custom event is dispatched, which is why i'm looking for an actual global solution to handling custom events.

Upvotes: 2

Views: 1040

Answers (3)

Evan Layman
Evan Layman

Reputation: 3721

I got this to work by creating a singleton: EventDispatchSingleton that extends EventDispatcher. It's basically an empty singleton that provides the dispatchEvent and add/removeEventListener methods (these are automatically provided by extending EventDispatcher).

Anywhere I want to dispatch an event I import EventDispatchSingleton and then call EventDispatchSingleton.instance.dispatchEvent(<someEvent>);.

Then, wherever I want to listen to that event, I just import EventDispatchSingleton and call EventDispatchSingleton.instance.addEventListener(eventName, callback);

Upvotes: 1

sberry
sberry

Reputation: 131998

I have been using this class for some time now. To use it you would do this in square:

data.EventManager.instance.publish("someName", randomColor);

and then in your Triangle:

data.EventManager.instance.subscribe("someName", handleColorChange);

private function handleColorChange(color:Number):void {
    // implementation here
}

You can even pass the ColorChangeEvent instead of just the color.

data.EventManager.instance.publish(ColorChangeEvent.CHANGE, new ColorChangeEvent(ColorChangeEvent.CHANGE, randomColor);

And then

data.EventManager.instance.subscribe(ColorChangeEvent.CHANGE, handleColorChange);

private function handleColorChange(colorChangeEvent:ColorChangeEvent):void {
    // implement here
}

I removed a lot of code that is specific to my projects, so I am not 100% it is usable exactly as-is. But, you should be able to modify it to get it working correctly. If not, let me know and I can try to work it out with you.

This class handles additional things that I will not go into, though you are free to explore. Be aware, however, that anything that subscribes for event notification has a strong reference by the EventManager. That means that if you want to destroy something for garbage collection, you need to call EventManager.instance.cancel(ColorChangeEvent.CHANGE, handleColorChange) before the Triangle instances can be collected.

package data {
    import flash.utils.*;

    public class EventManager extends Object {
        private var _subscribers:Dictionary;
        private var _calls:Dictionary;
        private var _feeds:Dictionary;
        private var _requests:Dictionary;
        private var _notify:Dictionary;
        private var _services:Dictionary;
        private static var __instance:EventManager;

        public function EventManager() {
            if (__instance) {
                trace("EventManager is a Singleton class which should only be accessed via getInstance()");
            }
            _feeds = new Dictionary(true);
            _subscribers = new Dictionary(true);
            _requests = new Dictionary(true);
            _services = new Dictionary(true);
            _notify = new Dictionary(true);
        }

        public function getFeedData($name:String) {
            if (_feeds[$name]) {
                return _feeds[$name];
            }
            return undefined;
        }


        public function unpublish($name:String) {
            var _post:* = _feeds[$name];
            delete _feeds[$name];
            return _post;
        }

        public function cancel($name:String, $subscriberFunc:Function, ...args): void {
            var _cnt:Number;
            var _subscriberArray:Array;
            if (_subscribers[$name]) {
                for (_cnt = 0; _cnt < _subscribers[$name].length; _cnt++) {
                    if (_subscribers[$name][_cnt] == $subscriberFunc) {
                        _subscribers[$name].splice(_cnt, 1);
                    }
                }
            }

            if (_requests[$name]) {
                _subscriberArray = _requests[$name];
                _cnt = _subscriberArray.length;

                while (_cnt > 0) {
                    if (_subscriberArray[_cnt] == $subscriberFunc) {
                        _subscriberArray.splice(_cnt, 1);
                    }
                    _cnt--;
                }
            }
        }

        public function subscribe($name:String, $subscriber:Function, ...args): void {
            var _funcArray:Array;
            var _func:Function;


            if (_feeds[$name]) {
                $subscriber(_feeds[$name]);
            }

            if (! _subscribers[$name]) {
                _subscribers[$name] = new Array();
            }
            _subscribers[$name].push($subscriber);

            if (_notify[$name]) {
                _funcArray = _notify[$name];

                for each (_func in _funcArray) {
                    _func();
                }
                delete _notify[$name];
            }
        }

        public function request($name:String, $feedFunction:Function): void {
            var _requestArray:Array;
            var _request:Function;

            if (! _feeds[$name]) {
                if (! _requests[$name]) {
                    _requests[$name] = new Array();
                }
                _requests[$name].push($feedFunction);
            } else {
                $feedFunction(_feeds[$name]);
            }

            if (_notify[$name]) {
                _requestArray = _notify[$name];

                for each (_request in _requestArray) {
                    _request();
                }
                delete _notify[$name];
            }
        }

        public function publish($name:String, $data:*, $args:Object = null): void {
            var _subscriberArray:Array;
            var _func:Function;
            var cnt:Number = 0;
            _feeds[$name] = $data;

            if (_subscribers[$name] != undefined) {
                _subscriberArray = _subscribers[$name].slice();
                _cnt = 0;

                while (_cnt < _subscriberArray.length) {
                    _func = _subscriberArray[_cnt] as Function;

                    if ($args) {
                        _func($data, $args);
                    }else {
                        _func($data);
                    }
                    _cnt++;
                }
            }

            if (_requests[$name]) {
                _subscriberArray = _requests[$name].slice();
                delete _requests[$name];
                _cnt = 0;

                while (_cnt < _subscriberArray.length) {
                    if (_subscriberArray[_cnt] != null) {
                        _subscriberArray[_cnt]($data);
                    }
                    _cnt++;
                }
            }
        }

        public function notify($name:String, $subscriber:Function): void {
            if (_requests[$name] || _subscribers[$name]) {
                $subscriber();
            }else {
                if (! _notify[$name]) {
                    _notify[$name] = new Array();
                }
                _notify[$name].push($subscriber);
            }
        }


        public static function getInstance(): EventManager {
            if (! __instance) {
                __instance = new EventManager();
            }
            return __instance;
        }

        public static function get instance(): EventManager {
            return getInstance();
        }
    }
}

Upvotes: 4

Adam Harte
Adam Harte

Reputation: 10510

You should look into event bubbling, specificly I think you will find the Capturing phase of the event propagation useful. Take a read of Event propagation from Adobe LiveDocs. It's in the Flex docs, but it is about AS3 Events.

Also Senocular has a good post on Flash Event Bubbling.

Upvotes: 0

Related Questions