102

How to call a function after jQuery .append is completely done?

Here's an example:

$("#root").append(child, function(){
   // Action after append is completly done
});

The issue: When a complex DOM structure is appended, calculation of new size of the root element within the append function callback is wrong. Assumptions are that the DOM in still not completely loaded, only the first child of the added complex DOM is.

David Horák
  • 5,535
  • 10
  • 53
  • 77

18 Answers18

75

You've got many valid answers in here but none of them really tells you why it works as it does.

In JavaScript commands are executed one at a time, synchronously in the order they come, unless you explicitly tell them to be asynchronous by using a timeout or interval.

This means that your .append method will be executed and nothing else (disregarding any potential timeouts or intervals that may exist) will execute until that method have finished its job.

To summarize, there's no need for a callback since .append will be run synchronously.

mekwall
  • 28,614
  • 6
  • 75
  • 77
  • 44
    This is all true, but here is my issue: I append complex DOM, and right after append, I calculate new size of root element, but here i got wrong number. And think that, problem is this: DOM in still not completly loaded, only first child (.append("
    "))
    – David Horák May 20 '11 at 08:49
  • @JinDave, What is this *size* you are referring to? Is it amount of children, height of parent or what is it that you need to calculate? – mekwall May 20 '11 at 08:52
  • @marcus-ekwall I need calculate total width of all child of root element, after I added some child into root. But child have many sub-child and sub-child make child width. – David Horák May 20 '11 at 08:55
  • @JinDave, not sure why you need to do that. Mind clarifying and add this to your question as well? I'll try to help, but need to understand first :) – mekwall May 20 '11 at 09:10
  • @JinDave, check this [test case on jsFiddle](http://jsfiddle.net/brszE/). Is it something like this you are doing? – mekwall May 20 '11 at 09:17
  • 1
    @marcus-ekwall http://jsfiddle.net/brszE/3/ it's only writing method, not working code, – David Horák May 20 '11 at 09:25
  • @marcus-ekwall var width i need for some other stuff. – David Horák May 20 '11 at 09:27
  • 23
    @DavidHorák so why is this the accepted answer? It's true that even though the change happens the code won't block until the complete reflow quite surprised this has 32 up-votes!(or it my not even cause reflow) something like the answers [here] (http://stackoverflow.com/questions/8840580/force-dom-redraw-refresh-on-chrome-mac) could be ore relevant. – Andreas Dec 03 '15 at 22:42
  • 18
    I agree with Andreas, this answer is not correct. The problem is that even though append has returned, the element still may not be available in the DOM (browser dependant, works in some browsers breaks in others). Using a timeout still does not guarantee that the element will be in the DOM. – Severun Jan 14 '16 at 02:01
  • @DavidHorák I have the same issue.I was trying to append like 100 lines to a table via jquery's `append()` and it doesn't seems to be a synchronous proccess. Right after adding those rows to the table I call the plugin to sort the table and it only works if I call it with 500ms timout after adding the new rows to the table. – Azevedo Mar 10 '18 at 14:44
  • Simply not true. I use .append() which calls some functions to build the text to append from a list of objects. I then run a function to set the default value of the select in which I am creating the options. My value setting function calls setTimeout to repeat itself every 50 ms until the value is set correctly. The order is 1,2,3,4,5,6,7,8,9,10,11,12,,13 (value properly set). How would it get to the value setting function at all, if it worked as you say? Sometimes it's 11 and sometimes 17, but it always gets to the value setting function first. – PRMan Aug 09 '22 at 16:39
37

Although Marcus Ekwall is absolutely right about the synchronicity of append, I have also found that in odd situations sometimes the DOM isn't completely rendered by the browser when the next line of code runs.

In this scenario then shadowdiver solutions is along the correct lines - with using .ready - however it is a lot tidier to chain the call to your original append.

$('#root')
  .append(html)
  .ready(function () {
    // enter code here
  });
Aurora0001
  • 13,139
  • 5
  • 50
  • 53
  • 1
    This is absolutely false and useless in every situation. http://api.jquery.com/ready – Kevin B Oct 12 '17 at 16:42
  • Hi Kevin, in what way? – Daniel - SDS Group Feb 05 '18 at 13:18
  • there is absolutely no benefit to using .ready to wait on the appended elements to “render” any moreso than you would get from simply using a settimeout, if and only if the document isn’t already ready. if the document IS ready the code will be ran synchronously, thus having zero useful impact. – Kevin B Feb 05 '18 at 15:27
  • I don't think the ready will wait for the appended document to load, but instead wait for the entire DOM to load. It was about 3 years since I wrote this answer but I think I was more commenting on shadow diver above, but didn't have facility to comment at the time. – Daniel - SDS Group Feb 06 '18 at 15:29
  • 1
    Actually, this works better than expected. Better than any other answer here. – Blue Aug 08 '19 at 12:25
  • Life saver. Thank you – Ikechukwu Oct 26 '20 at 15:21
  • ready is deprecated, what we can us now ? on('ready') doesn't work. – Julien Jan 26 '21 at 16:28
  • $().ready() is NOT deprecated https://api.jquery.com/ready/ – Art Feb 01 '23 at 17:03
22

Well I've got exactly the same problem with size recalculation and after hours of headache I have to disagree with .append() behaving strictly synchronous. Well at least in Google Chrome. See following code.

var input = $( '<input />' );
input.append( arbitraryElement );
input.css( 'padding-left' );

The padding-left property is correctly retrieved in Firefox but it is empty in Chrome. Like all other CSS properties I suppose. After some experiments I had to settle for wrapping the CSS 'getter' into setTimeout() with 10 ms delay which I know is UGLY as hell but the only one working in Chrome. If any of you had an idea how to solve this issue better way I'd be very grateful.

Womi
  • 229
  • 2
  • 2
  • 15
    this helped me A LOT: http://stackoverflow.com/q/8840580/176140 - meaning, append() IS synchronous, but the DOM update is not – schellmax Aug 29 '12 at 07:51
  • In that case, setTimeout is not ugly (only the 10ms is). Every time you request "dom" manipulation, chrome will wait for your code to end before executing it. So, if you call item.hide followed by item.show, it will just do nothing. When your executing function is over, then, it apply your changes to the DOM. If you need to wait for a "dom" update before continuing, just do your CSS/DOM action, then call settimeout(function(){what to do next});. The settimeout acts like a good old "doevents" in vb6! It tells the browser to do its waiting tasks (update DOM), and returns to your code after. – foxontherock Feb 22 '17 at 18:17
  • See the answer that makes use of `.ready()` much better and more elegant solution. – Mark Carpenter Jr Dec 06 '18 at 20:23
11

I'm surprised at all the answers here...

Try this:

window.setTimeout(function() { /* your stuff */ }, 0);

Note the 0 timeout. It's not an arbitrary number... as I understand (though my understanding might be a bit shaky), there's two javascript event queues - one for macro events and one for micro events. The "larger" scoped queue holds tasks that update the UI (and DOM), while the micro queue performs quick-task type operations.

Also realize that setting a timeout doesn't guarantee that the code performs exactly at that specified value. What this does is essentially puts the function into the higher queue (the one that handles the UI/DOM), and does not run it before the specified time.

This means that setting a timeout of 0 puts it into the UI/DOM-portion of javascript's event queue, to be run at the next possible chance.

This means that the DOM gets updated with all previous queue items (such as inserted via $.append(...);, and when your code runs, the DOM is fully available.

(p.s. - I learned this from Secrects of the JavaScript Ninja - an excellent book: https://www.manning.com/books/secrets-of-the-javascript-ninja )

jleach
  • 7,410
  • 3
  • 33
  • 60
  • I've been adding `setTimeout()` for years without knowing why. Thanks for the explanation! – steampowered Jan 20 '20 at 18:17
  • 1
    I have to do 13 setTimeouts with a 50 ms interval before my append() is finished. 0 is not enough for all situations. – PRMan Aug 09 '22 at 16:40
  • @PRMan I don't think that's the point of this answer. `setTimeout(fn(), 0)` won't wait for a couple seconds. It just gives the code before `setTimeout()` a higher priority. – NHerwich Aug 12 '22 at 07:31
  • Of course. But if you actually want to work on the append contents, you had better be prepared to loop for between 1-2 seconds to actually do that. Try it. You'll see that this answer doesn't work. – PRMan Dec 14 '22 at 18:01
6

I came across the same problem and have found a simple solution. Add after calling the append function a document ready.

$("#root").append(child);
$(document).ready(function () {
    // Action after append is completly done
});
shadowdiver
  • 101
  • 1
  • 6
  • Nice. This worked for me (I was appending HTML using Handlebars and JSON and of course a regular document.ready happens too early for all of that). – Katharine Osborne Jul 25 '16 at 15:26
  • 1
    @KatharineOsborne This is no different from a "regular document.ready". document.ready only gets called in one specific scenario, using it differently doesn't make it work differently. – Kevin B Oct 12 '17 at 16:43
  • Well it's been over a year since I left the comment, but IIRC, the context of where you call this is important (i.e. in a function). The code has since been passed over to a client so I don't have access to it any more to dig back in. – Katharine Osborne Oct 12 '17 at 19:27
5

I have another variant which may be useful for someone:

$('<img src="http://example.com/someresource.jpg">').load(function() {
    $('#login').submit();
}).appendTo("body");
msangel
  • 9,895
  • 3
  • 50
  • 69
  • 1
    For the ones thinking this is deprecated and removed, it is right but you can still use it like `...on('load', function(){ ... ` see here: http://stackoverflow.com/a/37751413/2008111 – caramba Sep 28 '16 at 14:01
5

Using MutationObserver can act like a callback for the jQuery append method:

I've explained it in another question, and this time I will only give example for modern browsers:

// Somewhere in your app:
var observeDOM = (() => {
    var MutationObserver = window.MutationObserver || window.WebKitMutationObserver;

    return function(obj, callback){
        if( MutationObserver ){
            // define a new observer
            var obs = new MutationObserver(function(mutations, observer){
                if( mutations[0].addedNodes.length || mutations[0].removedNodes.length )
                    callback(mutations);
            });
            // have the observer observe foo for changes in children
            obs.observe( obj, { childList:true, subtree:true });

            return obs;
        }
    }
})();

//////////////////
// Your code:

// setup the DOM observer (on the appended content's parent) before appending anything
observeDOM( document.body, ()=>{
    // something was added/removed
}).disconnect(); // don't listen to any more changes

// append something
$('body').append('<p>foo</p>');
Community
  • 1
  • 1
vsync
  • 118,978
  • 58
  • 307
  • 400
  • +1. Of all answers yours fits best. However it didn't work in my case, adding various rows to a table, because it goes into an infinite loop. The plugin that sorts the table will change the DOM so it will call the observer infinite times. – Azevedo Mar 10 '18 at 15:08
  • @Azevedo - why would it be infinite? i'm sure it is finite. anyway, you can separate a **sort** functionality from an added row "event" by being a bit creative. there are ways. – vsync Mar 10 '18 at 19:46
  • When the sort plugin sorts the table it triggers the observer (dom changed), which will trigger the sort again. Now I'm thinking... I could count the table rows and make the observer call the sorter only if rows counter was changed. – Azevedo Mar 10 '18 at 20:59
  • All I get is... Uncaught TypeError: Failed to execute 'observe' on 'MutationObserver': parameter 1 is not of type 'Node'. – Scott Dec 18 '20 at 20:40
  • @Scott - you **probably** wrote your script ***before*** the DOM was ready.....load your scripts only after or place the ` – vsync Dec 19 '20 at 08:30
  • @vsync - thanks - I solved my problem with a jquery promise $.when()... then() - we have massive dom changes and have to wait for the ajax/dom changes to happen - worked great – Scott Dec 20 '20 at 15:36
3

I think this is well answered but this is a bit more specific to handling the "subChild_with_SIZE" (if that's coming from the parent, but you can adapt it from where it may be)

$("#root").append(
    $('<div />',{
        'id': 'child'
    })
)
.children()
.last()
.each(function() {
    $(this).append(
        $('<div />',{
            'id': $(this).parent().width()
        })
    );
});
2

the Jquery append function returns a jQuery object so you can just tag a method on the end

$("#root").append(child).anotherJqueryMethod();
David
  • 8,340
  • 7
  • 49
  • 71
  • I need call me custom javascript function after .append, i need recalculate size of root – David Horák May 20 '11 at 07:56
  • you could possible use the jQuery each method, but append is a synchronous method so you should be ok just to do whatever you need to on the next line. – David May 20 '11 at 07:59
  • @JinDave, exactly what do you need to recalculate? Amount of children, height of parent, or what does *size* mean? – mekwall May 20 '11 at 08:03
1

$.when($('#root').append(child)).then(anotherMethod());

renatoluna
  • 497
  • 5
  • 7
0

For images and other sources you can use that:

$(el).one('load', function(){
    // completed
}).each(function() {
    if (this.complete)
        $(this).load();
});
Mycelin
  • 607
  • 8
  • 6
0

Cleanest way is to do it step by step. Use an each funciton to itterate through each element. As soon as that element is appended, pass it to a subsequent function to process that element.

    function processAppended(el){
        //process appended element
    }

    var remove = '<a href="#">remove</a>' ;
    $('li').each(function(){
        $(this).append(remove);   
        processAppended(this);    
    });​
hitwill
  • 575
  • 1
  • 9
  • 25
-1

I encountered this issue while coding HTML5 for mobile devices. Some browser/device combinations caused errors because .append() method did not reflect the changes in the DOM immediatly (causing the JS to fail).

By quick-and-dirty solution for this situation was:

var appint = setInterval(function(){
    if ( $('#foobar').length > 0 ) {
        //now you can be sure append is ready
        //$('#foobar').remove(); if elem is just for checking
        //clearInterval(appint)
    }
}, 100);
$(body).append('<div>...</div><div id="foobar"></div>');
Pyry Liukas
  • 431
  • 6
  • 10
-1

Yes you can add a callback function to any DOM insertion:
$myDiv.append( function(index_myDiv, HTML_myDiv){ //.... return child })

Check on JQuery documentation: http://api.jquery.com/append/
And here's a practical, similar, example: http://www.w3schools.com/jquery/tryit.asp?filename=tryjquery_html_prepend_func

Pedro Ferreira
  • 493
  • 7
  • 14
  • Fiddling with the w3schools example, you can check 2nd parameter, replacing the `.prepend()` method with: $("p").prepend(function(n, pHTML){ return "This p element (\""+pHTML+"\") has index " + n + ". "; – Pedro Ferreira Mar 24 '14 at 13:22
  • 1
    You clearly don't understand the purpose of that example..it it not a callback saying the content has been appended, but rather a callback function which returns **what** is to be appended. – vsync Jul 03 '16 at 15:13
  • @vsync: eactly. You clearly didn't understand my answer. "child" is **what** has been appended and **not** when it was appended. – Pedro Ferreira Oct 26 '17 at 22:20
-1

I ran into a similar problem recently. The solution for me was to re-create the colletion. Let me try to demonstrate:

var $element = $(selector);
$element.append(content);

// This Doesn't work, because $element still contains old structure:
$element.fooBar();    

// This should work: the collection is re-created with the new content:
$(selector).fooBar();

Hope this helps!

kralyk
  • 4,249
  • 1
  • 32
  • 34
-2

I know it's not the best solution, but the best practice:

$("#root").append(child);

setTimeout(function(){
  // Action after append
},100);
Manvel
  • 750
  • 7
  • 10
  • 8
    I don't think this is a good practice. 100 is an arbitrary number, and an event could take longer. – JasTonAChair Sep 17 '15 at 13:28
  • 1
    Wrong way to go. It might not always take 100ms – axelvnk Oct 01 '15 at 13:23
  • See my answer here for an explanation of why this works (the number could/should be 0, not 100): http://stackoverflow.com/questions/6068955/jquery-function-after-append/41893544#41893544 – jleach Jan 27 '17 at 12:03
  • 1
    it is at least better practice than the answers here using .ready. – Kevin B Feb 05 '18 at 15:32
-4
$('#root').append(child);
// do your work here

Append doesn't have callbacks, and this is code that executes synchronously - there is no risk of it NOT being done

David Fells
  • 6,678
  • 1
  • 22
  • 34
  • 3
    I see too many comments like this, and they're not helpful. @david fells, yes, the JS IS synchronious, but the DOM needs time to update. If you need to manipulate your DOM appended elements but the elements have not been rendered into your DOM yet, even if the append is done, your new manipulation will NOT work. (For example: $('#element').on(). – NoobishPro Nov 16 '15 at 18:50
-4
$('#root').append(child).anotherMethod();
Vivek
  • 10,978
  • 14
  • 48
  • 66