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?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
EtienneT
  • 5,045
  • 6
  • 36
  • 39
  • 1
    And you confirm that galleria works when you load it directly instead of through an iframe, correct? – Jason Kealey Oct 15 '08 at 15:10
  • Yes, galleria works perfectly when we use it directly in a normal page. – EtienneT Oct 15 '08 at 15:11
  • possible duplicate of [Javascript callback when IFRAME is finished loading?](http://stackoverflow.com/questions/164085/javascript-callback-when-iframe-is-finished-loading) – Robert MacLean May 28 '13 at 08:30

10 Answers10

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.

Community
  • 1
  • 1
Pier Luigi
  • 7,871
  • 9
  • 36
  • 46
  • 17
    Shouldn't you set the load event prior to calling the attr('src')? – Shay Erlichmen Dec 25 '10 at 19:30
  • 15
    No, it does not matter. Load event will not fire until the next event loop at minimum. – Barum Rho Jun 24 '11 at 16:14
  • 29
    the load event will not work for iframes that are used for download. like – Mike Starov Nov 03 '11 at 18:44
  • 2
    "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. – Pier Luigi Dec 12 '11 at 06:26
  • 6
    Problem with load is that it fires when all images and subframes have loaded. A jQuery like ready event would be more useful. – Tom Dec 14 '11 at 12:55
  • 2
    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. – Woodgnome Jan 17 '12 at 15:06
  • This wasn't working for some reason, so I tried replacing the first two lines with: `myFrame = $('').appendTo('body');` and everything worked fine. – Skunkwaffle Apr 30 '12 at 15:23
  • 1
    @PierLuigi Could you please explain the `callback` variable? What should it be? I really don't understand – Alex May 10 '12 at 14:09
  • 1
    `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. – Pier Luigi May 11 '12 at 09:26
  • 1
    Note that there are two different .load()-functions. http://api.jquery.com/load/ for loading content into an object, and http://api.jquery.com/load-event/ to bind the onLoad event. – Zut Dec 18 '12 at 09:02
  • 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? – NeoWang Mar 24 '14 at 12:18
  • 1
    Any workaround available for catching the `load` event for `pdf` files in `iframe`? – Zain Shaikh Apr 14 '14 at 07:33
  • 8
    [`.load()` event is deprecated. Use `.on( "load", handler )` instead.](http://api.jquery.com/load-event/) – Pang Oct 12 '16 at 03:04
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.

Már Örlygsson
  • 14,176
  • 3
  • 42
  • 53
  • What's is the movePreview fucntion referred to in setTimeout()? – cam8001 Nov 26 '09 at 03:59
  • @cam8001: It was a typo - has now been fixed. – Már Örlygsson Mar 28 '11 at 12:10
  • 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') – Julian Jun 18 '13 at 17:02
  • 2
    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. – eremzeit Dec 03 '15 at 11:32
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.

Tamas Czinege
  • 118,853
  • 40
  • 150
  • 176
  • 4
    +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. – David Murdoch Jan 21 '11 at 13:50
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);
    });
});
Ricardo Freitas
  • 533
  • 1
  • 6
  • 9
  • 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! – w-- Mar 22 '14 at 22:27
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.

Rion Williams
  • 74,820
  • 37
  • 200
  • 327
EtienneT
  • 5,045
  • 6
  • 36
  • 39
  • 9
    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. – Sky Sanders Apr 04 '10 at 08:39
  • 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")`.) – Teepeemm Oct 04 '14 at 19:05
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();
}

}

Crashalot
  • 33,605
  • 61
  • 269
  • 439
2

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

$('<iframe/>', {
    src: 'https://example.com/',
    load: function() {
        alert("loaded")
    }
}).appendTo('body');
udondan
  • 57,263
  • 20
  • 190
  • 175
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";
Pavel Savara
  • 3,427
  • 1
  • 30
  • 35
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().

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Danny G
  • 3,660
  • 4
  • 38
  • 50
  • 1
    Problem with load is that it fires when all images and subframes have loaded. A jQuery like ready event would be more useful. – Tom Dec 14 '11 at 12:54
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>
Community
  • 1
  • 1
Jon Freynik
  • 359
  • 2
  • 15