CLOSE ×
Get in Touch
Thank you for your interest! Please fill out the form below, and I will do my best to get back to you.

Thank you! Your submission has been received!

Oops! Something went wrong while submitting the form.

Custom Event Management in JavaScript

Garth Henson
|
JavaScript
|
27 Sep 2011

If you have used any JavaScript libraries before, you are most likely quite familiar with the concept of binding functions to specific events within an object, but have you ever considered creating your own custom events for your JavaScript classes to allow users even more flexibility in implementing your code? Binding listeners to user events (such as click or mouseover) is a necessity for robust coding, but what happens when I want to allow developers to execute a specific bit of logic only when my library element has been rendered to the page? I need to build my code in such a way as to give “hooks” to the coder in the form of events for which they can listen.

Some libraries like ExtJS already support quite extensive custom events, primarily on their UI elements. It is this behavior for which I was seeking in a current project. Quickly becoming tired of implementing the same addListener(), removeListener and fireEvent() methods on all my framework elements, I began to devise a more generic way of enabling and implementing custom events in my classes. The challenge was not blurring the line of context too much between the event management system and the actual object firing the events themselves. I wanted a system that would allow for object level interaction with events without having to duplicate the code over and over again. Enter fn.call() and fn.apply().

JavaScript call() and apply()

Those of you well versed in the specifics of executing JavaScript functions and providing a context in which they are to execute, feel free to jump ahead to the next section, but an understanding of this principle is paramount for fully understanding how this design works. In concept, the execution is very straight forward: I want to define a function that can be called from within the scope of a separate object. Basically, I want to create a function foo() and get it to behave as though it were part of a class – Foo.bar().

This is where call() comes in. By passing our context object (or scope) to call(), the function is executed in that context:

As you can see, our function is written to reference this.name from within the context in which it is executed. If you were to execute it directly in the global context (bar()this.name is not defined. However, by calling the function from the context of a Foo object, we can resolve the name and alert it successfully.

Additional parameters that are passed to call() will serve as parameters for the function definition itself:

This code will first alert “Foo”, as it is the member variable for the object on which bar() is executed. Secondly, “baz” will be alerted, since this is passed as the parameter to the function itself. Any number of additional parameters can be passed into the call() method, and all will be passed along to the underlying function call.

However, occasionally, you may have a need for a variable length of parameters to be passed along, so rather than duplicating code, we can use the apply() method instead. Rather than accepting an indefinite number of parameters, apply() takes the context for the first argument and an array as the second. This array will be expanded and passed along as individual parameters to the executed code:

As you might expect, this snippet will allow you to change the coordinates of the Point object by passing in an array of new coords.

Now that we have covered the basics behind the core JavaScript implementation we’re going to leverage, let’s see how this can be applied to custom event management.

Setting up the Event Manager

Our manager is fairly simple in design. It expects that an object on which it executes will have a hash of listener arrays keyed by the event to which they are bound. In this way, if we fire a “render” event, all the listeners should be stored under this.listeners.render. By looping over and triggering whatever exists in that portion of the hash, we have successfully executed our event listeners.

With this alone, we already have something functional we can use on an object to fire a custom event. By calling fireEvent on the context of an object, that object will be checked for any internal listeners for the event being fired:

Now that we are able to trigger events within our individual contexts, we want to set up a way to bind and release listeners from those contexts. To do so, let’s set up addListener() and removeListener() methods respectively:

This might look like a significant bit of code, but it’s very straight forward. If we call addListener() and provide both an event name and a function, the function will be bound to the event name we provide. Of course, we want to do a little sanity checking, so we want to validate that the second parameter is indeed a Function instance. Likewise, if we provide an event to removeListener(), we can clear out a specific action from the assigned behavior. Consider the following:

While functional, this is still not ideal. For one thing, the average JavaScript coder who picks up your class to use may not understand the gist of using call() for all the assignments and firing of events. In order to simplify this a bit, we do a little bit of voodoo that will set up object methods for each class in which we want to use events that can be used as standard methods. By creating an enable() method, we can take the context from the user once and implement the other Events calls without the need for intricate JavaScript knowledge. Consider the following code (our last method within the Events object):

All we’re doing is creating wrappers for our Events methods, but we’re creating a mechanism by which we pass along the context of the current object. So, once we have enabled event management for a class (within its initialization logic is ideal), we can simply call the management methods directly on the object itself:

To see the fully functional and documented definition for the Events object, feel free to view the source here (opens in new window).

Implementation and Usage

Now that we have a fairly robust events management system, let’s put it to good use. By defining the API for each event that is fired, we can provide very nice interaction and customization to our users. Let’s take a very basic use case and look at how we might want to use some event management for logging or debugging purposes. Let’s build a simple Car object with accel() and decel() methods that modify a member variable this.speed. We will enable event mapping and fire the “accel” and “decel” events upon successful execution of each method:

Notice that we’re adding in some callback parameters here. In this case, we’re providing the current speed as a parameter to the listeners when we fire the event. By defining the API of the event listeners within our own classes, we can very easily instruct our users on what information is available to them at which time. In a case like this, we could easily build a new Car object and slap on a listener to the “accel” event to check that we’re not going too fast:

Of course, this is a very basic example to show the power behind creating your own events within your own JavaScript classes. You can see how attaching events to the render mechanism for advance UI elements or to specific actions within a complex Ajax communication would come in handy. If you were to tack on a few listeners that simply did some debug logging, you could more easily trace and identify problems in your code by seeing where execution failed.

Additionally, be aware that the addListener() method simply pushes the new listener onto an array, so you can add multiple listeners from different places in your code. Each will be executed and passed the same set of parameters.

I hope this article has been clear and helpful. I have tested each code snippet to assure it works, but if you have questions or recommendations that would enhance this concept, please feel free to contact me.

-GH

Garth Henson
Garth is as a lead engineer at The Walt Disney Company, specializing in JavaScript applications.

Recent Blog Posts

Let's Work Together
Contact Me