jQueryでIME入力確定時にイベントを発行する

# 新しいものに書き直しました
下記のものではうまくイベントが発火しない場合がありましたので、[inputやtextareaの値が変わったらイベント発火（IME入力中を除く）](http://qiita.com/hrdaya/items/29576a5e1a303c074249)に新バージョンとして書き直しましたので、そちらも参照ください。

# はじめに
テキスト入力項目で英数字は半角に統一したいけど、ユーザに「英数字は半角で入力してください」なんてエラーを表示するのはいただけないよねと思い、「それなら自動でフィルタリングしてあげればいいじゃん」なんて考えてしまったのが運のつき :sweat: 
日本人には切っても切れないIME入力という魔物が住んでいました :scream: 

IMEの入力でなければ「keyup」のイベントを拾って変換してやればよいのですが、IME入力中にこれをやっちゃうと入力している文字が確定されちゃう :fearful: 
ならば「keyup」のイベントをディレイしてやる:heavy_exclamation_mark: と思ったら、キー入力の遅い人だと途中でやっぱり確定されちゃう :cold_sweat: 

はいはい手抜きしようと思った私が悪かったです、ちゃんとIME入力確定を見極めてやる :bangbang: と思ったらIME様は行く先のブラウザごとに違う動作をしやがる :weary: 
キーーーーーーッ :anger: 

ということで分かる範囲でIMEの入力時の挙動を調べてみました。
そしてフィルタリングの部分は人それぞれだと思ったので、イベントを発行することで汎用的に使えるものをIME様の動作に翻弄されながらなんとかjQueryのプラグインとして作ってみましたよ :joy: 

ソースは「[GitHub](https://github.com/hrdaya/jQuery.imeEnter)」に置いています。
ついでにカット・ペースト時にもイベントが発火するように仕込んでいます。

説明とデモは「[こちらのページ](http://hrdaya.github.io/jQuery.imeEnter/)」を見てください。

Windows7でしか確認していないのでベータ版みたいなものですが、GitHubのissueに「動作報告」や「バグ・改善の報告」をいただけると助かります。
というかWindows7しか持っていないのでプルリクあげてもらえると大いに助かります。

# ブラウザごとのIME入力時の挙動

## Internet Explorer11
珍しく一番素直な動作をしている :kissing_closed_eyes: 

* 「keydown」時にキーコードが「229」の時はIME入力中
* 入力の確定時に「keyup」でキーコード「13」が渡される
* 文字入力中も「keyup」が発火する

以上の特徴から「keydown」でキーコードが「229」の後に「keyup」でキーコード「13」がくれば入力確定となる

1. 「keydown」時にキーコードが「229」が渡されたときはIME入力中の変数にtrueをセット
2. 「keyup」時にキーコード「13」が渡され、IME入力中の変数の値がtrueの時はIME入力確定

## Chrome
一番厄介と感じているのは意外にもこいつ :astonished: 

* 「keydown」時にキーコードが「229」の時はIME入力中
* 入力の確定時には「keyup」は発火しない
* 通常の文字入力時は「keyup」は発火している

以上の特徴から「keydown」時にキーコードが「229」の時は一定時間後「keyup」が無ければ入力確定と考えられるので

1. 前回「keydown」時に設定したタイマー処理のクリア
2. 「keydown」時にキーコードが「229」の時に~~300~~500ミリ秒後にキーアップしたかどうか確認するタイマー処理をセット（タイマー処理内でキーアップが確認できないときは入力確定として強制的にキーコード「13（エンター）」の「keyup」イベントを発火）
3. 一旦キーアップ確認用の変数をfalseにセット
4. 「keyup」時にキーアップ確認用の変数をtrueにセット

以上でIMEの入力確定を捕捉することができる。
~~※タイマーのタイミングなのかキーを押しっぱなしにした時は最初の一文字目の時にIME入力確定のイベントが発火されてしまう。（タイマーの時間については今後調整必要）~~
キー入力の速い人や、押しっぱなし時の対策として500ミリ秒に設定しなおしました。

## Firefox
可とも不可ともいえない動作 :worried: 

* IME入力の一番最初に「keydown」でキーコード「0」が渡されるとIME入力モードに突入
* エンターキーを押すまではキーイベント一切無し
* 入力の確定時に「keyup」でキーコード「13」が渡される

以上の特徴から「keydown」でキーコードが「0」の後に「keyup」でキーコード「13」がくれば入力確定となる

1. 「keydown」時にキーコードが「0」が渡されたときはIME入力中の変数にtrueをセット
2. 「keyup」時にキーコード「13」が渡され、IME入力中の変数の値がtrueの時はIME入力確定

IME入力中にキーイベントがまったく起きないという実装もどうかと思うが...

# ソースの一部抜粋
2015/03/03内容更新

```js
var _this = this;

// タイマー用
var timer;

// IME入力中かどうかを判断するフラグ
var imeFlag = false;

// キーアップイベントが発火したかどうかを判断するフラグ
var keyUpFlag = true;

// 直前のキーアップコード
var beforeKey = 0;

// 捕捉するキーイベント
var keyEvents = [
    'keydown.' + pluginName,
    'keypress.' + pluginName,
    'keyup.' + pluginName
];

// カット・ペーストのイベント
var otherEvents = [
    'cut.' + pluginName,
    'paste.' + pluginName
];

// 一旦イベントのoff
this.off();

// カット・ペースト時
_this.$elm.on(otherEvents.join(' '), function (event) {
    var $elm = $(this);
    // 右クリックでのカット・ペーストのイベント対策
    setTimeout(function () {
        // enter.imeEnterイベントの発行
        _this._trigger($elm);
    }, 0);
});

// キーイベント
_this.$elm.on(keyEvents.join(' '), function (event) {
    var $elm = $(this);

    // イベント発火時のキーコード
    var keyCode = event.keyCode;

    // イベントを実行しないKeyupキーコード
    var exceptKeys = [
        3, // Cancel
        6, // Help
        9, // TAB
        15, // Command
        16, // Shift
        17, // Ctrl
        18, // Alt
        19, // Pause
        20, // Caps Lock
        21, // KANA
        22, // Mac"英数" キー
        23, // JUNJA
        24, // FINAL
        25, // KANJI
        27, // Esc
        28, // 「前候補・変換」
        29, // 「無変換」
        30, // ACCEPT
        31, // MODECHANGE
        33, // Page Up
        34, // Page Down
        35, // End
        36, // Home
        37, 38, 39, 40, // 矢印
        41, // SELECT
        42, // PRINT
        43, // EXECUTE
        44, // Print Screen
        45, // Ins
        91, // 左Windowsキー
        92, // 右Windowsキー
        93, // 右クリックキー
        95, // SLEEP
        112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, // ファンクションキー
        124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, // ファンクションキー
        144, // Num Lock
        145, // Scroll Lock
        224, // Command
        225, // AltGr
        240, 241, // Caps Lock
        242, // 「カタカナ・ひらがな」
        243, 244  // 「半角・全角」
    ];

    // イベントのタイプごとに処理
    //　通常は「keydown」→「keypress」→「keyup」の順にイベントが発火する
    switch (event.type) {
        case 'keydown':
            // 一旦IME入力中フラグのリセット
            imeFlag = false;

            // 設定していたタイマーのリセット
            clearTimeout(timer);

            // キーコードが229の場合はIME入力中とみなす（Chrome・IE用）
            // キーコードが0の場合はIME入力開始とみなす（FF用）
            if (keyCode === 229 || keyCode === 0) {
                // IME入力中フラグのセット
                imeFlag = true;
            }

            // Chrome対策
            // キーコードが229の場合
            // 直前のキーアップコードが「半角・全角」でない
            // 直前のキーアップコードが「カタカナ・ひらがな」でない
            // 直前のキーアップコードが「Caps Lock」でない
            // alt, ctrl, option, commandキーの同時押しでない
            if (keyCode === 229 && beforeKey !== 240 &&
                beforeKey !== 241 && beforeKey !== 242 &&
                beforeKey !== 243 && beforeKey !== 244 &&
                !event.altKey && !event.metaKey && !event.ctrlKey) {
                // キー入力から500ミリ秒後にkeyupイベントの
                // 無い場合は入力確定とする
                timer = setTimeout(function () {
                    if (!keyUpFlag) {
                        // enter.imeEnterイベントの発行
                        _this._trigger($elm);
                    }
                }, 500);

                // キーアップフラグのリセット
                keyUpFlag = false;
            }
            break;
        case 'keypress':
            // IME入力中はキープレスイベントは発火しないので
            // このイベントが発火した場合はIME入力中以外とみなす
            imeFlag = false;
            break;
        case 'keyup':
            // キーアップフラグのセット
            keyUpFlag = true;

            // 直前のキーコード確認用
            beforeKey = keyCode;

            // 文字入力以外のキー入力は除外
            // alt, ctrl, option, commandキーの同時押しの場合は除外
            if (exceptKeys.indexOf(keyCode) !== -1 ||
                event.altKey || event.metaKey || event.ctrlKey) {
                return;
            }

            // IME入力中でない
            // IME入力中でキーコード13が発行された
            if (!imeFlag || (imeFlag && keyCode === 13)) {
                // enter.imeEnterイベントの発行
                _this._trigger($elm);
            }
            break;
    }
});
```

# まとめ
まとめというほどでもありませんが、ブラウザごとにIME入力時の挙動が異なるためとても悩みました...
IMEの入力確認だとテストコードも書けないし...

~~Chromeが一番厄介に感じましたが、レンダリングエンジンがIMEに関係しているとすると、もしかするとSafariも同じような実装なのかもしれないですね。~~
どうやらSafariはIEと同じ動作のようです。
こちら参照[freefielder.jp](http://freefielder.jp/blog/2010/11/javascript.html)

現状でタイマーを使用して確認していますが、マシンパワーによってはタイミングが異なるかもしれません。~~私の環境では300ミリ秒がベストかなと感じました。（これ以上短くするとkeyupが発火していないと判断されるときが多くなる）~~
キー入力の早い人や、キー押しっぱなしの時は「keyup」イベントがスキップされてしまうので500ミリ秒にしてみました。もっさりした動作になっちゃうのですが仕方が無い...

この辺の挙動は何とかしてほしいところですね　全く :triumph: 

キーコードの確認には下記のサイトを利用させていただきました。

* [ブラウザごとのキーコード表](http://www.programming-magic.com/file/20080205232140/keycode_table.html)
* [日本語入力時に発生するキーイベントのテスト](http://freefielder.jp/lab/javascript/im.html)
* [KeyboardEvent　MDN](https://developer.mozilla.org/ja/docs/Web/API/KeyboardEvent)