Jul 04

Dealing with W3C Events; A story of running around in circles

Ajax, Tech with tags: , , 3 Comments »

I am working on an interesting pet project that has a fairly rich UI. A rich UI means dealing with events, and I had a wake up call on what a pain it can be to deal with in the browser world.

I ended up on a wild goose chase that ended up having a simple solution.

Imagine one section of an application that is a canvas element, and another piece that is a input type="text". I wanted a way to jump between the two with a keystroke, e.g. Ctrl-J for jump.

Jumping from the canvas element to textbox was fine, but it wouldn’t work on the way back. I was trying to $('canvas').focus() but it wouldn’t work.

initMouseEvent

I stupidly thought that I should just simulate the click on the canvas element, since that was working just fine for me.

We are pretty fortunate that the ability to kick off true events programatically is bound into the event model itself.

This means that I could:

if (document.createEvent) {
   var evt = document.createEvent('MouseEvents');
   evt.initMouseEvent('click',
            true,     // Click events bubble
            true,     // and they can be cancelled
            document.defaultView,  // Use the default view
            1,        // Just a single click
            0,        // Don't bother with coordinates
            0,
            0,
            0,
            false,    // Don't apply any key modifiers
            false,
            false,
            false,
            0,        // 0 - left, 1 - middle, 2 - right
            null);    // Click events don't have any targets other than
                      // the recipient of the click
 $('canvas').dispatchEvent(evt);
}

This only slightly worked. I could test that the click was going through. I would add an onclick handler to canvas and it would fire just fine. However, something weird was happening. The focus was still in the textbox even with this click happening (which didn’t happen when I actually clicked on the darn thing).

Events from within events

Since the click was working, why not just force the blur on the textbox?

var events = document.createEvent('HTMLEvents');
var blur = events.initEvent('blur', false, false);
his.dispatchEvent(blur);

But that got me:

Component returned failure code: 0×80070057 (NS_ERROR_ILLEGAL_VALUE) [nsIDOMEventTarget.dispatchEvent.....]

Hmm, on wait. This was happening from within an onkeypress handler and it appears that you can’t do this in a nested way. A setTimeout(..) to push it off until later didn’t make it any happier either.

tabindex=0

As I looked at this mess, I quickly realised that I had been unravelling a string, and I should take a step back.

I talked to Alex Russell, and he immediately said “erm, and you made sure that tabindex was set on the canvas element?”

Doh, it wasn’t. This is why the focus method (which did exist) didn’t do anything. I quickly added tabindex=-1 and now I could simply $('canvas').focus() and lo-and-behold it would focus!

Event.stop(e)

Great, the home stretch. I took the test and put it on the live code and it… still didn’t quite work. God damn it.

I took another step back.

A global key handler was set via document.onkeydown that was handling the world. When it saw a Ctrl-J it would short circuit and give focus to the textbox. The textbox has its own onkeydown that would do the opposite. A global flag held the state of global/textbox.

This is the problem. I had mucked up the event propogation, so in the case of jumping from textbox to canvas it would first go through the textbox handler, but after it was done it would STILL run the global handler which would kick it right back!

I just needed to e.stopProgagation(), or even better Event.stop(e) to stop any default action too.

Phew, all that running around to get back to square one. Now it works, and it reminded me of how the simplest things can be tricky.