JavaScriptの動かないコード (中級編) イベント強制発生までの遅延時間をなくしたい
以下のJavaScriptコードが意図した動作をしないのは,なぜですか。(制限時間1分)
やりたい事:
- 「フォーカスを外す(blur)とメッセージを表示する」という,テキストボックスがある。
- このテキストボックスの動作テストをしたい。blurのイベントを自動的に発生させ,ちゃんとメッセージが出るかをコードで確認する。
なおIE・FF共に,テキストボックス要素は blur() というメソッドを持っている。
<body> <!-- イベントの発生源となるテキストボックス --> <input type="text" onblur="onblur_func()" id="my_text"> <!-- blur時に,ここにメッセージが表示されます。 --> <div id="my_div"></div> <!-- テスト --> <input type="button" onclick="test_onblur()" value="onblurの単体テストを実行"> <script language="JavaScript"> // blur時 function onblur_func(){ my_div.innerHTML = "テキストボックスからblurしました。"; } // onblurの動作テスト function test_onblur(){ // blurをエミュレート my_text.focus(); my_text.blur(); // その結果,ちゃんと文字が表示されたか? if( my_div.innerHTML == "テキストボックスからblurしました。" ) { alert( "OKです。(divの中身:'" + my_div.innerHTML + "')" ); } else { alert( "NGです。(divの中身:'" + my_div.innerHTML + "')" ); } } </script> </body>
答え
発生する不具合
Firefoxでは「OKです。(divの中身:'テキストボックスからblurしました。')」が出る。div要素の中に,メッセージも表示されている。
blurイベントのエミュレートが成功している。
IEでは,「NGです。(divの中身:'')」が出る。しかし,div要素の中にはちゃんとメッセージが出ている。
blurイベントのエミュレートが成功しているのに,その単体テストは失敗している。
だが,2回目のクリック以降では,「OKです」が出る。
理由
Firefoxでは,
- 要素.blur() というコードのところで,blurイベントのイベントリスナ(onblur_func)が即座に実行され,
- そのイベントリスナの実行終了後にif (my_div.innerHTML ==〜のチェックが走っている
ように見える。
一方IEでは,
- 要素.blur() というコードのところで,blurイベントのイベントリスナ(onblur_func)が実行キューに登録され,
- if (my_div.innerHTML ==〜のチェックが走る時点ではまだメッセージが表示されていないので,単体テストでNGが出て,
- NGのアラートが表示された時点でブラウザに制御が戻るから,そこで初めてblurのイベントリスナが実行され,メッセージが表示される
となっているようだ。
.blur() のような記法では,イベント強制発生時のしくみがブラウザごとに違ってしまうらしい。
※「アラートが表示された時点でブラウザに制御が戻る」という点については,下記の記事を参照。
setTimeoutのタイマーが指定時刻に動かないエラー(setTimeout問題について)
http://language-and-engineering.hatenablog.jp/entry/20090614/p1
回避策
前回(「clickイベントを強制的に発生させたい (fireEvent/createEventの使い方)」)と同じく,対策は2つある。
- 旧式の方法:イベントハンドラを「プロパティ」として呼び出す。
- 現行の方法:DOM Eventオブジェクトを使う。
(1)イベントハンドラを「プロパティ」として呼び出す。
blur()でなくonblur()として,そのプロパティに入っている関数オブジェクトを直接呼び出す。
// blurをエミュレート my_text.focus(); my_text.blur(); ↓ // blurをエミュレート my_text.onblur();
この方法だと,もうイベントとか全然関係ない。
イベント用に用意されたはずの関数を直接呼び出しているだけ。
なので,キュー登録による実行遅延などは発生せず,その場でonblur_func()が実行される。
事前にfocus()しておく必要もない。
単体テストを通すことだけを考えれば,これが一番シンプルでクロスブラウザな解法だ。
しかし,前回と同じく,これだとイベントリスナを1つしか設定できない。
また,addEventListenerとかattachEventで設定されたイベントリスナは呼び出せない。
(2)DOM Eventオブジェクトを使う方法
複数のイベントリスナの設定も可能になる。
// blurをエミュレート my_text.focus(); my_text.blur(); ↓ // blurをエミュレート var elem = document.getElementById("my_text"); if( /*@cc_on ! @*/ false ) { // IEの場合 elem.fireEvent( "onblur" ); } else { // Firefoxの場合 var evt = document.createEvent("HTMLEvents"); // イベントを作成 evt.initEvent( "blur", false, true ); // イベントの詳細を設定 elem.dispatchEvent( evt ); // イベントを強制的に発生させる }
参考:
Dealing with W3C Events; A story of running around in circles
http://almaer.com/blog/dealing-with-w...
イベントの中でさらにイベント発生させるにはsetTimeoutで遅延させてから。