borisbaer: JS script sowohl bei dynamisch als auch statisch geladenem content ausführen

problematische Seite

Hallo zusammen,

auf meiner Seite wird der content je nachdem, wie er aufgrufen wird, entweder statisch (refresh oder URL-Direkteingabe) oder dynamisch (man befindet sich bereits auf der Seite und wechselt zwischen den „Tabs“) geladen.

Für bspw. click handler gibt es ja Möglichkeiten, dass jene in beiden Fällen anspringen (z.B. durch .on() bei jQuery). Nun geht es mir aber eher um scripts, die bei Aufruf des contents laden sollen, z.B. dieses script, wodurch in einem container per Maus gescrollt werden kann.

Ich weiß, dass man mit einem MutationObserver scripts bei Veränderungen des DOM ausführen lassen kann, aber dann bräuchte ich – meiner Logik nach – zweimal dasselbe script: einmal für den Fall, dass die Seite „normal“ (also statisch) geladen wird, und ein andermal für den Fall, dass die Seite dynamisch geladen wird. Das würde ich gerne vermeiden.

Eine Alternative, die ich sehe, ist, das script direkt ans Ende der HTML-Datei zu setzen. Dann würde es automatisch anspringen, sobald diese Seite (wie auch immer) geladen wird. Allerdings ist auch das irgendwie suboptimal, da ich ungern ein script in eine HTML-Seite verfrachte.

Gibt es noch andere Möglichkeiten?

Vielen Dank schon mal!

akzeptierte Antworten

  1. problematische Seite

    Hallo,

    für so einen Fall habe ich eine Funktion, die das Dokument nach bestimmten Elementen, z.B. über eine Klasse, absucht und mit dem Script verknüpft. Diese funktion wird bei DOMContendLoaded aufgerufen. Diese Funktion wird bei dynamisch generiertem oder nachgeladenem Inhalten vom „Generator“ aufgerufen.

    Gruß
    Jürgen

    1. problematische Seite

      Hallo Jürgen,

      danke für die Antwort!

      Das funktioniert aber nur beim initialen Seitenaufruf, oder?

      Sagen wir, ich lade die Seite foo.inc initial, diese besitzt die id, nach der das script sucht, dann läuft es.

      Dann wechsle ich auf die Seite bar.inc, die dynamisch bspw. über einen ajax call aufgerufen wird (und foo.inc ersetzt) und gleich wieder zurück auf foo.inc, das dieses Mal ebenfalls über einen ajax call aufgerufen wird.

      Jetzt funktioniert das script nicht mehr, weil es vermutlich nur ein Mal am Anfang läuft und wenn anschließend der content mit der gesuchten id dynamisch verschwindet und dynamisch wieder erscheint, dann gibt es ein Problem.

      1. problematische Seite

        Hallo,

        Jetzt funktioniert das script nicht mehr, weil es vermutlich nur ein Mal am Anfang läuft und wenn anschließend der content mit der gesuchten id dynamisch verschwindet und dynamisch wieder erscheint, dann gibt es ein Problem.

        der Script-Teil, der „content mit der gesuchten id dynamisch verschwindet und dynamisch wieder erscheint“, kann doch die Funktion aufrufen, die sonst nur per DOMContendLoaded aufgerufen wird.

        Gruß
        Jürgen

        1. problematische Seite

          Hallo Jürgen,

          ja, allerdings nur, wenn ich die Funktion bzw. das script direkt in diese Datei kopiere, nicht?

          Es funktioniert auch wunderbar, wenn ich das mache. Das script läuft immer dann, wenn dieser content bzw. diese Datei geladen wird – ob über php include (statisch) oder über einen ajax call (dynamisch). Das ist für mich die einfachste Lösung.

          Was mir daran jedoch nicht gefällt, ist eben der Umstand, dass dafür das script in der Datei sein muss. Ich hätte es gerne extern im head eingebunden. Aber das geht leider nicht … zumindest nicht, wenn ich nicht denselben Code zweimal haben möchte – einmal für den Fall des php include und einmal für den des ajax calls.

          Ich benötige nämlich zwei EventListener, einmal DOMContentLoaded für das Einfügen des Inhalts über php include und einmal einen EventListener für das Einfügen über den ajax call – eben einen MutationObserver.

          Meines Wissen kann ich für dieselbe Funktion aber nicht diese beiden EventListener vereinen, oder? Da bin ich mir eben nicht sicher, ob das vielleicht doch irgendwie geht.

  2. problematische Seite

    Hallo borisbaer,

    du musst bei sowas höllisch aufpassen. Wenn Du Teile des DOM entlädst, werden die Eventhandler, die auf den entladenen Elementen registriert sind, entfernt.

    Und wenn Du Eventhandler im "Außenbereich" registriert hast, also in dem Teil der Seite, der nicht entladen wird, diese Eventhandler auf Events aus dynamisch geladenem HTML reagieren, dann könnte das zu argem Kuddelmuddel führen.

    Man kann das im Griff haben - aber - wie gesagt - höllisch aufpassen.

    Die Abneigung gegen Script im Ajax-Teil ist nicht unberechtigt. Ich bin mir nicht so klar, wie der Browser damit umgeht, wenn Du fünf mal zwischen Subpage A und B hin und her schaltest und jedesmal das Script für die Subpage lädst. Es könnte nachher 5x im Speicher sein.

    Da wär's besser, das Script für alle Subpages einmal zu laden (das sollte von den Daten wohl unabhängig sein) und nach dem Load der Subpage eine Initialisierungsfunktion aufzurufen, die die nötigen Eventhandler registriert.

    Rolf

    --
    sumpsi - posui - obstruxi
    1. problematische Seite

      Hallo Rolf,

      du musst bei sowas höllisch aufpassen. Wenn Du Teile des DOM entlädst, werden die Eventhandler, die auf den entladenen Elementen registriert sind, entfernt.

      Und wenn Du Eventhandler im "Außenbereich" registriert hast, also in dem Teil der Seite, der nicht entladen wird, diese Eventhandler auf Events aus dynamisch geladenem HTML reagieren, dann könnte das zu argem Kuddelmuddel führen.

      normalerweise werden die scripts ja im head der HTML-Datei eingespeist, also wahrscheinlich das, was du mit „Außenbereich“ meinst. Eigentlich möchte ich das weiterhin so beibehalten für sämtlich JS scripts. Im Grunde müsste man ja dann bei jedem dynamischen Inhalt, der Teil eines JS scripts ist, genau überlegen, was mit den Eventhandlern passiert, wenn dieser Inhalt wieder verschwindet. Habe ich dich da richtig verstanden?

      Du hattest mir ja vor einiger Zeit geholfen zu bewerkstelligen, dass der Hauptinhalt meiner Seite je nach Situation statisch oder dynamisch geladen wird. Statisch ist ja gar kein Problem in Bezug auf die JS scripts, aber ich muss es irgendwie einrichten, dass sie auch dann funktionieren, wenn der Inhalt mal dynamisch geladen wird.

      Die Abneigung gegen Script im Ajax-Teil ist nicht unberechtigt. Ich bin mir nicht so klar, wie der Browser damit umgeht, wenn Du fünf mal zwischen Subpage A und B hin und her schaltest und jedesmal das Script für die Subpage lädst. Es könnte nachher 5x im Speicher sein.

      Das weiß ich leider auch nicht.

      Da wär's besser, das Script für alle Subpages einmal zu laden (das sollte von den Daten wohl unabhängig sein) und nach dem Load der Subpage eine Initialisierungsfunktion aufzurufen, die die nötigen Eventhandler registriert.

      Hmm, also ich weiß leider nicht so recht, wie du das hier meinst. Meinem Verständnis nach läuft ein JS script einmal durch, wenn du die Seite lädst. Es funktioniert dann mit allem drum und dran, aber wenn ich den Inhalt, auf den sich das script bezieht, dynamisch rausnehme und wieder einfüge, dann ist das script gewissermaßen „blind“ in Bezug auf diesen Teil des Codes.

      Man müsste das script beim dynamischen Laden noch einmal laufen lassen, oder? Wenn ich bspw. genau das gleiche script in einen MutationObserver kopiere, der so eingestellt ist, dass er schaut, ob in dem Container, in dem Inhalte dynamisch ausgetauscht werden, der entsprechende Inhalt eingefügt wurde, von dem das script abhängig ist, dann läuft zwar nicht dasselbe script erneut, aber das gleiche bzw. ein identisches.

      Ich hatte es bereits so ausprobiert und es hat auch geklappt, aber dann habe ich halt zweimal das gleiche script, was irgendwie doof ist, denn wenn ich da mal was ändere, muss ich das beim script im MutationObserver auch immer ändern.

      1. problematische Seite

        Hallo borisbaer,

        du kannst die Scripte zu Beginn der Seite laden, klar. Typischerweise hat man dann einen Eventhandler für ein load oder DOMContentLoaded Event, um zu wissen, wann das DOM zu Schandtaten bereit ist.

        Mit "Außenbereich" meine ich nicht den Ort, wo das Script liegt, sondern worauf es wirkt. Du hast ja Seitenteile, die beim Subpage-Wechsel unverändert bleiben (der unveränderliche Rahmen) und andere, die beim Subpage-Wechsel nachgeladen werden (der Innenteil des Rahmens).

        Dieses Laden erfolgt JavaScript-getrieben. Dieses Javascript lädt das gewünschte HTML (per Ajax), und bringt es ins DOM. Das ist Zeile 55-69 im main.js, wenn ich deiner "problematischen Seite" folge. Da hast Du einen Success-Handler, und der bringt das neue HTML ins DOM.

        		$.ajax({
        
        			url: "subpages/" + href,
        
        			error: function() {
        
        				alert( "Seite nicht gefunden." );
        
        			},
        
        			success: function( html ) {
        
        				$( ".append" ).html( html );
        
        			}
        
        		});
        

        (ich hasse Code, wo jede 2. Zeile eine Leerzeile ist...)

        Jedenfalls musst Du nach diesem .html Aufruf, der das neue HTML zuweist, die nötigen Events für dieses HTML registrieren. Wie Du das steuerst, musst Du aus deinem Code selbst ausbaldowern. Du musst dabei nur aufpassen, dass Du keine Handler neu registriert, deren Element außerhalb dieses <div class="append"> liegt. Nicht einfach, man muss dafür sehr genau überlegen, was man tut, aber machbar. Good Luck 😀

        Rolf

        --
        sumpsi - posui - obstruxi
        1. problematische Seite

          Hallo Rolf,

          mir ist gerade eine Idee gekommen: Man könnte doch das script in den MutationObserver packen und direkt, nachdem die Seite geladen hat, mit JS irgendein unsichtbares div oder so aus dem DOM entfernen. Dann würde der MutationObserver direkt anspringen und mit ihm das script. Dann bräuchte ich nicht zweimal das gleiche script.

          Spricht da irgendetwas dagegen?

          1. problematische Seite

            Hallo borisbaer,

            Spricht da irgendetwas dagegen?

            Ich finde es unnötig kompliziert. Warum einen Industrieroboter aufbauen, nur um ein Bild aufzuhängen?

            Es gibt zwei Momente, wo Du Registrierungen durchführen musst.

            • Beim Laden der Seite vom Server. Da muss alles registriert werden, egal ob innerhalb oder außerhalb des .append Elements
            • Nach dem Laden einer Seite per Ajax. Da müssen Dinge auf den Elemente registriet werden, der innerhalb des .append Elements liegen. Das tust Du derzeit mittels MutationObserver, wenn ich das richtig verstehe.

            Du musst das aber nicht über den Observer steuern. Den Aufruf der Erstregistrierung kannst Du in einen DOMContentLoaded Eventhandler stecken. Und die Nachregistrierung rufst Du einfach "von Hand" auf, nachdem Du das Ergebnis des Ajax-Calls ins DOM gesteckt hast. Fertig.

            Das JavaScript-Modul dazu lädst Du einmalig beim Seitenabruf. Entweder klassisch im <head> mit DOMContentLoaded Event, oder Du notierst am script-Element das defer Attribut, dann wird das Script im Hintergrund geladen, aber erst ausgeführt, wenn das DOM bereit ist. Und zwar direkt vor dem DOMContentLoaded Event. Das kann sogar schon der IE10.

            Noch besser ist ein ES6-Modul (also <script type="module">). ES6-Module haben zwei Vorteile:

            • Sie werden automatisch mit defer ausgeführt. Kein DOMContentLoaded Event nötig
            • Sie werden automatisch gekapselt, d.h. du kannst nach Herzenslust "globale" Variablen und Funktionen erzeugen - sie bleiben im Modul.
            • Sie laufen automatisch im strict mode.

            Der Nachteil war einmal: Der IE kann das nicht.

            Der Nachteil ist aber auch: Alles, was im Modul steckt, ist gekapselt. Wenn Du Funktionen eines Modul von anderen Scripts aus nutzen willst, musst Du Dich mit export und import von ES6 Modulen beschäftigen. Das heißt auch: Ein Eventhandler in einem Modul ist mit onclick="..." nicht ansprechbar. Du musst alle Events mit addEventListener registrieren.

            Darauf gehe ich ein, wenn es relevant sein sollte.

            Rolf

            --
            sumpsi - posui - obstruxi
            1. problematische Seite

              Hallo Rolf,

              Du musst das aber nicht über den Observer steuern. Den Aufruf der Erstregistrierung kannst Du in einen DOMContentLoaded Eventhandler stecken. Und die Nachregistrierung rufst Du einfach "von Hand" auf, nachdem Du das Ergebnis des Ajax-Calls ins DOM gesteckt hast. Fertig.

              ha! So einfach! Und ich doktor die ganze Zeit mit dem MutationObserver herum.

              Das JavaScript-Modul dazu lädst Du einmalig beim Seitenabruf. Entweder klassisch im <head> mit DOMContentLoaded Event, oder Du notierst am script-Element das defer Attribut, dann wird das Script im Hintergrund geladen, aber erst ausgeführt, wenn das DOM bereit ist. Und zwar direkt vor dem DOMContentLoaded Event. Das kann sogar schon der IE10.

              Jetzt habe ich wieder etwas gelernt. Ich kann also einfach mein script foo mit dem Befehl document.addEventListener('DOMContentLoaded', foo); aufrufen. Jeder weitere Aufruf ginge nun theoretisch auch mit einem MutationObserver, aber ich kann auch einfach foo(); am Ende des AJAX calls schreiben. Wenn man wollte könnte man das Ganze sicher auch so einrichten, dass foo(); nur dann aufgerufen wird, wenn auch ein bestimmter URL-Parameter angezeigt wird, damit das script nicht unnötig häufig aufgerufen wird.

              Oder vielleicht besser direkt am Anfang des scripts ein if clause einfügen, das prüft, ob sich eine bestimmte bestimmte id oder class im DOM befindet, und ansonsten einfach return?

              Geht ein MutationObserver eigentlich sehr auf die Performance? Oder spricht in der Hinsicht nichts dagegen?

              Noch besser ist ein ES6-Modul (also <script type="module">). ES6-Module haben zwei Vorteile:

              • Sie werden automatisch mit defer ausgeführt. Kein DOMContentLoaded Event nötig
              • Sie werden automatisch gekapselt, d.h. du kannst nach Herzenslust "globale" Variablen und Funktionen erzeugen - sie bleiben im Modul.
              • Sie laufen automatisch im strict mode.

              Der Nachteil war einmal: Der IE kann das nicht.

              Der Nachteil ist aber auch: Alles, was im Modul steckt, ist gekapselt. Wenn Du Funktionen eines Modul von anderen Scripts aus nutzen willst, musst Du Dich mit export und import von ES6 Modulen beschäftigen. Das heißt auch: Ein Eventhandler in einem Modul ist mit onclick="..." nicht ansprechbar. Du musst alle Events mit addEventListener registrieren.

              Das klingt ziemlich„advanced“. Ich kann wohl vorerst ohne JS-Module auskommen. Aber beizeiten schaue ich mir das auf jeden Fall an.

              Vielen Dank, Rolf!

              1. problematische Seite

                Hallo borisbaer,

                wie Du deine Logik optimierst, überlasse ich Dir. Mir ist zu warm, um mich da hineinzuwühlen.

                Geht ein MutationObserver eigentlich sehr auf die Performance?

                Kommt drauf an wie oft Du im DOM rumbaust 😉

                Ich habe mit den Dingern keine Erfahrung. Ich weiß z.B. nicht, was passiert, wenn Du in dein .append div sowas einfügst:

                <div class="append">
                   <div>Dings</div>
                   <div>Bums</div>
                </div>
                

                Jetzt bekommt das append div zwei Kinder. Feuert der Observer nun zweimal? Wie gesagt - ich weiß es schlicht nicht; ich müsste jetzt erstmal nachlesen. Und dafür ist's mir, wie gesagt, zu warm. Sagte ich. Sagenderweise. (to sag, engl. durchhängen)

                Rolf

                --
                sumpsi - posui - obstruxi
                1. problematische Seite

                  Hallo Rolf,

                  <div class="append">
                     <div>Dings</div>
                     <div>Bums</div>
                  </div>
                  

                  ohne Dings kein Bums.
                  Und ohne Bums kein Fallera.

                  Sorry, aber das drängte sich mir gerade auf. 😉

                  Einen schönen Tag noch
                   Martin

                  --
                  Мир для України.
                2. problematische Seite

                  Hallo Rolf,

                  Ich habe mit den Dingern keine Erfahrung. Ich weiß z.B. nicht, was passiert, wenn Du in dein .append div sowas einfügst:

                  <div class="append">
                     <div>Dings</div>
                     <div>Bums</div>
                  </div>
                  

                  Jetzt bekommt das append div zwei Kinder. Feuert der Observer nun zweimal? Wie gesagt - ich weiß es schlicht nicht; ich müsste jetzt erstmal nachlesen. Und dafür ist's mir, wie gesagt, zu warm. Sagte ich. Sagenderweise. (to sag, engl. durchhängen)

                  ich glaube, damit er zweimal feuert, muss ein wenig Zeit dazwischen verstreichen, man müsste also ein setTimout für das Bums einbauen.

          2. problematische Seite

            @@borisbaer

            mir ist gerade eine Idee gekommen: Man könnte doch das script in den MutationObserver packen und direkt, nachdem die Seite geladen hat, mit JS irgendein unsichtbares div oder so aus dem DOM entfernen. Dann würde der MutationObserver direkt anspringen und mit ihm das script. Dann bräuchte ich nicht zweimal das gleiche script.

            Brauchst du auch so nicht:

            const tuWas = function () {
              // was getan werden muss
            };
            
            tuWas();
            const myObserver = new MutationObserver(tuWas);
            myObserver.observe();
            

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

            --
            When the power of love overcomes the love of power the world will know peace.
            — Jimi Hendrix
            1. problematische Seite

              Hallo Gunnar,

              so habe ich das jetzt letztlich auch gemacht …

              const observer = new MutationObserver(function( mutationList ) {
              	console.log( mutationList );
              	mutationList.forEach(function( mutation ) {
              		mutation.addedNodes.forEach(function( addedNode ) {
              			if ( addedNode.className === "maps" ) {
              				draggable();
              			}
              		});
              	});
              });
              
              const target = append;
              const options = { childList: true, subtree: true };
              observer.observe( target, options );
              

              Da ich das wohl noch häufiger brauchen werde, habe ich allerdings noch eingebaut, dass der MutationObserver auch schaut, welche nodes geladen wurden.

              Ich habe jetzt übrigens auch den Großteil meiner js scripts von jQuery in Vanilla JavaScript umgeschrieben. Bald sollte ich ganz darauf verzichten können.

        2. problematische Seite

          @@Rolf B

          du kannst die Scripte zu Beginn der Seite laden, klar.

          Und damit das Rendern der Seite blockieren, klar.

          Kann man machen, ist aber Sch ungünstig.

          Typischerweise hat man dann einen Eventhandler für ein load oder DOMContentLoaded Event, um zu wissen, wann das DOM zu Schandtaten bereit ist.

          Typischerweise lädt man Scripte am Ende des body – und braucht dann keinen Eventhandler für DOMContentLoaded.

          ☞ Harry Roberts: Get Your <head> Straight slides, video + transcript

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

          --
          When the power of love overcomes the love of power the world will know peace.
          — Jimi Hendrix
          1. problematische Seite

            Hallo Gunnar,

            Typischerweise lädt man Scripte am Ende des body – und braucht dann keinen Eventhandler für DOMContentLoaded.

            Ja, schon. Aber dann hat man - es sei denn, ich irre mich - einen Moment, wo die Seite schon da ist, die Scripte aber noch nicht laufen.

            Je nach Natur der Scripte habe ich dann einen Flash of ... Something. Wenn die Scripte nicht zu groß sind, würde ich deshalb einen Delay beim Laden der Seite bevorzugen. Man könnte die Scripte natürlich auch per PHP gleich in den HTML Datenstrom einschließen, oder sich auf HTTP/2 verlasen

            Bei einer fetten SPA kann man überlegen, welche Teile man vorab braucht und welche Teile Zeit haben. Das ist bei Borisbär aber nicht der Fall.

            Den Vortrag von Harry Roberts werde ich mir noch genauer anschauen müssen, hatte gerade nur Zeit für ein Durchblättern der Slides. Ob man daraus einen Blog-Beitrag (oder einen Wiki-Artikel) machen darf?

            Rolf

            --
            sumpsi - posui - obstruxi