How can I tell when changes to jquery html() have finished?
Asked Answered
G

3

12

I'm using jQuery to change the HTML of a tag, and the new HTML can be a very long string.

$("#divToChange").html(newHTML);

I then want to select elements created in the new HTML, but if I put the code immediately following the above line it seems to create a race condition with a long string where the changes that html() is making may not necessarily be finished rendering. In that case, trying to select the new elements won't always work.

What I want to know is, is there an event fired or some other way of being notified when changes to html() have finished rendering ? I came across the jQuery watch plugin, which works alright as workaround but it's not ideal. Is there a better way ?

Grits answered 3/4, 2010 at 2:45 Comment(2)
JS is single threaded so that really shouldn't be happening...can you post a repro on jsbin.com?Elyn
@Michael - Check out jsFiddle, found it much more useful: jsfiddle.netWear
R
6

As a commenter already mentioned, JavaScript is single threaded, so you can't get race conditions.

What may trip you up however, is the fact that the UI will not update itself based on JavaScript, until a thread is finished. This means that the entire method must finish, including all code after you call html(...), before the browser will render the content.

If your code after calling html(...) relies on the layout of the page being recalculated before continuing, you can do something like this:

$("#divToChange").html(newHTML);
setTimeout(function() {
    // Insert code to be executed AFTER
    // the page renders the markup
    // added using html(...) here
}, 1);

Using setTimeout(...) with a time of 1 in JavaScript defers execution until after the current JavaScript code in the calling function finishes and the browser has updated the UI. This may solve your problem, though it is difficult to tell unless you can provide a reproducible example of the error you're getting.

Reunion answered 3/4, 2010 at 2:55 Comment(4)
Reflow doesn't wait until all running code is completed. Running several operations in sequence which each can cause a reflow will suffer substantially more of a reflow performance hit than a single operation (even if the reflow's consequences are identical).Judijudicable
@Judijudicable Unless I misunderstand what you are saying, this simply is not true. This can be demonstrated by going to this simple test I threw together: jsbin.com/avuli/6 Click the document once, and the code will perform a while loop for 5 seconds. Notice that the UI is not updated until the end of the 5 seconds. The second time you click, I use a timer to update the UI in a loop. This time, you'll notice that the UI updates itself in "real time" as the DOM is changed.Reunion
To clarify, "reflow" doesn't necessarily mean the document will be visually updated. Your while loop is a blocking operation, and as such no visual update will occur until it completes. But reflow will occur with each operation, with the consequence that querying the DOM for data that depends on a reflow will give you correct results even if the page is visually stalled. See my edit of your script: jsbin.com/ayulu3 (see console after first click)Judijudicable
To expand on the topic, the fact that a reflow occurs with each operation is a reason that batch DOM operations perform much better when they can force only a single reflow (eg. appending either a single <div> or DocumentFragment containing a set of elements to be appended).Judijudicable
S
2

use .ready jQuery function

$("#divToChange").html(newHTML).ready(function () {
                    // run when page is rendered
                    });
Sow answered 19/10, 2020 at 14:45 Comment(0)
C
1

It's 7 years latter and I just ran into a scenario exactly like the one @mikel described, where I couldn't avoid a "timer based solution". So, I'm just sharing the solution I developed, in case anyone out there is still having issues with this.

I hate having setTimeouts and setIntervals in my code. So, I created a small plugin that you can put where you think it's best. I used setInterval, but you can change it to setTimeout or another solution you have in mind. The idea is simply to create a promise and keep checking for the element. We resolve the promise once it is ready.

// jquery.ensure.js

  $.ensure = function (selector) {
    var promise = $.Deferred();
    var interval = setInterval(function () {
      if ($(selector)[0]) {
        clearInterval(interval);
        promise.resolve();
      }
    }, 1);
    return promise;
  };

// my-app.js

  function runWhenMyElementExists () {
    // run the code that depends on #my-element
  }

  $.ensure('#my-element')
    .then(runWhenMyElementExists);
Collard answered 10/1, 2018 at 16:20 Comment(1)
Very nice, I was exactly search for this very simple and short code. It is definitely working for me too.Syzran

© 2022 - 2024 — McMap. All rights reserved.