Wednesday, March 14, 2007

Cross Browser Keyboard Handler

If you ever thought of writing a javascript editor you will know what I am talking about. Thats right. I am writing a cross browser WYSIWIG In Place javascript editor! Well this post is not about the editor itself. I may do that some time in the future.

The first problem you have to solve is cross browser keystroke handling because almost every browser has some quirks. I needed a keystroke handler that really was cross browser. Running a search on net, i didnt find a complete solution though there were a lot of interesting partial solutions. So i had to put these together and come up with my own.

Here is an excellant article on keystroke detection http://www.quirksmode.org/js/keys.html

As you should know by now, all browsers report the keypress, keydown, keyup events. And each of these events should report the KeyCode and Charcode. Of cource it doesnt allways happen. Ideally for each key event the browser should report the Charcode (the ascii character pressed) and the KeyCode (actual key). So if you hit "a" the Charcode would be 95 and the keycode 65.

For the sake of this discussion let us separate key events into character events and non character events, you will see why soon. Character events occur when a printable character is pressed and non character event occurs when a non printable character (navigation keys, backspace, delete etc) is pressed.

My Ideal browser (there is none) would for character events give the charcode and 0 for keycode. For non character events it would give keycodes and charcode 0. Your keystroke handler would be simple to write.

Mozilla comes closest to this ideal but it also reports keycodes for character events, like in the case of character "a" above. Even then it would be easy to write the keyboard handler if all the browsers behaved the same.

Why do we need keycodes and charcodes? Why not just one CODE? Well for starters there is an overlap of character event codes and non character event codes in the range 33 to 47. Character "%" has the same charcode 37, as the keycode 37 of non character "left arrow".

There are a lot of inconsistencies between browsers as highlighted in the link I gave above. However to solve my problem i needed to find the consistencies between browsers rather than the inconsistencies. Fortunately there are some consistencies.

For the keypress event all the browsers are consistent when reporting character events. Either the charcode or keycode will give you the right character code. But the keypress event is useless for non character events. In fact IE does not even fire the keypress event for non characters.

However for keydown event all browsers are consistent when reporting non character event codes. They correctly give you the right keycode. Even Safari which reports keycodes greater than 64000 for navigation keys on keypress, reports correctly for keydown.

KeyUp also works more or less like keyDown. Except on IE I wasnt able to capture the backspace key on KeyUp. It is important to capture the backspace key because the browser would otherwise resort to its default behaviour and mimick the back button and get off your page completely!

Here is the code.



document.onkeydown = function(e) {handleKeys(e)}
document.onkeypress = function(e) {handleKeys(e)}
var nonChar = false;

function handleKeys(e) {
var char;
var evt = (e) ? e : window.event; //IE reports window.event not arg
if (evt.type == "keydown") {
char = evt.keycode;
if (char < 16 || // non printables
(char > 16 && char < 32) || // avoid shift
(char > 32 && char < 41) || // navigation keys
char == 46) { // Delete Key (Add to these if you need)
handleNonChar(char); // function to handle non Characters
nonChar = true;
} else
nonChar = false;
} else { // This is keypress
if (nonChar) return; // Already Handled on keydown
char = (evt.charCode) ?
evt.charCode : evt.keyCode;
if (char > 31 && char < 256) // safari and opera
handleChar(char); //
}
if (e) // Non IE
Event.stop(evt); // Using prototype
else if (evt.keyCode == 8) // Catch IE backspace
evt.returnValue = false; // and stop it!
}