jQuery .ready in a dynamically inserted iframe
Asked Answered
L

10

191

We are using jQuery thickbox to dynamically display an iframe when someone clicks on a picture. In this iframe, we are using galleria a javascript library to display multiple pictures.

The problem seems to be that $(document).ready in the iframe seems to be fired too soon and the iframe content isn't even loaded yet, so galleria code is not applied properly on the DOM elements. $(document).ready seems to use the iframe parent ready state to decide if the iframe is ready.

If we extract the function called by document ready in a separate function and call it after a timeout of 100 ms. It works, but we can't take the chance in production with a slow computer.

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

My question: which jQuery event should we bind to to be able to execute our code when the dynamic iframe is ready and not just it's a parent?

Lampas answered 15/10, 2008 at 15:9 Comment(3)
And you confirm that galleria works when you load it directly instead of through an iframe, correct?Katelin
Yes, galleria works perfectly when we use it directly in a normal page.Lampas
possible duplicate of Javascript callback when IFRAME is finished loading?Heterotypic
L
294

I answered a similar question (see Javascript callback when IFRAME is finished loading?). You can obtain control over the iframe load event with the following code:

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

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

In dealing with iframes I found good enough to use load event instead of document ready event.

Lacuna answered 15/10, 2008 at 15:41 Comment(13)
Shouldn't you set the load event prior to calling the attr('src')?Awad
No, it does not matter. Load event will not fire until the next event loop at minimum.Lever
the load event will not work for iframes that are used for download. like <iframe src="my.pdf"/>Gareri
"Dynamically inserted" because the iframe tag is inserted in the DOM by javascript on the client. So the iframe can exixts or not on the page, based on condition or event that happens on the client.Lacuna
Problem with load is that it fires when all images and subframes have loaded. A jQuery like ready event would be more useful.Darrel
The load event will not fire for iFrames that have already loaded by the time the load handler is bound. As such it is not comparable to jQuery(document).ready() for iFrames. The only solution I can think of is copying how jQuery does it for the frame that jQuery has been loaded in.Eumenides
This wasn't working for some reason, so I tried replacing the first two lines with: myFrame = $('<iframe src="' + src + '" id="myFrame"></iframe>').appendTo('body'); and everything worked fine.Gemmagemmate
@PierLuigi Could you please explain the callback variable? What should it be? I really don't understandLasso
callback is any javascript function you want to be called once iframe has loaded. It can be a function local to the caller window or even a function defined in iframe window. You can access iframe window with window['myId'] on the caller.Lacuna
Note that there are two different .load()-functions. api.jquery.com/load for loading content into an object, and api.jquery.com/load-event to bind the onLoad event.Beghard
What if I want to call a callback when the DOM of iframe is loaded? $.load is triggered only when everything(a lot of imgs) are loaded. What I need is the DOM ready event of the iframe. Can it be done without calling js across iframes?Tanker
Any workaround available for catching the load event for pdf files in iframe?Backgammon
.load() event is deprecated. Use .on( "load", handler ) instead.Wendywendye
C
31

Using jQuery 1.3.2 the following worked for me:

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

REVISION:! Actually the above code sometimes looks like it works in Firefox, never looks like it works in Opera.

Instead I implemented a polling solution for my purposes. Simplified down it looks like this:

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

This doesn't require code in the called iframe pages. All code resides and executes from the parent frame/window.

Ctn answered 9/4, 2009 at 17:37 Comment(4)
What's is the movePreview fucntion referred to in setTimeout()?Ipomoea
@cam8001: It was a typo - has now been fixed.Christi
I ended up using a polling solution too. Other solutions seemed to work only with partial success. However, for me, I needed to check for existence of a javascript function, rather than just for the contents of the iframe, before I had success. eg. (typeof iframe.contentWindow.myfunc == 'function')Whiplash
This solution is recursive which takes up memory for each call. If the iframe never loads, then it'll call deeper and deeper forever until memory runs out. I'd recommend setInterval for polling instead.Granth
N
15

In IFrames I usually solve this problem by putting a small script to the very end of the block:

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

This work most of the time for me. Sometimes the simplest and most naive solution is the most appropriate.

Nealey answered 15/10, 2008 at 15:54 Comment(1)
+1 This solution works great for me! One great addition is that you can reach up to the parent to grab a copy of jQuery and use parent.$(document).ready(function(){ parent.IFrameLoaded( ); }); to initialize the iframe.Nonobservance
A
9

Following DrJokepu's and David Murdoch idea I implemented a more complete version. It requires jQuery on both the parent and iframe and the iframe to be in your control.

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

parent test code:

$(function(){
    $("iframe").on("iframeloading iframeready iframeloaded iframebeforeunload iframeunloaded", function(e){
        console.log(e.type);
    });
});
Adalbert answered 30/4, 2013 at 1:49 Comment(1)
for whatever reason using $(iframe).ready(function...) in the parent would not work for me. It seemed like the callback function was getting executed before the iframe dom was ready. Using this method worked great!Citation
L
4

Found the solution to the problem.

When you click on a thickbox link that open a iframe, it insert an iframe with an id of TB_iframeContent.

Instead of relying on the $(document).ready event in the iframe code, I just have to bind to the load event of the iframe in the parent document:

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

This code is in the iframe but binds to an event of a control in the parent document. It works in FireFox and IE.

Lampas answered 15/10, 2008 at 17:0 Comment(2)
Found the solution? Looks like Pier had already posted it. Whether you found it on your own or not, etiquette would be to accept his answer, thus rewarding the time he spent answering you.Flessel
The difference between Pier's solution and this (and what I was missing in my code) is the context top.document that allows me to have code inside the iframe be able to tell when the iframe has loaded. (Although load has been deprecated since this answer, and should be replaced with on("load").)Mark
P
3

This function from this answer is the best way to handle this as $.ready explicitly fails for iframes. Here's the decision not to support this.

The load event also doesn't fire if the iframe has already loaded. Very frustrating that this remains a problem in 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();
}

}

Pertussis answered 23/5, 2020 at 6:59 Comment(0)
C
2

Basically what others have already posted but IMHO a bit cleaner:

$('<iframe/>', {
    src: 'https://example.com/',
    load: function() {
        alert("loaded")
    }
}).appendTo('body');
Congest answered 1/8, 2017 at 14:19 Comment(0)
G
1

Try this,

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

All you need to do then is create the JavaScript function testframe_loaded().

Gangling answered 12/10, 2009 at 17:4 Comment(1)
Problem with load is that it fires when all images and subframes have loaded. A jQuery like ready event would be more useful.Darrel
S
1

I'm loading the PDF with jQuery ajax into browser cache. Then I create embedded element with data already in browser cache. I guess it will work with iframe too.


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

You have to set your http headers correctly as well.


HttpContext.Response.Expires = 1;
HttpContext.Response.Cache.SetNoServerCaching();
HttpContext.Response.Cache.SetAllowResponseInBrowserHistory(false);
HttpContext.Response.CacheControl = "Private";
Shauna answered 16/8, 2012 at 14:32 Comment(1)
Note, that it doesn't work well on mobile browsers where the cache could be smaller than the size of PDF.Shauna
M
1

This was the exact issue I ran into with our client. I created a little jquery plugin that seems to work for iframe readiness. It uses polling to check the iframe document readyState combined with the inner document url combined with the iframe source to make sure the iframe is in fact "ready".

The issue with "onload" is that you need access to the actual iframe being added to the DOM, if you don't then you need to try to catch the iframe loading which if it is cached then you may not. What I needed was a script that could be called anytime, and determine whether or not the iframe was "ready" or not.

Here's the question:

Holy grail for determining whether or not local iframe has loaded

and here's the jsfiddle I eventually came up with.

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

In the jsfiddle above, I am waiting for onload to append an iframe to the dom, then checking iframe's inner document's ready state - which should be cross domain because it's pointed to wikipedia - but Chrome seems to report "complete". The plug-in's iready method then gets called when the iframe is in fact ready. The callback tries to check the inner document's ready state again - this time reporting a cross domain request (which is correct) - anyway it seems to work for what I need and hope it helps others.

<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>
Martsen answered 10/2, 2016 at 14:17 Comment(1)
Worked well for meVirulence

© 2022 - 2024 — McMap. All rights reserved.