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

Beitrag lesen

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