V JavaScriptu můžeme nejen pÅiÅazovat handlery, ale také generovat události.
Vlastnà události můžeme použÃvat k vytváÅenà âgrafických komponentâ. NapÅÃklad koÅenový element naÅ¡eho vlastnÃho menu založeného na JS může spouÅ¡tÄt události, které budou ÅÃkat, co se dÄje s menu: otevÅi (otevÅenà menu), vyber (položka je vybrána) a podobnÄ. Dalšà kód může tÄmto událostem naslouchat a pozorovat, co se s menu dÄje.
Můžeme generovat nejenom úplnÄ nové události, které si vymyslÃme pro vlastnà úÄely, ale také vestavÄné události, napÅ. click, mousedown atd. To může být užiteÄné pro automatické testovánÃ.
Konstruktor události
TÅÃdy vestavÄných událostà tvoÅà hierarchii, podobnÄ jako tÅÃdy DOM elementů. JejÃm koÅenem je vestavÄná tÅÃda Event (událost).
Objekty tÅÃdy Event můžeme vytváÅet následovnÄ:
let událost = new Event(typ[, možnosti]);
Argumenty:
-
typ â typ události, ÅetÄzec, napÅ.
"click"nebo náš vlastnÃ, tÅeba"moje-událost". -
možnosti â objekt se dvÄma nepovinnými vlastnostmi:
bubbles: true/falseâ pokud jetrue, pak událost bublá.cancelable: true/falseâ pokud jetrue, pak lze zakázat âstandardnà akciâ. PozdÄji uvidÃme, co to znamená pro vlastnà události.
StandardnÄ jsou obÄ vlastnosti false:
{bubbles: false, cancelable: false}.
dispatchEvent
Když vytvoÅÃme objekt události, mÄli bychom ji âspustitâ na nÄjakém elementu volánÃm elem.dispatchEvent(událost).
Pak na ni handlery budou reagovat, jako by to byla bÄžná událost prohlÞeÄe. Pokud byla událost vytvoÅena s pÅÃznakem bubbles, bude bublat.
V následujÃcÃm pÅÃkladu je v JavaScriptu spuÅ¡tÄna událost click. Handler funguje stejnÄ, jako pÅi kliknutà na tlaÄÃtko:
<button id="elem" onclick="alert('Klik!');">Automatický klik</button>
<script>
let událost = new Event("click");
elem.dispatchEvent(událost);
</script>
Existuje způsob, jak poznat âopravdovouâ uživatelskou událost od události generované skriptem.
Vlastnost událost.isTrusted je true pro události, které pocházejà od skuteÄných uživatelských akcÃ, a false pro události generované skriptem.
PÅÃklad bublánÃ
Můžeme vytvoÅit bublajÃcà událost s názvem "ahoj" a zachytávat ji v document.
StaÄÃ nastavit bubbles na true:
<h1 id="elem">Ahoj ze skriptu!</h1>
<script>
// zachytávánà v dokumentu...
document.addEventListener("ahoj", function(událost) { // (1)
alert("Ahoj z " + událost.target.tagName); // Ahoj z H1
});
// ...vyvoláme ji na elem!
let událost = new Event("ahoj", {bubbles: true}); // (2)
elem.dispatchEvent(událost);
// handler na dokumentu se aktivuje a zobrazà zprávu.
</script>
Poznámky:
- NaÅ¡e vlastnà události bychom mÄli pÅidávat pomocÃ
addEventListener, jelikožon<událost>existuje jedinÄ pro vestavÄné události,document.onahojnebude fungovat. - MusÃme nastavit
bubbles:true, jinak událost nebude bublat.
Mechanika bublánà funguje pro vestavÄné (click) a vlastnà (ahoj) události stejnÄ. I u nich probÃhá fáze zachytávánà a fáze bublánÃ.
MouseEvent, KeyboardEvent a jiné
Následuje krátký seznam událostà uživatelského rozhranà (UI) ze specifikace UI událostÃ:
UIEventFocusEventMouseEventWheelEventKeyboardEvent- â¦
Pokud chceme vytvoÅit takovou událost, mÄli bychom mÃsto new Event použÃvat je, napÅÃklad new MouseEvent("click").
Správný konstruktor nám umožÅuje specifikovat standardnà vlastnosti pro pÅÃsluÅ¡ný typ události.
NapÅÃklad clientX/clientY pro událost myÅ¡i:
let událost = new MouseEvent("click", {
bubbles: true,
cancelable: true,
clientX: 100,
clientY: 100
});
alert(událost.clientX); // 100
ProsÃme vÅ¡imnÄte si, že generický konstruktor Event to neumožÅuje.
Zkusme to:
let událost = new Event("click", {
bubbles: true, // v konstruktoru Event fungujÃ
cancelable: true, // pouze bubbles a cancelable
clientX: 100,
clientY: 100
});
alert(událost.clientX); // undefined, neznámá vlastnost je ignorována!
Technicky bychom to mohli obejÃt tak, že po vytvoÅenà události pÅÃmo pÅiÅadÃme událost.clientX=100. Je to tedy otázka konvencà a dodržovánà pravidel. Události generované prohlÞeÄem majà vždy správný typ.
Ãplný seznam vlastnostà různých UI událostà je uveden ve specifikaci, napÅÃklad MouseEvent.
Vlastnà události
Pro naÅ¡e vlastnÃ, zcela nové typy událostÃ, napÅ. "ahoj", bychom mÄli použÃvat new CustomEvent. Technicky je CustomEvent totéž jako Event, ale s jednou výjimkou.
Ve druhém argumentu (v objektu) můžeme pÅidat dalšà vlastnost detail s jakoukoli vlastnà informacÃ, kterou chceme události pÅedat.
PÅÃklad:
<h1 id="elem">Ahoj pro Jana!</h1>
<script>
// s událostà pÅijdou do handleru dalšà detaily
elem.addEventListener("ahoj", function(událost) {
alert(událost.detail.jméno);
});
elem.dispatchEvent(new CustomEvent("ahoj", {
detail: { jméno: "Jan" }
}));
</script>
Vlastnost detail může obsahovat jakákoli data. Technicky se můžeme obejÃt i bez nÃ, protože bÄžnému objektu new Event můžeme po vytvoÅenà pÅiÅazovat jakékoli vlastnosti, ale CustomEvent k tomu poskytuje speciálnà pole detail, abychom se vyhnuli konfliktům s jinými vlastnostmi události.
KromÄ toho tÅÃda události popisuje, o âjaký druh událostiâ jde, a je-li událost naÅ¡e vlastnÃ, mÄli bychom použÃvat CustomEvent prostÄ proto, aby bylo zÅejmé, co je zaÄ.
událost.preventDefault()
Mnoho událostà prohlÞeÄe má âstandardnà akciâ, napÅ. navigaci na odkaz, zahájenà výbÄru a podobnÄ.
U nových, vlastnÃch událostà samozÅejmÄ Å¾Ã¡dné standardnà akce prohlÞeÄe nejsou, ale kód, který takovou událost vyvolává, může mÃt vlastnà plány, co bude po spuÅ¡tÄnà události dÄlat.
VolánÃm událost.preventDefault() může handler události poslat signál, že tyto akce by mÄly být zruÅ¡eny.
V takovém pÅÃpadÄ volánà elem.dispatchEvent(událost) vrátà false. A kód, který událost vyvolal, vÃ, že by nemÄl pokraÄovat.
PodÃvejme se na praktický pÅÃklad â skrývajÃcà se králÃk (ale může to být i zavÃrajÃcà se menu nebo cokoli jiného).
NÞe vidÃte #králÃk a funkci skryj(), která na nÄm vyvolává událost "skryj", aby oznámila vÅ¡em stranám, které o to majà zájem, že se králÃk hodlá skrýt.
Jakýkoli handler může této události naslouchat pomocà králÃk.addEventListener('skryj',...) a pak, je-li to nutné, zruÅ¡it akci volánÃm událost.preventDefault(). Pak králÃk nezmizÃ:
<pre id="králÃk">
|\ /|
\|_|/
/. .\
=\_Y_/=
{>o<}
</pre>
<button onclick="skryj()">Skryj()</button>
<script>
function skryj() {
let událost = new CustomEvent("skryj", {
cancelable: true // bez tohoto pÅepÃnaÄe preventDefault nefunguje
});
if (!králÃk.dispatchEvent(událost)) {
alert('Akce byla zakázána handlerem');
} else {
králÃk.hidden = true;
}
}
králÃk.addEventListener('skryj', function(událost) {
if (confirm("Volat preventDefault?")) {
událost.preventDefault();
}
});
</script>
ProsÃme vÅ¡imnÄte si, že událost musà mÃt pÅepÃnaÄ cancelable: true, jinak bude volánà událost.preventDefault() ignorováno.
Události v událostech jsou synchronnÃ
Události se obvykle zpracovávajà ve frontÄ. To znamená, že jestliže prohlÞeÄ zpracovává onclick a objevà se nová událost, napÅ. pohyb myÅ¡i, pak se jejà zpracovánà uložà do fronty a odpovÃdajÃcà handlery mousemove budou volány po skonÄenà zpracovánà onclick.
Pozoruhodnou výjimkou je situace, kdy je událost vyvolána zevnitÅ jiné události, napÅ. pomocà dispatchEvent. Takové události se zpracujà okamžitÄ: volajà se handlery nové události a teprve pak se obnovà zpracovánà aktuálnà události.
NapÅÃklad v následujÃcÃm kódu se událost otevÅi-menu spustà bÄhem události onclick.
Bude zpracována okamžitÄ bez Äekánà na skonÄenà handleru onclick:
<button id="menu">Menu (klikni na mÄ)</button>
<script>
menu.onclick = function() {
alert(1);
menu.dispatchEvent(new CustomEvent("otevÅi-menu", {
bubbles: true
}));
alert(2);
};
// pÅepÃná mezi 1 a 2
document.addEventListener('otevÅi-menu', () => alert('vnoÅená'));
</script>
PoÅadà výstupů je: 1 â vnoÅená â 2.
ProsÃme vÅ¡imnÄte si, že vnoÅená událost otevÅi-menu se zachytává v document. PÅedávánà a zpracovánà vnoÅené události skonÄà dÅÃve, než se proces vrátà do vnÄjÅ¡Ãho kódu (onclick).
Neplatà to jen pro dispatchEvent, jsou i jiné pÅÃpady. Jestliže handler události volá metody, které spouÅ¡tÄjà jiné události, budou také zpracovány synchronnÄ, vnoÅeny do sebe.
ÅeknÄme, že se nám to nelÃbÃ. Chceme, aby byla nejdÅÃve zcela zpracována onclick, nezávisle na otevÅi-menu nebo jiných vnoÅených událostech.
Pak můžeme buÄ umÃstit dispatchEvent (nebo jiné volánà spouÅ¡tÄjÃcà událost) na konec onclick, nebo, což je možná lepÅ¡Ã, zabalit je do setTimeout s nulovou prodlevou:
<button id="menu">Menu (klikni na mÄ)</button>
<script>
menu.onclick = function() {
alert(1);
setTimeout(() => menu.dispatchEvent(new CustomEvent("otevÅi-menu", {
bubbles: true
})));
alert(2);
};
document.addEventListener('otevÅi-menu', () => alert('vnoÅená'));
</script>
Nynà se dispatchEvent spustà asynchronnÄ po skonÄenà výkonu aktuálnÃho kódu, vÄetnÄ menu.onclick, takže handlery událostà budou zcela oddÄlené.
PoÅadà výstupů se zmÄnà na: 1 â 2 â vnoÅená.
ShrnutÃ
Abychom generovali událost z kódu, musÃme nejprve vytvoÅit objekt události.
Obecný konstruktor Event(název, možnosti) pÅijÃmá libovolný název události a objekt možnosti se dvÄma vlastnostmi:
bubbles: true, pokud událost má bublat.cancelable: true, pokud má fungovatudálost.preventDefault().
Jiné konstruktory nativnÃch událostÃ, napÅ. MouseEvent, KeyboardEvent a podobnÄ, pÅijÃmajà vlastnosti specifické pro pÅÃsluÅ¡ný typ události, napÅÃklad clientX pro události myÅ¡i.
Pro vlastnà události bychom mÄli použÃvat konstruktor CustomEvent. Ten má dalšà možnost nazvanou detail, do nÞ bychom mÄli uložit data specifická pro naÅ¡i událost. K nim pak vÅ¡echny handlery budou moci pÅistupovat pomocà událost.detail.
PÅestože je technicky možné generovat události prohlÞeÄe, napÅ. click nebo keydown, mÄli bychom je použÃvat s velkou obezÅetnostÃ.
NemÄli bychom generovat události prohlÞeÄe jen jako trik pro spuÅ¡tÄnà handlerů. To je ve vÄtÅ¡inÄ pÅÃpadů Å¡patná architektura.
Nativnà události můžeme generovat:
- Jako oÅ¡klivý způsob, jak pÅinutit knihovny tÅetÃch stran fungovat tak, jak potÅebujeme, pokud nám neposkytujà jinou možnost interakce.
- Pro automatické testovánÃ, abychom ve skriptu âklikli na tlaÄÃtkoâ a vidÄli, zda rozhranà správnÄ reaguje.
Vlastnà události s naÅ¡imi vlastnÃmi názvy se Äasto generujà z architektonických důvodů, aby signalizovaly, co se dÄje uvnitÅ naÅ¡ich menu, posuvnÃků, kolotoÄů a podobnÄ.
KomentáÅe
<code>, pro nÄkolik Åádků je obalte znaÄkou<pre>, pro vÃce než 10 Åádků vložte odkaz na pÃskoviÅ¡tÄ (plnkr, jsbin, codepenâ¦)