Nico R.: removeEventListener funktioniert nicht mit Funktionsparametern

Hallo,

ich habe li-Elemente, die sich per "click" öffnen lassen. Das click-Event hänge ich per addEventListener an. Beim Öffnen gebe ich als Parameter diverse Informationen mit, die dann in view_person() angezeigt werden.


var li = liste_view_inc.querySelectorAll("li");	

function click_view_person(li) {
  view_person(li.dataset.id, li.dataset.modus, li.dataset.vorname, li.dataset.nachname, li.dataset.telefon, li.dataset.email, li.dataset.notiz, li.dataset.anwesend);
}	

for(let i=0; i < li.length; i++) {
  li[i].addEventListener("click", function() {
    click_view_person(li[i]);
  });
}

Das funktioniert auch. Nun gibt es noch einen weiteren Modus, der den Listeneintrag nicht öffnet, sondern auswählt. Dazu möchte ich das "click"-Event der li-Elemente entfernen und ein neues hinzufügen.

liste_nav_menue_ausw.addEventListener("click", function() {
  for(let i=0; i < li.length; i++) {
    li[i].removeEventListener("click", click_view_person); 
    li[i].addEventListener("click", function(){
      // select-Funktion
    }); 
  }
});

Das funktioniert leider nicht. Die Funktion des zweiten Event-Listeners wird zugefügt, die erste bleibt aber ebenfalls erhalten. Das remove funktioniert nur, wenn ich eine test-Funktion ohne Parameter anhänge. Also so...

var li = liste_view_inc.querySelectorAll("li");	

function test() {
  console.log("test");
}	

for(let i=0; i < li.length; i++) {
  li[i].addEventListener("click", test);
}

liste_nav_menue_ausw.addEventListener("click", function() {
  for(let i=0; i < li.length; i++) {
    li[i].removeEventListener("click", test); 
    li[i].addEventListener("click", function(){
      // select-Funktion
    }); 
  }
});

Gehe ich das grundsätzlich falsch an oder bin ich kurz vor der Lösung? Ich hab irgendwie gerade ein Brett vorm Kopf.

Schöne Grüße

Nico

akzeptierte Antworten

  1. Ah. Ich glaube da was zu sehen... Du notierst

      li[i].addEventListener("click", function() {
        click_view_person(li[i]);
      });
    }
    

    Aber dann:

        li[i].removeEventListener("click", click_view_person); 
    

    Tja. Das funktioniert so nicht. Du kannst nur den Event-Listener entfernen, den Du auch hinzugefügt hast. Mit der anonymen Funktion ist das „eher schwierig”.

    1. Hallo Raketenwilli,

      das dacht ich mir auch schon. Ich hab ihn daher versucht so zu entfernen, wie ich ihn registriert habe:

      li[i].addEventListener("click", function() {
        click_view_person(this);
      });
      
      li[i].removeEventListener("click", function() {
        click_view_person(this);
      });
      

      Das funktioniert aber ebenfalls nicht. Und irgendwie fühlt sichs auch nicht richtig an.

      Schöne Grüße

      Nico

      1. Hallo Nico R.,

        das Problem ist: Woher soll JavaScript wissen, dass die anonyme Funktion, die Du beim add übergibst, die gleiche ist wie die, die Du beim remove übergibst?

        Deswegen schrieb Jörg, dass dein Vorhaben mit anonymen Funktionen "eher schwierig" ist. Genauer gesagt: unmöglich.

        Leider bekommst Du von addEventListener kein "remove handle" oder sowas in der Art. Du hast darum nur eine Möglichkeit:

        function click_view_person(event) {
           // view me 
        }
        
        element.addEventListener(click_view_person);
        element.removeEventListener(click_view_person);
        

        Jetzt ist's beide Male das gleiche Funktionsobjekt und der Remove wird funktionieren.

        Nun gibt es noch einen weiteren Modus, der den Listeneintrag nicht öffnet, sondern auswählt.

        Ist das ein Modus, der für die ganze Liste gilt? Oder nur für dieses Element?

        Je nach dem gib dem Listencontainer (ul, ol) oder dem li Element eine Klasse "selection-mode" oder ähnlich und frage im click-Handler ab, ob die Klasse vorhanden ist. Je nach Ergebnis machst Du view oder select.

        Umregistrieren von Eventhandlern sollte man eher unterlassen, das ist zu umständlich.

        Und nun zu deiner wichtigsten Frage:

        Gehe ich das grundsätzlich falsch an

        Ja.

        li Elemente sind keine interaktiven Elemente. Eine Klick-Behandlung für li ist grundsätzlich falsch. Technisch geht es, aber technisch kann ich auch bei Tempo 200 den Zündschlüssel rausziehen und aus dem Fenster werfen. Oder eine Zigarette am Filterende anzünden. Kann man machen, wäre aber grundsätzlich falsch.

        Wenn Du auf einen Klick reagieren willst, brauchst Du interaktive Elemente wie a oder button. Da Du auch markieren können willst, riecht das nach einem Button. Dem kannst Du mit CSS alles wegnehmen, was ihn wie einen Button aussehen lässt. Aber dann stimmt die Semantik, und eine Tastaturbedienung wird möglich.

        Es ist auch umständlich, den Eventhandler auf jedem einzelnen li oder button zu registrieren. Kennst Du "Event Bubbling"? Du kannst den Handler auf dem Listencontainer einmal registrieren und die Events für alle Listenelemente dort behandeln. Das Event-Objekt, das Du im Eventhandler bekommst, hat zwei wichtige Eigenschaften:

        currentTarget
        Wohin ist das Event gerade gebubbelt - sprich: das ist das Element, auf dem der Handler registriert ist
        target
        Wo wurde das Element ausgelöst - bei Maus- oder Touch-Bedienung das Element, über dem die Mausspitze war oder die Fingerspitze zugestoßen hat. Bei Tastaturbedienung zwangsläufig der Button selbst.
        <ul id="liste">
          <li><button type="button">Erika Mustermann</button></li>
          <li><button type="button">Otto Normalprogrammierer</button></li>
          <li><button type="button">Ada Lovelace</button></li>
        </ul>
        
        const liste = document.querySelector("#liste");
        liste.addEventListener("click", function(event) {
           const button = event.target.closest("button");
           if (button) {
              if (liste.classList.contains("selection-mode")) {
                // Selektiere Listeneintrag
              }
              else {
                // Zeige Listeneintrag
              }
           }
        });
        

        Die closest-Abfrage brauchst Du nur, wenn deine Buttons HTML Elemente enthalten. event.target liefert Dir das geclickte HTML Element - das könnte auch ein <strong> innerhalb des Buttons sein. Mit closest gehst Du die Elternkette hoch und suchst den Button heraus. Oder mit der parentElement Eigenschaft das li Element - je nach Bedarf.

        Wenn Du neben einen Button klickst, aber immer noch in den Bereich des ul Elements, würde die closest-Methode null liefern. Deshalb die Abfrage, ob ein Button gefunden wurde.

        In meinem Beispiel wäre dann auch gleich die Weiche für die Betriebsarten "Anzeigen" oder "Auswählen" drin. Denk dran, dass Du beim Auswählen ggf. Aria-Attribute setzen musst, damit Assistenztechniken damit klar kommen. Ich frag mich nur, welche. aria-checked oder aria-selected sind für die Rolle listitem nicht vorgesehen, und ob man deinen li eine andere Rolle geben sollte, weiß ich nicht so recht. @Gunnar Bittersmann, wie sollte man hier aria-mäßig vorgehen? Oder würdest Du die Selected-Eigenschaft der Listen durch eine visuell versteckte Checkbox implementieren? Käme mir eigentlich auch falsch vor, vor allem müsste die ja auch ggf. das Styling des li beeinflussen, in dem sie steckt (was heute mit :has() natürlich machbar wäre, aber ist das richtig so?)

        Rolf

        --
        sumpsi - posui - obstruxi
        1. Hallo Rolf,

          Deswegen schrieb Jörg, dass dein Vorhaben mit anonymen Funktionen "eher schwierig" ist. Genauer gesagt: unmöglich.

          Schade. Dann hatte ich wohl in das "eher schwierig" zu viel Hoffnung gepackt.

          Je nach dem gib dem Listencontainer (ul, ol) oder dem li Element eine Klasse "selection-mode" oder ähnlich und frage im click-Handler ab, ob die Klasse vorhanden ist. Je nach Ergebnis machst Du view oder select.

          Das ist ne sehr gute Idee. Ich könnte jedem Listen-Element beim Aufruf der Liste die Klasse "show" geben und diese beim Klick auf 'Auswählen' durch die Klasse "select" ersetzen und beim li-click abfragen, welche Klasse gesetzt ist. Zwischendurch hatte ich noch zwei andere Möglichkeiten gefunden...

          Variante 1: Über das li-Element ein div-Element mit position:absolute und display:none; mit <input type="checkbox"> und eigenem addEventListener("click") legen und das dann bei 'Bearbeiten' einblenden.

          Variante 2: Dem Listeneintrag ein onclick="show_li()" verpassen und nach Klick auf 'Auswählen' li[i].onclick = null setzen sowie li[i].addEventListener("click"). Funktioniert, ist aber wohl ziemlich schmutzig.

          Ich werd mal jetzt schauen, ob ich Rolfs Vorschlag oder Variante 1 umsetze und was es noch für Probleme gibt…

          li Elemente sind keine interaktiven Elemente. Eine Klick-Behandlung für li ist grundsätzlich falsch... Wenn Du auf einen Klick reagieren willst, brauchst Du interaktive Elemente wie a oder button. Da Du auch markieren können willst, riecht das nach einem Button. Dem kannst Du mit CSS alles wegnehmen, was ihn wie einen Button aussehen lässt. Aber dann stimmt die Semantik, und eine Tastaturbedienung wird möglich.

          Es geht hier wieder rein um eine Anwendung für Mobilgeräte. Es handelt sich um eine Liste wie z.B. in einer E-Mail-App, wo beim Klick auf einen Listeneintrag die E-Mail angezeigt wird oder beim Klick auf "Bearbeiten" die Listeneinträge ausgewählt werden können, um sie z.B. zu löschen. Das mit Buttons im li-Element zu lösen wäre unübersichtlich und entspricht auch nicht mehr den Gewohnheiten der Nutzer.

          Es ist auch umständlich, den Eventhandler auf jedem einzelnen li oder button zu registrieren. Kennst Du "Event Bubbling"?

          Du hast recht. Ich habe das "Bubbling" erst kürzlich dazugelernt, in älteren Scripts habs ichs noch nicht eingesetzt. Es ist also eher ein Überbleibsel.

          In meinem Beispiel wäre dann auch gleich die Weiche für die Betriebsarten "Anzeigen" oder "Auswählen" drin. Denk dran, dass Du beim Auswählen ggf. Aria-Attribute setzen musst, damit Assistenztechniken damit klar kommen. Ich frag mich nur, welche. aria-checked oder aria-selected sind für die Rolle listitem nicht vorgesehen, und ob man deinen li eine andere Rolle geben sollte, weiß ich nicht so recht. @Gunnar Bittersmann, wie sollte man hier aria-mäßig vorgehen? Oder würdest Du die Selected-Eigenschaft der Listen durch eine visuell versteckte Checkbox implementieren? Käme mir eigentlich auch falsch vor, vor allem müsste die ja auch ggf. das Styling des li beeinflussen, in dem sie steckt (was heute mit :has() natürlich machbar wäre, aber ist das richtig so?)

          Tja, am Ende ist das li-Element hier sowohl Liste, als auch button, als auch checkbox. In meiner bisherigen Version ist es so, dass ich im 'Auswählen'-Modus im <li></li> ein <input type="checkbox"> einblende.

          Über role- oder aria-Attribute hab ich mir ehrlich gesagt keine Gedanken gemacht, weil es hier um einen sehr begrenzten Nutzerkreis geht, der definitiv ein Mobilgerät nutzt und mit 99,9%-er Wahrscheinlichkeit keinen Screenreader.

          Schöne Grüße

          Nico

          1. Das ist ne sehr gute Idee. Ich könnte jedem Listen-Element beim Aufruf der Liste die Klasse "show" geben und diese beim Klick auf 'Auswählen' durch die Klasse "select" ersetzen und beim li-click abfragen, welche Klasse gesetzt ist... Ich werd mal jetzt schauen, ob ich Rolfs Vorschlag oder Variante 1 umsetze und was es noch für Probleme gibt…

            Update: Ich hab mich für Rolfs Vorschlag entschieden und es hat funktioniert (hier noch ohne Bubbling):

            // click "Auswählen"
            li[i].classList.remove("view");
            li[i].classList.add("select");
            ...
            
            li[i].addEventListener("click", function() {
              if(this.classList.contains("view")) {
                // view...
              }
              else if(this.classList.contains("select")) {  
                // select...
              }
            }
            

            Die Idee mit dem removeEventListener war wirklich eine Sackgasse. Am Ende wars gar keine große Sache, aber manchmal braucht man einfach einen Stoß in die richtige Richtung.

            Danke und schöne Grüße

            Nico

            1. @@Nico R.

              Update: Ich hab mich für Rolfs Vorschlag entschieden und es hat funktioniert

              Rolf hat dir sehr deutlich zu verstehen gegeben, dass li[i].addEventListener("click",) nicht funktioniert.

              🖖 Живіть довго і процвітайте

              --
              „Ukončete, prosím, výstup a nástup, dveře se zavírají.“
          2. @@Nico R.

            li Elemente sind keine interaktiven Elemente. Eine Klick-Behandlung für li ist grundsätzlich falsch... Wenn Du auf einen Klick reagieren willst, brauchst Du interaktive Elemente wie a oder button.

            Es geht hier wieder rein um eine Anwendung für Mobilgeräte.

            Das ist kein Grund, die HTML-Spezifikation zu missachten.

            Es handelt sich um eine Liste wie z.B. in einer E-Mail-App, wo beim Klick auf einen Listeneintrag die E-Mail angezeigt wird oder beim Klick auf "Bearbeiten" die Listeneinträge ausgewählt werden können, um sie z.B. zu löschen. Das mit Buttons im li-Element zu lösen wäre unübersichtlich

            Das halte ich für Quatsch. Und wenn, dann ist das dein Problem, welches du nicht auf die Nutzer abwälzen solltest.

            und entspricht auch nicht mehr den Gewohnheiten der Nutzer.

            Das halte ich für Quatsch.

            Über role- oder aria-Attribute hab ich mir ehrlich gesagt keine Gedanken gemacht, weil es hier um einen sehr begrenzten Nutzerkreis geht, der definitiv ein Mobilgerät nutzt und mit 99,9%-er Wahrscheinlichkeit keinen Screenreader.

            Survivorship Bias. Genausogut könntest du von einer Website, die nur mit JavaScript funktioniert, behaupten, dass bei 99.9% der Nutzerschaft JavaScript ausgeführt wird.

            Und Ableismus.

            🖖 Живіть довго і процвітайте

            --
            „Ukončete, prosím, výstup a nástup, dveře se zavírají.“
            1. Hallo Gunnar,

              es gibt durchaus Anwendungsfälle, bei denen klar ist, auf welchen Geräten die Seite läuft. Du solltest Nico zugestehen, seine Anwender zu kennen, wenn er von einem sehr begrenzten Nutzerkreis spricht.

              Beispielsweise wird die Handy-App, mit der die Schaffner in der Bahn neuerdings zum Kontrollieren rumlaufen, nie auf einem Desktop oder Laptop laufen. Außer vielleicht zu Testzwecken beim Entwickler.

              Eine Intranet-Webseite, mit der man durch's Lager läuft und Inventur macht, dürfte auch sehr handylastig sein.

              Das zum Anlass für einen Ableismus-Rant zu wählen ist auch eine Form von Ableismus, gelle?

              Rolf

              --
              sumpsi - posui - obstruxi
              1. @@Rolf B

                es gibt durchaus Anwendungsfälle, bei denen klar ist, auf welchen Geräten die Seite läuft.

                Was nichts damit zu tun hat, ob ein Nutzer auf assistive Technologie angewiesen ist.

                Du solltest Nico zugestehen, seine Anwender zu kennen, wenn er von einem sehr begrenzten Nutzerkreis spricht.

                Auch jemand aus einem sehr begrenzten Nutzerkreis, der heute nicht auf assistive Technologie angewiesen ist, kann es morgen durch Unfall oder Krankheit sein.

                Oder der Nutzerkreis soll um eine Person erweitert werden. Geht aber nicht, weil diese Person auf assistive Technologie angewiesen ist und der Entwickler der Anwendung zu uneinsichtig war.

                „Scher dich weg! Krüppel können wir hier nicht gebrauchen!“

                Und jemand, der es bei einem Projekt falsch macht, wird es auch bei anderen Projekten falsch machen.

                🖖 Живіть довго і процвітайте

                --
                „Ukončete, prosím, výstup a nástup, dveře se zavírají.“
            2. Guten Abend,

              Das ist kein Grund, die HTML-Spezifikation zu missachten.

              Es gibt, wenn ich in einer 30er-Zone mit 31 km/h geblitzt werde, auch keinen Grund, mir vorzuwerfen, ich würde rasen. Ich habe keine Tabelle zum layouten verwendet, keine div-Suppe gekocht und auch keine Katzenbabys gegen die Wand geworfen.

              In einem "vorschriftsmäßigen" Aufbau:

              <header>
                <nav>
                  <ul>
                    <li></li>
                  </ul>
                </nav>
              </header> 
              

              habe ich lediglich innerhalb des <li>-Elementes auf ein weiteres a- oder button-Element verzichtet. Ich sehe aber ein, dass hieraus in der Tat nicht ersichtlich wird, dass die Listenelemente interaktiv sind.

              Daher habe habe ich jetzt für den Standardmodus dem <li>-Element das Attribut role="link" gegeben. Sobald ich in den Auswahlmodus wechsle, tausche ich das gegen role="checkbox" aus und füge, wenn checked, noch aria-checked hinzu. Das triffts für mein Gefühl noch besser, als <a> oder <button>, da das <li> hier wirklich seine Rolle wechselt.

              Und Ableismus.

              Das ist ziemlich starker Tobak.

              Nun, es ist so, dass die Arbeit, die die Anwendung hoffentlich einmal übernehmen/unterstützen/erleichtern wird, zur Zeit noch mit Stift und Zettel betrieben wird und von blinden Personen definitiv nicht ausgeübt werden kann. Deshalb war ich für meine Anwendung ebenfalls von dieser Situation ausgegangen. Aber vielleicht führt ja in diesem Fall die Digitalisierung dazu, dass in Zukunft auch Sehbehinderte den Job ausüben können.

              Insofern ist der Einwurf gar nicht verkehrt gewesen. Aber die Art und Weise... Naja, vielleicht Geschmackssache.

              Danke nochmal und einen schönen Abend

              Nico