DailyJS

Let's Make a Framework: Events

Alex R. Young

Subscribe

@dailyjs

Facebook

Google+

tutorials frameworks events web lmaf

Let's Make a Framework: Events

Posted by Alex R. Young on .
Featured

tutorials frameworks events web lmaf

Let's Make a Framework: Events

Posted by Alex R. Young on .

Welcome to part 9 of Let's Make a Framework, the ongoing series about
building a JavaScript framework. This part introduces events.

If you haven't been following along, these articles are tagged with
lmaf. The project we're creating is called Turing and is available on GitHub:
turing.js.

This part will look at how events work, event handler implementations in
various frameworks, and event handler API designs. I'll select an API
design at the end of the article and implement it for next week's
lesson.

Basics

Events and JavaScript are closely related -- can you imagine writing
scripts for web pages that don't respond to user interaction? That means
that as soon as JavaScript appeared, events did. Early event handlers
were written inline, like this:

You've probably seen this type of inline event handler before. This came
from Netscape originally, and due to the popularity of early versions of
Netscape, Microsoft implemented a compatible system.

This can be written in JavaScript like this:

// assume 'element' is the previous link minus the onclick
element.onclick = function() { alert('Hello World!'); };

Accessing the Event

An event handler can access an event like this:

function handler(event) {
    if (!event) var event = window.event;
}

window.event is a Microsoft property for the last event. I
always felt like this was dangerous and could lead to to a clobbered
value, but as JavaScript is single-threaded it's safe enough that most
frameworks depend on it.

jQuery does it like this:

handle: function( event ) {
  var all, handlers, namespaces, namespace_sort = [], namespace_re, events, args = jQuery.makeArray( arguments );
  event = args[0] = jQuery.event.fix( event || window.event );

Stopping Events

I used to get default actions and bubbling confused, because I thought
stopping an event just meant everything stopped. These two things are
different.

Default Action

Returning false from an event handler prevents the default
action:

element.onclick = function() { alert('Hello World!'); return false; };

Now the link won't be followed.

Capturing and Bubbling

Attaching an onClick to two elements, where one is the
ancestor of the other, makes it difficult to tell which element should
take precedence. Different browsers do it different ways. However,
fortunately it's very unusual to actually care about this -- in most
cases we just need to stop the event.

function handler(event) {
    if (!event) var event = window.event;
    event.cancelBubble = true;
    if (event.stopPropagation) event.stopPropagation();
}

As far as I know, only IE uses cancelBubble, but it's safe
to set it in browsers that don't use it. stopPropagation is
used by most browsers.

jQuery's implementation in
event.js is similar to the above, and most frameworks are broadly similar.

Multiple Handlers

If events were this simple we'd barely need frameworks to help us. One
of the things that makes things less simple is attaching multiple
events:

element.onclick = function() { alert('Hello World!'); return false; };
element.onclick = function() { alert('This was the best example I could think of'; return false; };

This example overwrites the onClick handler, rather than
appending another one.

The solution isn't as simple as wrapping functions within functions,
because people might want to remove event handlers in the future.

Framework APIs

The job of an event framework is to make all of these things easy and
cross-browser. Most do the following:

  • Normalise event names, so onClick becomes click
  • Easy event registration and removal
  • Simple cross-browser access to the event object in handlers
  • Mask the complexities of event bubbling
  • Provide cross-browser access to keyboard and mouse interaction
  • Patch browser incompetency like IE memory leaks

jQuery

Interestingly, jQuery's event
handling
approach
is to make the event object behave like the W3C standards. The reason
for this is that Microsoft failed to implement a compatible API.

jQuery wraps methods into its internal DOM list objects, so the API
feels very easy to use:

$('a').click(function(event) {
  alert('A link has been clicked');
});

This adds the click handler to every link.

  • The Event's element is in event.target
  • The current event within the bubbling phase is in event.currentTarget -- also found in the this object in the function
  • The default action can be prevented with event.preventDefault()
  • Bubbling can be stopped with event.stopPropagation();
  • Events can be removed with \$('a').unbind('click');
  • Events can be fired with \$('a').trigger('click');

Prototype

Prototype's event handling is closely linked to its core DOM Element class. Events are
registered like this:

$('id').observe('click', function(event) {
  var element = Event.element(event);
});
  • The Event's element can be accessed with Event.element(event) or event.element();
  • Events are stopped with Event.stop()
  • Events can be removed with Event.stopObserving(element, eventName, handler);
  • Events can be fired with Event.fire(element)

Glow

Glow's event handling is found in
glow.events. Events are registered with addListener:

glow.events.addListener('a', 'click', function(event) {
  alert('Hello World!');
});
  • The Event's element is in event.source
  • The default action can be prevented by returning false
  • Events can be removed with glow.events.removeListener(handler) where handler is the returned value from glow.events.addListener
  • Events can be fired with glow.events.fire()

Glow's jQuery influence is evident here.

Dojo

Rather than ironing out the problems in browser W3C event handling
supporting, dojo uses a class-method-based system like Prototype, but
with a different approach. Dojo's API is based around connections
between functions. Registering a handler looks like this:

dojo.connect(dojo.byId('a#hello'), 'onclick', function(event) {
  alert('Hello World!');
});

Notice that the event names aren't mapped. Like the other frameworks,
the event object is normalised.

  • The Event's element is in event.target
  • The current event within the bubbling phase is in event.currentTarget -- this is also this in the function
  • The default action can be prevented with event.preventDefault()
  • Bubbling can be stopped with event.stopPropagation();
  • Events can be removed with dojo.disconnect()

Conclusions

Out of all these frameworks, jQuery's event handling is the most
fascinating. It makes a familiar W3C event system available to all
browsers, carefully namespaced, and completely takes over event bubbling
to achieve this. This is partially because Microsoft's API has major
problems with bubbling.

Building something in between jQuery and Glow is suitable for Turing --
I don't want to worry about bubbling too much, but we will need a system
that will cope with multiple handlers on the same event and the removal
and firing of handlers.

Next week I'll start building the event handling code, building with the
following goals in mind:

  • Normalise event names, so onClick becomes click
  • Easy event registration and removal
  • Simple cross-browser access to the event object in handlers
  • Mask the complexities of event bubbling
  • Provide cross-browser access to keyboard and mouse interaction
  • Patch browser incompetency like IE memory leaks

If you want to read more about events, browser wars, and Microsoft's
frustrating inability to make life easy, Introduction to
Events
on QuirksMode
covers the history and basics, and branches out into an entire series.