Introduction
One of the core objects used in all my current development, the object almost all higher level objects rely on to interact with each other, is my custom event listener. Custom event listeners allow me broadcast and capture events across JavaScript objects. An example of a custom event is if I have a JS menu objects for which I want to detect an 'open' event from another JS object. I could detect the mouse click and assume the menu is open, but what if the menu object has to do determine whether it's allowed to open or what if the menu is opened programmatically triggered by some other event? Obviously you can't always make assumptions based on built-in event, but instead have to handle object defined events in a robust manner.
Why a Custom Event Handler?
Why did I create a custom event handler rather than use the browsers built-in event handling system (built-in listeners are those such as mouseout, mouseover, click, etc. that are the result of a user's actions or a browser event)? Well the primary reason was that at the time I developed this IE 6 had no way to create user defined event handlers; Mozilla based browsers did have the capability, but it was easier to treat all browser the same way. I'm not sure if IE 7 still can't handle user defined events, but at this point I like the way mine works so I haven't taken any time to research it.
Bind
One of the keys to the custom event handler (and actually built-in event handling in object oriented JS programming) is the ability for the events to fire within the appropriate scope, the Function.prototype.bind function. If an event simply called the method of an object the method would execute as its own function without any understanding of its parent object. However, by binding the method to its parent it is able to see the object's properties and sibling methods. The bind function is pretty straight-forward as all it does is "apply" the parent object to the method and pass along the arguments from the function call.
Event
The base Event object is pretty basic. It's just a constructor declaring two empty arrays. The this.events array will be used to hold custom events while the this.builtinEvts array will be used to hold built-in events.
getActionIdx
In order to determine whether a listener has been registered already or not, and to have the ability to remove listeners, there needs to be a method to retrieve the index of the event. In order to do that I need to work my way down through the tree I've created to store the event for each object. The tree looks like this:
this.event[]
|
object[]
|
event[]
|
listener{}
|
----------------
| |
action bindingThe index returned will be that of the listener within the final event array. If the object, event, or listener is not defined then getActionIdx will return -1. I could have defined listener as another object with two properties, action and binding, but decided against it. In the future if I determine the listener needs to be more complex I may do so to make the code more readable, but for now it suits my needs.
addListener
Now to the meat of the Event object.
The addListener method will add a new listener to an object for a given event. Because odd things can happen if a listener gets registered multiple times it needs to use getActionIdx to determine whether the listener already exists or not. If the object hasn't had any listeners registered then a new array will need to be created for the given object. If the specified event hasn't been registered under the object then a new array will need to be created as well. Finally, if the specified action and binding haven't been registered under the event then the listener will be added.
removeListener
Removing a listener is as simple as getting its index and splicing it out.
fireEvent
Adding and removing listeners is all well and good, but what's the point if you can't use it for anything. What fireEvent does is make it all work. When a JS object performs an action that you want to broadcast and then catch within another object you use fireEvent to broadcast that event to all registered listeners of that object. It does this by looking for the object, then the event within that object, and finally executing all actions registered for that event. The reason I pass through "e" is because many of my events do occur as a result of a built-in listener and may need to do something with the event object (like get the mouse x/y).
Built-in Listeners
The Event object I use also includes built-in listeners, but I removed the functionality from this example to shorten the article; I will add built-in listeners back into the Event object in a later article. I originally dealt with built-in listeners completely outside of my custom event handler because of the browsers native capability to handle them. However, two things changed my mind.
The first thing that changed my mind was that I ended up having to keep a global array of built-in listeners so I could clean up after them properly to prevent memory leaks within browsers. It made more sense to clean up both built-in and custom listeners at the same time. I've also removed the cleanup to shorten and simplify the article, but again I'll follow up on it another time and explain why it's even a necessity.
The second thing that changed my mind was that I was using custom and built-in listeners in very similar ways. In the end it was just easier to remember two similar method calls within a single Event object.
An Example
If at any point test.initialize() is called then it will fire an "initialize" event which in turn calls the anonymous function to open an alert box. The example is over simplified to make it easy to understand, but you could easily replace the anonymous function with one from another JS object (just don't forget to bind it to its parent object using the bind argument in addListener).
Conclusion
Hopefully, this makes sense and will be helpful to some. Being my first "how to" article I'm sure there are a lot of improvements I can make and I hope you'll offer suggestions on how to improve my writing style. I also know there are improvements I can make to my code too. Please, please, please tell me how I can improve. If I wanted to just have a very good custom event handler to use in my future projects I would use one of the many frameworks out there, but my goal is to improve my programming skills. I learn a great deal by going through the exercise of trying to solve the problem myself. I learn even more when my work is critiqued and I'm told how I can improve.
And, finally, here's the Event code all in one piece:
Example
Initialize Test [1]
Here are the source files:
custom-event-listeners.html [2]
custom-event-listeners.js [3]
Links:
[1] http://www.josh-davis.org/2007/04/10/custom-event-listeners#testlink
[2] http://www.josh-davis.org/files/uploads/2007/05/custom-event-listeners.html
[3] http://www.josh-davis.org/files/uploads/2007/05/custom-event-listeners.js