Mnoho událostà automaticky vede k tomu, že prohlÞeÄ vykoná urÄitou akci.
NapÅÃklad:
- Kliknutà na odkaz â vyvolá navigaci na jeho URL.
- Kliknutà na tlaÄÃtko odeslánà formuláÅe â vyvolá jeho odeslánà na server.
- Stisknutà tlaÄÃtka myÅ¡i nad textem a pÅesun ukazatele â vybere (oznaÄÃ) text.
Jestliže v JavaScriptu zpracováváme událost, můžeme chtÃt, aby se pÅÃsluÅ¡ná akce prohlÞeÄe nevykonala, protože mÃsto nà chceme implementovat jiné chovánÃ.
Zákaz akcà prohlÞeÄe
Existujà dva způsoby, jak sdÄlit prohlÞeÄi, že nechceme, aby konal svou akci:
- HlavnÃm způsobem je použità objektu
událost, který obsahuje metoduudálost.preventDefault(). - Jestliže je handler pÅiÅazen prostÅednictvÃm
on<událost>(ne metodouaddEventListener), pak bude stejným způsobem fungovat i vrácenÃfalse.
V následujÃcÃm HTML kódu kliknutà na odkaz nevyvolá navigaci; prohlÞeÄ neudÄlá nic:
<a href="/" onclick="return false">KliknÄte sem</a>
nebo
<a href="/" onclick="event.preventDefault()">sem</a>
V dalÅ¡Ãm pÅÃkladu pomocà této techniky vytvoÅÃme JavaScriptové menu.
false z handleru je výjimkaHodnota vrácená handlerem událostà je zpravidla ignorována.
Jedinou výjimkou je return false z handleru pÅiÅazeného pomocà on<událost>.
Ve vÅ¡ech ostatnÃch pÅÃpadech se návratová hodnota ignoruje. Nemá tedy smysl vracet napÅÃklad true.
PÅÃklad: menu
Uvažujme menu na stránce, napÅÃklad takto:
<ul id="menu" class="menu">
<li><a href="/html">HTML</a></li>
<li><a href="/javascript">JavaScript</a></li>
<li><a href="/css">CSS</a></li>
</ul>
Takto bude vypadat s urÄitým CSS:
Položky menu jsou implementovány jako HTML odkazy <a>, ne jako tlaÄÃtka <button>. Má to nÄkolik důvodů, napÅÃklad:
- Mnozà lidé rádi použÃvajà âkliknutà pravým tlaÄÃtkemâ â âotevÅenà v novém oknÄâ. Pokud bychom použili
<button>nebo<span>, nefungovalo by to. - Vyhledávacà stroje pÅi indexovánà následujÃ
<a href="...">.
V tomto kódu tedy použÃváme <a>. Chceme vÅ¡ak kliknutà zpracovávat v JavaScriptu, takže bychom mÄli zakázat standardnà akci prohlÞeÄe.
NapÅÃklad:
menu.onclick = function(událost) {
if (událost.target.nodeName != 'A') return;
let href = událost.target.getAttribute('href');
alert( href ); // ...může naÄÃtat ze serveru, generovat UI atd.
return false; // zakáže akci prohlÞeÄe (nepÅejde na URL)
};
Kdybychom vypustili return false, pak by po provedenà naÅ¡eho kódu prohlÞeÄ vykonal svou âstandardnà akciâ â navigaci na URL v href. A to tady nepotÅebujeme, jelikož kliknutà zpracováváme sami.
Mimochodem, použità delegovánà událostà zde uÄinà naÅ¡e menu velmi flexibilnÃm. Můžeme pÅidávat vnoÅené seznamy a pomocà CSS je stylizovat, aby âklouzaly dolůâ.
NÄkteré události plynou jedna do druhé. Pokud zakážeme prvnà událost, nedojde ke druhé.
NapÅÃklad událost mousedown na poli <input> vede k zamÄÅenà vstupu na pole a k vyvolánà události focus. Pokud událost mousedown zakážeme, k zamÄÅenà nedojde.
Zkuste si kliknout nÞe na prvnà <input> â událost focus nastane. Pokud vÅ¡ak kliknete na druhý, zamÄÅenà nenastane.
<input value="ZamÄÅenà funguje" onfocus="this.value=''">
<input onmousedown="return false" onfocus="this.value=''" value="Klikni na mÄ">
Je to tÃm, že akce prohlÞeÄe je v mousedown zruÅ¡ena. Stále je možné na pole zamÄÅit vstup, jestliže použijeme jiný způsob, jak do nÄj vstoupit, napÅÃklad klávesou Tab se pÅepneme z prvnÃho vstupu do druhého. KliknutÃm myšà to vÅ¡ak už možné nenÃ.
Možnost handleru âpassiveâ
Volitelná možnost passive: true metody addEventListener oznamuje prohlÞeÄi, že handler nebude volat preventDefault().
K Äemu to může být potÅeba?
Existujà události, napÅ. touchmove na mobilnÃch zaÅÃzenÃch (když se uživatel posune prstem na obrazovce), které standardnÄ vyvolajà rolovánÃ, ale v handleru lze toto rolovánà zakázat volánÃm preventDefault().
Když tedy prohlÞeÄ detekuje takovou událost, musà nejprve zpracovat vÅ¡echny handlery, a teprve pokud nikde nebylo voláno preventDefault, může provádÄt rolovánÃ. To může způsobit nechtÄné prodlevy a âzasekávánÃâ v UI.
Možnost passive: true ÅÃká prohlÞeÄi, že handler nebude rolovánà bránit. Pak prohlÞeÄ provede rolovánà okamžitÄ a poskytne maximálnÄ plynulý zážitek, zatÃmco bude zpracována událost.
V nÄkterých prohlÞeÄÃch (Firefox, Chrome) je passive standardnÄ true pro události touchstart a touchmove.
událost.defaultPrevented
Vlastnost událost.defaultPrevented je true, pokud byla standardnà akce zakázána, v opaÄném pÅÃpadÄ je false.
Má to zajÃmavý pÅÃpad použitÃ.
VzpomÃnáte si, jak jsme v kapitole Bublánà a zachytávánà hovoÅili o událost.stopPropagation() a o tom, proÄ je Å¡patné zastavovat bublánÃ?
NÄkdy mÃsto toho můžeme použÃt událost.defaultPrevented, abychom oznámili ostatnÃm handlerům událostÃ, že událost byla již zpracována.
PodÃvejme se na praktický pÅÃklad.
StandardnÄ prohlÞeÄ na událost contextmenu (kliknutà pravým tlaÄÃtkem myÅ¡i) zobrazà kontextové menu se standardnÃmi možnostmi. Můžeme to zakázat a zobrazit vlastnà menu, napÅÃklad:
<button>Pravé kliknutà zobrazà kontextové menu prohlÞeÄe</button>
<button oncontextmenu="alert('ZobrazÃme své menu'); return false">
Pravé kliknutà zobrazà naše kontextové menu
</button>
Nynà navÃc k tomuto kontextovému menu chceme implementovat kontextové menu pro celý dokument.
Po kliknutà pravým tlaÄÃtkem by se mÄlo zobrazit nejbližšà kontextové menu.
<p>Pravé kliknutà sem zobrazà kontextové menu dokumentu</p>
<button id="elem">Pravé kliknutà sem zobrazà kontextové menu tlaÄÃtka</button>
<script>
elem.oncontextmenu = function(událost) {
událost.preventDefault();
alert("Kontextové menu tlaÄÃtka");
};
document.oncontextmenu = function(událost) {
událost.preventDefault();
alert("Kontextové menu dokumentu");
};
</script>
Problém je v tom, že když klikneme na elem, obdržÃme dvÄ menu: pro tlaÄÃtko a (událost probublá výše) pro dokument.
Jak to opravit? Jedno z ÅeÅ¡enà je pomyslet si: âKdyž zpracujeme pravé kliknutà v handleru tlaÄÃtka, zastavÃme bublánÃâ a použÃt událost.stopPropagation():
<p>Pravým kliknutÃm zobrazÃte menu dokumentu</p>
<button id="elem">Pravým kliknutÃm zobrazÃte menu tlaÄÃtka (oÅ¡etÅeno pomocà událost.stopPropagation)</button>
<script>
elem.oncontextmenu = function(událost) {
událost.preventDefault();
událost.stopPropagation();
alert("Kontextové menu tlaÄÃtka");
};
document.oncontextmenu = function(událost) {
událost.preventDefault();
alert("Kontextové menu dokumentu");
};
</script>
Nynà menu pro tlaÄÃtko funguje tak, jak jsme zamýšleli, ale cena za to je vysoká. Navždy se vzdáváme pÅÃstupu k informaci o pravém kliknutà v celém vnÄjÅ¡Ãm kódu, vÄetnÄ poÄÃtadel, která shromažÄujà statistiku a podobnÄ. To nenà pÅÃliÅ¡ moudré.
AlternativnÃm ÅeÅ¡enÃm by bylo v handleru v document provÄÅovat, zda byla standardnà akce zakázána. Pokud ano, událost byla zpracována a my na ni nemusÃme reagovat.
<p>Pravým kliknutÃm zobrazÃte menu dokumentu (pÅidána kontrola na událost.defaultPrevented)</p>
<button id="elem">Pravým kliknutÃm zobrazÃte menu tlaÄÃtka</button>
<script>
elem.oncontextmenu = function(událost) {
událost.preventDefault();
alert("Kontextové menu tlaÄÃtka");
};
document.oncontextmenu = function(událost) {
if (událost.defaultPrevented) return;
událost.preventDefault();
alert("Kontextové menu dokumentu");
};
</script>
I nynà vÅ¡echno funguje správnÄ. Fungovalo by to i tehdy, kdybychom mÄli vnoÅené elementy a každý z nich obsahoval vlastnà kontextové menu. Jenom je tÅeba kontrolovat událost.defaultPrevented v každém handleru contextmenu.
Jak jasnÄ vidÃme, událost.stopPropagation() a událost.preventDefault() (známá také jako return false) jsou dvÄ různé vÄci a nemajà k sobÄ navzájem žádný vztah.
Jsou i jiné způsoby, jak implementovat vnoÅená kontextová menu. JednÃm z nich je mÃt jednoduchý globálnà objekt s handlerem pro document.oncontextmenu a také metodami, které nám umožÅujà uložit do nÄj jiné handlery.
Objekt bude zachytávat vÅ¡echna kliknutà pravým tlaÄÃtkem, procházet uloženými handlery a spouÅ¡tÄt ten správný.
Potom by vÅ¡ak každá Äást kódu, která chce kontextové menu, mÄla tento objekt znát a mÃsto vlastnÃho handleru contextmenu použÃvat jeho pomoc.
ShrnutÃ
Existuje mnoho standardnÃch akcà prohlÞeÄe:
mousedownâ zahájenà výbÄru textu (pohybem myÅ¡Ã).clickna<input type="checkbox">â zaÅ¡krtne/odÅ¡krtne tentoinput.submitâ tuto událost vyvolá kliknutà na<input type="submit">nebo stisknutà Enter uvnitÅ formuláÅového pole, prohlÞeÄ pak formuláŠodeÅ¡le.keydownâ stisknutà klávesy může vést k pÅidánà znaku do pole nebo jiným akcÃm.contextmenuâ událost se stane pÅi kliknutà pravým tlaÄÃtkem, akcà je zobrazenà kontextového menu prohlÞeÄe.- â¦jsou i dalÅ¡Ãâ¦
VÅ¡echny standardnà akce je možné zakázat, pokud chceme událost zpracovávat výluÄnÄ v JavaScriptu.
Abychom zakázali standardnà akci, použijeme buÄ událost.preventDefault(), nebo return false. Druhý způsob funguje jen pro handlery pÅiÅazené pomocà on<událost>.
Možnost passive: true metody addEventListener oznamuje prohlÞeÄi, že akce nebude zakázána. To je užiteÄné pro nÄkteré mobilnà události, napÅ. touchstart a touchmove, abychom sdÄlili prohlÞeÄi, že pÅed rolovánÃm nemusà Äekat na skonÄenà vÅ¡ech handlerů.
Pokud byla standardnà akce zakázána, hodnota událost.defaultPrevented se nastavà na true, v opaÄném pÅÃpadÄ je false.
Zákazem standardnÃch akcà a pÅidánÃm JavaScriptu si technicky můžeme pÅizpůsobit chovánà kteréhokoli elementu. Můžeme napÅÃklad způsobit, že odkaz <a> bude fungovat jako tlaÄÃtko a tlaÄÃtko <button> se bude chovat jako odkaz (pÅesmÄruje na jiné URL a podobnÄ).
ObecnÄ bychom vÅ¡ak mÄli zachovávat sémantický význam HTML elementů. NapÅÃklad navigaci by mÄlo provádÄt <a>, ne tlaÄÃtko.
KromÄ toho, že je to âprostÄ pÄknéâ, to zlepšà náš HTML kód co do pÅÃstupnosti.
NavÃc když uvážÃme pÅÃklad s <a>, pak si prosÃme vÅ¡imnÄte, že prohlÞeÄ nám umožÅuje otevÃrat takové odkazy v novém oknÄ (kliknutÃm pravým tlaÄÃtkem a jinými způsoby). A lidem se to lÃbÃ. Ale i když pomocà JavaScriptu pÅimÄjeme tlaÄÃtko chovat se jako odkaz a dokonce pomocà CSS vypadat jako odkaz, stále na nÄm nebudou fungovat prvky prohlÞeÄe specifické pro <a>.
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â¦)