$('someElement')

Thursday, March 29, 2007

EventPublisher: custom events a la pub/sub using prototype.js

Look at this: two blog posts in as many weeks. Maybe I can do this after all :-)

Ok, so I'm kind of building up to a post I have in mind for a little later where I'll explore, in-depth, my simple approaches to implementing classical underpinnings in javascript (including a discussion on the many benefits of doing so). My previous post speaks to one of the fundamental things that would be on any seasoned OO dev's checklist: a robust inheritance model. This week I'll be sharing another of those fundamental checklist items, which is a robust event model (meaning class level custom events, not the already available DOM events like "click" and "mouseover").

Also, in a later post I'll share why I'm stuck on prototype.js as the basis for my code. For now I'll assume that you either already love it and use it, are interested in using it, or can make the connection from what I share here to your library of choice if you are using one of the other ones (and of course there are many options for what I'll call the "Javascript Abstraction Provider" layer besides just prototype.js).

For now, on to EventPublisher.

Why the need?


To put it simply: a robust class level custom event model is a critical component to creating a scalable, maintainable foundation for any application development platform. I talk to seasoned "old-line" engineers and consultants fairly frequently who still, to this day, dismiss serious javascript development for the same reasons almost everyone did before we all recently became a lot smarter [a lot of us anyway]. That is, they still think of scripts with disdain, fearing the spaghetti messes that usually resulted from the mish mashing approaches of yore. "Javascript is not maintainable" they still tell me. "It's messy... it doesn't scale well... it's not well suited for team development" - I still hear this stuff. Clearly, we still have a great deal of misinformed people out there.

So, the basis for my whole educational series on this blog, which started last week, and my answer to those people still not wanting to learn how to write javascript for whatever old reason, is this: "Javascript is the language of the web document. Learn it, buddy. It's expressive, powerful and malleable. Here are some ways to maintain its expressive, dynamic nature while enabling scalable, maintainable, collaborative, object oriented application development."

The code


I first wrote EventPublisher about a year and a half ago, and first posted it to the Ruby on Rails Spinoffs mailing list just over a year ago (btw, this is the official [unofficial] support list for the prototype.js library, it's not just for RoR users). That original version can be found here, and a quick walk through can be found here.

Since that version, I have adopted a slightly different object model, which I touched on near the end of my post last week. So naturally I modified the EventPublisher class to take advantage of that.

Here is the new code:

EventPublisher = Class.create();
EventPublisher.prototype = {
/**
* @constructor
*/
initialize: function() {
//private variables
var allEvents = {}; // cache object

//public instance methods
/**
* Attaches a {handler} function to the publisher's {eventName} event for execution upon the event firing
* @param {String} eventName
* @param {Function} handler
* @param {Boolean} asynchFlag [optional] Defaults to false if omitted. Indicates whether to execute {handler} asynchronously (true) or not (false).
*/
this.attachEventHandler = function(eventName, handler) {
eventName = eventName + "_evt"; // appending _evt to event name to avoid collisions
// using an event cache array to track all handlers for proper cleanup
if (allEvents[eventName] == null)
allEvents[eventName] = [];
//create a custom object containing the handler method and the asynch flag
var asynchVar = arguments.length > 2 ? arguments[2] : false;
var handlerObj = {
method: handler,
asynch: asynchVar
};
allEvents[eventName].push(handlerObj);
};

/**
* Removes a single handler from a specific event
* @param {String} eventName The event name to clear the handler from
* @param {Function} handler A reference to the handler function to un-register from the event
*/
this.removeEventHandler = function(eventName, handler) {
eventName = eventName + "_evt"; // appending _evt to event name to avoid collisions
if (allEvents[eventName] != null)
allEvents[eventName] = allEvents[eventName].reject(function(obj) { return obj.method == handler; });
};

/**
* Removes all handlers from a single event
* @param {String} eventName The event name to clear handlers from
*/
this.clearEventHandlers = function(eventName) {
eventName = eventName + "_evt"; // appending _evt to event name to avoid collisions
allEvents[eventName] = null;
};

/**
* Removes all handlers from ALL events
*/
this.clearAllEventHandlers = function() {
allEvents = {};
};

/**
* Fires the event {eventName}, resulting in all registered handlers to be executed.
* @param {String} eventName The name of the event to fire
* @params {Object} args [optional] Any object, will be passed into the handler function as the only argument
*/
this.fireEvent = function(eventName) {
var evtName = eventName + "_evt"; // appending _evt to event name to avoid collisions
if (allEvents[evtName] != null) {
var len = allEvents[evtName].length; //optimization
for (var i = 0; i < len; i++) {
try {
if (arguments.length > 1) {
if (allEvents[evtName][i].asynch) {
var eventArgs = arguments[1];
var method = allEvents[evtName][i].method.bind(this);
setTimeout(function() { method(eventArgs) }.bind(this), 10);
}
else
allEvents[evtName][i].method(arguments[1]);
} else {
if (allEvents[evtName][i].asynch) {
var eventHandler = allEvents[evtName][i].method;
setTimeout(eventHandler, 1);
}
else if (allEvents && allEvents[evtName] && allEvents[evtName][i] && allEvents[evtName][i].method)
allEvents[evtName][i].method();
}
} catch (e) {
if (this.id)
{
alert("error: error in " + this.id + ".fireEvent():\n\nevent name: " + eventName + "\n\nerror message: " + e.message);
}
else
alert("error: error in [unknown object].fireEvent():\n\nevent name: " + eventName + "\n\nerror message: " + e.message);
}
}
}
};
}
};

My reasons for defining classes as above (defining instance only members within the constructor) are, again, explained in last week's post.

Creating a global event dispatcher object


Ok, now we have the EventPublisher class as a tool in our box. How to put it to use. The first way is to create an instance of it that is accessible at the global level:

var globalEvents = new EventPublisher();

...which all your other objects will be able to use to publish and subscribe to global application-level events:

someClass = Class.create();
someClass.prototype = {
initialize: function() {
this.name = "Foo";

//do some stuff...

//something happened that might be interesting at a global level
globalEvents.fireEvent( "someEvent", {name: this.name} );
}
};

anotherClass = Class.create();
anotherClass.prototype = {
initialize: function() {
//private methods
function someEventHappened(args) {
alert(args.name);
}

//this class is interested in knowing whenever "someEvent" happens
globalEvents.attachEventHandler( "someEvent", someEventHappened );
}
};

var testSub = new anotherClass();
var testPub = new someClass();

On that last line, when the someClass instance is created and it fires the global "someEvent" event, the anotherClass instance "hears" it and the registered handler is executed, resulting in "Foo" being alerted.

Some notes about event arguments (and javascript object notation)


Notice above that the 2nd argument to the .fireEvent() method allows you to pass arguments. The arguments are in the form of a plain old javascript object. For anyone that may be reading this that is still a little new to javascript, I just wanted to take a brief moment to explain this. The syntax I used above: "{name: this.name}", created what's known as an anonymous object. The object had 1 property "name" with the value of this.name ("Foo" in our case). The syntax I used is also frequently referred to as "object notation" or "javascript object notation". The term "javascript object notation" is also known as JSON, although usually people use that acronym when referring to using it as a data transfer format during Ajax operations (more on JSON can be found on Douglas Crockford's json.org, and of course on Wikipedia).

someClass's arguments object for the fired event could have been written in any of the following semantically identical ways:

//using js's Object primitive
var args = new Object();
args.name = this.name;
globalEvents.fireEvent( "someEvent", args );

//assigning to a variable using object notation
var args = {name: this.name};
globalEvents.fireEvent( "someEvent", args );

//creating an anonymous object inline (the original version)
globalEvents.fireEvent( "someEvent", {name: this.name} );

Subclassing EventPublisher for class-level event notification


NOTE: The code example below assumes that we are using the inheritance model from my previous post.

Ok, now we have a global event dispatcher, but what if our events are not really interesting at a global level? For any class that needs to fire instance level events, simply inherit from EventPublisher:

class1 = Class.create();
Object.inherit(class1, EventPublisher);
Object.extend(class1.prototype, {
initialize: function() {
//base class construction
this.base();

//instance methods
this.doSomething = function () {
//something happened...
this.fireEvent( "somethingHappened" );
};
}
});

var test = new class1();
//listen
test.attachEventHandler( "somethingHappened", function () { alert("IT HAPPENED!!!"); } );

//make the event fire
test.doSomething();

I hope you are getting the gist. Now any object, or any code for that matter, that can see the "test" instance can listen for its events to fire, and then take action. I will leave it at that for now, and explore some more complicated event pub/sub scenarios in future posts as I continue to discuss the implementation of classical underpinnings in the dynamic javascript language.

Summary


So at this point in the game, we're armed with both a robust inheritance model and a robust event model. These are two of the big items on the checklist for implementing a truly powerful application platform. One which is scalable, maintainable, is well suited to team development, and is fun to work with. Really, using the bits from this post, my last post, and of course prototype.js itself, you should have all you need to get started. The rest of the posts in this series will discuss the general approach, explore some of the merits of prototype.js, and build on what I've already shared. The end result is going to be a full scale, yet lightweight, flexible platform, which you will be able to use to create some really solid web applications that will live long happy lives.

-Ryan Gahl
I work as a professional software engineering consultant, specializing in web-based (inter/intra/extranet) applications, Ajax, and C#. My services can be purchased by calling the following number: 1-262-951-6727
I am becoming an expert engineer.

Friday, March 23, 2007

Multiple inheritance with prototype.js

As I promised way back in my first post, (which was only 3 posts ago so not really that long right? lol), here is a bit of code.

Really, my contribution to this code is almost nothing, but I have changed someone else's code enough to where I think the result could be useful for someone else, so here it is.

The original code is from Kevin Lindsey's blog. He offers up a pretty nice little inheritance model for javascript. Since I am deeply enamored of the prototype.js library, I took it upon myself to make it into a prototype friendly inheritance model.

So, without further ado, here is the code:
UPDATE: 5/4/07 - changed last line... for info, read my comment below this post.

Object.inherit = function(subClass, baseClass, baseFuncName) {
function inheritance() {}
inheritance.prototype = baseClass.prototype;
Object.extend(subClass.prototype, new inheritance());
subClass.prototype.constructor = subClass;
if (!baseFuncName || baseFuncName == "")
baseFuncName = "base";
subClass[baseFuncName] = baseClass.prototype.initialize;
}


What's different?
The bits that I changed were that I use Object.extend() (which is a prototype.js provided function) on the subClass's prototype to enable multiple inheritance scenarios, and I auto map a base() function to the baseClass's constructor (which in the prototype.js world is the 'initialize' function).

What happened to 'superClass'?
I also removed the reference he put in to the 'superClass', as it is not necessary, and even potentially dead wrong to be in there to begin with. I could have left it in, and accommodated for multiple inheritance the same as with the constructors, but its inclusion is fairly puzzling to me in Kevin's version to begin with. If you have a subclass, and you then have a method that tries to access this.superClass.someMethod()... well first of all the superClass does not point to an instance of the base class, but rather directly to its prototype, so at best you're accessing a static method of the base class's prototype. But if the base class takes advantage of true instance member privilege restrictions (i.e. public instance properties and methods are only given to instantiated objects via the constructor call, and are not tacked directly on to the prototype object), then someMethod() may not even exist at the static level. I think I understand what his intention was (accessing the super class methods from within overridden subclass methods), but his technique only takes static methods into account, and not truly instance-only members. (Sorry for that long explanation... hopefully I can clear it up better a little bit further down with some code examples).

And here are the examples:

Basic single inheritance

class1 = Class.create();
class1.prototype = {
initialize: function(name) {
this.name = name;
},
sayName: function() {
alert(this.name);
}
}

class2 = Class.create();
Object.inherit(class2, class1);
Object.extend(class2.prototype, {
initialize: function(name, title) {
//base class construction
class2.base.call(this, name);

this.title = title;
},
sayTitle: function() {
alert(this.title);
}
});

var test1 = new class1('Bob');
var test2 = new class2('Joe', 'Mr.');

test1.sayName(); // alerts Bob
test2.sayName(); // alerts Joe
test2.sayTitle(); // alerts Mr.

One thing to note here is that I used an Object.extend() call after the inherit call. If I didn't do that (and just did 'class2.prototype = {...}'), you would be overwriting the prototype object, thus doing away with the prototype level inheritance that the inherit() function sets up.

Basic multiple inheritance
Sometimes it may be handy to have a derived class draw functionality from more than one base class, but where those two base classes don't inherit from each other.

Using the same two classes from above...

class3 = Class.create();
class3.prototype = {
initialize: function(nickName) {
this.nickName = nickName;
},
sayNickName: function() {
alert(this.nickName);
}
}

class4 = Class.create();
Object.inherit(class4, class1, "class1base");
Object.inherit(class4, class3, "class3base");
Object.extend(class4.prototype, {
initialize: function(name, nickName) {
//base class construction
class4.class1base.call(this, name);
class4.class3base.call(this, nickName);
}
});

var test4 = new class4("Jim", "Jimmy");
test4.sayName(); // alerts Jim
test4.sayNickName(); // alerts Jimmy

In the code above, you can specify what to call each base class's constructor by providing a 3rd argument to Object.inherit().

NOTE: There is a reason that multiple inheritance is not widely supported in many languages. If you do choose to use it, you should be aware of the fact that if class1 and class3 from above both have methods or properties of the same name, whichever one is inherited last will be the only one available in the new class. That is, if class3 also defined a sayName() method, that's the one that would be available in class4.

Static prototype members vs. true instance-only members and more on why I removed 'superClass' from Kevin's implementation


Many people who use prototype.js will write a class definition something like this (which I stuck with for the above basic examples just to keep people comfortable while demonstrating the inheritance model):

someClass = Class.create();
someClass.prototype = {
initialize: function(name) {
this.name = name;
},
sayName: function() {
alert(this.name);
}
}

So, why is this not exactly the most reliable way? If you look inside the sayName function, you'll of course notice that it accesses 'this.name'. That would be completely fine if you could guarantee that sayName will only ever be called from instances of someClass. But written as above, you cannot guarantee that. Take the following code for example:

var test = new someClass("Bob");
test.sayName() // works fine, alerts Bob

// but...
someClass.prototype.sayName(); // alerts undefined

In this simple case, it's not a wholly terrible problem, as it will just alert 'undefined', but what if it was expecting an object:

someClass = Class.create();
someClass.prototype = {
initialize: function(person) {
this.person = person;
},
sayName: function() {
alert(this.person.name);
}
}

Now the attempt at accessing sayName() statically via 'someClass.prototype.sayName()' causes the application to crash with an error saying something like "object has no properties".

So, if we were to use Kevin's original code, which points a 'superClass' variable to the baseClass's prototype object, and we then tried to access any members of the superClass, we could be in for some surprises.

Furthermore, what if we use a class model that allows us to truly limit members to instances only? I.e. what if sayName were not available at all via the static 'someClass.prototype.sayName()' call?

One can achieve this by encapsulating instance-only members inside the constructor:

someClass = Class.create();
someClass.prototype = {
initialize: function(name) {
//public instance properties
this.name = name;

//public instance methods
this.sayName = function() {
alert(this.name);
};
}
}

I hope you can see how in a class model such as above, only instances of someClass will ever even have a sayName method. This is as it should be, as now I can be certain that whenever 'sayName()' is called, 'this.name' will have been set.

So how can you override base class methods and make sure that the original base class method still gets called?


Let's use the above class as the example. Let's say you want to inherit from that class, but you want to define a new sayName() method that, if the name is "Bob" it says "Robert" (because Bob is a very professional person), but for any other name it just calls the original method. We also want to maintain the fact that sayName should be a true instance-only member.

To achieve the above requirement, use the following:

newClass = Class.create();
Object.inherit(newClass, someClass);
Object.extend(newClass.prototype, {
initialize: function(name) {
//base class construction
newClass.base.call(this, name);

//override
var oldSayName = this.sayName.bind(this);
this.sayName = function() {
if (this.name == "Bob")
alert("Robert");
else
oldSayName();
};
}
});

var test = new newClass("Bob");
var test2 = new newClass("Joe");

test.sayName(); // alerts Robert
test2.sayName(); // alerts Joe

Note that we store a reference to the original method and then call it from within the new one. The '.bind(this)' is another helpful thing we get because we use prototype.js. Without that there, the call to the oldSayName() function would not work right. The "this" keyword within that call would refer not to our instance, but to the function itself. The "this" problem has been explained by many others before me, so I leave it to you if you'd like to research that further (as it is beyond the scope of this post).

So there you go. A more robust single inheritance model (than just using Object.extend), that plays well with prototype's Class model, and that supports multiple inheritance if needed. Thanks Kevin for the inspiration, and all the great people who contribute daily to the prototype.js evolution, and of course to Sam for creating it to begin with.

I hope this helps someone. Cheers.

-Ryan Gahl
I work as a professional software engineering consultant, specializing in web-based (inter/intra/extranet) applications, Ajax, and C#. My services can be purchased by calling the following number: 1-262-951-6727
I am becoming an expert engineer.