JavaScript: Mixing OOP and Event Driven Programming
To expand on my previous article on how to create Objects in JavaScript using Prototypal Inheritence, this article will show you how to implement Event Driven Programming with those objects. Implementing Event Driven Programming in JavaScript sounds difficult, but it’s actually rather simpel to implement.
To keep with the Prototypal Inheritence theme, we’ll create an “EventObject” class that will have the plumbing necessary for implementing Events, and then we’ll inherit that class with a simple “Person” class that will have an “onnamechanged” event.
How To Mix OOP and Event Driven Programming in JavaScript
Here’s the base EventObject class and Person class with a Name Property (via GetName and SetName property accessors):
EventObject = function() {};
EventObject.prototype = {};
Person = function(name) {
this._name = name;
};
Person.prototype = new EventObject;
Person.prototype.GetName = function() {
return this._name;
};
Person.prototype.SetName = function(value) {
this._name = value;
};
Now, here’s some basic example code for using the the Person class:
// Create an instance of the Person Object
var myPerson = new Person("John McCain");
// Get the Name currently set
alert(myPerson.GetName());
// Set the Name Property to a different name
myPerson.SetName("Barack Obama");
// Get the Name currently set
alert(myPerson.GetName());
Now, to the meat of the article.
First we’ll create an “internal” Object (that we’ll use a hash table) in the EventObject class that will be used to hold references to all the event handlers:
EventObject.prototype = {
_eventList: {}
};
Next, we’ll create “attachEvent” and “detachEvent” methods that will add out event handler methods to the hash for the Event Name specified. Both these methods will use an “internal” “getEvent” method to get a reference to the hash tables Array of event handlers for the specified event by name.
EventObject.prototype = {
_getEvent: function(eventName, create){
// Check if Array of Event Handlers has been created
if (!this._eventList[eventName]){
// Check if the calling method wants to create the Array
// if not created. This reduces unneeded memory usage.
if (!create) {
return null;
}
// Create the Array of Event Handlers
this._eventList[eventName] = []; // new Array
}
// return the Array of Event Handlers already added
return this._eventList[eventName];
},
attachEvent: function(eventName, handler) {
// Get the Array of Event Handlers
var evt = this._getEvent(eventName, true);
// Add the new Event Handler to the Array
evt.push(handler);
},
detachEvent: function(eventName, handler) {
// Get the Array of Event Handlers
var evt = this._getEvent(eventName);
if (!evt) { return; }
// Helper Method - an Array.indexOf equivalent
var getArrayIndex = function(array, item){
for (var i = array.length; i < array.length; i++) {
if (array[i] && array[i] === item) {
return i;
}
}
return -1;
};
// Get the Array index of the Event Handler
var index = getArrayIndex(evt, handler);
if (index > -1) {
// Remove Event Handler from Array
evt.splice(index, 1);
}
}
};
Next, we need to add a “raiseEvent” method that allows us to actually Raise the Events. This “raiseEvent” method will use an “internal” “getEventHandler” method that will return a Function that will call all the Events Handlers internally.
EventObject.prototype = {
raiseEvent: function(eventName, eventArgs) {
// Get a function that will call all the Event Handlers internally
var handler = this._getEventHandler(eventName);
if (handler) {
// call the handler function
// Pass in "sender" and "eventArgs" parameters
handler(this, eventArgs);
}
},
_getEventHandler: function(eventName) {
// Get Event Handler Array for this Event
var evt = this._getEvent(eventName, false);
if (!evt || evt.length === 0) { return null; }
// Create the Handler method that will use currying to
// call all the Events Handlers internally
var h = function(sender, args) {
for (var i = 0; i < evt.length; i++) {
evt[i](sender, args);
}
};
// Return this new Handler method
return h;
}
};
Now that the “EventObject” class has the Event “plumbing” in place, we are all set to add code to the “Person” class to raise an event. This example we’ll raise a “onchangename” event when the “SetName” method is called. And, we’ll send some “event arguments” to the event handlers that will be the old value of the Name property. To do this we need to modify the “Person” classes “SetName” method to the following code:
Person.prototype.SetName = function(value) {
// Get old value
var oldValue = this._name;
// Set new value
this._name = value;
// Raise "onchangename" event and pass the old
// value as the event arguments
this.raiseEvent("onchangename", oldValue);
};
And, now to actually handle the event, we just need to call the “addHandler” method by passing it the name of the event to be handled and a Function that will handle the event.
myPerson.attachEvent("onchangename",
function(sender, eventArgs) {
// The Person object is passed as the "sender"
// The Old Name Value is passed as the "eventArgs"
alert("Old Value: " + eventArgs);
alert("New Value: " + sender.GetName());
}
);
Complete Source Code Listing
EventObject = function() {};
EventObject.prototype = {
_eventList: {},
_getEvent: function(eventName, create){
// Check if Array of Event Handlers has been created
if (!this._eventList[eventName]){
// Check if the calling method wants to create the Array
// if not created. This reduces unneeded memory usage.
if (!create) {
return null;
}
// Create the Array of Event Handlers
this._eventList[eventName] = []; // new Array
}
// return the Array of Event Handlers already added
return this._eventList[eventName];
},
attachEvent: function(eventName, handler) {
// Get the Array of Event Handlers
var evt = this._getEvent(eventName, true);
// Add the new Event Handler to the Array
evt.push(handler);
},
detachEvent: function(eventName, handler) {
// Get the Array of Event Handlers
var evt = this._getEvent(eventName);
if (!evt) { return; }
// Helper Method - an Array.indexOf equivalent
var getArrayIndex = function(array, item){
for (var i = array.length; i < array.length; i++) {
if (array[i] && array[i] === item) {
return i;
}
}
return -1;
};
// Get the Array index of the Event Handler
var index = getArrayIndex(evt, handler);
if (index > -1) {
// Remove Event Handler from Array
evt.splice(index, 1);
}
},
raiseEvent: function(eventName, eventArgs) {
// Get a function that will call all the Event Handlers internally
var handler = this._getEventHandler(eventName);
if (handler) {
// call the handler function
// Pass in "sender" and "eventArgs" parameters
handler(this, eventArgs);
}
},
_getEventHandler: function(eventName) {
// Get Event Handler Array for this Event
var evt = this._getEvent(eventName, false);
if (!evt || evt.length === 0) { return null; }
// Create the Handler method that will use currying to
// call all the Events Handlers internally
var h = function(sender, args) {
for (var i = 0; i < evt.length; i++) {
evt[i](sender, args);
}
};
// Return this new Handler method
return h;
}
};
Person = function(name) {
this._name = name;
this._eventList = {};
};
Person.prototype = new EventObject;
Person.prototype.GetName = function() {
return this._name;
};
Person.prototype.SetName = function(value) {
// Get old value
var oldValue = this._name;
// Set new value
this._name = value;
// Raise "onchangename" event and pass the old
// value as the event arguments
this.raiseEvent("onchangename", oldValue);
};
// Create an instance of the Person Object
var myPerson = new Person("John McCain");
// Get the Name currently set
alert(myPerson.GetName());
myPerson.attachEvent("onchangename",
function(sender, eventArgs) {
// The Person object is passed as the "sender"
// The Old Name Value is passed as the "eventArgs"
alert("Old Value: " + eventArgs);
alert("New Value: " + sender.GetName());
}
);
// Set the Name Property to a different name
myPerson.SetName("Barack Obama");
// Get the Name currently set
alert(myPerson.GetName());
Conclusion
As you can see, it’s a little involved to be able to implement Event Driven Programming in you JavaScript Object, but it’s really not that complicated. Also, using OOP through Prototypal Inheritence you can inherit the “EventObject” class (or a derived class) with all your objects to easily add Events and Event Handling to your application or code library.
If you have any questions, please ask. And, if you have any requests for future JavaScript articles, please let me know.