9

I'm setting the html of the body element in an iframe to a string str, then I want to access that content on the next line (here just using an alert call to display the content) but the html and append functions haven't completed by the time the alert statement is called.

$(function(){
....
$("#notes-tab span.add_draft").bind("click", function(e){
    var str = '';
    $(this).parent().siblings(".tab-content").children(".para").each(function(ind) {
        str += $(this).find('div:first').html();
    });
    var curr = $("#content_rte").contents().find("body").html();
    if (curr == ' ' || curr == '<br>') {
        $("#content_rte").contents().find("body").html(str);
    }
    else {
        $("#content_rte").contents().find("body").append(str);
    }
    alert($("#content_rte").contents().find("body").html());
});
});

And of course neither the html nor the append functions take callbacks.

Could someone tell me how one normally accomplishes waiting for the DOM to be changed before proceeding?

Adam Wagner
  • 15,469
  • 7
  • 52
  • 66
Craig
  • 93
  • 1
  • 1
  • 4

8 Answers8

11

If you are using jQuery you can rely on .closest() to figure out if the element you just created has a parent body tag, and if it does it means it's been added to the DOM. You can write a function such as the following to allow you to actually do something when your element is ready:

callWhenReady: function (selector, callback, scope) {
    var self = this;
    if ($(selector).closest('body').length) {
        callback.call(scope);
    } else {
        setTimeout(function () {
            self.callWhenReady(selector, callback, scope);
        }, 1);
    }
}
Grancalavera
  • 143
  • 1
  • 8
  • I like this! You should add "`if (!$(selector).length) return;`" to the beginning to avoid infinite loops when the selector doesn't match any elements. – flu May 30 '13 at 09:29
2

html and append both call domManip, which is a synchronous method, so alert shouldn't even execute until those calls have completed.

Are you sure that the values you expect are being copied into the locations you expect?

Jeff Sternal
  • 47,787
  • 8
  • 93
  • 120
  • Agreed. @Craig, does the append happen successfully? Also, append() returns jQuery. You can get the html contents without searching a 2nd time. – Brian Pan Jan 08 '10 at 21:29
  • Yes, bith the append and html write work successfully, after the alert shows a blank. I didn't realise append returned anything. – Craig Jan 09 '10 at 00:28
  • @Craig, are you able to post a public link to your page or a version of the page that reproduces the problem? – Jeff Sternal Jan 09 '10 at 03:43
  • @Jeff - IFRAME handling is tricky, I've experienced the same issue before. It's probably worth a completely separate question to resolve exactly why retrieval fails (pertinent to IFRAMEs and the jQuery library itself). – Corey Ballou Jan 09 '10 at 16:01
  • @cballou - now I'm getting curious! I see there have been some other questions related to jquery and iframes (like http://stackoverflow.com/questions/205087/jquery-ready-in-a-dynamically-inserted-iframe). Now to find some time ... – Jeff Sternal Jan 09 '10 at 18:58
  • I have the same issue - so it's looks like command can be executed before 'append' call have completed. – Frankovskyi Bogdan Jun 12 '12 at 15:02
2

Try to use .ready(), which is "a way to run JavaScript code as soon as the page's Document Object Model (DOM) becomes safe to manipulate."

$('#yourID').html('new content').ready(function(){
    // Do stuffs
});
Worst
  • 169
  • 8
1

You need to wait for the dom load event so that the iframe has time to finish loading:

$(document).ready(function(){
    var str = '<div>HelloWorld</div>';
    var curr = $("#content_rte").contents().find("body").html();
    if (curr == ' ' || curr == '<br>') {
        //$("#content_rte").contents().find("body").html(str);
        $("#content_rte").contents().find("body").append(str);
    }
    else {
        $("#content_rte").contents().find("body").append(str);
    }
    alert($("#content_rte").contents().find("body").html());
});
jjacka
  • 523
  • 4
  • 9
  • You may have to actually listen for the iframe's ready event ("#content_rte").contents().ready(fuction(){...}); – jjacka Jan 08 '10 at 21:06
  • it wouldnt be ready it'd be load ready defines full rendering of dom elements load is both dom elements and content thats what they need – Michael Stone Jan 08 '10 at 21:20
0

you should try $(window).load(function()) rather than document.ready also you could always fire the event after your appends. that way the event(s) will only run once the append has successfully rendered.

the window.load only runs once the dom has loaded and then all content within the dom too.

if that doesnt work for you, then you could always have a setTimeout() run on the document.ready or window.load and put your functions/events in there and it will load after a specific amount of seconds.

you could try:

$(document).ready(function(){
  var curr = $("#content_rte").contents().find("body").html();
    if (curr == ' ' || curr == '<br>') {
        $("#content_rte").contents().find("body").html(str);
    }
    else {
        $("#content_rte").contents().find("body").append(str);
    }
});
$(window).load(function(){
    alert($("#content_rte").contents().find("body").html());
});
Michael Stone
  • 950
  • 2
  • 14
  • 30
0

You need to use $( document ).ready( function( ){ } ) or $( function( ){ } ) but with that iframe's document element.

To do this, you can put your script into the iframe's HTML

theIframeHTMLContent += '<script type="text/javascript" src="jquery.js"></script>' +
                        '<script type="text/javascript">' +
                          '$( function( ){ alert( "I\'m ready!" ); } );' +
                        '</script>';

$( 'body' ).append( '<iframe id="cool"></iframe>' );
$( '#cool' ).html( theIframeHTMLContent );

Or you can try to do it on the newly appended iframe DOM element itself, but from the parent frame. This is different across browsers, but the following example SHOULD work.

$( 'body' ).append( '<iframe id="cool"></iframe>' )
var theFrame = $( '#cool' )[ 0 ];
var docEl = theFrame.contentDocument || theFrame.contentWindow || theFrame.document;
$( docEl ).ready( function( ){ /* ready stuff */ } );

Tell me if I'm wrong, like I said, haven't tested this.

Dan Beam
  • 3,632
  • 2
  • 23
  • 27
0

I followed the grancalavera's answer, but I used the lengh because my selector already exists in body... so:

var finalLength = $(selector).html().length + data.length;
$(selector).html(data); //or $(selector).append(data);
//...
callWhenReady: function (selector, callback, finalLength, scope) {
    var self = this;
    if ($(selector).html().length === finalLength) {
        callback.call(scope);
    } else {
        setTimeout(function () {
            self.callWhenReady(selector, callback, scope);
        }, 1);
    }
}
Community
  • 1
  • 1
Bocapio
  • 109
  • 4
-7

There's really no direct method for waiting other than setting an arbitrary timeout before you attempt to retrieve the HTML. The code to do so would be:

// wait for 250 ms, then try and retrieve contents
setTimeout(function() {
    alert($("#content_rte").contents().find("body").html());
}, 250);

You should try and make the timeout small enough to go unnoticed but large enough to allow for the contents to update, so you could get away with much less than 250ms.

Corey Ballou
  • 42,389
  • 8
  • 62
  • 75
  • 3
    Bad answer, you will never know what value to pick here. This will work for 99% of your customers and fail for that 1% that is actually bringing in most of the cash... – Kasper Peeters Aug 16 '11 at 18:45
  • 3
    Yep, terrible answer, despite it may work for most cases. What about race conditions? (http://en.wikipedia.org/wiki/Race_condition) – German Latorre Feb 27 '13 at 16:50