Let's Make a Framework: Events Part 3

Alex R. Young





tutorials frameworks events web lmaf

Let's Make a Framework: Events Part 3

Posted by Alex R. Young on .

tutorials frameworks events web lmaf

Let's Make a Framework: Events Part 3

Posted by Alex R. Young on .

Welcome to part 11 of Let's Make a Framework, the ongoing series about
building a JavaScript framework. This part continues looking at 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:

Stopping Events

Once an event has been triggered it can propagate to other elements --
this is known as event bubbling. To understand this, try to think
about what happens if you have a container element and attach an event
to an element inside it. When the event is triggered, which elements
should receive the event?

We often don't want to propagate events at all. In addition, some
elements have default actions -- a good example is how a link tag's
default action makes the browser follow the link.

Prototype's Event.stop() method simplifies event
management by cancelling event propagation and default actions. We
generally want to do both at the same time.

jQuery models the W3C's Document Object Model Events
providing lots of methods on the event object itself:

  • event.preventDefault(): Stop the default action of the event from being triggered
  • event.stopPropagation(): Prevents the event from bubbling up the DOM tree, preventing any parent handlers from being notified of the event
  • event.stopImmediatePropagation(): Keeps the rest of the handlers from being executed and prevents the event from bubbling up the DOM tree

Our Stop API

I've modelled Turing's API on jQuery, with the addition of
stop(). The reason I like jQuery's approach is it creates a
cross-browser W3C API, which may future-proof the library.

Event objects are extended with:

  • event.stop() - Prevents the default handler and bubbling
  • event.preventDefault() - Prevents default handler
  • event.stopPropagation() - Stops the event propagating

Usage is best illustrated with a test from

should('stop', function() {
  var callback = function(event) { event.stop(); };
  turing.events.add(turing.dom.get('#link2')[0], 'click', callback);
  // ...

The Implementation

I've created a private function to extend and fix event objects. This
essentially patches IE and adds stop():

function stop(event) {

function fix(event, element) {
  if (!event) var event = window.event;

  event.stop = function() { stop(event); };

  if (typeof event.target === 'undefined')
    event.target = event.srcElement || element;

  if (!event.preventDefault)
    event.preventDefault = function() { event.returnValue = false; };

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

  return event;

Other Browser Fixes

Most frameworks patch other browser inconsistencies as well. Keyboard
and mouse handling in particular are problematic.

jQuery corrects the following:

  • Safari's handling of text nodes
  • Missing values for event.pageX/Y
  • Key events get event.which and event.metaKey is corrected
  • event.which is also added for mouse button index

Prototype also has similar corrections:

var _isButton;
if (Prototype.Browser.IE) {
  // IE doesn't map left/right/middle the same way.
  var buttonMap = { 0: 1, 1: 4, 2: 2 };
  _isButton = function(event, code) {
    return event.button === buttonMap[code];
} else if (Prototype.Browser.WebKit) {
  // In Safari we have to account for when the user holds down
  // the "meta" key.
  _isButton = function(event, code) {
    switch (code) {
      case 0: return event.which == 1 && !event.metaKey;
      case 1: return event.which == 1 && event.metaKey;
      default: return false;
} else {
  _isButton = function(event, code) {
    return event.which ? (event.which === code + 1) : (event.button === code);

You can also find similar patching in MooTools:

if (type.test(/key/)){
  var code = event.which || event.keyCode;
  var key = Event.Keys.keyOf(code);
  if (type == 'keydown'){
    var fKey = code - 111;
    if (fKey > 0 && fKey < 13) key = 'f' + fKey;
  key = key || String.fromCharCode(code).toLowerCase();
} else if (type.match(/(click|mouse|menu)/i)){
  doc = (!doc.compatMode || doc.compatMode == 'CSS1Compat') ? doc.html : doc.body;
  var page = {
    x: event.pageX || event.clientX + doc.scrollLeft,
    y: event.pageY || event.clientY + doc.scrollTop
  var client = {
    x: (event.pageX) ? event.pageX - win.pageXOffset : event.clientX,
    y: (event.pageY) ? event.pageY - win.pageYOffset : event.clientY
  if (type.match(/DOMMouseScroll|mousewheel/)){
    var wheel = (event.wheelDelta) ? event.wheelDelta / 120 : -(event.detail || 0) / 3;


Over the last 3 weeks I've introduced event handling, explained how to
use the W3C and Microsoft APIs, and built an event handling framework.
The life cycle of event handling has been explained an implemented, from
creating events to stopping and removing them. I've also demonstrated
how differences between browsers are dealt with.

You can find the current version of Turing on GitHub: