Back

Events for Web Applications

Last week I ran into a problem while developing my smart indicator prototype. The front-end of the indicator layer requires updates if a user interacts with the user-interface. However, I wanted to avoid repetitive polling with the server. Instead, the indicator layer should update if the front-end sensors detected some changes, because the information from the front-end sensors is the most critical for some indicators. In general terms, I want to have several parts of a web-application that should be informed on what is going on in the business logic, on one hand. On the other hand, the parts of the web-application should not be too dependent on each other. As a solution I wrote a little javascript event manger, that allows components to peak and hook into the business logic of an AJAX application, without the need of touching the business logic.

Experienced web-programmers will now think that we already have DOM events, what do we need application events for. Well, there is a major difference between DOM events and application events. While DOM events are triggered by user interactions with the DOM structure that is displayed in the browser window, application events are triggered by the application itself. These events usually have richer semantic information than simply mouse-click or mouse-down, but they can inform other parts of the same environment that something happened they might to react upon. For example, in my prototype I have an authentication widget, which allows users to register as well as to log in and out the system. If a sensor wants to track all logins, it can simply add an event listener function that is triggered if a user logs into the system.

As long only sensors were working, that was fine. Now I need to include the indicator layer, that should be initialized when a user logs into the system and removed when logs off the system. The straight forward solution would be to replace single callbacks with an array of callbacks. But there is one big drawback with application callbacks: all modules depend strictly on the component that calls the callback. That means if I choose to use a different session management approach, my sensors will not detect the incoming information; and the indicator layer will not get initialized. Therefore, I wondered how to make the different components less dependent on each other.

My idea was to have something similar to DOM events, however, with more semantics attached to it: an event manager, to which components can assign callbacks for event handling and that other components can use to inform the system about events that occur in the business logic. This allows other components to react on the event without the need to be aware where the event has been issued.

As smart people suspect, this requires some adaptation of the business logic. Thus, if an application is not willing to interact, the entire principle will not work ;-)

OK, so much for the theory, but what is the solution? Well, I wrote a tiny event manager for application events. So, in order to make use of the application events you need to load this extension first. The following code snippet provides an example how to integrate application events into existing code.

// first initialize the event manager
EventManager = new MlyEventManager();
function handle_login() {
   // ... your business logic ...
   if ( EventManager ) {
       // if the event manager is active
       // invoke the event
       EventManager.trigger( 'login' );
   }
}

That looks simple - and in fact it is. First, the event manager is initialized. Once the event manager is available functions can trigger() events in order to inform other components about the event. Other components can register event handlers to application events as one would register a DOM event. Your business logic is no longer affected if other components want to hook into it. The if-clause that tests for the event manager assures that your code is not affected if application events are not available in an environment.

For the other side some more work is required. If a component wants to handle an application event it has to register an event handler first.

 function fHandler(e) {
     alert( 'caught event ' + e.event );
 }
 eventManager.addEventListener( 'login', fHandler );

The fHandler() function is invoked if a login event is raised. If the handle_login() function raises the login event, the event manager will lookup the event handlers for this event. In our case it will find that the fHandler() function has been registered and invokes it. With the addEventListener() function it is possible to stack multiple handlers for an event.

Lets have a closer look at event handlers. Each event handler has to accept one argument. This argument will be a reference to the event manager that invoked the event handler. The event manager instance tells the event handler for example, which event is currently processed. This information is stored in the event variable. In the example I used it only for an alert box that shows the current event name. More complex handlers might get registered to many events, simultaneously, and adapt their behavior according to the active event.

Moreover, event handlers may also call other application events, or cancel the event handling process. For this purpose I implemented three functions: cancelEvent(), stopPropagation(), and cancelEverything(). cancelEvent() stops the event processing for this event. In case a previous event handler called another event, this event is still handled. In order to stop any further handling of issued events, I provide the stopPropagation() function. It cancels all previously issued events but leaves the current event unaffected. This means that following handlers will still be called and may raise new events, themselves. Finally, there is an emergency break which is named cancelEverything(). This function is shortcut to cancelEvent() and stopPropagation(). You can think of it as an emergency break for the event handling.

Finally, I added a little feature that I call attached handlers. These handlers are event handlers that are called after each event. However, these handlers are independent from those handlers that are assigned to a specific event. The purpose of these handlers is for debugging purposes and other low level tricks, because these handlers are not affected by any of the canceling functions.