Linuchs: <button> in <p>: obj.parentElement is null

problematische Seite

Moin,

verstehe den Fehler nicht. Wenn ich irgendwohin klicke, soll the TagName und the TagName des Elter auf der Console gezeigt werden:

<p>Lesezeichen (cookie) für Mitglied <button class=neu onclick="setCookie(this,'my_VIP',1281)">setzen</button></p>
  document.getElementsByTagName( "body" )[0].addEventListener('click', function (event) {
    var obj = event.target;
    console.log ( "target=[" +obj.tagName +"]" );
    console.log ( "target=[" +obj.tagName +"] target.parent=[" +obj.parentElement.tagName +"]" );
...

Bei Klick auf den <button> erwarte ich die Angabe BUTTON und P, die Konsole meldet

target=[BUTTON]

Uncaught TypeError: obj.parentElement is null

Das hatte schon funktioniert, aber lange Zeit nicht verwendet. Wieso meint der Firefox, [BUTTON] sei Waise?

Linuchs

  1. problematische Seite

    Tach!

    Wieso meint der Firefox, [BUTTON] sei Waise?

    Setz einen Breakpoint auf die Zeile mit dem Fehler und schau zu dem Zeitpunkt das DOM an.

    dedlfix.

  2. problematische Seite

    Lieber Linuchs,

    wenn Du etwas daneben zielst, könnte es den Textknoten in <p> erwischen. Der hat natürlich keine tagName-Eigenschaft.

    Uncaught TypeError: obj.parentElement is null

    Was ist eigentlich des Buttons parentNode-Eigenschaft?

    Liebe Grüße

    Felix Riesterer

    1. problematische Seite

      Lieber Felix,

      was du mir sagen willst, verstehe ich nicht.

      Der Knoten <p> sollte its tagName P verraten.

      Wieso hat <p> keine tagName-Eigenschaft?

      Was ist eigentlich des Buttons parentNode-Eigenschaft?

      Mit Element untersuchen sehe ich, dass <button> ein child von <p> ist wie erwartet:

      <p>
        Lesezeichen (cookie) für Mitglied
        <button class="neu" onclick="setCookie(this,'my_VIP',1281)">setzen</button>
      </p>
      

      Wie geschrieben, es hat ehedem funktioniert, the cookie zu setzen. Was ist neu, was habe ich verpasst?

      Linuchs

      1. problematische Seite

        Tach!

        Mit Element untersuchen sehe ich, dass <button> ein child von <p> ist wie erwartet:

        Du musst dann schauen, wenn die Fehlermeldung auftritt, nicht vorher. Nach setCookie() existiert das <p> nicht mehr. Der Button ist auch nicht mehr im DOM, aber er existiert noch, weil der Eventhandler noch eine Referenz darauf hält. Nur hat er kein Elternelement mehr, weil er eben nebst diesem nicht mehr im DOM ist.

        dedlfix.

        1. problematische Seite

          Hallo dedlfix,

          Nach setCookie() existiert das <p> nicht mehr.

          Hmm ... ich sehe ein, mit

          obj.parentNode.innerHTML  = "gespeichert";
          

          den im parentNode <p> enthaltenen <button> überschrieben und gelöscht zu haben.

          Der Button ist auch nicht mehr im DOM, aber er existiert noch, weil der Eventhandler noch eine Referenz darauf hält.

          Ich versuche, zu verstehen.

          Der Spezial-Klick, der function setCookie() aufruft, entfernt den button, um stattdessen die Erfolgsmeldung „gespeichert“ anzuzeigen. Erst nach der Anzeige wird the cookie gesetzt.

          Zeitlich parallel versucht der Standard-Klick, the parent von <button> zu erkennen, aber <button> wurde kurz vorher gestorben und Tote im „Nirwana“ haben beim Firefox keine Eltern mehr.

          Sofort wird die parallel laufende function setCookie() abgebrochen, der vorschnellen Erfolgsmeldung folgt nicht mehr das Setzen des Cookies.

          Richtig verstanden?

          Linuchs

          1. problematische Seite

            Tach!

            Ich versuche, zu verstehen.

            Dieser "Infinitiv mit zu" ist nicht erweitert, also kommt da kein Komma.

            Der Spezial-Klick, der function setCookie() aufruft, entfernt den button, um stattdessen die Erfolgsmeldung „gespeichert“ anzuzeigen. Erst nach der Anzeige wird the cookie gesetzt.

            Der Cookie spielt keine Rolle bei dem Problem.

            Zeitlich parallel versucht der Standard-Klick, the parent von <button> zu erkennen, aber <button> wurde kurz vorher gestorben und Tote im „Nirwana“ haben beim Firefox keine Eltern mehr.

            Der Button existiert weiterhin, aber er ist nicht mehr im DOM. Erst wenn alle Referenzen darauf weg sind, kann er wirklich verschwinden. Weil er noch existiert, ist in obj deines mit addEventListener hinzugefügten Eventhandlers der Button selbst noch verfügbar. Nur sein Bezug zum Elternelement ist nicht mehr da, weil das P noch im DOM ist, er aber nicht mehr.

            Sofort wird die parallel laufende function setCookie() abgebrochen, der vorschnellen Erfolgsmeldung folgt nicht mehr das Setzen des Cookies.

            In Javascript läuft nichts parallel. setCookie() wird zuerst vollständig abgehandelt, danach kommt der addEventListener-Handler zum Zug.

            dedlfix.

            1. problematische Seite

              In Javascript läuft nichts parallel.

              Das kenne ich anders.

              • Ajax wartet auf eine Antwort vom Server, blockiert aber nicht zeitgleiche andere JS-Aktivitäten,

              • mehrere setTime(), setInterval() sind unabhängig voneinander.

              Es sieht so aus, als ob es mehrere voneinander unabhängige JS-Instanzen geben kann. Dokumentation A und Dokumentation B schweigt darüber.

              1. problematische Seite

                Hallo Linuchs,

                das ist alles pseudoparallel. Natürlich, wenn Du einen Ajax-Request machst, läuft der am Server parallel zu deinem Script ab. Aber innerhalb des JavaScript ist alles sequenziell. Und es gibt sogenannte Webworker, die echt parallel zum JavaScript-Hauptthread laufen können, aber die musst Du dann auch asynchron über Messaging ansprechen. Das passiert hier nicht.

                Das entscheidende Vehikel in JavaScript sind die Makro- und Mikrotaskqueue. DOM Events und setTimeout erzeugen Einträge in der Makrotaskqueue, bestimmte andere Vorgänge (wie Promises) erzeugen Einträge in der Mikrotaskqueue.

                Pro Eintrag in der Makrotaskqueue ruft die JS Engine den entsprechenden Handler auf. Folge dieses Handlers können neue Einträge in Mikro- und Makrotaskqueue sein. Dann endet der Handler. Und erst jetzt schaut JavaScript nach weiteren Tasks. Zuerst die Mikrotasks, und erst wenn es davon keine mehr gibt, guckt es nach neuen Makrotasks.

                Was du hast, sind zwei Handler für ein click Event. Das click-Event wird vom Browser in die Makrotask-Queue gestellt. Irgendwann beschließt JavaScript, diesen Makrotask abzuarbeiten, und ruft dafür nacheinander die registrierten Handler auf. In welcher Reihenfolge die laufen, ist auch nicht ganz trivial, da gibt's ja die Capture-Phase, und die Bubbling-Phase. Du hast keinen capture-Handler registriert, und da onclick ein Attribut ist, wird dieser Handler unweigerlich der erste registrierte sein, alle anderen kommen später.

                Deswegen passiert folgendes:

                • click wird auf dem Button ausgelöst
                • makrotask wird in die queue gestellt
                • makrotask beginnt
                • ein Click-Event wird erstellt und die Elternkette des Button gebildet
                • Capture-Phase: das Click-Event wird allen Elementen der Elternkette vom Root bis zum Button. Hat eins davon einen capture-Handler, kommt der jetzt zum Zuge (Netscape-Modell in den Browserkriegen).
                • Bubble-Phase: Das Click-Event wird allen Elementen der Elternkette vom Button bis zum Root präsentiert. Hat eins davon einen bubble-Handler (das ist der Default, das ist bei Dir der Fall), wird er nun aufgerufen.

                Interessant ist: Das click-Event bleibt das gleiche. Egal was die Handler am DOM herummachen. Und das event.target im Click-Event bleibt erhalten, egal ob es noch im DOM steht oder nicht.

                Und weil setCookie ein bubble-Handler am Button ist, und dein addEventListener Handler ein bubble-Handler am Body, kommt setCookie zuerst dran. Es nimmt den Button aus dem DOM, wodurch sein parentElement auf null gesetzt wird.

                Und der nächste Handler ist angeschmiert.

                Rolf

                --
                sumpsi - posui - obstruxi
              2. problematische Seite

                Hallo Linuchs,

                Nachtrag: In unserem Wiki steht nichts darüber (oder ich finde es nicht), aber das MDN schreibt eine Menge über Tasks und Microtasks.

                Ich wollte man in unserem Wiki etwas verfassen, aber einen Abschrieb der MDN zu produzieren kann nicht in unserem Sinn sein, und eine eigenformulierte, vollqualifizierte Erklärung zu verfassen, das ist nicht trivial. Vor allem, wenn man letztlich alles, was man weiß, bei MDN oder ähnlichen Seiten gelernt hat.

                Rolf

                --
                sumpsi - posui - obstruxi
                1. problematische Seite

                  Servus!

                  Nachtrag: In unserem Wiki steht nichts darüber (oder ich finde es nicht), aber das MDN schreibt eine Menge über Tasks und Microtasks.

                  Ich wollte man in unserem Wiki etwas verfassen, aber einen Abschrieb der MDN zu produzieren kann nicht in unserem Sinn sein, und eine eigenformulierte, vollqualifizierte Erklärung zu verfassen, das ist nicht trivial. Vor allem, wenn man letztlich alles, was man weiß, bei MDN oder ähnlichen Seiten gelernt hat.

                  Wir können und wollen die MDN nicht kopieren. Es wäre aber gut, wenn Du an passender Stelle (wo wäre das?) einen Link zur MDN einbaust!

                  Vielen Daank im Voraus.

                  Herzliche Grüße

                  Matthias Scharwies

                  --
                  Einfach mal was von der ToDo-Liste auf die Was-Solls-Liste setzen.“
                  1. problematische Seite

                    Hallo Matthias,

                    hab grad mal gesucht und dabei meine Promises Baustelle wiedergefunden. Schäm...

                    Einen richtigen Ort für eine Einführung in das Timingverhalten von JS habe ich aber noch nicht.

                    Rolf

                    --
                    sumpsi - posui - obstruxi
              3. problematische Seite

                Tach!

                • Ajax wartet auf eine Antwort vom Server, blockiert aber nicht zeitgleiche andere JS-Aktivitäten,

                Den Request arbeitet der Browser ab, aber nicht mit Javascript. Javascript gibt den Request ab und kommt erst wieder zum Zug, wenn der Browser etwas mitzuteilen hat, und dazu eins der Callbacks aufruft.

                • mehrere setTime(), setInterval() sind unabhängig voneinander.

                Auch hier zählt nicht Javascript die Zeit, sondern irgendeine Funktion im Browser oder Betriebssystem gibt nach Ablauf Bescheid.

                Dass Dinge asynchron ablaufen können, heißt nicht, dass sie innherhalb Javascripts parallel laufen.

                Es sieht so aus, als ob es mehrere voneinander unabhängige JS-Instanzen geben kann.

                Ja, je Browsertab eine, aber nicht mehrere in einem Browsertab. Zusätzlich gibt es noch die Web Worker, aber das ist eine andere Geschichte.

                dedlfix.

          2. problematische Seite

            Sofort wird die parallel laufende function setCookie() abgebrochen, ...

            Offenbar ist es nicht so einfach zu verstehen.

            setCookie setzt die Meldung „gespeichert“ ab, aber das vorherige Setzen von the cookie ist nicht erfolgt:

            function setCookie( obj, name, value ) {
            // obj    => button.parent fuer Rueckmeldung
            // name   => my_ORT, my_TYP, my_VIP
            // value  => my_ORT_id, ... wenn leer, dann loeschen
              var d = new Date();
              var expires = "expires="+d.toUTCString();
              var cookie_string = name + "=" + value + ";" + expires;     // fuer Host remso.eu
              document.cookie   = cookie_string;
              if ( value == "loeschen" || value < " " ) {
                d.setTime(d.getTime() - (24*60*60*1000));     // 1 Tag Vergangenheit
                obj.parentNode.innerHTML  = "###geloescht###deleted###verwijderd###";
              } else {
                d.setTime(d.getTime() + (90*24*60*60*1000));  // 90 Tage aufbewahren
                obj.parentNode.innerHTML  = "###gespeichert###saved###opgeslagen###";
              }
            }
            

            Womöglich bricht Javascript nur die Instanz ab, in der ein Fehler aufgetreten ist, andere Instanzen laufen weiter.

            Linuchs

        2. problematische Seite

          Tach!

          Nur hat er kein Elternelement mehr, weil er eben nebst diesem nicht mehr im DOM ist.

          Korrektur: Das p als ehemaliges Elternelement ist weiterhin im DOM, nur mit geändertem Inhalt, ohne den Button.

          dedlfix.

  3. problematische Seite

    @@Linuchs

      document.getElementsByTagName( "body" )[0].addEventListener('click', function (event) {
    

    Das ist Unfug.

    Mit document.body hat man bereits die Referenz. Es ist sinnlos, sich den über document.getElementsByTagName( "body" )[0] zu suchen.

    😷 LLAP

    --
    „Sag mir, wie Du Deine Maske trägst, und ich sage Dir, ob Du ein Idiot bist.“ —@Ann_Waeltin
    1. problematische Seite

      Mit document.body hat man bereits die Referenz.

      Das ist wohl eine Ausnahme?

      Firefox lässt mehrere <header> und <footer> zu, da dürfte document.header wohl einen Fehler erzeugen.

      Warum die Ausnahmen besonders behandeln, wenn sie auch mit den Regeln erfasst werden können?

      1. problematische Seite

        Hallo Linuchs,

        das ist wohl eine Ausnahme

        Nein, es ist die Regel, dass ein Dokument nur einen head und einen body hat. Darum gibt's für die ein Attribut. Für html auch.

        Es gibt noch einige Collections für häufig vorkommende Elementtypen (forms, images, fonts, scripts, styleSheets, all als böses Erbe aus dem IE, und vermutlich noch ein paar mehr), aber eigentlich sind das Relikte aus der Zeit wo es noch kein richtiges DOM mit richtigen Zugriffsfunktionen gab. Gunnar empfiehlt gerne die forms-Collection für schnelle Zugriffe auf forms, aber wirklich nötig ist die Collection nicht.

        Ob man head und body ebenfalls als Relikte der Prä-DOM-Zeit sehen muss, weiß ich nicht.

        Warum die Ausnahmen besonders behandeln, wenn sie auch mit den Regeln erfasst werden können?

        Weil sie hystorisch gewachsen und nicht entfernt werden konnten, ohne das Web zu zerbröseln. HTML5 hat etliche Altlasten restandardisiert. Nun sind sie da, und warum dann mit document.getElementsByTagName("body")[0] einen Pulitzer-Preis anstreben? Abgesehen davon, dass man in diesem Code eigentlich noch prüfen muss, ob getElementsByTagName überhaupt was geliefert hat. Woohoo!

        Rolf

        --
        sumpsi - posui - obstruxi
        1. problematische Seite

          @@Rolf B

          Nein, es ist die Regel, dass ein Dokument nur einen head und einen body hat. Darum gibt's für die ein Attribut.

          Attriwas? Du meinst eine Eigenschaft des document-Objekts: document.head, document.body.

          Für html auch.

          Ja, die heißt aber anders: document.documentElement.

          warum dann mit document.getElementsByTagName("body")[0] einen Pulitzer-Preis anstreben? Abgesehen davon, dass man in diesem Code eigentlich noch prüfen muss, ob getElementsByTagName überhaupt was geliefert hat.

          Nö, wenn man das erste foo-Element sucht, nimmt man document.querySelector('foo').

          😷 LLAP

          --
          „Sag mir, wie Du Deine Maske trägst, und ich sage Dir, ob Du ein Idiot bist.“ —@Ann_Waeltin
          1. problematische Seite

            Hallo Gunnar,

            Attribut, Property, ist doch alles das gleiche 😉

            Nein, natürlich nicht.

            document.documentElement

            Da bin ich dann auf meinen Chrome hereingefallen, der präsentiert mir am document ein Property namens html. Inhaltsgleich zu documentElement.

            Nö, wenn man das erste foo-Element sucht, nimmt man document.querySelector('foo')

            Na gut. Ich habe about:blank aufgerufen und selbst dann liefert document.querySelector('body') noch ein body Element. Linuchs' Einreichung für den Pulitzerpreis aber auch...

            Rolf

            --
            sumpsi - posui - obstruxi
  4. problematische Seite

    Hallo Linuchs,

    das von Dir beschriebene Verhalten ist untypisch und mein Firefox zeigt es nicht.

    Was tut setCookie()? Warum hast Du einen click-Handler auf dem Body, wenn Du den click auf den Button separat abfängst?

    Rolf

    --
    sumpsi - posui - obstruxi
    1. problematische Seite

      Hallo Rolf,

      erstmal einen besonderen Dank an dich. Ich fühle mich von der SELF-Community bestens beraten, aber du bist immer dabei, als ob wir Kollegen und Freunde wären. Ich sitze hier allein, und diese virtuelle Freundschaft weiß ich sehr zu schätzen, obwohl wir uns nie persönlich begegnet sind. Vielleicht sehen wir uns in Mannheim?

      Was tut setCookie()?

      Mit Javascript können drei Lesezeichen erzeugt werden in Form von Cookies. Wenn remso.eu aufgerufen wird, wird dem Leser „sein“ gewählter Ort, Veranstaltungstyp und Verein als Link vorgeschlagen.

      Ich könnte auch die jeweils nächste(n) Veranstaltung(en) anzeigen.

      function setCookie( obj, name, value ) {
      // obj    => button.parent fuer Rueckmeldung
      // name   => my_ORT, my_TYP, my_VIP
      // value  => my_ORT_id, ... wenn leer, dann loeschen
        var d = new Date();
        if ( value == "loeschen" || value < " " ) {
          d.setTime(d.getTime() - (24*60*60*1000));     // 1 Tag Vergangenheit
          obj.parentNode.innerHTML  = "###geloescht###deleted###verwijderd###";
        } else {
          d.setTime(d.getTime() + (90*24*60*60*1000));  // 90 Tage aufbewahren
          obj.parentNode.innerHTML  = "###gespeichert###saved###opgeslagen###";
        }
        var expires = "expires="+d.toUTCString();
        var cookie_string = name + "=" + value + ";" + expires;     // fuer Host remso.eu
        document.cookie   = cookie_string;
      }
      

      Warum hast Du einen click-Handler auf dem Body, wenn Du den click auf den Button separat abfängst?

      Per Klick kann in Tabellen <tr> und Containern class=position eine Position farbig hinterlegt werden, bevor sie in einem neuen Tab bearbeitet wird. Z.B. eine Adresse, ein Event.

      Nach erfolgter Bearbeitung und Rückkehr kann gezielt die nächste Position bearbeitet werden. Die Markierung von Positionen habe ich nie bei Listen oder Tabellen gesehen, sie ist (vermutlich) eine „Erfindung“ von mir.

      Zusatznutzen: Durch Klick in eine freie Fläche (nicht auf einen Link) kann man Positionen zählen. Rechts unten der Counter angeklickter Positionen.

      Hast du den Verdacht, dass der beschriebene Standard-Klick mit dem button-Spezialklick konkurriert?

      Zwecks Test lasse ich beim Standard-Klick in der Konsole tagName des geklickten HTML-Elements und parent.tagName anzeigen. Weil das fehlert, wird JS abgebrochen, die Funktion setCookie nicht aufgerufen.

      Ich wiederhole mich: Ich verstehe nicht, warum Javascript den Elter von <button> nicht erkennt.

      Linuchs