Orlok: Event nicht weiterreichen stopPropagation

Beitrag lesen

Hallo Linuchs

mein Thema ist "Event nicht weiterreichen".

Das habe ich schon vernommen. ;-)

Und genau da habe ich nicht kapiert, wo das in deinem Text verborgen ist, bzw. wie ich das erreichen kann.

Die offenbar leider verborgene Botschaft meines Textes war, dass es überhaupt nicht notwendig ist, das Ereignis daran zu hindern durch das DOM zu propagieren.

Kann doch soo schwer nicht sein, hatten wir doch schon vor 20 Jahren in der Groß-EDV mit Oracle. War irgendwas mit "throw ..." (werfen).

Ja, die throw-Anweisung gibt es auch in JavaScript, allerdings wird der Effekt nicht unbedingt der sein, den du dir anscheinend davon erhoffst. ;-)

Es nutzt mir nichts, wenn sämtliche (übergeordneten, untergeordneten) Events feuern.

Mit „übergeordnet“ oder „untergeordnet“ hat das nichts zu tun. Das Event feuert.

<!DOCTYPE html>
<head>

Das öffnende Tag des html-Elementes fehlt. → <html lang="de">

<meta http-equiv="content-type" content="text/html;charset=UTF-8">

Du verwendes HTML5, also kannst du auch einfach <meta charset="UTF-8"> schreiben. Auch sollte, selbst wenn es sich nur um eine Testseite handelt, dennoch eine Angabe zum Viewport gemacht werden:

<meta name="viewport" content="width=device-width, initial-scale=1.0">

Weiter im Text:

</head>
<body>
<style type="text/css">
div {
  height: 10em;
  background-color: #8f8;
}
</style>

Das Element style muss Kindelement von head sein, in body hat es nichts verloren. Außerdem ist die Angabe type="text/css" überflüssig, da dies der Standardwert ist. Die kann also weg.

<div id="demo_1" onclick="funktion1()">
  <p id="demo_2">1. Klick auf diesen Absatz, um den Text auszutauschen.</p>
  <p id="demo_3">2. Klick auf dieses Rechteck zum Farbwechsel.</p>
  <p id="demo_4">3. Klick unterhalb des Rechtecks, um diesen Text zu ändern.</p>
</div>

Erwähnte ich schon, dass Eventhandler nicht im HTML registriert werden sollten, sondern direkt im Script? Davon abgesehen wäre eine ordered list hier wohl semantischer, aber sei’s drum …

<script type="text/javascript">

Auch hier ist die Angabe des type-Attributes überflüssig weil Standard. Also weg damit.

function funktion1 () {
  document.getElementById("demo_1").style.backgroundColor = "#f88";
}

document.addEventListener("click", function(){
    document.getElementById("demo_4").innerHTML = "3. unterhalb des Rechtecks wurde geklickt";
});

document.getElementById("demo_2").addEventListener("click", function(){
    document.getElementById("demo_2").innerHTML = "1. Text wurde getauscht";
});

Ich klicke also auf den 1. Absatz, um den Text zu tauschen. Das funktioniert soweit. Aber dann geht's weiter wie in der Schlachterei: "Darf es etwas mehr sein?" Das Rechteck wird umgefärbt und im Text von Absatz 3 wird erklärt, es wurde unterhalb des Rechtecks geklickt.

Es passiert genau das, was du mit deinen Anweisungen angeordnet hast. ;-)

Du hast zunächst mal einen Eventhandler für das Container-Element mit der ID 'demo_1' und das Ereignis click registriert. Dieser Handler wird immer aufgerufen, wenn innerhalb dieses divs geklickt wird. Das heißt, beim ersten Klick wird dieses div rot gefärbt und so bleibt es. Dabei spielt es wie gesagt auch keine Rolle, ob der Klick direkt auf diesem div oder auf einem seiner Kindelemente erfolgt ist, da das Ereignis im DOM aufsteigt und den für das div registrierten Handler entsprechend auslöst.

Als nächstes hast du für das Objekt document – diesmal glücklicherweise unter Verwendung der Methode addEventListener – einen weiteren Eventhandler registriert, wieder für das Ereignis click. Dieser wird aus dem selben Grund wie bei dem für das div registrierten Handler immer aufgerufen, wenn irgendwo bei einem seiner Abkömmlinge geklickt wird. Da document das Dokument repräsentiert, wird von diesem Handler schlicht jeder Klick abgefangen. Entsprechend wird also bei jedem Klick der Inhalt des Elementes mit der ID 'demo_4' ersetzt.

Schließlich hast du noch einen dritten Eventhandler registriert, und zwar für den Absatz mit der Kennzeichnung 'demo_2'. Dieser Handler wird nur dann aufgerufen, wenn du tatsächlich auf dieses Element klickst. Sonst nicht.

Wenn du nun also auf diesen ersten Absatz mit der ID 'demo_2' klickst, dann passiert folgendes:

  • Das Ereignis click wird gefeuert, sprich es wird ein entsprechendes Event-Objekt erzeugt und in das DOM eingespeist.

  • Dabei wird es als erstes dem globalen Objekt window übergeben, dann passiert es document und html und body und so weiter und so fort. → Die Capturing-Phase, in der noch nichts weiter passiert, da du für diese Phase keinen Eventhandler registriert hast.

  • Dann kommt das Event-Objekt bei seinem Zielobjekt an, nämlich dem Absatz auf den geklickt wurde, hier also das Element mit der ID 'demo_2'. → Die Target-Phase, in der nun der für dieses Objekt registrierte Eventhandler aufgerufen wird, sodass dessen Anweisung, den Inhalt dieses Absatzes zu ersetzen ausgeführt wird. Entsprechend wird der Inhalt von 'demo_2' auf „1. Text wurde getauscht“ geändert.


Das wäre bezogen auf dieses Beispiel nun der richtige Zeitpunkt, um die weitere Ausbreitung des Ereignisses im DOM zu unterbinden, wenn man das denn wollte, damit die anderen beiden für das Ereignis click registrierten Handler nicht aufgerufen werden, wenn auf diesen Absatz geklickt wird:

document.getElementById('demo_2').addEventListener('click', function (event) {
  // verhindere die weitere Ausbreitung im DOM
  event.stopPropagation( );
  // ersetze den textuellen Inhalt des Elements
  this.textContent = "1. Text wurde getauscht";
});

Dazu müsstest du wie du siehst deinen Eventhandler etwas umschreiben. Die Weitergabe des Ereignisses wird nämlich durch eine Methode des Event-Objektes unterbunden, welches wie in meinen anderen Postings bereits ausführlich beschrieben, der Handlerfunktion bei ihrem Aufruf als Argument übergeben wird. Zwar könntest du auch über arguments[0] darauf zugreifen, aber das wäre Quatsch, also notieren wir einen entsprechenden Parameter für die Funktion. → event

Haben wir das gemacht, stehen dir hier sogar zwei Varianten zur Verfügung, wie du das Ereignis an seiner Weiterreise hindern kannst. Nämlich erstens die Methode stopPropagation, die verhindert, dass andere Objekte innerhalb des DOM von dem Ereignis erreicht werden, und zweitens die Methode stopImmediatePropagation, deren Aufruf verhindert, dass andere Eventhandler erreicht werden, also auch solche nicht, welche unter Umständen für dasselbe Objekt registriert wurden.

Da wir (du) hier nur verhindern willst, dass Eventhandler aufgerufen werden, die für andere Objekte registriert wurden, genügt es also, auf dem Event-Objekt die Methode stopPropagation aufzurufen.

Das Ergebnis wäre dann, dass tatsächlich nur der Inhalt von 'demo_2' ersetzt wird, und sonst passiert nichts mehr weiter. – Dabei sei übrigens noch angemerkt, dass ich hier in meinem modifizierten Eventhandler innerHTML durch textContent ersetzt habe. Es ist nämlich nicht notwendig den HTML-Parser anzuwerfen, nur um den textuellen Inhalt eines Elementes zu ersetzen. Aber das nur nebenbei …


Nehmen wir nun an, wir hätten das Ereignis nicht daran gehindert, weiter durch das DOM zu propagieren, also ganzo so, wie es in deinem Code notiert ist, dann würde es folgendermaßen weitergehen:

  • Nachdem der Eventhandler auf 'demo_2' aufgerufen und dessen Inhalt ersetzt wurde, ist die Target-Phase beendet. Nun wird der Ereignispfad umgedreht. Das heißt, das Ereignis tritt nun in seine Bubbling-Phase ein, sprich, es passiert nun in umgekehrter Reihenfolge dieselben Objekte, die es schon in der Capturing-Phase passiert hat, als es zum Zielobjekt herabgestiegen ist.

  • Die erste Station ist nun also das Elternelement von 'demo_2', nämlich das Container-Element div mit der ID 'demo_1'. Da nun für dieses Element, beziehungsweise das Objekt das es repräsentiert ebenfalls ein Eventhandler für das Ereignis click registriert wurde, und weil das stop propagation flag des Ereignisses hier ja nun nicht gesetzt ist, wird dem zur Folge auch dieser Handler aufgerufen und es wird der Hintergrund des div-Elementes rot eingefärbt.

  • Nachdem dieser Eventhandler also abgearbeitet ist, setzt das Ereignis seine Reise fort und landet schließlich kurz vor der endgültigen Zielline bei dem Objekt document. Da nun für dieses Objekt und den Ereignistyp click ebenfalls ein Eventhandler registriert wurde, wird auch dieser Handler aufgerufen und die darin notierte Anweisung ausgeführt, sodass der Elementinhalt des letzen Absatzes mit der ID 'demo_4' durch den Textknoten mit dem Wert „3. unterhalb des Rechtecks wurde geklickt“ ersetzt wird.

Du siehst also: Das von dir beschriebene Verhalten entspricht exakt dem Verhalten, dass du durch deine Anweisungen implementiert hast. ;-)

Hilfe, ich will nur eine Scheibe Wurst und nicht den ganzen Laden !!!

Die Scheibe Wurst hast du jetzt, denn du hast nun hoffentlich verstanden, wie sich das Ereignis durch das DOM bewegt und wie du die Ausbreitung unter Verwendung der Methoden stopPropagation und stopImmediatePropagation des Event-Objektes an der richtigen Stelle unterbinden kannst.


Aber es geht mir hier nicht darum, dich mit einer Scheibe Wurst abzuspeisen, sondern ich versuche dir das Kochen beizubringen!


Will sagen, ich habe dir das alles hier nicht aufgeschrieben weil mir langweilig ist, sondern weil es mein Ziel ist, dass du verstehst was du hier eigentlich machst. Denn die Förderung des Verständnisses ist meiner Ansicht nach der Hauptzweck dieses Forums und der Grund für mein Engagement.

Der eigentliche Punkt auf den ich in meinen Beiträgen hier im Thread nämlich hinaus will ist, dass der ganze Ansatz mit stopPropagation hier ganz einfach grundsätzlich unsinnig ist. Denn was wäre denn die Folge?

Die Folge wäre, dass du für jeden Listeneintrag einen Eventhandler registrierst und dann die Anweisung event.stopPropagation() einfügst, damit deine für Elternelemente registrierten Handler nicht ebenfalls aufgerufen werden. → Und das ist einfach unpraktikabel!

Es sollte hier also nicht darum gehen, die Ausbreitung des Ereignisses zu verhindern, sondern im Gegenteil, sich diese zu Nutze zu machen, weshalb ich dir hier versucht habe das Konzept der Event Delegation näherzubringen.

Um also bei dem von dir hier selbst angebrachten Beispiel zu bleiben: Statt für jeden einzelnen Absatz einen Eventhandler zu registrieren, solltest du nur einen Handler hinzufügen, und zwar bei dem gemeinsamen Elternelement 'demo_1'. Oder du gehst gegebenenfalls noch ein paar Stufen weiter in der Hierarchie deines DOM und registrierst den Handler zum Beispiel gleich für das body-Element.

Dieser globale Eventhandler würde dann jedesmal aufgerufen werden, wann immer irgendwo geklickt wird, und du kannst dann innerhalb dieser Funktion die Entsprechenden Anweisungen vornehmen, eben abhändig davon, bei welchem Element das Ereignis aufgetreten ist. Durch bedingte Anweisungen und über event.target, wo wie beschrieben immer eine Referenz auf das Objekt hinterlegt ist, bei dem das Ereignis tatsächlich aufgetreten ist.

Das Ziel ist also, in der Funktion zu selektieren und nicht bei der Registrierung der Eventhandler …

Viele Grüße,

Orlok

--
„Das Wesentliche einer Kerze ist nicht das Wachs, das seine Spuren hinterlässt, sondern das Licht.“ Antoine de Saint-Exupéry