Handling IME events in JavaScript
Stack Overflow has been expanding past the English-speaking community for a while, and with the launch of both a Japanese version of Stack Overflow and a Japanese Language Stack Exchange (for English speakers interested in learning Japanese) we now have people using IME input regularly.
For those unfamiliar with IME (like I was a week ago), it's an input help where you compose words with the help of the operating system:
In this clip, I'm using the cursor keys to go up/down through the suggestions list, and I can use the Enter
key to select a suggestion.
The problem here is that doing this actually sends keyup
and keydown
events, and so does pressing Enter
. Interestingly enough, IME does not send keypress
events. Since Enter
also submits Comments on Stack Overflow, the issue was that selecting an IME suggestion also submits the comment, which was hugely disruptive when writing Japanese.
Browsers these days emit Events for IME composition, which allowed us to handle this properly now. There are three events: compositionstart, compositionupdate and compositionend.
Of course, different browsers handle these events slightly differently (especially compositionupdate), and also behave differently in how they treat keyboard events.
- Internet Explorer 11, Firefox and Safari emit a
keyup
event aftercompositionend
- Chrome and Edge do not emit a
keyup
event aftercompositionend
- Safari additionally emits a
keydown
event (event.which is 229)
So the fix is relatively simple: When you're composing a Word, we should not have Enter
submit the form. The tricky part was really just to find out when you're done composing, which requires swallowing the keyup
event that follows compositionend
on browsers that emit it, without requiring people on browsers that do not emit the event to press Enter
an additional time.
The code that I ended up writing uses two boolean variables to keep track if we're currently composing, and if composition just ended. In the latter case, we swallow the next keyup
event unless there's a keydown
event first, and only if that keydown
event is not Safari's 229. That's a lot of if's, but so far it seems to work as expected.
submitFormOnEnterPress: function ($form) { var $txt = $form.find('textarea'); var isComposing = false; // IME Composing going on var hasCompositionJustEnded = false; // Used to swallow keyup event related to compositionend $txt.keyup(function(event) { if (isComposing || hasCompositionJustEnded) { // IME composing fires keydown/keyup events hasCompositionJustEnded = false; return; } if (event.which === 13) { $form.submit(); } }); $txt.on("compositionstart", function(event) { isComposing = true; }) .on("compositionend", function(event) { isComposing = false; // some browsers (IE, Firefox, Safari) send a keyup event after // compositionend, some (Chrome, Edge) don't. This is to swallow // the next keyup event, unless a keydown event happens first hasCompositionJustEnded = true; }) .on("keydown", function(event) { // Safari on OS X may send a keydown of 229 after compositionend if (event.which !== 229) { hasCompositionJustEnded = false; } }); },
Here's a jsfiddle to see the keyboard events that are emitted.