Home > JavaScript > JavaScript: Event aggregator

JavaScript: Event aggregator

    JavaScript is an event-driven language. The most of JavaScript objects (from document to button) contains events, which some response actions can be bound to. Suddenly I wanted to use the event-driven approach with my own objects, so that they could subscribe to events of each other. I implemented a special object – EventAggregator, which allows to manage listeners of events. EventAggregator is a pure JavaScript implementation, without jQuery and others.

function EventAggregator() {

    this._eventToListenersMap = {}; // key-value dictionary: key is an event name, value is an array of listeners

    // Method adds listener
    // eventName - a string event name
    // callback  - a function that has to be called, when event is raised
    // context   - object containing the callback function
    this.Subscribe = function(eventName, callback, context) {

        if (!this._eventToListenersMap.hasOwnProperty(eventName)) // check if key exists
            this._eventToListenersMap[eventName] = []; // create an array to store listeners

        // add listener of a certain event to the array. Listener is a combination of
        // a callback function to call when event is raised and an object containing the callback function
        this._eventToListenersMap[eventName].push({ 'Callback': callback, 'Context': (typeof context != "undefined" ? context : null) });
    }

    // Method removes listener
    // eventName - a string event name
    // callback  - a function that has to be removed
    this.Unsubscribe = function(eventName, callback) {

        if (this._eventToListenersMap.hasOwnProperty(eventName)) { // check if key exists
            // get through the array of listeners of a certain event to find the listener that has to be removed
            for (var i = 0; i < this._eventToListenersMap[eventName].length; ++i) {
                // check if it's a required listener
                if (callback == this._eventToListenersMap[eventName][i].Callback) {
                    this._eventToListenersMap[eventName].splice(i, 1); // remove the found listener
                    break;
                }
            }

        }
    }

    // Method raises event
    // eventName - a string event name
    // args      - a data that has to be passed into a callback function
    this.Fire = function(eventName, args) {

        if (this._eventToListenersMap.hasOwnProperty(eventName)) { // check if key exists
            // get through the array of listeners of a certain event to find the listeners that has to be called
            for (var i = 0; i < this._eventToListenersMap[eventName].length; ++i) {
                try {
                    // get the object containing the callback function
                    var contextObj = this._eventToListenersMap[eventName][i].Context;
                    // invoke callback in context of the object
                    this._eventToListenersMap[eventName][i].Callback.call(contextObj, args);
                } catch (e) {
                }
            }
        }
    }
}

Note that if you use the Subscribe method and pass a callback function, which is a member of some object, you have to pass this object as well. If you pass null as the parameter context or pass nothing, EventAggregator try to call the callback function of the top-level object of the client-side object hierarchy. This top-level object is the window object.

Code sample how to use EventAggregator is shown below:

function Dog() {
    this.Events = new EventAggregator();

    this.ON_BARK  = "OnBark";
    this.ON_WHINE = "OnWhine";   

    this.Bark = function() {
        this.Events.Fire(this.ON_BARK, this);
    }
    this.Whine = function() {
        this.Events.Fire(this.ON_WHINE, this);
    }
}

function Master() {
    this.Events = new EventAggregator();
    this.Dog    = new Dog();

    this.ON_WAKE_UP = "OnWakeUp";

    this._init = function() {
        this.Dog.Events.Subscribe(this.Dog.ON_BARK, this._wakeUp, this);
        this.Dog.Events.Subscribe(this.Dog.ON_WHINE, this._wakeUp, this);
    }

    this._wakeUp = function(args) {
        this.Events.Fire(this.ON_WAKE_UP, args);
    }

    this._init();
}

var ownerlessDog = new Dog();
ownerlessDog.Events.Subscribe(ownerlessDog.ON_BARK, function() { alert('Dog barked'); }, null);
ownerlessDog.Bark();

var master = new Master();
master.Events.Subscribe(master.ON_WAKE_UP, function() { alert('Master woke up') }, null);
master.Dog.Whine();

Probably, It will be useful for somebody.

 
Categories: JavaScript Tags: ,
  1. wizmagister
    March 8th, 2012 at 18:26 | #1

    I’m surprised no one commented this post. This is very well done and functionnal.

    Thanks for this

    • Administrator
      March 8th, 2012 at 19:05 | #2

      Thank you!

  2. Rory
    March 27th, 2012 at 10:27 | #3

    On first viewing looks good, will download and play with it

    I come from a SL/WPF background so event aggregion is common place in those frameworks

  3. Rory
    June 18th, 2012 at 04:15 | #4

    Had a play with it, but can’t seem to send a “message” to the subscriber
    By message I mean a payload i.e an object with data in it or a sting var

  4. Rory
    June 18th, 2012 at 04:19 | #5

    Also, an instance of dog is required in master so as to reference the event name
    Wouldn’t it be better to have the event reference names in a common object.
    I don’t like the coupled dependency

  5. Rory
    June 18th, 2012 at 04:35 | #6

    Got the message passing working :-)

  1. No trackbacks yet.