jQuery. già in un iframe inserito dinamicamente


184

Stiamo usando jQuery thickbox per visualizzare dinamicamente un iframe quando qualcuno fa clic su un'immagine. In questo iframe, stiamo usando la galleria una libreria javascript per visualizzare più immagini.

Il problema sembra essere che $(document).readynell'iframe sembra essere stato lanciato troppo presto e il contenuto dell'iframe non è ancora stato ancora caricato, quindi il codice della galleria non viene applicato correttamente sugli elementi DOM. $(document).readysembra utilizzare lo stato pronto parent iframe per decidere se iframe è pronto.

Se estraiamo la funzione chiamata dal documento pronta in una funzione separata e la chiamiamo dopo un timeout di 100 ms. Funziona, ma non possiamo correre il rischio in produzione con un computer lento.

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

La mia domanda: a quale evento jQuery dovremmo legarci per poter eseguire il nostro codice quando l'iframe dinamico è pronto e non è solo un genitore?


1
E confermi che la galleria funziona quando la carichi direttamente invece che attraverso un iframe, giusto?
Jason Kealey,

Sì, la galleria funziona perfettamente quando la utilizziamo direttamente in una pagina normale.
Etienne T

Risposte:


291

Ho risposto a una domanda simile (vedi callback Javascript al termine del caricamento di IFRAME? ). È possibile ottenere il controllo sull'evento di caricamento iframe con il seguente codice:

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

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

Nel trattare con iframe ho trovato abbastanza buono da usare l'evento load invece dell'evento pronto per il documento.


17
Non dovresti impostare l'evento load prima di chiamare l'attr ('src')?
Shay Erlichmen,

15
No, non importa. L'evento di caricamento non si attiva fino al successivo loop di eventi al minimo.
Barum Rho,

29
l'evento load non funzionerà per gli iframe utilizzati per il download. come <iframe src = "my.pdf" />
Mike Starov,

5
Il problema con il caricamento è che si attiva quando tutte le immagini e i sottotelai sono stati caricati. Un evento jQuery come pronto sarebbe più utile.
Tom,


30

Utilizzando jQuery 1.3.2 per me ha funzionato:

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

REVISIONE:! In realtà il codice sopra a volte sembra funzionare in Firefox, non sembra mai funzionare in Opera.

Invece ho implementato una soluzione di polling per i miei scopi. Semplificato in basso, si presenta così:

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

Questo non richiede codice nelle pagine iframe chiamate. Tutto il codice risiede e viene eseguito dal frame / dalla finestra padre.


Qual è la modalità movePreview a cui si fa riferimento in setTimeout ()?
cam8001,

@ cam8001: era un errore di battitura - ora è stato corretto.
Már Örlygsson,

Ho finito per usare anche una soluzione di polling. Altre soluzioni sembravano funzionare solo con parziale successo. Tuttavia, per me, ho dovuto verificare l'esistenza di una funzione javascript, piuttosto che solo per il contenuto dell'iframe, prima di avere successo. per esempio. (typeof iframe.contentWindow.myfunc == 'function')
Julian

2
Questa soluzione è ricorsiva che occupa memoria per ogni chiamata. Se l'iframe non viene mai caricato, chiamerà sempre più in profondità per sempre fino a quando la memoria si esaurisce. Consiglierei setIntervalinvece per il polling.
eremzeit,

15

In IFrames di solito risolvo questo problema mettendo un piccolo script alla fine del blocco:

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

Questo lavoro per la maggior parte del tempo per me. A volte la soluzione più semplice e ingenua è la più appropriata.


4
+1 Questa soluzione funziona benissimo per me! Un'ottima aggiunta è che puoi raggiungere il parentper prendere una copia di jQuery e usarlo parent.$(document).ready(function(){ parent.IFrameLoaded( ); });per inizializzare l'iframe.
David Murdoch,

9

Seguendo l'idea di DrJokepu e David Murdoch ho implementato una versione più completa. Si richiede jQuery sia sul genitore e iframe e iframe per essere nel vostro controllo.

codice iframe:

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");
        });
    });
}

codice test genitore:

$(function(){
    $("iframe").on("iframeloading iframeready iframeloaded iframebeforeunload iframeunloaded", function(e){
        console.log(e.type);
    });
});

per qualunque motivo usare $ (iframe) .ready (funzione ...) nel genitore non funzionerebbe per me. Sembrava che la funzione di callback fosse stata eseguita prima che iframe dom fosse pronto. L'uso di questo metodo ha funzionato alla grande!
w--

4

Ho trovato la soluzione al problema.

Quando si fa clic su un collegamento thickbox che apre un iframe, inserisce un iframe con un ID di TB_iframeContent.

Invece di fare affidamento $(document).readysull'evento nel codice iframe, devo solo associare l'evento di caricamento dell'iframe nel documento principale:

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

Questo codice si trova nell'iframe ma si lega a un evento di un controllo nel documento principale. Funziona in FireFox e IE.


9
Hai trovato la soluzione? Sembra che Pier l'abbia già pubblicato. Che tu l'abbia trovata da solo o no, l'etichetta sarebbe accettare la sua risposta, premiando così il tempo che ha trascorso a risponderti.
Sky Sanders,

La differenza tra la soluzione di Pier e questo (e quello che mi mancava nel mio codice) è il contesto top.documentche mi permette di avere il codice all'interno dell'iframe in grado di dire quando l'iframe è stato caricato. (Sebbene loadsia stato deprecato da questa risposta, e dovrebbe essere sostituito con on("load").)
Teepeemm,

2

Fondamentalmente ciò che altri hanno già pubblicato ma IMHO un po 'più pulito:

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

1

Prova questo,

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

Tutto ciò che devi fare è creare la funzione JavaScript testframe_loaded ().


1
Il problema con il caricamento è che si attiva quando tutte le immagini e i sottotelai sono stati caricati. Un evento jQuery come pronto sarebbe più utile.
Tom,

1

Sto caricando il PDF con jQuery Ajax nella cache del browser. Quindi creo un elemento incorporato con i dati già nella cache del browser. Immagino che funzionerà anche con iframe.


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();
    }
});

Devi impostare correttamente anche le intestazioni http.


HttpContext.Response.Expires = 1;
HttpContext.Response.Cache.SetNoServerCaching();
HttpContext.Response.Cache.SetAllowResponseInBrowserHistory(false);
HttpContext.Response.CacheControl = "Private";

1
Si noti che non funziona bene sui browser mobili in cui la cache potrebbe essere più piccola della dimensione del PDF.
Pavel Savara,

1

Questo è stato il problema esatto che ho riscontrato con il nostro cliente. Ho creato un piccolo plugin jquery che sembra funzionare per la prontezza di iframe. Utilizza il polling per verificare il documento iframe readyState combinato con l'URL del documento interno combinato con l'origine iframe per assicurarsi che l'iframe sia effettivamente "pronto".

Il problema con "onload" è che è necessario accedere all'iframe effettivo che viene aggiunto al DOM, in caso contrario è necessario provare a rilevare il caricamento dell'iframe che, se viene memorizzato nella cache, potrebbe non essere possibile. Ciò di cui avevo bisogno era uno script che potesse essere chiamato in qualsiasi momento e determinare se l'iframe fosse "pronto" oppure no.

Ecco la domanda:

Sacro Graal per determinare se iframe locale è stato caricato o meno

ed ecco il jsfiddle che alla fine mi è venuto in mente.

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

Nella jsfiddle sopra, sto aspettando che onload aggiunga un iframe al dom, quindi controllo lo stato pronto del documento interno di iframe - che dovrebbe essere interdominio perché è indicato su Wikipedia - ma Chrome sembra riportare "completo". Il metodo iready del plug-in viene quindi chiamato quando l'iframe è effettivamente pronto. Il callback tenta di controllare di nuovo lo stato pronto del documento interno - questa volta riportando una richiesta tra domini (che è corretta) - comunque sembra funzionare per quello di cui ho bisogno e spero che aiuti gli altri.

<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>

Ha funzionato bene per me
Hassan Tareq,

0

Questa funzione di questa risposta è il modo migliore per gestirla poiché $.readyfallisce esplicitamente per iframe. Ecco la decisione non supportarlo.

Inoltre, l' loadevento non si attiva se l'iframe è già stato caricato. Molto frustrante che questo rimanga un problema nel 2020!

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();
}

}

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.