Sunday, September 8, 2013

Keyboard Events with Character Data Stink in Every Language There Is


Well if the mountain won't come to Muhammad then Muhammad must go to the mountain. Or something like that.

Dart has eliminated every possible way to add character data to keyboard events—the way that is possible and relatively easy in JavaScript with KeyboardEvent. As I was busily marking all of my keyboard tests as “skip,” I got a note from Ewetumo Alexander suggesting that I try generating keyboard events with JavaScript.

So if Dart (for the moment) refuses to generate keyboard events with character data, why not go back to JavaScript?

Well, it turns out that it is neither easy or possible to generate keyboard events with character data in Chrome. In fact it is an incredible pain to generate events in JavaScript for any browser. I start with a little js-interop magic to create and dispatch a character keydown event:
type(String key) {
  var keyCode = KeyIdentifier.keyCodeFor(key);

  var event = js.context.document.createEvent('KeyboardEvent');
  event.initKeyboardEvent(
    "keydown",         //  in DOMString typeArg,
    true,              //  in boolean canBubbleArg,
    true,              //  in boolean cancelableArg,
    js.context.document.defaultView, //  in nsIDOMAbstractView viewArg,
    false,             //  in boolean ctrlKeyArg,
    false,             //  in boolean altKeyArg,
    false,             //  in boolean shiftKeyArg,
    false,             //  in boolean metaKeyArg,
    keyCode,           //  in unsigned long keyCodeArg,
    KeyCode            //  in unsigned long charCodeArg
  );
  js.context.document.dispatchEvent(event);
}
That KeyIdentifier.keyCodeFor() will return a integer keyCode give a character, so KeyIdentifier.keyCodeFor('A') will return 65. So I dispatch a keydown event with:
type('A');
I listen for the event in Dart with:
    document.onKeyDown.listen((KeyboardEvent e) {
      print('e.which: ${e.which}');
      print('e.keyCode: ${e.keyCode}');
      print('e.charCode: ${e.charCode}');
    });
Which results in:
e.which: 0
e.keyCode: 0
e.charCode: 0
What?

Eliminating potential points of failure, I dispatch events in JavaScript and listen in JavaScript. And find the same thing. It turns out that libraries like jQuery don't just normalize keyboard events in which because of different browser implementations. These libraries also do it so that they can trigger keyboard events with data. It seems that this is a known bug in Webkit browsers and one that Blink has yet to resolve.

There are a few solutions for dealing with this in JavaScript. Some follow jQuery's example by decorating the event with attributes. Others try dispatching vanilla events with enough attributes so that they can behave like keyboard events. Neither is of much use to me in Dart. Any attributes that I might add in JavaScript will be ignored in Dart because the KeyboardEvent is unaware of them. If I dispatch a vanilla event from JavaScript, Dart sees it as a vanilla Event object which lacks any keyboard-related properties.

Static typing giveth and static typing taketh.

I think my only option is to use JavaScript both to listen to events and to dispatch events. That presents its own challenges that make me wary of tackling it tonight. The primary challenge seems to be identifying the element on which to listen in JavaScript. I cannot just invoke js.context.document.getElementById because the element in question might not have an ID. Event if it does, I am in a situation in which I am replicating not only Dart's keyboard event classes but also its listener streams.

I may give that a shot tomorrow—especially if I can think of a way to limit scope. But wow. The mountain of generating keyboard events is way higher than I realized. I cannot wait until Dart's KeyEvent supports even a tiny bit of decoration.


Day #868

No comments:

Post a Comment