jQuery .ready en un iframe insertado dinámicamente

184

Estamos utilizando jQuery thickbox para mostrar dinámicamente un iframe cuando alguien hace clic en una imagen. En este iframe, estamos utilizando galleria, una biblioteca de JavaScript para mostrar varias imágenes.

El problema parece ser que $(document).readyen el iframe parece dispararse demasiado pronto y el contenido del iframe ni siquiera está cargado, por lo que el código de galería no se aplica correctamente en los elementos DOM. $(document).readyparece usar el estado de padre preparado de iframe para decidir si el iframe está listo.

Si extraemos la función llamada por documento listo en una función separada y la llamamos después de un tiempo de espera de 100 ms. Funciona, pero no podemos arriesgarnos en la producción con una computadora lenta.

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

Mi pregunta: ¿a qué evento jQuery deberíamos unirnos para poder ejecutar nuestro código cuando el iframe dinámico está listo y no solo es un padre?

EtienneT
fuente
1
Y confirma que la galería funciona cuando la carga directamente en lugar de a través de un iframe, ¿correcto?
Jason Kealey
Sí, la galería funciona perfectamente cuando la usamos directamente en una página normal.
EtienneT
posible duplicado de la devolución
Robert MacLean

Respuestas:

291

Respondí una pregunta similar (ver devolución de llamada de Javascript cuando IFRAME ha terminado de cargarse ). Puede obtener control sobre el evento de carga de iframe con el siguiente código:

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

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

Al tratar con iframes, encontré lo suficientemente bueno como para usar el evento de carga en lugar del evento de documento listo.

Pier Luigi
fuente
17
¿No debería configurar el evento de carga antes de llamar al attr ('src')?
Shay Erlichmen
15
No, no importa El evento de carga no se disparará hasta el próximo bucle de evento como mínimo.
Barum Rho
29
el evento de carga no funcionará para los iframes que se usan para descargar. como <iframe src = "my.pdf" />
Mike Starov el
55
El problema con la carga es que se dispara cuando todas las imágenes y subtramas se han cargado. Un evento preparado como jQuery sería más útil.
Tom
30

Usando jQuery 1.3.2, lo siguiente funcionó para mí:

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

REVISIÓN:! En realidad, el código anterior a veces parece funcionar en Firefox, nunca parece funcionar en Opera.

En cambio, implementé una solución de sondeo para mis propósitos. Simplificado hacia abajo se ve así:

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

Esto no requiere código en las páginas de iframe llamadas. Todo el código reside y se ejecuta desde el marco / ventana principal.

Már Örlygsson
fuente
¿Cuál es la función movePreview mencionada en setTimeout ()?
cam8001
@ cam8001: fue un error tipográfico, ahora se ha solucionado.
Már Örlygsson
Terminé usando una solución de sondeo también. Otras soluciones parecían funcionar solo con un éxito parcial. Sin embargo, para mí, necesitaba comprobar la existencia de una función de JavaScript, en lugar de solo el contenido del iframe, antes de tener éxito. p.ej. (typeof iframe.contentWindow.myfunc == 'function')
Julian
2
Esta solución es recursiva y ocupa memoria para cada llamada. Si el iframe nunca se carga, llamará más y más para siempre hasta que se agote la memoria. Lo recomendaría setIntervalpara votar en su lugar.
eremzeit
15

En IFrames, generalmente resuelvo este problema colocando un pequeño script al final del bloque:

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

Esto funciona la mayor parte del tiempo para mí. A veces, la solución más simple e ingenua es la más adecuada.

Tamas Czinege
fuente
44
+1 ¡Esta solución funciona muy bien para mí! Una gran adición es que puede comunicarse con el parentpara obtener una copia de jQuery y usarlo parent.$(document).ready(function(){ parent.IFrameLoaded( ); });para inicializar el iframe.
David Murdoch
9

Siguiendo la idea de DrJokepu y David Murdoch, implementé una versión más completa. Se requiere jQuery en tanto el padre como marco flotante y el iframe para estar en su control.

código de 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");
        });
    });
}

código de prueba de los padres:

$(function(){
    $("iframe").on("iframeloading iframeready iframeloaded iframebeforeunload iframeunloaded", function(e){
        console.log(e.type);
    });
});
Ricardo Freitas
fuente
por alguna razón, usar $ (iframe) .ready (function ...) en el padre no funcionaría para mí. Parecía que la función de devolución de llamada se ejecutaba antes de que el dom iframe estuviera listo. ¡Usar este método funcionó muy bien!
w--
4

Encontró la solución al problema.

Cuando hace clic en un enlace de caja gruesa que abre un iframe, inserta un iframe con una identificación de TB_iframeContent.

En lugar de confiar en el $(document).readyevento en el código del iframe, solo tengo que vincularme al evento de carga del iframe en el documento principal:

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

Este código está en el iframe pero se une a un evento de un control en el documento principal. Funciona en Firefox e IE.

EtienneT
fuente
9
¿Encontraste la solución? Parece que Pier ya lo había publicado. Ya sea que lo encuentre por su cuenta o no, la etiqueta sería aceptar su respuesta, recompensando así el tiempo que pasó respondiéndole.
Sky Sanders
La diferencia entre la solución de Pier y esta (y lo que me faltaba en mi código) es el contexto top.documentque me permite tener el código dentro del iframe para saber cuándo se ha cargado el iframe. (Aunque loadha quedado en desuso desde esta respuesta, y debe reemplazarse por on("load")).
Teepeemm
2

Básicamente lo que otros ya han publicado, pero en mi humilde opinión, es un poco más limpio:

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

Prueba esto,

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

Todo lo que necesita hacer es crear la función JavaScript testframe_loaded ().

Danny G
fuente
1
El problema con la carga es que se dispara cuando todas las imágenes y subtramas se han cargado. Un evento preparado como jQuery sería más útil.
Tom
1

Estoy cargando el PDF con jQuery ajax en la memoria caché del navegador. Luego creo un elemento incrustado con datos que ya están en la memoria caché del navegador. Supongo que también funcionará 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();
    }
});

También debe configurar sus encabezados http correctamente.


HttpContext.Response.Expires = 1;
HttpContext.Response.Cache.SetNoServerCaching();
HttpContext.Response.Cache.SetAllowResponseInBrowserHistory(false);
HttpContext.Response.CacheControl = "Private";
Pavel Savara
fuente
1
Tenga en cuenta que no funciona bien en los navegadores móviles donde el caché podría ser más pequeño que el tamaño de PDF.
Pavel Savara
1

Este fue el problema exacto con el que me encontré con nuestro cliente. Creé un pequeño complemento jquery que parece funcionar para la preparación de iframe. Utiliza el sondeo para verificar el documento iframe readyState combinado con la url interna del documento combinada con la fuente del iframe para asegurarse de que el iframe esté "listo".

El problema con "onload" es que necesita acceso al iframe real que se agrega al DOM; si no lo hace, debe intentar atrapar la carga del iframe, que si está en caché, entonces no puede. Lo que necesitaba era un script que pudiera llamarse en cualquier momento, y determinar si el iframe estaba "listo" o no.

Aquí está la pregunta:

Santo Grial para determinar si el iframe local se ha cargado o no

y aquí está el jsfiddle que eventualmente se me ocurrió.

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

En el jsfiddle anterior, estoy esperando que onload agregue un iframe al dom, luego verifico el estado listo del documento interno del iframe, que debería ser dominio cruzado porque apunta a wikipedia, pero Chrome parece informar "completo". El método iready del complemento se llama cuando el iframe está listo. La devolución de llamada intenta verificar el estado listo del documento interno nuevamente, esta vez informando una solicitud de dominio cruzado (que es correcta), de todos modos parece funcionar para lo que necesito y espero que ayude a otros.

<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
fuente
Funcionó bien para mí
Hassan Tareq
0

Esta función de esta respuesta es la mejor manera de manejar esto, ya que $.readyexplícitamente falla para iframes. Aquí está la decisión de no apoyar esto.

El loadevento tampoco se activa si el iframe ya se ha cargado. ¡Muy frustrante que esto siga siendo un problema en 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();
}

}

Crashalot
fuente