jQuery. bereits in einem dynamisch eingefügten Iframe

184

Wir verwenden jQuery Thickbox , um einen Iframe dynamisch anzuzeigen, wenn jemand auf ein Bild klickt. In diesem Iframe verwenden wir Galleria, eine Javascript-Bibliothek, um mehrere Bilder anzuzeigen.

Das Problem scheint zu sein, dass $(document).readyder Iframe zu früh ausgelöst wird und der Iframe-Inhalt noch nicht einmal geladen ist, sodass der Galleria-Code nicht ordnungsgemäß auf die DOM-Elemente angewendet wird. $(document).readyscheint den übergeordneten Bereitschaftsstatus des Iframes zu verwenden, um zu entscheiden, ob der Iframe bereit ist.

Wenn wir die von document ready aufgerufene Funktion in einer separaten Funktion extrahieren und nach einer Zeitüberschreitung von 100 ms aufrufen. Es funktioniert, aber wir können die Chance in der Produktion mit einem langsamen Computer nicht nutzen.

$(document).ready(function() { setTimeout(ApplyGalleria, 100); });

Meine Frage: An welches jQuery-Ereignis sollten wir binden, um unseren Code ausführen zu können, wenn der dynamische Iframe bereit ist und nicht nur ein übergeordnetes Element?

EtienneT
quelle
1
Und Sie bestätigen, dass Galleria funktioniert, wenn Sie es direkt anstatt über einen Iframe laden, richtig?
Jason Kealey
Ja, Galleria funktioniert perfekt, wenn wir es direkt auf einer normalen Seite verwenden.
EtienneT
Mögliches Duplikat des Javascript-Rückrufs, wenn IFRAME vollständig geladen ist?
Robert MacLean

Antworten:

291

Ich habe eine ähnliche Frage beantwortet (siehe Javascript-Rückruf, wenn IFRAME vollständig geladen ist? ). Sie können die Kontrolle über das iframe-Ladeereignis mit dem folgenden Code erhalten:

function callIframe(url, callback) {
    $(document.body).append('<IFRAME id="myId" ...>');
    $('iframe#myId').attr('src', url);

    $('iframe#myId').load(function() {
        callback(this);
    });
}

Im Umgang mit iframes fand ich gut genug, um ein Ladeereignis anstelle eines dokumentenfertigen Ereignisses zu verwenden.

Pier Luigi
quelle
17
Sollten Sie das Ladeereignis nicht vor dem Aufruf von attr ('src') festlegen?
Shay Erlichmen
15
Nein, das spielt keine Rolle. Das Ladeereignis wird mindestens bis zur nächsten Ereignisschleife ausgelöst.
Barum Rho
29
Das Ladeereignis funktioniert nicht für Iframes, die zum Herunterladen verwendet werden. wie <iframe src = "my.pdf" />
Mike Starov
5
Problem beim Laden ist, dass es ausgelöst wird, wenn alle Bilder und Unterrahmen geladen wurden. Ein jQuery-ähnliches Ereignis wäre nützlicher.
Tom
30

Mit jQuery 1.3.2 hat Folgendes für mich funktioniert:

$('iframe').ready(function() {
  $('body', $('iframe').contents()).html('Hello World!');
});

REVISION:! Tatsächlich sieht der obige Code manchmal so aus, als würde er in Firefox funktionieren, nie so, als würde er in Opera funktionieren.

Stattdessen habe ich eine Polling-Lösung für meine Zwecke implementiert. Vereinfacht sieht es so aus:

$(function() {
  function manipIframe() {
    el = $('body', $('iframe').contents());
    if (el.length != 1) {
      setTimeout(manipIframe, 100);
      return;
    }
    el.html('Hello World!');
  }
  manipIframe();
});

Dies erfordert keinen Code auf den aufgerufenen Iframe-Seiten. Der gesamte Code befindet sich im übergeordneten Frame / Fenster und wird ausgeführt.

Már Örlygsson
quelle
Worauf bezieht sich die movePreview-Funktion in setTimeout ()?
cam8001
@ cam8001: Es war ein Tippfehler - wurde jetzt behoben.
Már Örlygsson
Am Ende habe ich auch eine Polling-Lösung verwendet. Andere Lösungen schienen nur teilweise erfolgreich zu sein. Für mich musste ich jedoch überprüfen, ob eine Javascript-Funktion vorhanden ist, und nicht nur den Inhalt des Iframes, bevor ich Erfolg hatte. z.B. (typeof iframe.contentWindow.myfunc == 'function')
Julian
2
Diese Lösung ist rekursiv und belegt für jeden Aufruf Speicherplatz. Wenn der Iframe nie geladen wird, wird er für immer tiefer und tiefer aufgerufen, bis der Speicher leer ist. Ich würde stattdessen setIntervalzum Abrufen empfehlen .
Eremzeit
15

In IFrames löse ich dieses Problem normalerweise, indem ich ein kleines Skript ganz am Ende des Blocks platziere:

<body>
The content of your IFrame
<script type="text/javascript">
//<![CDATA[
   fireOnReadyEvent();
   parent.IFrameLoaded();
//]]>
</script>
</body>

Diese Arbeit die meiste Zeit für mich. Manchmal ist die einfachste und naivste Lösung die am besten geeignete.

Tamas Czinege
quelle
4
+1 Diese Lösung funktioniert hervorragend für mich! Eine großartige Ergänzung ist, dass Sie bis zu erreichen können, parentum eine Kopie von jQuery zu erhalten und parent.$(document).ready(function(){ parent.IFrameLoaded( ); });den Iframe zu initialisieren.
David Murdoch
9

Nach der Idee von DrJokepu und David Murdoch habe ich eine vollständigere Version implementiert. Es erfordert jQuery sowohl auf dem übergeordneten als auch auf dem iframe und dem iframe, um in Ihrer Kontrolle zu sein.

Iframe-Code:

var iframe = window.frameElement;

if (iframe){
    iframe.contentDocument = document;//normalization: some browsers don't set the contentDocument, only the contentWindow

    var parent = window.parent;
    $(parent.document).ready(function(){//wait for parent to make sure it has jQuery ready
        var parent$ = parent.jQuery;

        parent$(iframe).trigger("iframeloading");

        $(function(){
            parent$(iframe).trigger("iframeready");
        });

        $(window).load(function(){//kind of unnecessary, but here for completion
            parent$(iframe).trigger("iframeloaded");
        });

        $(window).unload(function(e){//not possible to prevent default
            parent$(iframe).trigger("iframeunloaded");
        });

        $(window).on("beforeunload",function(){
            parent$(iframe).trigger("iframebeforeunload");
        });
    });
}

übergeordneter Testcode:

$(function(){
    $("iframe").on("iframeloading iframeready iframeloaded iframebeforeunload iframeunloaded", function(e){
        console.log(e.type);
    });
});
Ricardo Freitas
quelle
Aus irgendeinem Grund würde die Verwendung von $ (iframe) .ready (Funktion ...) im übergeordneten Element für mich nicht funktionieren. Es schien, als würde die Rückruffunktion ausgeführt, bevor der Iframe-Dom bereit war. Mit dieser Methode hat super funktioniert!
w--
4

Habe die Lösung für das Problem gefunden.

Wenn Sie auf einen Thickbox-Link klicken, der einen Iframe öffnet, wird ein Iframe mit der ID TB_iframeContent eingefügt.

Anstatt $(document).readymich auf das Ereignis im Iframe-Code zu verlassen, muss ich mich nur an das Ladeereignis des Iframes im übergeordneten Dokument binden:

$('#TB_iframeContent', top.document).load(ApplyGalleria);

Dieser Code befindet sich im Iframe, ist jedoch an ein Ereignis eines Steuerelements im übergeordneten Dokument gebunden. Es funktioniert in FireFox und IE.

EtienneT
quelle
9
Die Lösung gefunden? Sieht so aus, als hätte Pier es bereits gepostet. Unabhängig davon, ob Sie es selbst gefunden haben oder nicht, besteht die Etikette darin, seine Antwort zu akzeptieren und so die Zeit zu belohnen, die er damit verbracht hat, Ihnen zu antworten.
Sky Sanders
Der Unterschied zwischen Piers Lösung und dieser (und dem, was mir in meinem Code gefehlt hat) ist der Kontext top.document, in dem ich Code im Iframe haben kann, um zu erkennen, wann der Iframe geladen wurde. (Obwohl loadseit dieser Antwort veraltet und sollte durch ersetzt werden on("load").)
Teepeemm
2

Grundsätzlich was andere schon gepostet haben aber IMHO ein bisschen sauberer:

$('<iframe/>', {
    src: 'https://example.com/',
    load: function() {
        alert("loaded")
    }
}).appendTo('body');
Udondan
quelle
1

Versuche dies,

<iframe id="testframe" src="about:blank" onload="if (testframe.location.href != 'about:blank') testframe_loaded()"></iframe>

Sie müssen dann nur noch die JavaScript-Funktion testframe_loaded () erstellen.

Danny G.
quelle
1
Problem beim Laden ist, dass es ausgelöst wird, wenn alle Bilder und Unterrahmen geladen wurden. Ein jQuery-ähnliches Ereignis wäre nützlicher.
Tom
1

Ich lade das PDF mit jQuery Ajax in den Browser-Cache. Dann erstelle ich ein eingebettetes Element mit Daten, die sich bereits im Browser-Cache befinden. Ich denke, es wird auch mit iframe funktionieren.


var url = "http://example.com/my.pdf";
// show spinner
$.mobile.showPageLoadingMsg('b', note, false);
$.ajax({
    url: url,
    cache: true,
    mimeType: 'application/pdf',
    success: function () {
        // display cached data
        $(scroller).append('<embed type="application/pdf" src="' + url + '" />');
        // hide spinner
        $.mobile.hidePageLoadingMsg();
    }
});

Sie müssen auch Ihre http-Header richtig einstellen.


HttpContext.Response.Expires = 1;
HttpContext.Response.Cache.SetNoServerCaching();
HttpContext.Response.Cache.SetAllowResponseInBrowserHistory(false);
HttpContext.Response.CacheControl = "Private";
Pavel Savara
quelle
1
Beachten Sie, dass dies in mobilen Browsern nicht gut funktioniert, in denen der Cache möglicherweise kleiner als die Größe von PDF ist.
Pavel Savara
1

Dies war genau das Problem, auf das ich bei unserem Kunden gestoßen bin. Ich habe ein kleines JQuery-Plugin erstellt, das für die Iframe-Bereitschaft zu funktionieren scheint. Es verwendet Polling, um das iframe-Dokument readyState in Kombination mit der inneren Dokument-URL in Kombination mit der iframe-Quelle zu überprüfen, um sicherzustellen, dass der iframe tatsächlich "ready" ist.

Das Problem mit "onload" ist, dass Sie Zugriff auf den tatsächlichen Iframe benötigen, der dem DOM hinzugefügt wird. Wenn Sie dies nicht tun, müssen Sie versuchen, das Laden des Iframes abzufangen. Wenn es zwischengespeichert wird, ist dies möglicherweise nicht der Fall. Was ich brauchte, war ein Skript, das jederzeit aufgerufen werden konnte und feststellte, ob der Iframe "bereit" war oder nicht.

Hier ist die Frage:

Heiliger Gral, um festzustellen, ob ein lokaler Iframe geladen wurde oder nicht

und hier ist die jsfiddle, die ich mir schließlich ausgedacht habe.

https://jsfiddle.net/q0smjkh5/10/

In der obigen jsfiddle warte ich darauf, dass beim Laden ein Iframe an den Dom angehängt wird, und überprüfe dann den Bereitschaftsstatus des inneren Dokuments des Iframes - der domänenübergreifend sein sollte, da er auf Wikipedia verweist -, aber Chrome scheint "vollständig" zu melden. Die iready-Methode des Plug-Ins wird dann aufgerufen, wenn der iframe tatsächlich bereit ist. Der Rückruf versucht erneut, den Bereitschaftszustand des inneren Dokuments zu überprüfen - diesmal wird eine domänenübergreifende Anfrage gemeldet (was korrekt ist) -, trotzdem scheint es für das zu funktionieren, was ich brauche, und ich hoffe, es hilft anderen.

<script>
  (function($, document, undefined) {
    $.fn["iready"] = function(callback) {
      var ifr = this.filter("iframe"),
          arg = arguments,
          src = this,
          clc = null, // collection
          lng = 50,   // length of time to wait between intervals
          ivl = -1,   // interval id
          chk = function(ifr) {
            try {
              var cnt = ifr.contents(),
                  doc = cnt[0],
                  src = ifr.attr("src"),
                  url = doc.URL;
              switch (doc.readyState) {
                case "complete":
                  if (!src || src === "about:blank") {
                    // we don't care about empty iframes
                    ifr.data("ready", "true");
                  } else if (!url || url === "about:blank") {
                    // empty document still needs loaded
                    ifr.data("ready", undefined);
                  } else {
                    // not an empty iframe and not an empty src
                    // should be loaded
                    ifr.data("ready", true);
                  }

                  break;
                case "interactive":
                  ifr.data("ready", "true");
                  break;
                case "loading":
                default:
                  // still loading
                  break;   
              }
            } catch (ignore) {
              // as far as we're concerned the iframe is ready
              // since we won't be able to access it cross domain
              ifr.data("ready", "true");
            }

            return ifr.data("ready") === "true";
          };

      if (ifr.length) {
        ifr.each(function() {
          if (!$(this).data("ready")) {
            // add to collection
            clc = (clc) ? clc.add($(this)) : $(this);
          }
        });
        if (clc) {
          ivl = setInterval(function() {
            var rd = true;
            clc.each(function() {
              if (!$(this).data("ready")) {
                if (!chk($(this))) {
                  rd = false;
                }
              }
            });

            if (rd) {
              clearInterval(ivl);
              clc = null;
              callback.apply(src, arg);
            }
          }, lng);
        } else {
          clc = null;
          callback.apply(src, arg);
        }
      } else {
        clc = null;
        callback.apply(this, arguments);
      }
      return this;
    };
  }(jQuery, document));
</script>
Jon Freynik
quelle
Hat gut für mich
Hassan Tareq
0

Diese Funktion aus dieser Antwort ist der beste Weg, um damit $.readyumzugehen, da dies für iframes explizit fehlschlägt. Hier ist die Entscheidung nicht zu unterstützen.

Das loadEreignis wird auch nicht ausgelöst, wenn der Iframe bereits geladen wurde. Sehr frustrierend, dass dies auch 2020 ein Problem bleibt!

function onIframeReady($i, successFn, errorFn) {
    try {
        const iCon = $i.first()[0].contentWindow,
        bl = "about:blank",
        compl = "complete";
        const callCallback = () => {
            try {
                const $con = $i.contents();
             if($con.length === 0) { // https://git.io/vV8yU
                throw new Error("iframe inaccessible");
             }


   successFn($con);
     } catch(e) { // accessing contents failed
        errorFn();
     }
  };
  const observeOnload = () => {
    $i.on("load.jqueryMark", () => {
        try {
            const src = $i.attr("src").trim(),
            href = iCon.location.href;
            if(href !== bl || src === bl || src === "") {
                $i.off("load.jqueryMark");
                callCallback();
            }
        } catch(e) {
            errorFn();
        }
    });
  };
  if(iCon.document.readyState === compl) {
    const src = $i.attr("src").trim(),
    href = iCon.location.href;
    if(href === bl && src !== bl && src !== "") {
        observeOnload();
    } else {
        callCallback();
    }
  } else {
    observeOnload();
  }
} catch(e) {
    errorFn();
}

}}

Crashalot
quelle