Peter Später: switch case beachtet nur letzten Wert

Hallo, ich hätte folgende Frage… Ich habe einige DIVs mit derselben Klasse (nennen wir sie hier zur besseren Verständlichkeit einfach "my_class") aber (logischerweise) unterschiedlicher Id.

Nun will ich den DIVs per Event eine Funktion zuordnen und hätte das wie folgt ausgeführt:

var alle_DIVs = document.querySelectorAll('.my_class');

for (var i=0; i<alle_DIVs.length; i++) {
	var my_DIV = alle_DIVs[i];
	var my_DIV_id = alle_DIVs[i].id;
	my_DIV.addEventListener("click", function() {

		switch(my_DIV_id) {
		case "id_1":
		document.write("Erstes Statement");
      break;

		case "id_2":
		document.write("Zweites Statement");
    break;

// CODE

Problem ist, dass bei KLICK allen DIVs die Eigenschaften des letzten DIVs übertragen wurden, heißt, wäre hier id_2 das letzte DIV, spuckt auch id_1 "Zweites Statement" aus... ...warum?

Muss ich alle Fälle umständlich in if (blablabla) {dann blablabla} packen?

Danke für eure Hilfe! Peter.

  1. Tach!

    var alle_DIVs = document.querySelectorAll('.my_class');
    
    for (var i=0; i<alle_DIVs.length; i++) {
    	var my_DIV = alle_DIVs[i];
    	var my_DIV_id = alle_DIVs[i].id;
    	my_DIV.addEventListener("click", function() {
    
    		switch(my_DIV_id) {
    		case "id_1":
    		document.write("Erstes Statement");
          break;
    
    		case "id_2":
    		document.write("Zweites Statement");
        break;
    
    // CODE
    
    

    Problem ist, dass bei KLICK allen DIVs die Eigenschaften des letzten DIVs übertragen wurden, heißt, wäre hier id_2 das letzte DIV, spuckt auch id_1 "Zweites Statement" aus... ...warum?

    Im Eventhandler sprichst du my_DIV_id an. Das ist aber eine Variable aus dem äußeren Scope. Zum Aufrufzeitpunkt hat diese eine Variable den Wert vom letzten Schleifendurchlauf. Du musst aber deren Inhalt in einem weiteren Scope/Closure pro Eventhandler kapseln, also pro Schleifendurchlauf eine Kopie anlegen.

    Beim addEventListener darf nicht nur eine Funktion auf direktem Wege zugewiesen werden. Es sollte stattdessen eine Funktion (beispielsweise als IIFE) aufgerufen werden, die einerseits den aktuellen Wert von my_DIV_id in einer lokalen Variable ablegt, und andererseits den Eventhandler liefert. Somit ist der jeweilige Wert von my_DIV_id konserviert und nicht nur ein einzelner Zustand.

    Andererseits liefert so ein Click-Event auch ein Event-Objekt an den Event-Handler. Man muss das nur entgegennehmen und findet darin auch eine Referenz auf das auslösende Elmenent und darüber dann auch dessen ID. Da kann man sich auch die Closure und die Hilfsvariable sparen.

    dedlfix.

    1. @@dedlfix

      Im [click-]Eventhandler sprichst du my_DIV_id an.

      Hier hätten deine Alarmglocken schrillen müssen: click und div in einem Satz … Sowas darf dir nicht durch die Lappen gehen.

      Ansonsten war deine Antwort eine Ansammlung an Begriffen, bei denen ich sage „ja, schon mal gehört“, mit denen ein Anfänger aber wohl nichts anfangen kann.

      LLAP 🖖

      --
      „Wer durch Wissen und Erfahrung der Klügere ist, der sollte nicht nachgeben. Und nicht aufgeben.“ —Kurt Weidemann
  2. Hallo Peter

    Nun will ich den DIVs per Event eine Funktion zuordnen

    Da ist dein erster Fehler. div-Elemente sind kein interaktiver Inhalt. Das heißt, sie können ohne Weiteres nicht mit der Tastatur bedient werden. Viele Nutzer sind aber auf Tastaturbedienbarkeit angewiesen. Das ist also schlecht. Verwende statt div-Elementen lieber buttons. Die können per Tastatur angewählt und aktiviert werden, ohne dass du zusätzlichen Aufwand betreiben musst.

    var alle_DIVs = document.querySelectorAll('.my_class');
    
    for (var i=0; i<alle_DIVs.length; i++) {
    	var my_DIV = alle_DIVs[i];
    	var my_DIV_id = alle_DIVs[i].id;
    	my_DIV.addEventListener("click", function() {
    
    		switch(my_DIV_id) {
    		case "id_1":
    		document.write("Erstes Statement");
          break;
    
    		case "id_2":
    		document.write("Zweites Statement");
        break;
    
    // CODE
    
    

    Schauen wir uns an, warum das nicht funktioniert:

    Du referenzierst in der Schleife ein Element nach dem anderen und registrierst für jedes Element einen Eventhandler. Du sagst also: „Wenn auf dieses Element geklickt wird, führe diese Funktion aus.“ Dann kommt das nächste Element an die Reihe. Die Funktion, die du als Eventhandler registrierst, wird also nicht direkt ausgeführt, sondern nur hinterlegt und erst zu irgendeinem späteren Zeitpunkt aufgerufen, wenn jemand auf das entsprechende Element geklickt hat. Die Schleife ist zu diesem Zeitpunkt längst abgearbeitet.

    Aus Sicht der als Eventhandler registrierten Funktionen haben die außerhalb definierten Variablen dann den Wert, der ihnen im letzten Schleifendurchlauf zugewiesen wurde.

    Das liegt daran, dass der Anweisungsblock, der den Schleifenrumpf beinhaltet, für Variablen die mit var deklariert werden keinen lokalen Scope darstellt, der in einem Funktionsabschluss konserviert werden könnte. Solche Variablen werden grundsätzlich an den Ausführungskontext gebunden, in dem sie deklariert wurden. Das heißt, sie sind prinzipiell funktionsweit sichtbar, auch wenn sie innerhalb eines Blocks deklariert wurden, der zu einer bedingten Anweisung oder wie hier zu einer Schleife gehört.

    Es wird in deinem Code also nicht in jedem Schleifendurchlauf eine neue Variablenbindung angelegt, sondern bloß ein und derselben Variable ein neuer Wert zugewiesen.

    Es gibt in JavaScript aber auch die Möglichkeit, Konstanten und Variablen zu deklarieren, die Blockscope besitzen, die also nur innerhalb des Anweisungsblocks sichtbar sind, in dem sie deklariert wurden. Hierfür werden die Schlüsselwörter const und let verwendet. Ich würde dir empfehlen, nur solche Konstanten und Variablen zu verwenden. Konstanten, wenn nicht beabsichtigt ist nochmal einen anderen Wert zuzuweisen, andernfalls Variablen.

    const buttons = document.querySelectorAll('button');
    
    
    for (let i = 0; i < buttons.length; i++) {
        const button = buttons[i];
    
        button.addEventListener('click', function(event) {
            console.log(button.id);
        });
    }
    

    Dieses Beispiel ist so ähnlich wie deins, aber hier wird in jedem Schleifendurchlauf eine neue Bindung für die Konstante mit dem Bezeichner button angelegt, in der eine Referenz auf einen Button gespeichert wird. Wenn der registrierte Eventhandler ausgeführt wird, dann wird auf die Konstante zugegriffen, die im selben Schleifendurchlauf deklariert wurde, in dem auch die Funktion definiert wurde.

    Wie dedlfix bereits sagte, ist das alles aber unnötig kompliziert. Jedem Eventhandler wird beim Aufruf ein Eventobjekt übergeben, welches über die Eigenschaften currentTarget und target verfügt. Erstgenannte Eigenschaft enthält eine Referenz auf das Element, auf dem der Eventhandler registriert wurde. Die Eigenschaft target enthält eine Referenz auf das Element, welches das Ereignis ausgelöst hat. Das muss nicht zwingend dasselbe sein, was man sich auch zunutze machen kann.

    document.body.addEventListener('click', function(event) {
    
        if (event.target.tagName === 'BUTTON') {
            console.log(button.id);
        }
    
    });
    

    Statt für jedes Element einen eigenen Eventhandler zu registrieren, kannst du auch auf einem Elternelement – etwa wie im Beispiel oben dem body-Element – einen Eventhandler registrieren. Wird dann auf ein Kindelement geklickt, wird auch dieser Handler aufgerufen. Über die Eigenschaft target des Eventobjekts kannst du dann das Element referenzieren, auf das geklickt wurde und die gewünschte Aktion ausführen. Unter dem Stichwort Event Delegation wirst du mehr zu dieser Technik finden.

    Viele Grüße,

    Orlok

    1. Tach!

      Es gibt in JavaScript aber auch die Möglichkeit, Konstanten und Variablen zu deklarieren, die Blockscope besitzen, die also nur innerhalb des Anweisungsblocks sichtbar sind, in dem sie deklariert wurden. Hierfür werden die Schlüsselwörter const und let verwendet.

      for (let i = 0; i < buttons.length; i++) {
          const button = buttons[i];
      

      Dass das i mit dem let in einer Schleife konserviert wird, wenn es wie im Beispiel-Ausschnitt definiert ist, hab ich auch schon mal gesagt bekommen, aber wieder verdrängt. Das muss ich mir mal merken, dass für let und const innerhalb ihrer Klammern (plus wenn sie im Statement davor angelegt sind, für das dieser Block erstellt wurde) eine eigene Closure erstellt wird. Damit ginge das Vorhaben einfacher zu erledigen als mit einer IIFE zu hantieren. Also zumindest, wenn in vergleichbaren Fällen kein Event-Objekt zur Verfügung steht, das die Information eh schon enthält.

      dedlfix.

    2. Hallo Orlok,

      auch von mir danke für den Hinweis auf das andere Closure-Verhalten von let und const.

      Ich hätte aber auch einen für Dich, dir ist beim Schreiben deines Romans etwas durchgegangen:

      document.body.addEventListener('click', function(event) {
      
          if (event.target.tagName === 'BUTTON') {
              console.log(button.id);  // <<------------ Undefinierte Variable!
          }
      
      });
      

      In diesem Beispiel wird mit dem Event-Target gearbeitet, darum sollte auch event.target verwendet werden und nicht button. Bzw. per let oder var eine Variable button erzeugt und aus event.target befüllt werden.

      Diese Frage ist schon so oft gestellt worden, dazu müsste man eigentlich mal was bloggen oder ein Wiki-Tutorial schreiben. Obwohl - gibt ja schon was... Da müsste man nur ein bisschen updaten.

      BTW - im Wiki-Anfängertutorial zur Ereignisverarbeitung ist ein Platzhalter für Capturing und Bubbling. Das wollte ich ausbauen, sah dann aber dass Du dazu in der Beschreibung des Event-Objekts einen Roman verfasst hast. Wie sollte man das sortieren?

      Rolf

      --
      sumpsi - posui - clusi
    3. Super Beitrag. Ein kleiner Trick noch von mir, wenn man statt der Eigenschaft tagName JavaScripts eingebauten instanceof-Operator benutzt, bekommt man in vielen Editoren bessere Autovervollständigung für den Code innerhalb der if-Verzweigung:

      document.body.addEventListener('click', function(event) {
          const target = event.target;
          if (target instanceof HTMLButtonElement) {
              console.log(target.id);
          }
      });
      

      In Visual Studio Code, zum Beispiel, mit tagName:

      Visual Studio Code Autovervollständigung mit der tagName-Eigenschaft

      Und mit intsanceof:

      Visual Studio Code Autovervollständigung mit instanceof-Operator

      1. Hallo 1unitedpower,

        document.body.addEventListener('click', function(event) {
            const target = event.target;
            if (target instanceof HTMLButtonElement) {
                console.log(target.id);
            }
        });
        

        In Visual Studio Code, zum Beispiel, mit tagName:

        Visual Studio Code Autovervollständigung mit der tagName-Eigenschaft

        Und mit intsanceof:

        Visual Studio Code Autovervollständigung mit instanceof-Operator

        Oh man, VS Code ist wirklich eine von den Sachen, bei denen ich „leider geil“ sagen muss. Ich möchte es doof finden, aber ich kann einfach nicht: der Editor ist ziemlich genial.

        LG,
        CK

        1. Aloha ;)

          Oh man, VS Code ist wirklich eine von den Sachen, bei denen ich „leider geil“ sagen muss. Ich möchte es doof finden, aber ich kann einfach nicht: der Editor ist ziemlich genial.

          EXAKT SO fühl ich mich auch.

          Grüße,

          RIDER

          --
          Camping_RIDER a.k.a. Riders Flame a.k.a. Janosch Zoller
          # Twitter # Steam # YouTube # Self-Wiki # Selfcode: sh:) fo:) ch:| rl:) br:^ n4:? ie:% mo:| va:) js:) de:> zu:} fl:( ss:) ls:[
          1. Wenn ihr das schon cool fandet, guckt euch das mal an

            https://twitter.com/typescriptlang/status/1045326697401389057 🍷

            1. Hallo 1unitedpower,

              Wenn ihr das schon cool fandet, guckt euch das mal an

              https://twitter.com/typescriptlang/status/1045326697401389057 🍷

              Das finde ich jetzt wenig beeindruckend. Das ist ja nur etwas refactoring, das konnte mein Emacs auch schon (mit js2-mode).

              LG,
              CK