ハンドラーを割り当てるだけでなく、JavaScriptからイベントを生成することもできます。
カスタムイベントは、「グラフィカルコンポーネント」を作成するために使用できます。たとえば、独自のJSベースのメニューのルート要素は、メニューで何が起こるかを示すイベント(open
(メニューが開く)、select
(アイテムが選択される)など)をトリガーする可能性があります。別のコードはイベントをリッスンし、メニューで何が起こっているかを監視できます。
独自の目的で考案したまったく新しいイベントだけでなく、click
、mousedown
など、組み込みのイベントも生成できます。これは自動テストに役立つ場合があります。
イベントコンストラクタ
組み込みのイベントクラスは、DOM要素クラスと同様に階層を形成します。ルートは組み込みのEventクラスです。
Event
オブジェクトは次のように作成できます。
let event = new Event(type[, options]);
引数
-
type – イベントタイプ。「
click
」のような文字列、または「my-event
」のような独自の文字列。 -
options – 2つのオプションのプロパティを持つオブジェクト。
bubbles: true/false
–true
の場合、イベントはバブリングします。cancelable: true/false
–true
の場合、「デフォルトアクション」を阻止できます。後でカスタムイベントでそれが何を意味するのか説明します。
デフォルトではどちらもfalseです:
{bubbles: false, cancelable: false}
。
dispatchEvent
イベントオブジェクトが作成された後、elem.dispatchEvent(event)
呼び出しを使用して、要素上で「実行」する必要があります。
その後、ハンドラーはそれが通常のブラウザイベントであるかのように反応します。イベントがbubbles
フラグで作成された場合、バブリングします。
次の例では、click
イベントがJavaScriptで開始されます。ハンドラーは、ボタンがクリックされた場合と同じように動作します。
<button id="elem" onclick="alert('Click!');">Autoclick</button>
<script>
let event = new Event("click");
elem.dispatchEvent(event);
</script>
スクリプトで生成されたイベントと「リアル」なユーザーイベントを区別する方法があります。
プロパティevent.isTrusted
は、実際のユーザー操作からのイベントではtrue
、スクリプトで生成されたイベントではfalse
になります。
バブリングの例
"hello"
という名前のバブリングイベントを作成し、document
でキャッチできます。
必要なのは、bubbles
をtrue
に設定することだけです。
<h1 id="elem">Hello from the script!</h1>
<script>
// catch on document...
document.addEventListener("hello", function(event) { // (1)
alert("Hello from " + event.target.tagName); // Hello from H1
});
// ...dispatch on elem!
let event = new Event("hello", {bubbles: true}); // (2)
elem.dispatchEvent(event);
// the handler on document will activate and display the message.
</script>
注意事項
- カスタムイベントには
addEventListener
を使用する必要があります。なぜなら、on<event>
は組み込みイベントにしか存在せず、document.onhello
は機能しないためです。 bubbles:true
を設定する必要があります。そうしないと、イベントはバブリングしません。
バブリングメカニズムは、組み込み(click
)イベントとカスタム(hello
)イベントで同じです。キャプチャとバブリングのステージもあります。
MouseEvent、KeyboardEventなど
UIイベント仕様からのUIイベントのクラスの簡単なリストを以下に示します。
UIEvent
FocusEvent
MouseEvent
WheelEvent
KeyboardEvent
- …
このようなイベントを作成したい場合は、new Event
の代わりにそれらを使用する必要があります。たとえば、new MouseEvent("click")
です。
適切なコンストラクタを使用すると、そのタイプのイベントの標準プロパティを指定できます。
マウスイベントのclientX/clientY
など。
let event = new MouseEvent("click", {
bubbles: true,
cancelable: true,
clientX: 100,
clientY: 100
});
alert(event.clientX); // 100
注意:汎用Event
コンストラクタでは、それは許可されません。
試してみましょう
let event = new Event("click", {
bubbles: true, // only bubbles and cancelable
cancelable: true, // work in the Event constructor
clientX: 100,
clientY: 100
});
alert(event.clientX); // undefined, the unknown property is ignored!
技術的には、作成後にevent.clientX=100
を直接割り当てることで回避できます。そのため、これは便宜上の問題であり、ルールに従うことです。ブラウザで生成されたイベントは常に正しいタイプです。
さまざまなUIイベントのプロパティの完全なリストは、仕様(たとえばMouseEvent)にあります。
カスタムイベント
「hello
」のような独自のまったく新しいイベントタイプには、new CustomEvent
を使用する必要があります。技術的にはCustomEventはEvent
と同じですが、1つの例外があります。
2番目の引数(オブジェクト)で、イベントとともに渡したい任意のカスタム情報を格納する追加のプロパティdetail
を追加できます。
たとえば
<h1 id="elem">Hello for John!</h1>
<script>
// additional details come with the event to the handler
elem.addEventListener("hello", function(event) {
alert(event.detail.name);
});
elem.dispatchEvent(new CustomEvent("hello", {
detail: { name: "John" }
}));
</script>
detail
プロパティには任意のデータを含めることができます。技術的には、作成後に通常のnew Event
オブジェクトに任意のプロパティを割り当てることができるため、なくても構いません。しかし、CustomEvent
は、他のイベントプロパティとの競合を回避するために、特別なdetail
フィールドを提供します。
さらに、イベントクラスはそれが「どのような種類のイベント」であるかを記述し、イベントがカスタムである場合、それが何であるかを明確にするためにCustomEvent
を使用する必要があります。
event.preventDefault()
多くのブラウザイベントには、「デフォルトアクション」(リンクへの移動、選択の開始など)があります。
新しいカスタムイベントには、間違いなくデフォルトのブラウザアクションはありませんが、そのようなイベントをディスパッチするコードは、イベントのトリガー後に実行する独自の計画を持っている可能性があります。
event.preventDefault()
を呼び出すことで、イベントハンドラーはそれらのアクションをキャンセルする必要があるというシグナルを送信できます。
その場合、elem.dispatchEvent(event)
への呼び出しはfalse
を返します。そして、それをディスパッチしたコードは、続行するべきではないことを認識します。
実用的な例を見てみましょう。隠れるウサギ(メニューの閉じたり、何か他のものかもしれません)。
以下に、#rabbit
と、関心のあるすべての当事者にウサギが隠れることを知らせるために、その上で"hide"
イベントをディスパッチするhide()
関数があります。
どのハンドラーでも、rabbit.addEventListener('hide',...)
を使用してそのイベントをリッスンし、必要に応じてevent.preventDefault()
を使用してアクションをキャンセルできます。そうすると、ウサギは消えません。
<pre id="rabbit">
|\ /|
\|_|/
/. .\
=\_Y_/=
{>o<}
</pre>
<button onclick="hide()">Hide()</button>
<script>
function hide() {
let event = new CustomEvent("hide", {
cancelable: true // without that flag preventDefault doesn't work
});
if (!rabbit.dispatchEvent(event)) {
alert('The action was prevented by a handler');
} else {
rabbit.hidden = true;
}
}
rabbit.addEventListener('hide', function(event) {
if (confirm("Call preventDefault?")) {
event.preventDefault();
}
});
</script>
注意:イベントにはcancelable: true
フラグを設定する必要があります。そうしないと、event.preventDefault()
の呼び出しは無視されます。
イベント内イベントは同期しています
通常、イベントはキューで処理されます。つまり、ブラウザがonclick
を処理していて、新しいイベント(マウスが移動したなど)が発生した場合、その処理はキューに入れられ、対応するmousemove
ハンドラーはonclick
の処理が完了した後に呼び出されます。
注目すべき例外は、別のイベント内(たとえば、dispatchEvent
を使用して)でイベントが開始された場合です。このようなイベントはすぐに処理されます。新しいイベントハンドラーが呼び出され、その後、現在のイベント処理が再開されます。
たとえば、以下のコードでは、onclick
中にmenu-open
イベントがトリガーされます。
onclick
ハンドラーが終了するのを待たずに、すぐに処理されます。
<button id="menu">Menu (click me)</button>
<script>
menu.onclick = function() {
alert(1);
menu.dispatchEvent(new CustomEvent("menu-open", {
bubbles: true
}));
alert(2);
};
// triggers between 1 and 2
document.addEventListener('menu-open', () => alert('nested'));
</script>
出力順序は1→ネスト→2です。
ネストされたイベントmenu-open
はdocument
でキャッチされていることに注意してください。ネストされたイベントの伝播と処理は、処理が外部コード(onclick
)に戻る前に終了します。
これはdispatchEvent
だけではありません。イベントハンドラーが他のイベントをトリガーするメソッドを呼び出す場合、それらもネストされた方法で同期的に処理されます。
気に入らないとしましょう。onclick
を、menu-open
やその他のネストされたイベントとは独立して、最初に完全に処理したいとします。
その場合、onclick
の最後にdispatchEvent
(または別のイベントトリガー呼び出し)を配置するか、おそらくより良い方法として、ゼロ遅延のsetTimeout
でラップします。
<button id="menu">Menu (click me)</button>
<script>
menu.onclick = function() {
alert(1);
setTimeout(() => menu.dispatchEvent(new CustomEvent("menu-open", {
bubbles: true
})));
alert(2);
};
document.addEventListener('menu-open', () => alert('nested'));
</script>
これで、dispatchEvent
は現在のコードの実行が完了した後に非同期的に実行されるため、menu.onclick
を含め、イベントハンドラーは完全に分離されます。
出力順序は1→2→ネストになります。
要約
コードからイベントを生成するには、最初にイベントオブジェクトを作成する必要があります。
汎用Event(name, options)
コンストラクタは、任意のイベント名と、2つのプロパティを持つoptions
オブジェクトを受け入れます。
- イベントをバブリングさせる必要がある場合、
bubbles: true
。 event.preventDefault()
を機能させる必要がある場合、cancelable: true
。
MouseEvent
、KeyboardEvent
など、ネイティブイベントの他のコンストラクタは、そのイベントタイプに固有のプロパティを受け入れます。たとえば、マウスイベントのclientX
。
カスタムイベントには、CustomEvent
コンストラクタを使用する必要があります。これは、detail
という名前の追加オプションがあり、イベント固有のデータをそれに割り当てる必要があります。その後、すべてのハンドラーはそれをevent.detail
としてアクセスできます。
click
やkeydown
などのブラウザイベントを生成する技術的な可能性がありますが、注意して使用する必要があります。
ハンドラーを実行するためのハッキーな方法であるため、ブラウザイベントを生成するべきではありません。ほとんどの場合、これは悪いアーキテクチャです。
ネイティブイベントが生成される可能性があります。
- サードパーティライブラリが適切に動作するように、他の相互作用手段を提供していない場合の、いわば苦肉の策です。
- 自動テストのために、スクリプトで「ボタンをクリック」し、インターフェースが正しく反応するかどうかを確認します。
独自の名称を持つカスタムイベントは、メニュー、スライダー、カルーセルなど内部で何が起こっているかを知らせるために、アーキテクチャ上の目的で頻繁に生成されます。
コメント
<code>
タグを使用し、複数行の場合は<pre>
タグで囲み、10行を超える場合は、サンドボックス(plnkr、jsbin、codepen…)を使用してください。