Webアプリケーションのイベントモデル
イベントとは
イベントとは、ユーザーの操作、サーバからのデータ受信、タイマーの満了(指定した時間が過ぎた)など、操作に対する反応や状態の変化(あるいは「これから状態が変化しようとしている」こと)、あるいはそれを外部に通知する手段のことです。
「イベントが発生した」または「イベントが発火した」等と言います。
具体的には、例えばマウスクリックやキーボード入力など、ページ上の要素に対して操作を行うとイベントが発生します。マウスクリックイベント、キー入力イベント、ページ読み込み完了イベントなど、様々な種類のイベントがあります。
イベント発生時に処理を実行する~イベントハンドラ
イベントが起きた時、それに対応して処理を行う関数のことをイベントハンドラまたはイベントリスナーといいます。
例えば「ボタンがクリックされたらJavaScriptで『hello』と画面に表示する」場合、「ボタンのクリックイベントに(メッセージを表示する)イベントハンドラが定義されている」というような言い方をします。
イベントハンドラの記述と登録
イベントハンドラの実体は(JavaScriptの)普通の関数です。ただし、その関数が呼ばれる時には引数としてイベントオブジェクトが渡されるという決まりがあります。(イベントオブジェクトについては後述します。)
hifiveでは、コントローラに、'(セレクタ) (イベント名)': funciton(){} という書き方で、イベントハンドラを記述、登録します。
詳細についてはチュートリアル/コントローラを参照してください。
また、セレクタについて詳しく知りたい場合は自習室/セレクタのページを参照してください。
以下に例を示します。
__name: 'sample.SampleController',
/**
* idがtestbtnの要素がクリックされたら、helloというメッセージを表示する
*/
'#testbtn click': function(context, $el) {
alert('hello');
}
}
イベントオブジェクトによるイベント詳細情報の取得
イベントハンドラには、イベントに関する情報が格納されたイベントオブジェクトが渡されます。
(イベントオブジェクトのことを省略して「イベント」と呼ぶ場合も多いです。)
hifiveでは、イベントハンドラの第一引数(context)のeventプロパティで参照できます。
__name: 'sample.SampleController',
/**
* idがtestbtnの要素がクリックされたときのイベントハンドラ
*/
'#testbtn click': function(context, $el) {
var eventObject = context.event;
alert(eventObject.type); //イベント名(click)が表示される
}
}
clickやmousedownなどマウス操作に関するイベントの場合、イベントオブジェクトはMouseEventのインスタンスになります。keydownやkeypressなどキーボード操作に関するイベントの場合はKeybordEventのインスタンスになります。
それぞれ、イベントオブジェクトに格納される情報が異なります。
(例:マウスイベントにはイベント発生時のマウス座標が含まれるが、キーボードイベントには含まれない)
Webアプリケーションのイベント機構の特徴:バブリング
イベントが発生すると、それが発生した要素を起点として、その親要素へとイベントが伝播(バブリング)します。
親要素への伝搬は再帰的に行われ、最終的にwindowオブジェクトに到達すると終了します。
例えば、以下のようなHTMLコードがあったとします。
<body>
<div id="contents">
<button id="testbtn">test</button>
</div>
</body>
</html>
このとき、button要素をクリックすると、イベントは button -> div -> body -> html -> document -> windowという順序で発生(伝搬)します。
最初にイベントが発生した要素を起点としてDOMツリーを上へとたどっていくことから、この動きをバブリング(泡が浮かんでいく)と呼びます。
従って、例えばid="contents"のdiv要素でクリックイベントに対してイベントハンドラを登録してある場合、直接div要素をクリックされた場合だけでなく、button要素のクリックでもハンドラが実行されます。
※注:IE8以下の場合はwindowには到達せず、documentまでとなります。
(jQuery1.7以上を使用している場合、jQueryを使用してイベントハンドラを登録しておき、jQueryのtriggerメソッドでイベントを発生させると、windowまで伝播します。)
イベントの制御
このように、デフォルトではイベントは発生元から順に親へとバブリングして状態変化を外部に通知します。
この動作は、イベントオブジェクトのメソッドを呼ぶことで制御できます。
また、一部のイベントには「デフォルトの動作」が定義されており、その動作を行うかどうかも同様に制御できます。
(例えばtextarea要素のkeydownイベントには「文字を入力する」というデフォルト動作が定義されており、これをキャンセルすることで、特定の文字の入力を制限することができます。)
詳しくは自習室/イベントオブジェクトのメソッド を参照してください。
高度なトピック
イベントハンドラをhifiveなしで書いた場合
上で説明したイベントハンドラのコードを、hifiveや他のライブラリを使用せずに書くと、概ね以下のようなコードになります(ただし、厳密には全く同じではありません)。
※下のコードはあくまで説明用です。関数や変数がグローバルに定義され名前の衝突などが起こる可能性があるので、このまま使用すべきではありません。
alert('hello');
}
var btn = document.getElementById('testbtn');
if(btn.addEventListener) {
// ボタンのDOM要素がaddEventListener関数を持っている場合はその関数でイベントハンドラを登録
btn.addEventListener('click', clickEventHandler);
}
else {
// IE8以下の場合はaddEventListener関数がないので、代替の関数を使用
btn.attachEvent('onclick', clickEventHandler);
}
また、hifiveを使用せずにイベントハンドラを登録した場合、第1引数には直接イベントオブジェクトが渡されます。
以下に例を示します。
alert(event.type); //イベント名(click)が表示される
}
var btn = document.getElementById('testbtn');
if(btn.addEventListener) {
btn.addEventListener('click', clickEventHandler);
}
else {
btn.attachEvent('onclick', clickEventHandler);
}
なお、jQueryを使用した場合は概ね以下のようなコードになります。
(jQuery内部でも、最終的にはaddEventListener(またはattachEvent)を使用しています。)
alert(event.type); //イベント名(click)が表示される
}
$('#testbtn').on('click', clickEventHandler);
jQueryを使ってイベントハンドラを登録した場合のイベントオブジェクト
jQueryを使ってイベントハンドラを登録していた場合、そのハンドラにはjQueryイベントオブジェクトが渡されます。
jQueryイベントオブジェクトは、ブラウザが発生させたオリジナルのイベントと概ね同じ情報を(コピーして)持っています。ただし、一部、ブラウザによるプロパティ名の違いや挙動の違いを吸収し、どのブラウザでも同じようにイベントを処理できるようにしています。
これは、特にIE8以下ではイベントオブジェクトの中身が他のブラウザ(またはWeb標準仕様)と異なっていたため、その違いを開発者が意識せずに済むように行われています。
なお、jQueryイベントオブジェクトでは、オリジナルのイベントオブジェクトの一部のプロパティはコピーされていません。
オリジナルのイベントだけが持つ情報を参照したい場合は、originalEventプロパティを参照します。
// 第1引数にはjQueryイベントオブジェクトが渡されます。
// event.originalEventがオリジナルのイベントです。
});
イベントハンドラの別の登録方法(非推奨)
イベントハンドラをDOM要素に登録する方法は、上で説明した以外にも方法があります。
①onclick属性に記述
function handler(){
alert('hello');
}
</script>
<button id="testbtn" onclick="handler()"></button>
②要素のプロパティに代入
alert('hello');
}
document.getElementById('testbtn').onclick = handler;
しかし、上記2つの方法では、ある要素のあるイベントに対しては1つのイベントハンドラしか登録できません。
特別な事情がない限り、これらの方法は使用しないでください。