ebody: jQuery Element selektieren, welches über ein externes Script generiert wird

Hallo,

hier ist ein Beispiel Code https://codepen.io/anon/pen/rZWqrQ Mein Ziel ist es später, den Klick auf den Link "Ok, verstanden" von dem Banner (rechts unten) abzufangen. Zu Testzwecken schaue ich aber erstmal, ob ich ein Element von dem Banner überhaupt selektieren kann.

Ich habe schon sehr viele Selektoren (heißt das so?) ausprobiert, aber es funktioniert nicht. Daher vermute ich, dass das etwas mit der Reihenfolge der Ladevorgänge zu tun haben könnte.

Da ich die Funktion aber unter dem Banner Script ausführe und auch innerhalb von $(document).ready(function() { // wenn das DOM fertig geladen ist sollte der Code vom Banner auch zu selektieren sein. Weiß jemand Rat?

Gruß ebody

  1. Hallo ebody, hallo Matthias,

    was ist denn hier passiert? Nach dem Edit (weshalb auch immer der erfolgte) ist das Posting völlig verstümmelt und das Problem nicht mehr erkennbar.

    Wenn das hier

    Statt $(document).ready() sollte $() verwendet werden.

    eine hineineditierte Antwort sein soll - nein, das ist keine hilfreiche Antwort, nur eine Umstellung von deprecated auf empfohlen. Das ändert nichts am Timing, aber es zu machen ist trotzdem richtig.

    In der Versionshistorie steht:

    Mein Ziel ist es später, den Klick auf den Link "Ok, verstanden" von dem Banner (rechts unten) abzufangen. Zu Testzwecken schaue ich aber erstmal, ob ich ein Element von dem Banner überhaupt selektieren kann.
    Da ich die Funktion aber unter dem Banner Script ausführe und auch innerhalb von $(document).ready(function() { // wenn das DOM fertig geladen ist sollte der Code vom Banner auch zu selektieren sein.

    Ich möchte jetzt nicht die Qualität dieses Cookie-Hinweis Scripts bewerten, sondern nur auf deine Aufgabenstellung eingehen.

    Ich habe mir das Script für den Cookie Hinweis mal angeschaut; das hat am Ende einen eigenen Ready-Handler, der über eine Abfrage von document.readyState === 'completed' funktioniert.

    MDN sagt zum readyState des Dokuments: completed: The document and all sub-resources have finished loading. The state indicates that the load event is about to fire.

    Dagegen sagt die jQuery Doku: The .ready() method offers a way to run JavaScript code as soon as the page's Document Object Model (DOM) becomes safe to manipulate.

    D.h. die Abfrage des ReadyState entspricht einer Registrierung auf das load-Event des Dokuments. jQuery.ready() (a.k.a. $()) entspricht aber einer Registrierung auf DOMContentReady, was früher stattfindet. Deswegen suchst Du in deinem Ready-Script nach DOM Elementen, die erst später erscheinen.

    Einen Hook, den Du im Script nutzen kannst um auf einen Klick dort zu reagieren, finde ich spontan nicht (wobei ich das Markup im Script auch für Käse halte; Buttons realisiert man nicht als Link!).

    Da der readyState-Change vor dem Load-Event kommt, solltest Du Dich auf das load Event des Dokuments registrieren - aber nur, wenn der readyState Wert des Dokuments nicht schon complete ist, sonst kommt kein load mehr. Ist er schon complete, brauchst Du keine Registrierung mehr sondern kannst direkt loslegen.

    Sinngemäß so:

    if (document.readyState === 'complete')
       registerForButton();
    else
       document.addEventListener('load', registerForButton);
    

    und in der registerForButton-Funktion solltest Du dann deine DOM Elemente finden können.

    Rolf

    --
    sumpsi - posui - clusi
    1. Tach!

      was ist denn hier passiert? Nach dem Edit (weshalb auch immer der erfolgte) ist das Posting völlig verstümmelt und das Problem nicht mehr erkennbar.

      Ich hab das Original mal wieder hergestellt. War vermutlich ein Unfall.

      dedlfix.

      1. Hallo dedlfix,

        das war mir zu mühsam... 🐯 Danke.

        Rolf

        --
        sumpsi - posui - clusi
    2. @@Rolf B

      MDN sagt zum readyState des Dokuments: completed: The document and all sub-resources have finished loading. The state indicates that the load event is about to fire.

      Dagegen sagt die jQuery Doku: The .ready() method offers a way to run JavaScript code as soon as the page's Document Object Model (DOM) becomes safe to manipulate.

      Was ist denn hier passiert? Der Teil des Postings ist völlig verstümmelt. Auch ein Unfall oder Unsitte?

      LLAP 🖖

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

        diese Form von Polemik braucht's wirklich nicht. Ich habe ich dem verlinkten Thread nicht mitgelesen, insofern war mir dein dortiger belehrender Zeigefinger unbekannt.

        Für mich ist wesentlich, dass der zitierte Text sich im Inline-Zitat deutlich absetzt. Wenn das unsittlich ist, nehme ich gerne ein besseres Markup dafür entgegen. Leider kann man ja nicht mehr mit {: beliebige Styles setzen. Und Inline-Quotes scheinen generell nicht im Forum vorgesehen.

        Rolf

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

          Für mich ist wesentlich, dass der zitierte Text sich im Inline-Zitat deutlich absetzt. Wenn das unsittlich ist, nehme ich gerne ein besseres Markup dafür entgegen.

          Was spricht gegen „“? Was spricht gegen Kursivschrift?

          Leider kann man ja nicht mehr mit {: beliebige Styles setzen.

          Ja, das Leid teile ich mit dir.

          Und Inline-Quotes scheinen generell nicht im Forum vorgesehen.

          Von Markdown generell nicht vorgesehen.

          LLAP 🖖

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

      ich musste das Script noch ergänzen...

      $(document).ready(function() {
      			
      	MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
      
      	var observer = new MutationObserver(function(mutations, observer) {
      
      					if (document.readyState === 'complete'){
      					
      						console.log("complete");
      						
      						$("a.cc_btn.cc_btn_accept_all").click(function(){
      						
      							//console.log($("a.cc_btn.cc_btn_accept_all").text());
      							console.log("Cookie wurde gesetzt");
      							
      						});
      						
      					}
      							
      							
      				});
      
        // define what element should be observed by the observer
      	// and what types of mutations trigger the callback
      	observer.observe(document, {
      		childList: true,
      		subtree: true
      	});				
      
      });
      

      Jetzt kann ich darauf reagieren, wenn der Link "Ok, verstanden" geklickt wurde. Hier ein Beispiel mit mouseover() statt click() https://codepen.io/anon/pen/rZWqrQ

      Hier noch ein paar ergänzende Links dazu (falls das Thema andere interessiert) https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver https://stackoverflow.com/questions/2844565/is-there-a-javascript-jquery-dom-change-listener https://stackoverflow.com/questions/12596231/can-jquery-selectors-be-used-with-dom-mutation-observers

      Gruß ebody

      1. Hallo ebody,

        leider muss ich Dir recht geben, ich hatte nicht aufmerksam genug geschaut. Registrieren auf window.load reicht nicht, weil das Cookie-Script CSS nachlädt und sich auf das Load-Event dieses Nachladens registriert (loadTheme Funktion). Erst wenn dieses Nachladen 'load' feuert, wird der Cookie-Hinweis erzeugt. An der Stelle bist Du ausgezählt, da kommst Du nicht direkt heran. Der MutationObserver ist also wohl die einzige Möglichkeit (außer fragilen Timeouts).

        Trotzdem gibt es an deinem Code noch Verbesserungsbedarf.

        #1. Registrierung des Ready-Handlers

        $(document).ready(function() {
           // ...
        });
        

        Zum einen ist diese Schreibung missbilligt, wie schon mehrfach erwähnt.

        Zum anderen funktioniert es nicht in der JS-Spalte von Codepen, weil ich bei meinen Spielereien gerade festgestellt habe, dass das Dokument beim Aufruf der ready-Funktion bereits im "complete" Status ist. Weshalb auch immer. Die jQuery-Doku suggeriert anderes. Vielleicht liegt es auch nur am Codepen (aber jsFiddle macht es auch).

        D.h. es gibt eine Race-Condition zwischen deiner ready()-Funktion und dem Nachladen des CSS (siehe oben). Und funktioniert dann ggf. nicht.

        Darum ist es meiner Meinung nach besser, explizit das DOMContentLoaded Event zu verwenden. Das feuert, sobald das HTML Dokument gelesen ist, und das Dokument ist noch im interactive-Status wenn die Funktion aufgerufen wird.

        document.addEventListener("DOMContentLoaded", function() {
           // ...
        });
        
        // oder wenn Du UNBEDINGT jQuery willst - dann aber auch mit one, nicht on. 
        $(document).one("DOMContentLoaded", function() {
           // ...
        });
        
        

        Dieser Code muss inline im HTML oder in einem synchron geladenen Script stehen. Sonst feuert DOMContentLoaded nicht. Asynchrone Script hat man, wenn man es explizit anfordert, oder wenn man Modularisierungstools wie require.js verwendet.

        #2. MutationObserver

        Dein MutationObserver ist wohl nötig, aber zu umfangreich. Er feuert bei JEDER Änderung am Dokument, während der ganzen Dauer der Seitenanzeige. Das kann unter Umständen oft passieren. Und wenn der Cookie-Hinweis da ist, registriert er jedesmal deinen Eventhandler neu. D.h. wenn der Cookie-Hinweis ignoriert wird und deine HTML Seite so aufgebaut ist, dass irgendwo ein child-Element hinzugefügt, entfernt oder ersetzt wird, wird jedesmal ein neuer Eventhandler an den OK VERSTANDEN Button angehängt. Das muss zurückhaltender sein.

        Im einfachsten Fall machst Du das so, dass Du prüfst, ob dein $("a....") Aufruf ein Ergebnis liefert (.length > 0). Wenn ja, registriert Du deinen click-Handler am a Element. Und danach schaltest Du den Observer ab, mit observer.disconnect(), damit er keine Zweitregistrierung veranlasst und keine Zeit mehr frisst.

        Eine andere Möglichkeit ist, die Eventhandler-Funktion nicht inline zu schreiben, sondern als separate Funktion zu notieren. Dann erkennt JavaScript, dass da zweimal der gleiche Handler registriert werden soll und verwirft die Folgeregistrierung. observer.disconnect() gehört trotzdem dazu.

        Nachteil in beiden Fällen: Der Observer hängt rekursiv auf dem ganzen DOM, bis der Cookie-Hinweis gefunden ist. Jedesmal wird nach dem a Element gesucht. Das kann auf langsameren Geräten die Ladezeit ausbremsen.

        Eine „präzisere“ Implementierung sähe so aus: Man hängt den MutationObserver nur an die Childlist des head-Element und prüft, ob ein link-Element mit einem href hinzukommt, das "cookie-hinweis" enthält. Also, ob genau das passiert, was ich eingangs beschrieben habe. In diesem Fall ersetzt man die onload-Eigenschaft durch eine eigene Funktion, die zuerst die originale onload-Funktion aufruft und danach das Event auf den OK VERSTANDEN Button registriert. Die originale onload-Funktion erzeugt nämlich die gesuchten DOM Elemente.

        document.addEventListener("DOMContentLoaded", function() {
          new (MutationObserver || WebKitMutationObserver)(function(mutations,observer) {
            try {
              var elem = mutations[0].addedNodes[0];
              if (elem.tagName == "LINK" && elem.rel == "stylesheet"
                  && elem.href.indexOf("cookie-hinweis") > 0
                  && elem.onload instanceof Function) {
                addOkVerstandenHandler(elem, observer);
              }
            }
            catch { /* gobble all errors */ }
          })
          .observe(document.head, {childList: true});
        
          function addOkVerstandenHandler(scriptElement, observer) {
            var oldLoad = scriptElement.onload;
            scriptElement.onload = function() {
              oldLoad();
              $a = $("a.cc_btn.cc_btn_accept_all");
              if ($a.length > 0) {
                $a.mouseover(handleOkVerstanden);
                observer.disconnect();
              }
            }
          }
        
          function handleOkVerstanden() {
            console.log(this.textContent);
            console.log("Cookie wurde gesetzt");
          }
        });
        

        Die zentrale Abfrage steckt in einem try/catch, falls nicht exakt die gewünschte Mutation gemeldet wurde. Insbesonderen var elem = mutations[0].addedNodes[0]; könnte Exceptions werfen. Damit kann ich mir dann auch einige Prüfungen auf "ist das null" oder "ist das Array gefüllt" ersparen. Im Zweifel wird es vom catch gefressen. Dafür ist er da :)

        Die Prüfung, ob es die richtige Mutation ist (Zeile 5-7) ist etwas fragil. Sie passt auf das existierende Script, aber falls sich an der Schreibung des darin hinzugefügten link Statements etwas ändert, oder wenn auf einmal mehr als ein cookie-hinweis CSS geladen wird, fällt sie ggf. auseinander. Das kannst Du vielleicht noch verbessern (z.B. case insensitive abfragen).

        Ganz wichtig ist der observer.disconnect wenn die erwartete Mutation entdeckt wurde. Damit bremst der Observer das DOM nicht mehr aus.

        Ob das so exakt produktionsreif ist, weiß ich noch nicht so ganz. Als nachteilig empfinde ich jedenfalls die enge Bindung an das Cookie-Hinweis Script. Vorteil ist jedenfalls, dass ganz gezielt auf den richtigen Moment reagiert wird.

        Rolf

        --
        sumpsi - posui - clusi
        1. Hi Rolf,

          erstmal vielen Dank für die sehr ausführliche Hilfe!

          Das Script, was ich gepostet habe funktioniert bei mir. Allerdings zeigen mir Deine Hinweise, dass das alles nicht wirklich eine gute Lösung ist. Daher 2 Gedanken/Fragen vorab:

          1. Ich bastel mir einen eigenen Cookiebanner. Existiert der entsprechende Cookie nicht, erscheint es. Über den "OK" Button wird ein Cookie gesetzt. Darauf könnte ich viel leichter und genauer reagieren. Das hört sich an, als ließe sich das recht schnell und mit wenig Code umsetzen oder unterschätze ich das und gibt es da vieles zu Bedenken?

          2. Kann man statt das DOM ganze zeit zu "überwachen", evtl. die Cookies kontrollieren und auf eine Änderung reagieren, ohne setInterval oder ähnliches wie hier? https://stackoverflow.com/questions/14344319/can-i-be-notified-of-cookie-changes-in-client-side-javascript

          cookies.onChanged funktioniert leider nicht im IE

          Noch zum Script, siehe Kommentare /* ... ***/ **

          
          
          /*** So könnte man MutationObserver noch weiter einschränken: 
          wenn Cookie nicht existiert, dann { ***/
          
          
          MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
          
          	var observer = new MutationObserver(function(mutations, observer) {
          
          					if (document.readyState === 'complete'){
          					
          						console.log("complete");
          						
          						$("a.cc_btn.cc_btn_accept_all").click(function(){
          						
          							//console.log($("a.cc_btn.cc_btn_accept_all").text());
          							console.log("Cookie wurde gesetzt");
                        
                        /*** Wenn der Cookie gesetzt wurde, wird MutationObserver beendet ***/
                        // Observer beenden
                        observer.disconnect();
          
          							
          						});
          						
          					}
          							
          							
          				});
          
            // define what element should be observed by the observer
          	// and what types of mutations trigger the callback
          	observer.observe(document, {
          		childList: true,
          		subtree: true
          	});			
          
          

          Ansonsten wüsste ich gerade nicht, wo das DOM noch geändert wird bei mir auf der Seite. Daher würde das evtl. schon so reichen.

          Gruß ebody