Mange events følger automatisk med bestemte handlinger, som udføres af browseren.
For eksempel:
- Et klik pÃ¥ et link â starter navigation til dens URL.
- Et klik pÃ¥ en form submit-knap â starter indsendelsen af formen til serveren.
- Tryk pÃ¥ en museknap over en tekst og flytter den efterfølgende â vælger teksten.
Hvis vi håndterer et event i JavaScript, vil vi måske ikke ønske den tilsvarende browserhandling skal ske. Det kan være, at vi ønsker at implementere en anden adfærd i stedet.
Forhindring af browserhandling
Der er to måder at sige til browseren, at vi ikke vil have den til at handle:
- Standardmetoden er at bruge
event-objektet. I den er der metodeevent.preventDefault(). - Hvis handleren er tildelt ved hjælp af
on<event>(ikke ved hjælp afaddEventListener), så virker det samme, hvis man returnererfalse.
I dette HTML vil et klik på et link ikke føre til navigation; browseren gør ikke noget:
<a href="/" onclick="return false">Klik her</a>
eller
<a href="/" onclick="event.preventDefault()">her</a>
I det næste eksempel vil vi bruge denne teknik til at skabe et menu baseret på JavaScript.
false fra en handler er en undtagelseVærdien der returneres fra en event handler ignoreres normalt.
Den eneste undtagelse er return false fra en handler tildelt ved hjælp af on<event>.
I alle andre tilfælde bliver return-værdien ignoreret. Der er i hvert fald ingen mening i at returnere true.
Eksempel: en menu
Forestil dig en menu på et websted, som denne:
<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>
Her er hvordan det ser ud med noget CSS:
Menupunkter implementeres som HTML-links <a>, ikke knapper <button>. Der er flere grunde til at gøre det på denne måde, for eksempel:
- Mange brugere foretrækker at bruge âhøjreklikâ â âÃ¥bn i et nyt vindueâ. Hvis vi bruger
<button>eller<span>, virker det ikke. - Søgemaskiner følger
<a href="...">links mens de indexerer.
SÃ¥ vi bruger <a> i markupâen. Men normalt vil vi hÃ¥ndtere klik i JavaScript. SÃ¥ vi bør forhindre den standard browserhandling.
SÃ¥dan her:
menu.onclick = function(event) {
if (event.target.nodeName != 'A') return;
let href = event.target.getAttribute('href');
alert( href ); // ...kan være at der hentes fra en server, generering af UI osv.
return false; // forhindrer browserhandling (gå ikke til URL'en)
};
Hvis vi udelader return false, vil browseren efter vores kode eksekveres køre sin âstandardhandlingâ â navigere til URLâen i href. Det har vi ikke brug for her, da vi hÃ¥ndterer klikket selv.
Forresten vil event delegation gøre vores menu meget fleksibel. Vi kan tilføje indre lister og style dem sÃ¥ de âglider nedâ ved hjælp af CSS.
Bestemte events flyder fra den ene til den anden. Hvis vi forhindrer det første event, vil der ikke være noget andet.
For eksempel fører mousedown på et <input>-felt til fokus i det, og eventet focus. Hvis vi forhindrer eventet mousedown, er der ingen fokus.
Prøv at klikke pÃ¥ det første <input> nedenfor â eventet focus sker. Men hvis du klikker pÃ¥ det andet, er der ingen fokus.
<input value="Fokus virker" onfocus="this.value=''">
<input onmousedown="return false" onfocus="this.value=''" value="Klik mig">
Det er fordi den browserhandling, der sker ved mousedown, bliver annulleret. Fokusering er stadig muligt, hvis vi bruger en anden måde at komme ind i inputfeltet på. For eksempel tasterne Tab for at skifte fra det 1. input til det 2. input. Men museklik virker ikke mere.
Den âpassiveâ handler mulighed
Den frivillige indstilling passive: true i addEventListener signalerer til browseren, at handleren ikke vil kalde preventDefault().
Hvorfor vil det være nødvendigt?
Der findes events som touchmove på mobile enheder (når brugeren flytter sin finger over skærmen), som forårsager rulning (scrolling) som standard, men hvor denne rulning kan forhindres ved hjælp af preventDefault() i handleren.
SÃ¥ nÃ¥r browseren registrerer et sÃ¥dant event, skal den først behandle alle handlers, og derefter, hvis preventDefault ikke kaldes nogen steder, kan den fortsætte med rulningen. Det kan forÃ¥rsage unødige forsinkelser og âhakkerâ i brugerfladen.
Indstillingen passive: true fortæller browseren, at handleren ikke vil annullere rulningen. Derefter ruller browseren umiddelbart, hvilket giver bedst mulighed for en flydende oplevelse, og eventet håndteres på den måde.
For noglebrowsere (Firefox, Chrome), er passive sat til true som standardværdi for touchstart og touchmove events.
event.defaultPrevented
Egenskaben event.defaultPrevented er true, hvis standardhandlingen blev forhindret, og false ellers.
Der er et interessant case for det.
Du husker måske kapitlet Bubbling og capturing hvor vi talte om event.stopPropagation() og hvorfor det er dårligt at stoppe bubbling?
Nogle gange kan vi bruge event.defaultPrevented i stedet for at signalere andre event handlers, at eventet er blevet håndteret.
Lad os se et praktisk eksempel.
Som udgangspunkt viser browseren ved et contextmenu-event (højre museklik) en kontekstmenu med standardindstillinger. Vi kan forhindre dette og vise vores egen menu, på følgende måde:
<button>Højreklik viser browserens kontekstmenu</button>
<button oncontextmenu="alert('Tegn vores kontekstmenu'); return false">
Højreklik viser vores kontekstmenu
</button>
Nu, udover den kontekstmenu, vi har, vil vi gerne implementere en kontekstmenu på dokumentniveau.
Ved højre museklik skal den nærmeste kontekstmenu vises.
<p>Højreklik her for dokumentets kontekstmenu</p>
<button id="elem">Højreklik her for knappens kontekstmenu</button>
<script>
elem.oncontextmenu = function(event) {
event.preventDefault();
alert("Knappens kontekstmenu");
};
document.oncontextmenu = function(event) {
event.preventDefault();
alert("Dokumentets kontekstmenu");
};
</script>
Problemet er, at når vi klikker på elem, får vi to menuer: den ene på knapniveau og (eventet bobler op) den anden på dokumentniveau.
Hvordan fikser vi det? En løsning kunne være at tænke: âNÃ¥r vi hÃ¥ndterer højre museklik i knap-hÃ¥ndteringen, sÃ¥ stopper vi dens boblingâ og bruger event.stopPropagation():
<p>Højreklik for dokumentets kontekstmenu</p>
<button id="elem">Højreklik for knappens kontekstmenu (rettet med event.stopPropagation)</button>
<script>
elem.oncontextmenu = function(event) {
event.preventDefault();
event.stopPropagation();
alert("Knappens kontekstmenu");
};
document.oncontextmenu = function(event) {
event.preventDefault();
alert("Dokumentets kontekstmenu");
};
</script>
Nu virker knappens kontekstmenu som forventet. Men prisen er høj. Vi forhindrer for altid adgang til information om højre museklik for enhver ydre kode, herunder tællere, der indsamler statistik og så videre. Det er ikke helt klogt.
En alternativ løsning ville være at tjekke i document-håndteringen, om standardhandlingen blev forhindret? Hvis det er tilfældet, så blev eventet håndteret, og vi behøver ikke reagere på det.
<p>Højreklik for dokumentets kontekstmenu (tilføjet en kontrol af event.defaultPrevented)</p>
<button id="elem">Højreklik for knappens kontekstmenu</button>
<script>
elem.oncontextmenu = function(event) {
event.preventDefault();
alert("Knappens kontekstmenu");
};
document.oncontextmenu = function(event) {
if (event.defaultPrevented) return;
event.preventDefault();
alert("Dokumentets kontekstmenu");
};
</script>
Nu virker alt som forventet. Hvis vi har indlejrede elementer, og hvert enkelt har sin egen kontekstmenu, vil det også virke. Sørg blot for at tjekke for event.defaultPrevented i hver contextmenu-håndtering.
Det er tydeligt, at event.stopPropagation() og event.preventDefault() (også kendt som return false) er to forskellige ting. De er ikke relaterede til hinanden.
Der er også alternative måder at implementere indlejrede kontekstmenuer. En af dem er at have et enkelt globalt objekt med en håndtering af document.oncontextmenu, og metoder, der tillader os at gemme andre håndteringer i det.
Objektet vil fange alle højreklik, kigge gennem de gemte håndteringer og køre den der passer.
Men så skal al kode der ønsker en kontekstmenu kende til det objekt og bruge dets hjælp i stedet for dens egen contextmenu-håndtering.
Opsummering
Der er mange standard browser-handlinger:
mousedownâ starter valg (flyt musen for at vælge).clickpÃ¥<input type="checkbox">â markerer/afmarkerer etinput.submitâ Klik pÃ¥<input type="submit">eller tast Enter inde i et formularfelt medfører at denne event opstÃ¥r, og browseren sender formen.keydownâ tryk pÃ¥ en tast kan medføre tilføjelse af et tegn i et felt, eller andre handlinger.contextmenuâ denne event opstÃ¥r ved et højreklik, og handlingen er at vise browserens kontekstmenu.- â¦og der er flereâ¦
Alle de standard handlinger kan forhindres, hvis vi ønsker at håndtere eventen udelukkende med JavaScript.
For at forhindre en standard handling â brug enten event.preventDefault() eller return false. Den anden metode virker kun for hÃ¥ndteringer, der er tildelt med on<event>.
Indstillingen passive: true på addEventListener fortæller browseren, at handlingen ikke vil blive forhindret. Det er nyttigt for nogle mobile events, som touchstart og touchmove, for at fortælle browseren, at den ikke skal vente på, at alle håndteringer er færdige, før den begynder at scrolle.
Hvis en standard handling blev forhindret, bliver værdien af event.defaultPrevented til true, ellers er det false.
Teknisk set, kan vi ved at forhindre standard handlinger og tilføje JavaScript tilpasse adfærd for ethvert element. For eksempel kan vi få et link <a> til at virke som en knap, og en knap <button> til at opføre sig som et link (omdirigere til en anden URL osv.).
Men vi bør generelt holde den semantiske betydning af HTML-elementer. For eksempel bør <a> udføre navigation, ikke en knap.
Udover bare at være âgod stilâ, sÃ¥ gør det ogsÃ¥ din HTML bedre i forhold til tilgængelighed.
Endelig, hvis vi tager eksemplet med <a> i betragtning, så bør vi bemærke følgende: En browser tillader os at åbne sådanne links i et nyt vindue (ved at højreklikke på dem og andre midler) ⦠og folk kan lide det. Men hvis vi designer en knap så den opfører sig som et link ved hjælp af JavaScript og endda ser ud som et link ved hjælp af CSS, så vil <a>-specifikke browserfunktioner stadig ikke virke for det.
Kommentarer
<code>-taggen, for flere linjer - omslut dem i<pre>-tag, for mere end 10 linjer - brug en sandbox (plnkr, jsbin, codepenâ¦)