wake-up-neo.com

Das Ausführen einer js-Datei wurde in einer Ajax-Antwort zurückgegeben

Ich habe eine HTML5-Anwendung, die Jquery 3.2.1 verwendet.

In einem Teil der Anwendung - einer Suchfunktion - stelle ich eine Ajax-Anfrage. Die Antwort der ajax-Anfrage ist HTML. Dazu gehört ein <script>-Tag, das eine Verknüpfung zu einer js-Datei herstellt, die auf demselben Server gehostet wird wie die Anwendung.

Der Ajax-Code sieht also so aus - für die Ajax-Anfrage und das Schreiben der Antwort in ein Div mit der ID #ajaxContent:

$.ajax({
    url: $('#searchRegulations').attr('action'),
    type: 'post',
    cache: false,
    data: $('#searchRegulations').serialize()
 }).done(function (response, status, xhr) {
        if (response) {
            $('main .content').hide();
            $('#ajaxContent').html(response).show();
            return false;
        }
    }
});

Wenn ich #ajaxContent inspiziere, kann ich sehen, dass das <script>-Tag in der Antwort von ajax enthalten ist:

 enter image description here

Ich habe auch die Registerkarte "Netzwerk" überprüft, um sicherzustellen, dass /js/search_regulations.js korrekt geladen wird und eine Antwort von 200 ergibt:

In search_regulations.js gibt es eine Jquery, die einige Filter umschaltet, die in #ajaxContent vorhanden sind. 

Das Problem ist, dass dieser Code nur etwa 50% von .__ zu funktionieren scheint. die Zeit. Wenn es funktioniert, wird der Status eines Filters geändert Schaltflächen durch Hinzufügen/Entfernen einer Klasse .active zu Elementen in .browse-ctp__filters-data und dann in eine verborgene Form schreiben mit der ID #tmpFilters.

Um sicherzustellen, dass das Skript "feuert", füge ich die Zeile console.log('search_regulations.js firing'); hinzu und dies wird in der Konsole jedes Mal angezeigt, unabhängig davon, ob das Skript funktioniert oder nicht.

Was merkwürdiger ist, wenn ich den Code ausschneide/in meine Konsole einfüge, nachdem die Ajax-Antwort auf die Seite geschrieben wurde, funktioniert immer wie erwartet.

Ist dies ein Problem mit der Art, wie das Skript in die Seite eingefügt wird?

Ich habe das Skript unten eingefügt, aber ich denke nicht, dass es ein Problem mit dem Code ist, sondern wie die Browser-/Ajax-Antwort behandelt wird:

$(function() {  

console.log('search_regulations.js firing');

/* toggle the active (applied) state on browse by filters */
/* @link https://stackoverflow.com/questions/48662677/switch-active-class-between-groups-of-include-exclude-buttons */
$(document).on('click', '.browse-ctp__filters-data .include, .exclude', function(){

    var $this = $(this);

    // Split name into array (e.g. "find_355" == ["find", "355"]) 
    var arr = $this.attr('name').split('_');


    // Toggle active class
    $this.toggleClass("active");
    if ($this.siblings().hasClass("active")) {
      $this.siblings().removeClass("active")
    }

    // Remove any existing instances of the filter from hidden form
    $('#tmpFilters input[value="exclude_'+arr[1]+'"]').remove();
    $('#tmpFilters input[value="find_'+arr[1]+'"]').remove();

    // If a filter has been applied then add it to hidden form
    if ($this.hasClass('active')) {
        $('#tmpFilters').append('<input type="hidden" name="tmpFilter[]" value="'+$this.attr('name')+'">');
    }
});    
}); 

Hinweise zur angebotenen Bounty:

Ich habe eine Belohnung angeboten, weil es kein triviales Problem zu lösen ist - ein Beweis dafür, dass niemand eine brauchbare Antwort gegeben hat. Ich erwarte die richtige Antwort auf:

  1. Seien Sie mit jsfiddle oder gleichwertig nachweisbar.
  2. Erklären Sie, wie und warum es funktioniert.
  3. Verstehen Sie, dass die Ajax-Antwort HTML und Js ist. Das js wirkt auf HTML-Elemente in der Antwort ein. Daher müssen both der HTML- und der js-Code in der Antwort enthalten sein - im Gegensatz zu "man muss nur die js zu einer globalen Datei hinzufügen" (ich möchte nicht, dass die js global sind, weil sie HTML-spezifisch sind.) Antwort gegeben und kann in verschiedenen Teilen der Anwendung variieren).
  4. Sollte kein Timeout verwenden (setTimeout oder was auch immer). Wenn der Benutzer mit den Elementen der Benutzeroberfläche (z. B. Schaltflächen) interagiert, die in der HTML-Antwort vorher zurückgegeben werden, wird das Zeitlimit und daher js ausgelöst. Dies führt zu demselben Problem, das wir jetzt haben. Für mich ist das also keine gültige Lösung.
  5. Wenn sich dieses Problem in HTML5/jquery nicht lösen lässt, erklären Sie den Grund und schlagen Sie alternative Wege vor.

jsfiddle zeigt den über ajax zurückgegebenen HTML- und Script-Tag an:

Mehrere Leute haben um eine Geige oder Demo gebeten. Keine zwei Personen würden das gleiche Ergebnis erzielen - was sehr um den Punkt der Frage geht -, also habe ich ursprünglich keinen gemacht. Der zurückgegebene HTML-Code, der in #ajaxContent geschrieben wird, wird hier angezeigt: http://jsfiddle.net/v4t9j32g/1/ - Dies ist, was dev-Tools im Browser nach der Ajax-Antwort zeigen. Bitte beachten Sie, dass die Länge des zurückgegebenen Inhalts variieren kann, da es sich um die Antwort auf eine Keyword-Suchfunktion handelt, die eine Menge Filterschaltflächen zur Folge hat. Beachten Sie auch, dass diese HTML-Antwort die Zeile <script src="/js/search_regulations.js"></script> enthält. Hier befindet sich das problematische js und der vollständige Inhalt davon wird oben in dieser Frage gezeigt - das Bit, das console.log('search_regulations.js firing') enthält.

15
Andy

Eines der Probleme, die ich sehe, ist, dass Sie die onclick mehrmals an die gleichen Elemente binden ...

Da die js mehrfach über ajax-Anforderungen geladen werden können, ist es wichtig, zuerst die Verbindung zu trennen, bevor Ereignisse erneut angefügt werden.

Das andere Problem ist, dass Sie den Code im $(document).ready -Ereignis ausführen (das heißt, wenn HTML-Document geladen ist und DOM bereit ist). Es wäre jedoch wahrscheinlich besser, wenn Sie den Code im $(window).load -Ereignis (das ein a ausführt) ausführen etwas letzteres, wenn die vollständige Seite vollständig geladen ist (einschließlich aller Frames, Objekte und Bilder)

Beispiel :

$(window).load(function() {  

    console.log('search_regulations.js firing');

    //off: detach the events
    //on: attach the events
    $('.browse-ctp__filters-data .include, .exclude').off('click').on('click', function(){
        ...
    }
}); 
4
bastos.sergio

HINWEIS:

  • @ bastos.sergios hat deine Frage bereits beantwortet - siehe seinen ersten Punkt.
  • @ yts sollte auch die Antwort von Bastos "gestärkt" werden.

Folgendes sollte das Problem mit Ihrem search_regulations.js-Skript beheben:

$(document)
  // Detach the event handler to prevent multiple/repeat triggers on the target elements.
  .off('click.toggleActive', '.browse-ctp__filters-data .include, .exclude')
  // Attach/re-attach the handler.
  .on('click.toggleActive', '.browse-ctp__filters-data .include, .exclude', function(){
    ... put your code here ...
  });

Meine primäre Absicht, diese Antwort zu schreiben, besteht darin, dass Sie das Problem mit Ihrem Skript sehen.

Und wenn Sie neugierig sind, hier ein Skript, das ich mit dieser Demo verwendet habe, die leicht modifiziert wurde:

https://codepen.io/anon/pen/RBjPjw.js

1
Sally CJ

Zusätzlich zu dem, worüber alle in den Kommentaren gesprochen haben, werde ich versuchen, dies in eine Antwort zu bringen. 

Ereignisgesteuert

JS ist ereignisgesteuert. Das bedeutet nicht, dass Sie Skripts im Handumdrehen laden müssen, damit sie Funktionen ausführen, wenn ein Ereignis ausgelöst wird. Ein besserer Ansatz wäre es, alles, was Sie zum Laden der Seite benötigen, zu laden und Ereignis-Listener hinzuzufügen. Dies würde die Benutzererfahrung (weniger HTTP-Anfragen) und die Codierbarkeit von Code erheblich verbessern.

TL; DR

Im Allgemeinen würde ich so etwas in Ihrer HTML-Datei machen:

<script src="/js/main.js">  
<script src="/js/search_regulations.js" async>  <!-- bonus tip! google "async script tag" -->

Dadurch werden alle benötigten Js geladen. 

Denken Sie daran, dass search_regulations.js den gewünschten Ereignis-Listener mit der jQuery-Methode on hinzufügen soll. Da die HTML-Datei jedoch nicht vorhanden war, als das Skript den Ereignis-Listener hinzufügte, sollten Sie this überprüfen.

Warum ist das besser?

  • Nun hatten Sie einen ziemlich einfachen Fall, bei dem eine Datei eine andere lädt. In der Zukunft möchten Sie möglicherweise weitere Funktionen und komplexeren Code hinzufügen. Das Debuggen wird schmerzhaft sein, wenn Sie Skripts haben, die sich gegenseitig laden.
  • Der Benutzer muss warten, bis die Anforderung für das Laden der search_regulations.js-Datei vorliegt, wenn sie sich in einem Mobilfunknetz befinden/die Datei wird größer, was möglicherweise eine schlechte Benutzererfahrung darstellt.
  • Sie können Ihren Code weiterhin mit search_regulations.js in der Anwendung wiederverwenden. 
1
Or Duan

Über die Anforderungen

Die Lösung des Problems ist entweder mit HTML5/jQuery oder einer Kombination aus einer vorherigen HTML-Version und nativem Javascript verfügbar. Der einzige Grund, eine Bibliothek wie jQuery zu verwenden, besteht darin, ältere Browser auf die gleiche Unterstützungsstufe zu bringen oder Fehler zu beheben und ein Standardframework für Entwicklergruppen zu verwenden.

Meiner Meinung nach spielt es keine Rolle, wo Sie die wenigen Codezeilen platzieren müssen, die zur Lösung des Problems erforderlich sind. Sie können sie im Kopfbereich der Seite, am Ende der Seite oder in Bezug auf die Anforderungen der Kopfgeld in der Ajax-Antwortnutzlast, die häufig nachgeladen wird.

Über das Problem

Der eigentliche Trick liegt in der Methode, die Sie zum Auslösen von Klickereignissen verwenden. Ihre Entscheidung, dies innerhalb des Skripts zu tun, das in den wiederholten Ajax-Antworten vorhanden ist, führt dazu, dass Ihre Gruppe irgendwann ausfällt.

Bei jeder Ajax-Anforderung fügt das Skript in der Antwort neue Ereignis-Listener hinzu. Diese summieren sich fortlaufend, bis die Browser aus irgendeinem Grund ausfallen (nicht genügend Arbeitsspeicher, erschöpfter Ereignisstapel oder Bedingungen für Klickereignisse).

Über die Lösung

Die Lösung Ihres Problems besteht also darin, das Hinzufügen von Ereignissen bei jeder Ajax-Anfrage Antwort-Antwort zu vermeiden. Dies ist eine Aufgabe, die leicht durch eine "Delegierung der Ereignisphase der Capture-Phase" gelöst werden kann (die leider nicht häufig verwendet wird).

Ein Ereignis-Listener wird nur einmal auf einem der obersten Knoten hinzugefügt, bei dem es sich um den "document" -Knoten selbst oder das "html" -Element oder das "body" -Element handeln kann, also um ein Element, das nach der Anfangsseite immer im DOM vorhanden ist Belastung.

Dieses Element ist dafür verantwortlich, alle Klicks auf der Seite zu erfassen, die gegenwärtig vorhanden sind oder zu einem späteren Zeitpunkt (durch Ihre Ajax-Anforderungen) geladen werden. Dieses Element wird während der gesamten Lebensdauer der Seite so gehandhabt. Es ist keine Einrichtung erforderlich/zerstöre Ereignisse, wie ich einige vorgeschlagen habe. Nun, das wird wahrscheinlich auch funktionieren, wird aber mehr Workload auf der Browser-Engine erzeugen, mehr Speicher verbrauchen, mehr Code erfordern und insgesamt langsamer und unnötig kompliziert sein, um es gelinde auszudrücken.

Code-Beispiele

Ich habe zwei CodePen-Beispiele für Sie eingerichtet:

Delegation1 enthält Ereignisbehandlungscode im Abschnitt HEAD. Es ist meine bevorzugte Art, diese Art von Problemen zu behandeln, wenn keine Einschränkungen oder andere unbekannte Macken vorhanden sind. Die Vorteile dieser Version sind kürzer/schneller und wartungsfreundlicherer Code.

/ - Delegation1

Delegation2 enthält Ereignisbehandlungscode in der Ajax-Antwort und sollte die Anforderungen für das fortlaufende Laden von HTML/JS erfüllen. Es wird ein Häkchen gesetzt, um den Listener nur einmal zu installieren und zu vermeiden, dass sich mehrere Ereignisse überschneiden.

Delegation2

Der Javascript-Code in den Beispielen enthält relevante Kommentare und sollte ausreichen, um die implementierte Strategie zu erläutern, um dies so einfach wie möglich zu machen und in ähnlichen Fällen wiederzuverwenden.

Diese Beispiele wurden unter Berücksichtigung älterer Browser erstellt. Mit neueren Browsern werden leistungsfähigere APIs wie element.matches(selector) bereitgestellt, die bei der Delegierung von Ereignissen sehr nützlich sind. Ich habe absichtlich darauf verzichtet, es für eine breitere Unterstützung zu verwenden.

1
Diego Perini

Um die Möglichkeit einer Race-Bedingung zu vermeiden, ohne einen flachen Timer zu implementieren, müssen Sie eine Ajax-Antwort zurückgeben, die das Skript von der HTML-Datei trennt, z. B. ein Json-Objekt, das zwei Elemente zurückgibt (das Skript oder ein Array von Skripts zur Unterstützung von Fällen) wo Sie mehrere und ein zweites Element benötigen, das das stringifizierte HTML-Markup enthält).

Beispiel einer Json-Antwort von Ajax:

{
    "script": "/yourscript.js",
    "html": "<p>Your markup...</p>"
}

Dieser (analysierte) Json wird der Kürze halber als xhrResponse bezeichnet.

Hängen Sie vor dem Anwenden eines der Dom-Elemente ein Preload-Tag für Ihr Skript bzw. Ihre Skripte an, bevor Sie das Dom-Element laden, sodass die Auflösung im Hintergrund ohne Laden beginnt. Dies verringert die Ladezeit und die Hilfe viel von der Möglichkeit laufender Rennbedingungen abmildern:

document.getElementsByTagName('head')[0].appendChild( '<link rel="preload" href="' + xhrResponse.script + '" as="script">' );

Dadurch wird das Skript im Hintergrund geladen, ohne dass es ausgeführt wird. Es kann also sofort angewendet werden, wenn Sie das Skripttag später auf den Dom anwenden.

Als Nächstes möchten Sie die dom-Elemente anhängen und die Skripts anwenden, nachdem der neue dom-Inhalt aufgelöst wurde. Sie können das Markup abwechselnd direkt in der Json-Antwort übergeben oder alternativ einen Link zu einer Vorlagendatei zurückgeben und diese zusammen mit der oben genannten Preload-Methode für die Vorlage selbst bereitstellen. Wenn Sie sich mehr mit der Möglichkeit beschäftigen, Probleme mit Inhalten zu umgehen, tun Sie letzteres. Wenn Sie sich mehr mit Bandbreite und Latenz beschäftigen, sollten Sie die erstere tun. Für beide gibt es einige kleinere Einschränkungen, abhängig von Ihrem Anwendungsfall.

document.getElementsByTagName('head')[0].appendChild( xhrResponse.html );

Sie möchten dann prüfen, ob der angewendete HTML-Inhalt innerhalb des Doms aufgelöst wurde. Ein beliebiger Wartezeit-Timer ist keine sehr gute Möglichkeit, dies zu tun, da er mit hoher Wahrscheinlichkeit dazu führt, dass der Benutzer länger als nötig warten muss. Gelegentliche Netzwerkengpässe können auch dazu führen, dass Ihr Skript gelegentlich verschluckt, wenn der Inhalt nicht vor aufgelöst wird Ihre willkürliche Timer-Länge (eher ein Problem, wenn Sie eine Vorlage über einen Link anstelle von stringifiziertem HTML-Markup bereitstellen) oder wenn der Endbenutzer derzeit über wenig Arbeitsspeicher verfügt (alte Maschinen, bzillion-Registerkarten geöffnet) oder sehr große HTML-Payloads direkt geknüpft serviert).

In diese Antwort finden Sie eine ziemlich robuste Lösung, um zu überprüfen, ob dynamisch hinzugefügter Dom-Inhalt korrekt aufgelöst wurde.

Sobald dies behoben ist, wenden Sie die Skriptelemente anschließend mit folgendem Code an:

var script = document.createElement('script');
script.src = xhrResponse.script;
script.async = true; // use false here if you want synchronous loading for some reason
document.head.appendChild(script);

Das zuvor erwähnte Preload-Tag sollte sicherstellen, dass Ihr Skript zu diesem Zeitpunkt bereits vom Browser lokalisiert wurde oder fast schon fertig ist. Sie können dann überprüfen, ob der Listener mit dem Statusereignis Status abgeschlossen ist und ob es weiterhin Probleme gibt:

script.onreadystatechange = function() {
    if (script.readyState === "complete") {
        // debug stuff, or call an init method for your js if you really want to be sure it fired after everything else is done.
    }
}

Es sollte beachtet werden, dass sich der Bereitschaftsstatus im obigen Bereitschaftsstatus-Listener nicht ändert (weil er bereits erledigt ist), und dass dieser Teil der Logik nicht ausgeführt wird, wenn der Vorabladevorgang vor diesem Teil Ihrer Logik aufgelöst wird. Abhängig vom Gewicht Ihres DOM-Inhalts kann dies für Sie zutreffen.


Durch diesen Ansatz wird das Laden in der richtigen Reihenfolge sichergestellt und das Skript und das Markup entkoppelt. Entweder können sie unabhängig voneinander woanders verwendet werden, falls dies jetzt oder zu einem späteren Zeitpunkt der Fall ist.

0
mopsyd

Sie müssen das <script> -Tag aus dem HTML-Code entfernen und ein Skripttag manuell erstellen, um es dem DOM hinzuzufügen. Im Wesentlichen können Sie das HTML in JS konvertieren mit:

var $html = $(data.Html)

Dann ziehen Sie alle Skript-Tags wie folgt heraus:

 var scripts = [];
        $html.each(function(i, item) {
            if (item instanceof HTMLScriptElement) {
                scripts.Push(item);
            }
        });

Dann müssen Sie alle <script> -Tags aus der HTML-Datei entfernen, bevor Sie sie an das DOM anhängen.

$html.find("script").remove();

$("placeToAddHtml").append($html);

Nachdem der HTML-Code ohne Tags entfernt wurde, können Sie am Ende jedes Skript manuell zum DOM hinzufügen:

           for (var i = 0; i < scripts.length; i++) {
                var script = document.createElement('script');

                $(scripts[i]).each(function() {
                    $.each(this.attributes, function(j, attrib) {
                        var name = attrib.name;
                        var value = attrib.value;
                        script[name] = value;
                    });
                });

                script.text = scripts[i].innerHTML;
                document.body.appendChild(script);
            }

Ich hoffe, das funktioniert so, wie Sie es vorhaben!

Bearbeiten: Eventuell müssen Sie das Skript-Tag so aufbauen, dass alle abgerufenen Skripts gleichzeitig angehängt werden, wenn sie voneinander abhängig sind. Auf diese Weise fügen Sie nur ein großes Skript-Tag hinzu, das alle anderen Tags intern auf einmal enthält.

0
Daniel Lorenz