スポンサーリンク

JavaScriptの動かないコード (中級編) clickイベントを強制的に発生させたい (fireEvent/createEventの使い方)


以下のJavaScriptコードが意図した動作をしないのは,なぜですか。(制限時間1分)


やりたい事:

  • 「クリックするとメッセージを表示する」という,テキストボックスがある。
  • このテキストボックスの動作テストをしたい。クリックのイベントを自動的に発生させ,ちゃんとメッセージが出るかをコードで確認する。

<body>


イベントの発生源となるテキストボックス:
<input type="text" onclick="onclick_func()" id="my_text">


click時に,ここにメッセージが表示されます。
<div id="my_div"></div>


テスト:
<input type="button" onclick="test_onclick()" value="onclickの単体テストを実行">


<script language="JavaScript">


// click時に呼ばれる関数
function onclick_func(){
	my_div.innerHTML = "テキストボックスをclickしました。";
}


// clickの動作テスト
function test_onclick(){
	
	// clickをエミュレート
	my_text.click();
	
	// その結果,ちゃんと文字が表示されたか?
	if( my_div.innerHTML == "テキストボックスをclickしました。" )
	{
		alert("OKです。");
	}
	else
	{
		alert("NGです。");
	}
	
}


</script>

</body>




以下は答えと解説。


発生する不具合


IEでは「OK」が出る。div要素の中に,メッセージも表示されている。

クリックイベントをエミュレートできている。



Firefoxでは,「NG」が出る。div要素の中は空のまま。

クリックイベントを擬似的に発火できていない。


理由

Firefoxでは,テキストボックス要素にclick()できない。

type属性が"button"とか"submit"ならば自動クリック可能だが,type="text"とか"file"の場合はだめ。

How to trigger file input .click() event in Firefox
http://bytes.com/topic/javascript/ans...
Opera and Firefox don't support the click() method for input file elements.


JS Input control .click() does not work under Firefox
http://support.mozilla.com/tiki-view_...
document.getElementById("fpDBox").click();
You can't do that in Firefox.

回避策

対策は2つある。

  1. 旧式の方法:イベントハンドラを「プロパティ」として扱う方法。1つしか関数をセットできない。
  2. 現行の方法:DOM Eventオブジェクトを使う方法。複数の関数をセットできる。
(1)イベントハンドラを「プロパティ」として扱う方法


冒頭のコードでは,タグに直接onclick=のように書いてある。

これは,HTML要素の「プロパティ」として,関数を代入する方法。

JavaScriptコードの中で 要素.onclick=〜 とするのも同じだ。



この場合,代入された関数は単なるプロパティだから,代入時と同じ形式で呼び出しもできる。

	// clickをエミュレート
	my_text.click();


	↓


	// clickをエミュレート
	my_text.onclick(); // IE,FF共に○

のようにする。onclickに代入したのだから,onclickを呼び出せばいい,というだけ。


しかし,これだとイベントリスナを1つしか設定できない。
(単なる属性なので,2回目の設定で上書きされてしまう。)

また,addEventListenerとかattachEventで設定されたイベントリスナは,onclick()では呼び出せない。

なので,この方法には限界がある。


(2)DOM Eventオブジェクトを使う方法

複数設定も可能になるようにイベントをちゃんと扱いたい場合,Event Objectを使う。

	// clickをエミュレート
	my_text.click();


	↓
	
	
	// clickをエミュレート
	var elem = document.getElementById("my_text");
	if( /*@cc_on ! @*/ false )
	{
		// IEの場合
		elem.fireEvent( "onclick" );
	}
	else
	{
		// Firefoxの場合
		var evt = document.createEvent( "MouseEvents" ); // マウスイベントを作成
		evt.initEvent( "click", false, true ); // イベントの詳細を設定
		elem.dispatchEvent( evt ); // イベントを強制的に発生させる
	}
	


これならIE・FF共に,DOM要素に登録されているすべてのイベントリスナを実行させることができる。

onclick=で属性として登録された関数でも,addEventListnerなどで登録された関数でもOK。

IEで,指定したオブジェクトの特定のイベントを実行する
http://www.openspc2.org/reibun/JS_Tip...
要素.fireEvent( "on" + イベント名 )


FirefoxでfireEvent
http://tsukamoto.tumblr.com/post/6590...
createEventを使う


createEventに渡すためのイベントタイプ一覧表
https://developer.mozilla.org/ja/DOM/...


event.initEventの引数について説明
https://developer.mozilla.org/ja/DOM:...

キーボードイベントの場合

clickやmousedownのようなマウス系のイベントだけでなく,キー押下のイベントも発火できる。

下記の関数を参照。

// elem : キー押下イベントを発生させたい要素
// key_code : 押下するキーのキーコード。"right"のような文字列指定も可
function fire_key_down_event( elem, key_code )
{
	// キーコードを調整
	if( key_code == "left"       ){ key_code = 37; }
	else if( key_code == "right" ){ key_code = 39; }
	else if( key_code == "up"    ){ key_code = 38; }
	else if( key_code == "down"  ){ key_code = 40; }
	
	// イベントを発火させる
	if( /*@cc_on ! @*/ false )
	{
		// IEの場合
		var evt = document.createEventObject();
		evt.keyCode = key_code;
		elem.fireEvent( "onkeydown", evt );
	}
	else
	{
		// Firefoxの場合
		var evt = document.createEvent("KeyboardEvent");
		evt.initKeyEvent("keydown", true, true, null, false, false, false, false, key_code, 0 );
		elem.dispatchEvent( evt );
	}
	
	
}

キーイベントを擬似的に発生させることは可能?
http://questionbox.jp.msn.com/qa46737...


event.initKeyEventの引数
https://developer.mozilla.org/en/DOM/...


document.createEventObject()とは
http://javascript.gakaa.com/document-...
IEで,fireEvent()に渡すためのイベントオブジェクトを作成する


補足

これらの情報は,複雑なUIのWebページをテスト駆動で開発するためには必須といえる。

なお,あの回帰テストツールのSeleniumでも,もちろん利用されている。

Seleniumのコアであるselenium-browserbot.jsの,214行目を見てみよう。

http://code.google.com/p/gmaps-api-is...

203 /* Fire a mouse event in a browser-compatible manner */

・・・

214     if (element.fireEvent) {

・・・

246     else {
           var evt = document.createEvent('MouseEvents');
           if (evt.initMouseEvent)

といった具合に,マウスイベントを発生させるために,このエントリーで取り上げたのと同じようにブラウザごとに異なった関数を呼び出しているのがわかる。



ほかの参考URL:

prototype.jsのEvent.fire()にネイティブイベントを対応させる
http://rakuto.blogspot.com/2008/01/ja...

Script.aculo.usに存在するイベント発火用関数(Firefox専用)
http://gihyo.jp/dev/feature/01/script...

prototype.js のイベント・トリガー
http://hwat.sakura.ne.jp/hpod/200611/...




関連する記事:

JavaScriptの動かないコード (中級編) 不要なイベントが連鎖で発生してしまう  (バブリングの対処)
http://language-and-engineering.hatenablog.jp/entry/20081128/1227843561


JavaScriptの動かないコード (中級編) setTimeoutのタイマーが指定時刻に動かないエラー (JavaScriptがマルチスレッドだという誤解)
http://language-and-engineering.hatenablog.jp/entry/20090614/p1


JavaScriptの動かないコード (中級編) 動的追加したイベントの実行順序 ( addEventListener vs attachEvent )
http://language-and-engineering.hatenablog.jp/entry/20081011/1223680300


JavaScriptの動かないコード (中級編) イベント強制発生までの遅延時間をなくしたい
http://language-and-engineering.hatenablog.jp/entry/20090909/p1