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.