Re-routing Events in Javascript

Sometimes, it is desirable to re-route an event in Javascript, from its original target to some other target. Suppose for example, you have two different elements that appear to be layered, one of top of the other, but they only appear this way because of CSS. In terms of the DOM, the elements would be siblings, as in:

<div id = "elem1">#elem1</div>
<div id = "elem2">#elem2</div>

With a little CSS, that HTML might look something like this:

#elem1
#elem2

It should be obvious, but I'll draw attention to it again because it's important for this discussion: #elem2 is not actually contained within #elem1, even though visually, it appears to be.

The implications of this are significant because it means that that any events that originate at #elem2 will not propagate (or bubble) up to the #elem1. You might very well want them to though.

For example, suppose you have an click event handler wired to #elem1 like so:

var elem1 = document.getElementById('elem1');
elem1.addEventListener('click', elem1Click, false);      // or your favorite addEvent(..) equalizer
function elem1Click(e)
{
   alert(e.target + ' was clicked');               // e.srcElement for IE
}

Clicking on #elem1 will cause an alert, but clicking on #elem2 won't, because #elem1 is not an ancestor of #elem2 and therefore, when the event that is dispatched from the 'click' event bubbles, it won't go through #elem1.

So how do you make the event go through #elem1? How you ensure that when a user clicks on #elem2 the 'click' handler for #elem1 fires? Through event re-routing!

An event detour

It's possible to manually dispatch an event in Javascript, and as usual, there are two (modern) ways to do it: The W3C DOM way, and the IE way. We'll start with the W3C method.

.dispatchEvent(evtObject)

The .dispatchEvent(..) method takes only one argument: the event object that you wish to dispatch. Sounds simple enough, but unfortunately it gets more complicated.

In order to dispatch an event, you must first create one. You do this through document.createEvent(evtType). But you're still not done. The event object you just created needs to be initialized, which can be done in a number of ways.

Here is a bare-bones example of creating and initializing a mouse event:

var myEvt = document.createEvent('MouseEvents');
myEvt.initEvent(
   'click'     // event type
   ,false     // can bubble?
   ,true      // cancelable?
);

At this point, you can go ahead and dispatch that lovely "click" event you just created/initialized to some DOM element of your choosing.

document.getElementById('myElem').dispatchEvent(myEvt);

And if all goes as planned, any "click" handlers wired to #myElem should fire.

This is all fantastic, but it leaves a number of important questions unanswered. Where was the mouse when the click occurred? Was the shift key depressed at the time? How about the ctrl key or the alt key?

To include this kind of meta-data with our event object, we need to change the syntax a bit, and supply a whole boat-load of additional parameters.

Here's an example.

var myEvt = document.createEvent('MouseEvents');
myEvt.initMouseEvent(
   'click'          // event type
   ,true           // can bubble?
   ,true           // cancelable?
   ,window      // the event's abstract view (should always be window)
   ,1              // mouse click count (or event "detail")
   ,100           // event's screen x coordinate
   ,200           // event's screen y coordinate
   ,100           // event's client x coordinate
   ,200           // event's client y coordinate
   ,false         // whether or not CTRL was pressed during event
   ,false         // whether or not ALT was pressed during event
   ,false         // whether or not SHIFT was pressed during event
   ,false         // whether or not the meta key was pressed during event
   ,1             // indicates which button (if any) caused the mouse event (1 = primary button)
   ,null          // relatedTarget (only applicable for mouseover/mouseout events)
);

// now dispatch the event
document.getElementById('myElem').dispatchEvent(myEvt);

The first thing you should notice is the use of .initMouseEvent(..) instead of just .initEvent(..), the former taking a much more complete set of parameters with which to initialize the event.

Generally speaking, you wouldn't do what I did in my example here. The parameters I passed in were pretty arbitrary (100, 200 for screen coordinates and such). Instead, if you're re-routing an event, you'll probably want to copy the properties of the original event. That would look something like this (comments omitted this time for brevity).

function clickHandler(e)
{
   var myEvt = document.createEvent('MouseEvents');
   myEvt.initMouseEvent(e.type, e.bubbles, e.cancelable, window, e.detail,
      e.screenX, e.screenY, e.clientX, e.clientY, e.ctrlKey, e.altKey, e.shiftKey,
      e.metaKey, e.button, e.relatedTarget);

   document.getElementById('someOtherElem').dispatchEvent(myEvt);
}

.fireEvent(evtType, [evtObject])

IE handles manual event dispatching a little differently. You're not forced to create an event object to pass into .fireEvent(..). That second parameter is optional actually. Something like this would be acceptable (though it still begs the same question that using .initEvent(..) does):

document.getElementById('myElem').fireEvent('onclick');     // don't forget the "on"

This would simply dispatch a "click" event to #myElem with a set of default values based on the current event object.

If you want change some property of the event object (e.g. you want to force the event object to interpret the ALT key as having been depressed), then you need to create a new event object, which is done with document.createEventObject([e]), and pass that into .fireEvent(..) as the 2nd parameter.

function clickHandler(e)
{
   // base this new event on the existing event object, e
   var myEvt = document.createEventObject(e);

   // force the ALT key to appear as having been pressed
   myEvt.altKey = true;

   document.getElementById('someOtherElem').fireEvent('onclick', myEvt);
}

Now, when clickHandler(..) executes, it will forwards its event (or more correctly, create a new event based on the current event and dispatches it) to #someOtherElem while also simulating that the ALT key was pressed.

One Caveat

You might wonder why we have to create a new event instead of just truly forwarding on the existing event object (I wondered this). To be honest, I don't know. All I know is that in Firefox, this didn't work. It generated one of those cryptic nsComponentSomethingOrOther errors, whereas creating a new event worked like a charm.

This might not be a problem in other browsers, but I didn't bother to check, since not supporting Firefox is never an option for me.

An Event Re-routing Demo

To drive home the things I've been talking about in this post, I've put together a simple demo involving two sibling <div> elements, one of which will forward its "click," "mousemove," and "mousedown" events to the other. To make the demo a little more interesting, I'll make it so that forwarded "click" events appear to have the SHIFT key pressed.

View the event re-routing demo

Comments, questions, suggestions, complaints? Speak thy mind!

sstchur posted this blog entry on at 1:47 am into the categories: Advanced Javascript // General

6 Responses

  1. kerry Says:

    Great Tutorial…easy to understand than the mozilla or xulplanet docs. Thank you!! very detailed indeed…

  2. Riccardo Says:

    Well, really well written and easy to understand. Thank you for sharing your knowledge

  3. Stephen Stchur Says:

    @kerry, Ricardo:

    Thanks for your comments. Glad it helped you out.

  4. stchur-BLOG » Blog Archive » Programmatically clicking a link in Javascript Says:

    […] anyone who has read my post about Re-routing events in Javascript post, you'll know that it is possible to simulate an event though the use of […]

  5. Mihai B Says:

    I know this is a very old post but I have found an easier and more generic way to route events using constructors:

    function eventHandler(e)
    {
    var myEvt = new e.constructor(e.type, e);
    document.getElementById('someOtherElem').dispatchEvent(myEvt);
    };

    And to attach listeners you can use a forEach loop:

    ['touchstart', 'touchmove', 'touchstop', 'mousedown', 'mousemove', 'mouseup'].forEach(function(key) {
    document.getElementById('someElem').addEventListener(key, eventHandler, false);
    });

  6. Sbcontt Says:

    I tried rerouting mousewheel using this, the event gets forwarded successfully, but the content inside elem1 is not scrolling. Scrolling is only visible in the logger.

Got something to say?

Please note: Constructive criticism is welcome. Rude or vulgar comments however, are not and will be removed during moderation.