Custom events in JavaScript
Without a doubt, the most often-used paradigm in JavaScript is events. Events are a manifestation of the observer pattern, a well-defined computer science design pattern for loose coupling. Loose coupling is incredibly important for creating maintainable, stable codebases. I talk a lot about loose coupling and its importance in my talk, Scalable JavaScript Application Architecture (video), so I won’t talk too much about it here. However, the concept is very important to grasp if you wish to progress as a software engineer.
Events
Unless you’ve never written any JavaScript before, you’ve used events at some point in time (admittedly, if you’ve never written JavaScript before, the chances of your reading my blog are probably pretty slim). Put quite simply: the way that you tie behavior to web pages is through events. Events are a way of letting interested parties know that an important moment has occurred in the lifecycle of the application. For instance:
window.onload = function(){
Application.init();
};
In this example, the load event is the interesting moment. I want to know when the window is fully loaded so that I can initialized the JavaScript application. The onload event handler is the location to where an event handler is assigned. The brilliant part is that window doesn’t care what web page is loaded or who is writing the code; it just knows that there’s a function to call when load occurs. This is the essence of loose coupling: when parts of an application have very limited knowledge of one another.
The Browser Object Model (BOM) and Document Object Model (DOM) publish events to allow developers access to the interesting moments of the browser and web page, respectively.
Custom events
It’s no surprise that most JavaScript libraries rely heavily on custom events since this is a pattern that web developers are familiar with. Every major JavaScript library provides its own events, components to enable easy custom event definition, or both. This makes sense, of course, since libraries want to be loosely-coupled to the execution environment, and therefore, to your code.
There’s nothing magic about custom events, though, and there’s no need to load an entire library if you’d like to experiment with custom events. An object that supports custom events needs to be able to do a small set of things:
- Assign an event handler for a particular event.
- Remove an event handler for a particular event.
- Fire an event and call all assigned event handlers.
The following implements all of this basic functionality:
//Copyright (c) 2010 Nicholas C. Zakas. All rights reserved.
//MIT License
function EventTarget(){
this._listeners = {};
}
EventTarget.prototype = {
constructor: EventTarget,
addListener: function(type, listener){
if (typeof this._listeners[type] == "undefined"){
this._listeners[type] = [];
}
this._listeners[type].push(listener);
},
fire: function(event){
if (typeof event == "string"){
event = { type: event };
}
if (!event.target){
event.target = this;
}
if (!event.type){ //falsy
throw new Error("Event object missing 'type' property.");
}
if (this._listeners[event.type] instanceof Array){
var listeners = this._listeners[event.type];
for (var i=0, len=listeners.length; i < len; i++){
listeners[i].call(this, event);
}
}
},
removeListener: function(type, listener){
if (this._listeners[type] instanceof Array){
var listeners = this._listeners[type];
for (var i=0, len=listeners.length; i < len; i++){
if (listeners[i] === listener){
listeners.splice(i, 1);
break;
}
}
}
}
};
The EventTarget type has three methods: addListener(), fire(), and removeListener.
The addListener() uses the private _listeners object to store event handlers for various events. When an event handler is added, the method first checks to see if there’s a named property for that event type on the _listeners object, and if not, creates one containing an array. The event handler function is then saved to the array for later.
The fire() method fires an event with a given name. In effect, this method’s only job is to execute each event handler for the given event type. The method accepts either an object, in which case it’s expected to have a type property, or a string, in which case a new object is created and the string is assigned as the value of type. Next, if the event object doesn’t have a target property assigned, it is set to the current instance. This effectively creates an event object similar to the one most are familiar with via the BOM and DOM. Once the event object is created, the _listeners object is checked for event handlers, and if found, they are executed. Note that in order to mimic the BOM/DOM approach, event handlers are executed in the scope of this via the call() method.
The last method, removeListener(), simply reverses the process of addListener(). It searches through the _listeners property for the given event type to locate the specified event handler. If found, the event handler is removed by using the array’s splice() method, and otherwise it exits without doing anything.
Basic usage:
var target = new EventTarget();
function handleEvent(event){
alert(event.type);
};
target.addListener("foo", handleEvent);
target.fire({ type: "foo" }); //can also do target.fire("foo")
target.removeListener("foo", handleEvent);
Practically speaking, you’ll likely not want to use an instance of EventTarget directly, but rather inherit from it:
function MyObject(){
EventTarget.call(this);
}
MyObject.prototype = new EventTarget();
MyObject.prototype.constructor = MyObject;
MyObject.prototype.foo = function(){
this.fire("foo");
};
var o = new MyObject();
o.addListener("foo", function(){
alert("Foo just happened.");
});
o.foo();
Typically, events are fired in reaction to some other method call, as in this example (events are usually not fired external to the object that is publishing the events).
What about…?
This is a pretty barebones implementation of a custom event providing object, so inevitably someone will come along and ask why I didn’t include one feature or another. There are, of course, a lot of enhancements you can make to custom events if you so desire. Some enhancements others have implemented:
- Bubbling of events
- Continue to execute event handlers even if one throws an error
- Allow event handlers to cancel further processing or default actions
Each of these can be built pretty easily on top of the base presented in this post.
Conclusion
Custom events are a very powerful and useful pattern in JavaScript programming, and your usage of them doesn’t have to rely on a large JavaScript library. Implementing your own custom events is easy. The implementation presented in this post is a minimum feature set that typically fulfills most requirements, but you can consider it as a starting point for more advanced functionality if your requirements are more complex.
Disclaimer: Any viewpoints and opinions expressed in this article are those of Nicholas C. Zakas and do not, in any way, reflect those of my employer, my colleagues, Wrox Publishing, O'Reilly Publishing, or anyone else. I speak only for myself, not for them.
Both comments and pings are currently closed.




9 Comments
If you use an OO approach to JavaScript programming you often to say “call method x on object y” when you attach an eventhandler.
This is often handled by passing in the “this” parameter for the call to the observer pattern.
I use a different approach, I use a closure to bind an object and a method-call together. So I get a function that when called, gets called with a specific “this”.
Here’s the code:
Uia.thisWrap = function(fnFunction, nvntThis) {
if (!Uia.isFunction(fnFunction)) throw new Error("Not a function");
var fnWrappedFunction = function(_this, _f) {
return function() { return _f.apply(_this, Uia.argumentsToArray(arguments)); };
} (nvntThis, fnFunction);
return fnWrappedFunction;
}
With this helper function:
Uia.argumentsToArray = function(argArguments, uintFromIndex) {
var intFromIndex = uintFromIndex || 0;
return Array.prototype.slice.apply(argArguments, [intFromIndex]); //convert arguments to array
}
In my framework, I’ve made it so I reuse the this-wrapped functions once they are created.
Allan Ebdrup on March 9th, 2010 at 9:25 am
I once wrote a script that would handle all the events. It’s a completely different way of thinking and writing applications. I did like it but to be honest, haven’t used that script again. Maybe node.js will change that some day
I think you’ll need that pattern a lot in node…
qFox on March 9th, 2010 at 12:46 pm
Nice!
I had a need to invoke not only when the event occurs but also in a certain order. I used conventions such (after) to bind appropriately that were pretty handy.
var listener = function(debug) {
var _events = [];
var _prevevent = null;
var _process = function(e, _args) {
_args = _args || '';
for (var i=0; i<_events[e].length; i++ ) {
if (debug) console.log(e, _events[e][i][1]);
_events[e][i][1].call(_events[e][i][0], _args);
}
};
this.bind = function(e, mod, func) {
if (e.constructor !== Array) {
e = [e];
}
for (var i in e) {
_events[e[i]] = _events[e[i]] || [];
_events[e[i]].push([mod, func]);
}
};
this.unbind = function(e) {
if (_events[e]) delete(_events[e]);
if (_events["<" + e]) delete(_events["" + e]) delete(_events[">" + e]);
};
this.invoke = function(e, _args) {
if (_prevevent != e) {
_prevevent = e;
if (_events[e]) {
if (_events["<" + e]) _process("" + e]) _process(">" + e, _args);
}
_prevevent = null;
} else {
throw ('Possibly event:' + e + ' invoked recursively.');
}
};
};
To bind:
listener.bind('custom_event', obj, obj.do_the_thing);
..
listener.bind('>custom_event', obj, obj.clean_up);
..
listener.bind('<custom_event', obj, obj.init);
Yusuf on March 13th, 2010 at 5:18 am
Another thing I stumbled upon in my custom events. What if calling the eventlistners adds another eventlistner. You might end up with an infinite loop. That actually happend to me. You need to copy the listeners to local array before you call them.
Allan Ebdrup on March 15th, 2010 at 3:07 pm
Yes, not cloning the array before entering the loop is a mistake I found in several implementations of event managers and the same problem exists also by removing event listeners from inside other event listeners.
By cloning the listeners array you avoid these problems and allow your code to be more specs compliant.
From W3C DOM Level 3 Events specifications (§ 3.1): “Once determined, the candidate event listeners cannot be changed; adding or removing listeners does not affect the current target’s candidate event listeners.”
Diego Perini on April 15th, 2010 at 3:44 pm
We have pretty huge codebase and a lot of legacy code, so I’ve start use “text events” only, example: utils.events.subscribe(‘core.widgets.widgetA’, handler)
so basically my utils.events became a proxy between new code and legacy one, few problems I have and still can’t solve in harmony way:
- same text event fired for all instances of core.widgets.colorPicker for example,
- lot of headache when you need to know both (callee and called objects or even full bunch of objects)
Probably it’s a wrong way from the beginning, but it works for 80% of integrations, so 20% hacking is a reasonable IMO, just interesting if you try something like this.
Regards
Anton Yatsenko on May 4th, 2010 at 8:39 pm
Hi Nick, in the event firing function, should the call method be passed the
event.targetrather thanthisas the first argument? If not, can you please explain why?Dean on July 29th, 2010 at 3:22 am
@Dean – Nicholas, not Nick. To answer your question, event handlers are typically fired with a this value equal to the object on which
addListener()exists, which is why I pass inthis.Nicholas C. Zakas on July 29th, 2010 at 6:27 pm
Apologies.
Thanking you kindly for the clarification.
Dean on July 29th, 2010 at 6:35 pm
Comments are automatically closed after 14 days.