Archive

Posts Tagged ‘event-driven’

JavaScript: Event aggregator

October 3rd, 2011 No comments

    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: ,