JavaScript: Mixing OOP and Event Driven Programming

Nov 4, 2008  • Javascript

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] &amp;&amp; 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] &amp;&amp; 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.