Event delegation
Angenommen, wir haben eine Liste mit Elementen und wir wollen auf einen Klick auf jedes dieser Elemente lauschen. Wir könnten nun jedem einzelnen Element einen EventListener zuweisen. Bei sehr vielen Items wird das aber ineffizient (man stelle sich eine Liste mit 100 oder 1000 Elementen vor …). Daher benutzen wir in solchen Fällen das event delegation pattern.
Event delegation pattern
Section titled “Event delegation pattern”Das event delegation pattern macht sich die event propagation zunutze. Wir hängen also einen EventListener an ein übergeordnetes Element und dieses lauscht auf alle Events, die von den nachfolgenden Elementen abgefeuert werden. (Das funktioniert natürlich nur mit Elementen, die bubbeln!)
<ul> <li class="item1">Item 1<li> <li class="item2">Item 2<li> <li class="item3">Item 3<li> <li class="item4">Item 4<li> <li class="item5">Item 5<li></ul>const ul = document.querySelector('ul');
ul.addEventListener('click', e => { // do stuff here});Determining the event target
Section titled “Determining the event target”Natürlich wollen wir wissen, welches der Elemente in der Liste den Event tatsächlich abgefeuert hat. Dieses Element wird event target genannt. Wir finden es mit der Eigenschaft target.
const ul = document.querySelector('ul');
ul.addEventListener('click', e => { console.log(e.target, e.currentTarget);});Ein Klick auf Item 1 ergibt:
<li class="item1"> <ul>Ein Klick auf Item 2 ergibt:
<li class="item2"> <ul>… und so weiter. Wir sehen also:
targetist das Element, das den Event abgefeuert hatcurrentTargetist das Element, dem wir den EventListener zugewiesen haben (und der die bubbelnden Events abfängt.)
Avoid misfires
Section titled “Avoid misfires”Eine Sache, die wir beim event delegation pattern im Auge behalten müssen, ist, dass es auch auf Klicks hört, die oberhalb der event targets (also in dem Fall oberhalb der Listenelemente) passieren. Das bedeutet, dass die Liste selbst gleichzeitig event target und event currentTarget sein kann.
Um das zu verhindern, müssen wir prüfen, ob das target element mit dem Element auch wirklich ein target element ist und nicht das currentTarget. Das machen wir mit der Methode matches.
matches prüft, ob das Element mit dem gewählten Selektor übereinstimmt.
element.matches(selector); // returns true or falseWir wollen nur einen Event abfeuern, wenn der User auf ein Listenelement klickt und nicht auf die Liste selbst:
const ul = document.querySelector('ul');
ul.addEventListener('click', e => { if (e.target.matches('li')) { console.log(e.target, e.currentTarget); }});Dealing with nested elements
Section titled “Dealing with nested elements”Angenommen, wir wollen einen EventListener an einem Button anbringen. Innerhalb dieses Buttons ist ein SVG-Icon.
<button> <svg><!-- Icon --></svg> Button Text</button>const button = document.querySelector('button');
button.addEventListener('click', e => { // do stuff here});Wenn wir auf das Icon klicken, ist das SVG unser e.target. Das sollte nicht sein.
Es gibt zwei Methoden, mit denen wir sicherstellen können, dass wir immer das button Element bekommen:
pointer-eventsaufnonesetzenclosestverwenden
Pointer events
Section titled “Pointer events”pointer-events ist eine CSS-Eigenschaft, die bestimmt, wie ein Element auf Mausklicks reagiert. Wenn ein Element die Eigenschaft pointer-event: none besitzt, reagiert es nicht auf Mausklicks. Wir können daher in einem Reset-Stylesheet allen Nachkommen des Button-Elements ein pointer-event: none mitgeben:
button * { pointer-events: none;}Closest
Section titled “Closest”closest sucht im DOM aufwärts nach einem Element, das auf den gewählten Selektor passt. Das schließt auch das Element selbst mit ein.
element.closest(selector)- Wenn es ein Element findet, das auf den Selektor passt, wird das Element zurückgegeben.
- Wenn es kein Element findet, gibt es
undefinedzurück.
Wenn wir nach button suchen, können wir sicherstellen, dass wir mit dem Button-Element arbeiten, auch wenn der User auf das SVG geklickt hat:
const button = document.querySelector('button');
button.addEventListener('click', e => { const button = e.target.closest('button'); if (button) { // do stuff here when user clicks any element within 'button' }});Excercise
Section titled “Excercise”<ul class="famous"> <li class="item-1"><a href="#">Benjamin Franklin</a></li> <li class="item-2"><a href="#">Thomas Edison</a></li> <li class="item-3"><a href="#">Franklin Roosevelt</a></li> <li class="item-4"><a href="#">Napoleon Bonaparte</a></li> <li class="item-5"><a href="#">Abraham Lincoln</a></li></ul>- Erstelle einen Event Listener, der das event delegation pattern benutzt
- Logge das Element, wenn target
matchesli - Versuche beide Varianten: pointer-events und
closest, um das event target zu filtern
const ul = document.querySelector('.famous');console.log(ul);
// 1.+2.ul.addEventListener('click', e => { const li = e.target.closest('li'); if (e.target.matches('li')) { console.log(e.target); }});// 3. closestul.addEventListener('click', e => { const li = e.target.closest('li'); if (li) { console.log(li); }});// 3. pointer-eventsul.famous { a { pointer-events: none; }}