Můžeme nejen přidělovat handlery, ale také generovat události z JavaScriptu.
Vlastní události lze použít k vytvoření „grafických komponent“. Například kořenový prvek naší vlastní nabídky založené na JS může spouštět události, které říkají, co se stane s nabídkou:open
(nabídka otevřena), select
(je vybrána položka) a tak dále. Jiný kód může naslouchat událostem a sledovat, co se děje s nabídkou.
Můžeme generovat nejen zcela nové události, které si vymyslíme pro vlastní účely, ale i vestavěné, jako je click
, mousedown
atd. To může být užitečné pro automatické testování.
Konstruktor událostí
Vestavěné třídy událostí tvoří hierarchii podobnou třídám prvků DOM. Kořen je vestavěná třída Event.
Můžeme vytvořit Event
objekty jako tento:
let event = new Event(type[, options]);
Argumenty:
-
typ – typ události, řetězec jako
"click"
nebo naše vlastní jako"my-event"
. -
možnosti – objekt se dvěma volitelnými vlastnostmi:
bubbles: true/false
– pokudtrue
, pak událost začne blikat.cancelable: true/false
– pokudtrue
, pak může být zabráněno „výchozí akci“. Později uvidíme, co to znamená pro vlastní události.
Ve výchozím nastavení jsou obě hodnoty false:
{bubbles: false, cancelable: false}
.
dispatchEvent
Po vytvoření objektu události bychom jej měli „spustit“ na prvku pomocí volání elem.dispatchEvent(event)
.
Potom na to handlery reagují, jako by to byla běžná událost prohlížeče. Pokud byla událost vytvořena pomocí bubbles
vlajka, pak to zabublá.
V níže uvedeném příkladu click
událost je spuštěna v JavaScriptu. Obslužná rutina funguje stejným způsobem, jako byste klikli na tlačítko:
<button id="elem" onclick="alert('Click!');">Autoclick</button>
<script>
let event = new Event("click");
elem.dispatchEvent(event);
</script>
event.isTrusted Existuje způsob, jak odlišit „skutečnou“ uživatelskou událost od události generované skriptem.
Vlastnost event.isTrusted
je true
pro události, které pocházejí ze skutečných uživatelských akcí a false
pro události generované skripty.
Příklad probublávání
Můžeme vytvořit bublinovou událost s názvem "hello"
a chyťte to na document
.
Vše, co potřebujeme, je nastavit bubbles
na 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>
Poznámky:
- Měli bychom použít
addEventListener
pro naše vlastní události, protožeon<event>
existuje pouze pro vestavěné události,document.onhello
nefunguje. - Nutno nastavit
bubbles:true
, jinak událost nevybuchne.
Mechanika bublání je stejná pro vestavěné (click
) a vlastní (hello
) Události. Existují také fáze zachycení a probublávání.
MouseEvent, KeyboardEvent a další
Zde je krátký seznam tříd pro události uživatelského rozhraní ze specifikace událostí uživatelského rozhraní:
UIEvent
FocusEvent
MouseEvent
WheelEvent
KeyboardEvent
- …
Měli bychom je používat místo new Event
pokud takové události chceme vytvářet. Například new MouseEvent("click")
.
Správný konstruktor umožňuje specifikovat standardní vlastnosti pro tento typ události.
Jako clientX/clientY
pro událost myši:
let event = new MouseEvent("click", {
bubbles: true,
cancelable: true,
clientX: 100,
clientY: 100
});
alert(event.clientX); // 100
Poznámka:obecný Event
konstruktor to neumožňuje.
Zkusme to:
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!
Technicky to můžeme obejít přímým přiřazením event.clientX=100
po vytvoření. Takže je to otázka pohodlí a dodržování pravidel. Události generované prohlížečem mají vždy správný typ.
Úplný seznam vlastností pro různé události uživatelského rozhraní je ve specifikaci, například MouseEvent.
Vlastní události
Pro naše vlastní, zcela nové typy událostí, jako je "hello"
měli bychom použít new CustomEvent
. Technicky je CustomEvent stejný jako Event
, s jedinou výjimkou.
Do druhého argumentu (objektu) můžeme přidat další vlastnost detail
pro jakékoli vlastní informace, které chceme s událostí předat.
Například:
<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
vlastnost může mít jakákoli data. Technicky bychom se bez toho obešli, protože běžnému new Event
můžeme přiřadit libovolné vlastnosti objekt po jeho vytvoření. Ale CustomEvent
poskytuje speciální detail
pole, aby se vyhnul konfliktům s jinými vlastnostmi události.
Kromě toho třída události popisuje „o jaký druh události se jedná“, a pokud je událost vlastní, měli bychom použít CustomEvent
jen aby bylo jasné, co to je.
event.preventDefault()
Mnoho událostí prohlížeče má „výchozí akci“, jako je navigace na odkaz, zahájení výběru a tak dále.
Pro nové, vlastní události rozhodně neexistují žádné výchozí akce prohlížeče, ale kód, který takovou událost odešle, může mít své vlastní plány, co dělat po spuštění události.
Zavoláním na číslo event.preventDefault()
, může obsluha události vyslat signál, že tyto akce by měly být zrušeny.
V takovém případě volání na elem.dispatchEvent(event)
vrátí false
. A kód, který jej odeslal, ví, že by neměl pokračovat.
Podívejme se na praktický příklad – schovávající se králík (může to být závěrečné menu nebo něco jiného).
Níže můžete vidět #rabbit
a hide()
funkce, která odesílá "hide"
událost na něm, aby všechny zainteresované strany věděly, že se králík bude schovávat.
Jakýkoli handler může na tuto událost naslouchat pomocí rabbit.addEventListener('hide',...)
a v případě potřeby akci zrušte pomocí event.preventDefault()
. Pak králík nezmizí:
<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>
Upozornění:událost musí mít příznak cancelable: true
, jinak volání event.preventDefault()
je ignorováno.
Události v událostech jsou synchronní
Události jsou obvykle zpracovávány ve frontě. To znamená:pokud prohlížeč zpracovává onclick
a dojde k nové události, např. myší přesunuta, pak se její zpracování zařadí do fronty, což odpovídá mousemove
handlery budou volány po onclick
zpracování je dokončeno.
Pozoruhodnou výjimkou je situace, kdy je jedna událost iniciována z jiné, např. pomocí dispatchEvent
. Takové události jsou zpracovány okamžitě:jsou volány nové obslužné rutiny událostí a poté je obnoveno zpracování aktuální události.
Například v kódu pod menu-open
událost je spuštěna během onclick
.
Zpracovává se okamžitě, bez čekání na onclick
handler na konec:
<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>
Výstupní pořadí je:1 → vnořeno → 2.
Upozorňujeme, že vnořená událost menu-open
je zachycen na document
. Šíření a zpracování vnořené události je dokončeno předtím, než se zpracování dostane zpět k vnějšímu kódu (onclick
).
To není jen o dispatchEvent
, jsou i další případy. Pokud obsluha události volá metody, které spouštějí jiné události – jsou také zpracovávány synchronně, vnořeným způsobem.
Řekněme, že se nám to nelíbí. Chtěli bychom onclick
být plně zpracován jako první, nezávisle na menu-open
nebo jakékoli jiné vnořené události.
Pak můžeme buď vložit dispatchEvent
(nebo jiné volání spouštějící událost) na konci onclick
nebo možná lépe, zabalit to do nulového zpoždění 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>
Nyní dispatchEvent
běží asynchronně po dokončení aktuálního spuštění kódu, včetně menu.onclick
, takže obslužné rutiny událostí jsou zcela samostatné.
Výstupní pořadí bude:1 → 2 → vnořeno.
Shrnutí
Abychom vygenerovali událost z kódu, musíme nejprve vytvořit objekt události.
Obecný Event(name, options)
konstruktor přijímá libovolný název události a options
objekt se dvěma vlastnostmi:
bubbles: true
pokud by událost měla probublávat.cancelable: true
pokudevent.preventDefault()
by mělo fungovat.
Další konstruktory nativních událostí jako MouseEvent
, KeyboardEvent
a tak dále přijměte vlastnosti specifické pro daný typ události. Například clientX
pro události myši.
Pro vlastní události bychom měli používat CustomEvent
konstruktér. Má další možnost s názvem detail
, měli bychom k němu přiřadit data specifická pro událost. Pak k němu mohou všichni handleři přistupovat jako event.detail
.
Navzdory technické možnosti generování událostí prohlížeče jako click
nebo keydown
, měli bychom je používat s velkou opatrností.
Neměli bychom generovat události prohlížeče, protože je to otřesný způsob spouštění obslužných programů. To je většinou špatná architektura.
Nativní události mohou být generovány:
- Jako špinavý hack, aby knihovny třetích stran fungovaly potřebným způsobem, pokud neposkytují jiné prostředky interakce.
- Pro automatické testování „klikněte na tlačítko“ ve skriptu a zjistěte, zda rozhraní reaguje správně.
Vlastní události s našimi vlastními názvy jsou často generovány pro architektonické účely, aby signalizovaly, co se děje uvnitř našich nabídek, posuvníků, kolotočů atd.