Two days ago, I found that a known bug in Dart prevented me from using the
KeyEvent
class. The KeyEvent
class is supposed to normalize all kinds of browser craziness surrounding keyboard handling, so I was bummed that I was unable to use it.But nothing is preventing me from creating a subclass of
KeyEvent
that does not suffer from the problem. So I do just that:
class KeyEventX extends KeyEvent { KeyboardEvent _parent; KeyEventX(KeyboardEvent parent): super(parent) { _parent = parent; } // Avoid bug in KeyEvent // https://code.google.com/p/dart/issues/detail?id=11139 String get $dom_keyIdentifier => _parent.$dom_keyIdentifier; String get keyIdentifier => _parent.$dom_keyIdentifier; // ... }The two getter methods for key identifier should avoid the “Unsupported operation: keyIdentifier is unsupported” error from the other day. Instead of accessing the
keyIdentifier
property of the soon-to-be-awesome-but-currently-buggy KeyEvent
class, I obtain it from the plain-old KeyboardEvent
that DOM programmers know and love.To make use of my new
KeyEventX
class, I need a stream that produces instances of it. I rather like the simplicity of the KeyboardEventStream class, so I extend its functionality into a new KeyboardEventStreamX
class:class KeyboardEventStreamX extends KeyboardEventStream { // ... static Stream<KeyEventX> onKeyDown(EventTarget target) { return Element. keyDownEvent. forTarget(target). map((e)=> new KeyEventX(e)); } }Given an event target (i.e. a DOM Element), I return a stream that maps regular
KeyboardEvent
instances into KeyEventX
instances. It is pretty awesome that streams are iterable—meaning that I transform my event without even the minor complexity of a stream transformer.With my
KeyboardEventStreamX
class that produces a stream of my non-buggy KeyEventX
objects, I can now rewrite some of the keyboard controls in the ICE Code Editor as: KeyboardEventStreamX.onKeyDown(document).listen((e) {
if (!e.ctrlKey) return;
switch(e.keyIdentifier) {
case 'U+004E': // N
new NewProjectDialog(this).open();
e.preventDefault();
break;
case 'U+004F': // O
new OpenDialog(this).open();
e.preventDefault();
break;
// ...
}
});
And that works! The dartanalyzer
tool is happy with the type information, the copious tests that I have written are all still passing and everything seems OK when I smoke test the sample application.Now, that is nice and all, but since I am writing this to support keyboard shortcuts, I do not want to be checking for control keys, then checking for the character key. I want a predicate method on my
KeyEventX
that tells me if it is a Ctrl+N or Ctrl+O.Thanks to last night's
keyIdentifierFor()
helper method, I can do just that:class KeyEventX extends KeyEvent { // ... bool isCtrl(String char) { if (!ctrlKey) return false; if (keyIdentifier == keyIdentifierFor(char)) return true; return false; } String keyIdentifierFor(char) { if (char.codeUnits.length != 1) throw "Don't know how to type “$char”"; // Keys are uppercase (see Event.keyCode) var key = char.toUpperCase(); return 'U+00' + key.codeUnits.first.toRadixString(16).toUpperCase(); } }With
isCtrl()
in hand, I can rewrite my keyboard shortcut code as: KeyboardEventStreamX.onKeyDown(document).listen((e) {
if (e.isCtrl('N')) {
new NewProjectDialog(this).open();
e.preventDefault();
}
if (e.isCtrl('O')) {
new OpenDialog(this).open();
e.preventDefault();
}
});
That is very nice.To be sure, there is some code duplication that I can clean-up in there. But I have made my keyboard handling code much cleaner and easier to read. Gone are the unicode strings. In are the the easy-to-follow
isCtrl('N')
booleans. I like.And, unless I am wrong (though I am often wrong), I do believe that I am dangerously close to a legitimately testable approach to keyboard events in Dart. Last night, I was able to generate keyboard events in test that registered identically to those produced in Chrome. Tonight, I have significantly improved handling those events. All that is lacking is the ability to normalize those events across browsers. Dart will get there eventually. Until that happens, I have the perfect place get started: in
KeyEventX
. I will pick up with that tomorrow.Day #838
No comments:
Post a Comment