jQuery .ready dans une iframe insérée dynamiquement

184

Nous utilisons jQuery Thickbox pour afficher dynamiquement une iframe lorsque quelqu'un clique sur une image. Dans cette iframe, nous utilisons galleria une bibliothèque javascript pour afficher plusieurs images.

Le problème semble être que $(document).readyl'iframe semble être déclenché trop tôt et que le contenu de l'iframe n'est même pas encore chargé, donc le code galleria n'est pas appliqué correctement sur les éléments DOM. $(document).readysemble utiliser l'état prêt du parent iframe pour décider si l'iframe est prêt.

Si nous extrayons la fonction appelée par document prête dans une fonction séparée et l'appelons après un timeout de 100 ms. Cela fonctionne, mais nous ne pouvons pas prendre le risque en production avec un ordinateur lent.

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

Ma question: à quel événement jQuery devons-nous nous lier pour pouvoir exécuter notre code lorsque l'iframe dynamique est prêt et pas seulement un parent?

EtienneT
la source
1
Et vous confirmez que Galleria fonctionne lorsque vous la chargez directement au lieu de passer par une iframe, n'est-ce pas?
Jason Kealey
Oui, galleria fonctionne parfaitement lorsque nous l'utilisons directement dans une page normale.
EtienneT
duplication possible du rappel Javascript lorsque IFRAME a fini de charger?
Robert MacLean

Réponses:

291

J'ai répondu à une question similaire (voir rappel Javascript quand IFRAME a fini de charger? ). Vous pouvez obtenir le contrôle sur l'événement de chargement iframe avec le code suivant:

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

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

En traitant les iframes, j'ai trouvé assez bon pour utiliser l'événement de chargement au lieu de l'événement prêt pour le document.

Pier Luigi
la source
17
Ne devriez-vous pas définir l'événement load avant d'appeler attr ('src')?
Shay Erlichmen du
15
Non, ça n'a pas d'importance. L'événement de chargement ne se déclenchera pas avant la prochaine boucle d'événements au minimum.
Barum Rho
29
l'événement de chargement ne fonctionnera pas pour les iframes utilisées pour le téléchargement. comme <iframe src = "my.pdf" />
Mike Starov
5
Le problème avec la charge est qu'il se déclenche lorsque toutes les images et sous-cadres sont chargés. Un événement jQuery comme ready serait plus utile.
Tom
30

En utilisant jQuery 1.3.2, les éléments suivants ont fonctionné pour moi:

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

RÉVISION:! En fait, le code ci-dessus semble parfois fonctionner dans Firefox, ne semble jamais fonctionner dans Opera.

Au lieu de cela, j'ai mis en œuvre une solution de sondage pour mes besoins. Simplifié vers le bas, il ressemble à ceci:

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

Cela ne nécessite pas de code dans les pages iframe appelées. Tout le code réside et s'exécute à partir du cadre / fenêtre parent.

Már Örlygsson
la source
Quelle est la fonction movePreview référencée dans setTimeout ()?
cam8001
@ cam8001: C'était une faute de frappe - a maintenant été corrigé.
Már Örlygsson
J'ai fini par utiliser une solution de sondage aussi. D'autres solutions ne semblent fonctionner qu'avec un succès partiel. Cependant, pour moi, je devais vérifier l'existence d'une fonction javascript, plutôt que juste pour le contenu de l'iframe, avant d'avoir du succès. par exemple. (typeof iframe.contentWindow.myfunc == 'function')
Julian
2
Cette solution est récursive ce qui prend de la mémoire pour chaque appel. Si l'iframe ne se charge jamais, elle appellera de plus en plus profondément jusqu'à ce que la mémoire soit épuisée. Je recommanderais setIntervalplutôt pour le sondage.
eremzeit
15

Dans IFrames, je résous généralement ce problème en mettant un petit script à la toute fin du bloc:

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

Ce travail la plupart du temps pour moi. Parfois, la solution la plus simple et la plus naïve est la plus appropriée.

Tamas Czinege
la source
4
+1 Cette solution fonctionne très bien pour moi! Un excellent ajout est que vous pouvez atteindre le parentpour récupérer une copie de jQuery et l'utiliser parent.$(document).ready(function(){ parent.IFrameLoaded( ); });pour initialiser l'iframe.
David Murdoch
9

Suite à l'idée de DrJokepu et David Murdoch, j'ai implémenté une version plus complète. Il nécessite jQuery sur le parent et l'iframe et l'iframe pour être sous votre contrôle.

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

code de test parent:

$(function(){
    $("iframe").on("iframeloading iframeready iframeloaded iframebeforeunload iframeunloaded", function(e){
        console.log(e.type);
    });
});
Ricardo Freitas
la source
pour une raison quelconque, utiliser $ (iframe) .ready (function ...) dans le parent ne fonctionnerait pas pour moi. Il semblait que la fonction de rappel était en cours d'exécution avant que le dom iframe ne soit prêt. L'utilisation de cette méthode a très bien fonctionné!
w--
4

J'ai trouvé la solution au problème.

Lorsque vous cliquez sur un lien Thickbox qui ouvre un iframe, il insère un iframe avec un identifiant TB_iframeContent.

Au lieu de $(document).readyme fier à l' événement dans le code iframe, je dois juste me lier à l'événement load de l'iframe dans le document parent:

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

Ce code est dans l'iframe mais se lie à un événement d'un contrôle dans le document parent. Cela fonctionne dans FireFox et IE.

EtienneT
la source
9
Vous avez trouvé la solution? On dirait que Pier l'avait déjà publié. Que vous l'ayez trouvé par vous-même ou non, l'étiquette serait d'accepter sa réponse, récompensant ainsi le temps qu'il a passé à vous répondre.
Sky Sanders
La différence entre la solution de Pier et celle-ci (et ce qui me manquait dans mon code) est le contexte top.documentqui me permet d'avoir du code à l' intérieur de l'iframe pour pouvoir dire quand l'iframe s'est chargé. (Bien qu'il loadsoit obsolète depuis cette réponse, et devrait être remplacé par on("load").)
Teepeemm
2

Fondamentalement, ce que d'autres ont déjà publié mais à mon humble avis un peu plus propre:

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

Essaye ça,

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

Il ne vous reste plus qu'à créer la fonction JavaScript testframe_loaded ().

Danny G
la source
1
Le problème avec la charge est qu'il se déclenche lorsque toutes les images et sous-cadres sont chargés. Un événement jQuery comme ready serait plus utile.
Tom
1

Je charge le PDF avec jQuery ajax dans le cache du navigateur. Ensuite, je crée un élément intégré avec des données déjà dans le cache du navigateur. Je suppose que cela fonctionnera aussi avec 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();
    }
});

Vous devez également définir correctement vos en-têtes http.


HttpContext.Response.Expires = 1;
HttpContext.Response.Cache.SetNoServerCaching();
HttpContext.Response.Cache.SetAllowResponseInBrowserHistory(false);
HttpContext.Response.CacheControl = "Private";
Pavel Savara
la source
1
Notez que cela ne fonctionne pas bien sur les navigateurs mobiles où le cache peut être plus petit que la taille du PDF.
Pavel Savara
1

C'était le problème exact que j'ai rencontré avec notre client. J'ai créé un petit plugin jquery qui semble fonctionner pour la préparation iframe. Il utilise l'interrogation pour vérifier le document iframe readyState combiné avec l'url du document interne combiné avec la source iframe pour s'assurer que l'iframe est en fait «prêt».

Le problème avec "onload" est que vous avez besoin d'accéder à l'iframe réel ajouté au DOM, si ce n'est pas le cas, vous devez essayer d'attraper le chargement de l'iframe qui, s'il est mis en cache, vous ne le pourrez peut-être pas. Ce dont j'avais besoin était un script qui pouvait être appelé à tout moment et déterminer si l'iframe était "prêt" ou non.

Voici la question:

Saint Graal pour déterminer si l'iframe locale a été chargée ou non

et voici le jsfiddle que j'ai finalement trouvé.

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

Dans le jsfiddle ci-dessus, j'attends que onload ajoute une iframe au dom, puis je vérifie l'état prêt du document interne de l'iframe - qui devrait être interdomaine car il pointe vers wikipedia - mais Chrome semble signaler "terminé". La méthode iready du plug-in est alors appelée lorsque l'iframe est en fait prête. Le rappel essaie de vérifier à nouveau l'état prêt du document interne - cette fois en signalant une requête interdomaine (ce qui est correct) - de toute façon, il semble fonctionner pour ce dont j'ai besoin et j'espère que cela aidera les autres.

<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
la source
A bien fonctionné pour moi
Hassan Tareq
0

Cette fonction de cette réponse est la meilleure façon de gérer cela car elle $.readyéchoue explicitement pour les iframes. Voici la décision de ne pas soutenir cela.

L' loadévénement ne se déclenche pas non plus si l'iframe est déjà chargé. Très frustrant que cela reste un problème 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
la source