Função jQuery após .append

90

Como chamar uma função depois que o jQuery .appendestiver completamente pronto?

Aqui está um exemplo:

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

O problema: quando uma estrutura DOM complexa é anexada, o cálculo do novo tamanho do elemento raiz no retorno de chamada da função anexa está errado. As suposições são de que o DOM ainda não está completamente carregado, apenas o primeiro filho do DOM complexo adicionado.

David Horák
fonte
Você deve estar bem encadeando eventos; append()leva muito pouco tempo.
Bojangles de
consulte stackoverflow.com/q/8840580/176140 (dom 'reflow' em navegadores do webkit)
schellmax
Isso pode ser útil: stackoverflow.com/a/1489987/1375015
Konstantin Schubert

Respostas:

70

Você tem muitas respostas válidas aqui, mas nenhuma delas realmente diz por que funciona dessa maneira.

Em JavaScript, os comandos são executados um de cada vez, de forma síncrona na ordem em que vêm, a menos que você diga explicitamente que sejam assíncronos usando um tempo limite ou intervalo.

Isso significa que seu .appendmétodo será executado e nada mais (desconsiderando quaisquer tempos limite ou intervalos potenciais que possam existir) será executado até que o método termine seu trabalho.

Para resumir, não há necessidade de um retorno de chamada, pois .appendserá executado de forma síncrona.

mekwall
fonte
39
Tudo isso é verdade, mas aqui está o meu problema: acrescento DOM complexo e, logo após o acréscimo, calculo o novo tamanho do elemento raiz, mas aqui achei o número errado. E pense nisso, o problema é este: o DOM ainda não foi totalmente carregado, apenas o primeiro filho (.append ("<div id =" child "> <div id =" subChild_with_SIZE "> </div> </div>"))
David Horák
@JinDave, Qual é esse tamanho que você está se referindo? É a quantidade de filhos, a altura dos pais ou o que você precisa calcular?
mekwall
1
@ marcus-ekwall jsfiddle.net/brszE/3 é apenas um método de escrita, não funciona código,
David Horák
22
@ DavidHorák então por que esta é a resposta aceita? É verdade que, mesmo que a mudança aconteça, o código não será bloqueado até o refluxo completo, bastante surpreso com 32 votos positivos! (Ou se eu nem mesmo causar refluxo) algo como as respostas [aqui] ( stackoverflow.com/questions/ 8840580 /… ) pode ser minério relevante.
Andreas
17
Concordo com o Andreas, essa resposta não é correta. O problema é que, embora append tenha sido retornado, o elemento ainda pode não estar disponível no DOM (dependente do navegador, funciona em alguns navegadores e quebra em outros). Usar um tempo limite ainda não garante que o elemento estará no DOM.
Severun
19

Bem, eu tenho exatamente o mesmo problema com o recálculo do tamanho e depois de horas de dor de cabeça, tenho que discordar do .append()comportamento estritamente síncrono. Bem, pelo menos no Google Chrome. Veja o código a seguir.

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

A propriedade padding-left foi recuperada corretamente no Firefox, mas está vazia no Chrome. Como todas as outras propriedades CSS, suponho. Depois de alguns experimentos, tive que me contentar em envolver o 'getter' CSS em um setTimeout()atraso de 10 ms, o que eu sei que é FEIO pra caramba, mas o único funcionando no Chrome. Se algum de vocês tiver uma ideia de como resolver esse problema da melhor maneira, eu ficaria muito grato.

Womi
fonte
15
isso me ajudou MUITO: stackoverflow.com/q/8840580/176140 - ou seja, append () É síncrono, mas a atualização do DOM não é
schellmax
Nesse caso, setTimeout não é feio (apenas 10ms é). Cada vez que você solicitar a manipulação "dom", o Chrome irá esperar que seu código termine antes de executá-lo. Portanto, se você chamar item.hide seguido por item.show, ele simplesmente não fará nada. Quando sua função de execução terminar, então, ele aplica suas alterações ao DOM. Se você precisar esperar por uma atualização "dom" antes de continuar, apenas execute sua ação CSS / DOM e chame settimeout (function () {o que fazer a seguir}) ;. O settimeout atua como um bom e velho "doevents" no vb6! Diz ao navegador para fazer suas tarefas de espera (atualizar o DOM) e retorna ao seu código depois.
foxontherock
Veja a resposta que faz uso de .ready()uma solução muito melhor e mais elegante.
Mark Carpenter Jr
19

Embora Marcus Ekwall esteja absolutamente certo sobre a sincronicidade do acréscimo, também descobri que, em situações estranhas, às vezes o DOM não é completamente renderizado pelo navegador quando a próxima linha de código é executada.

Nesse cenário, as soluções de shadowdiver seguem as linhas corretas - com o uso de .ready - no entanto, é muito mais organizado encadear a chamada para seu anexo original.

$('#root')
  .append(html)
  .ready(function () {
    // enter code here
  });
Daniel - Grupo SDS
fonte
Isso é absolutamente falso e inútil em todas as situações. api.jquery.com/ready
Kevin B
Oi Kevin, de que maneira?
Daniel - Grupo SDS de
não há absolutamente nenhum benefício em usar. ready to wait nos elementos acrescentados para “renderizar” qualquer coisa mais do que você obteria simplesmente usando um settimeout, se e somente se o documento ainda não estiver pronto. se o documento estiver pronto, o código será executado de forma síncrona, sem nenhum impacto útil.
Kevin B
Não acho que o Ready esperará o carregamento do documento anexado, mas sim o carregamento de todo o DOM. Já se passaram cerca de 3 anos desde que escrevi esta resposta, mas acho que estava mais comentando sobre o mergulhador sombra acima, mas não tive facilidade para comentar na época.
Daniel - Grupo SDS de
1
Na verdade, isso funciona melhor do que o esperado. Melhor do que qualquer outra resposta aqui.
Azul de
8

Estou surpreso com todas as respostas aqui ...

Experimente isto:

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

Observe o 0 tempo limite. Não é um número arbitrário ... como eu entendo (embora meu entendimento possa ser um pouco instável), há duas filas de eventos javascript - uma para eventos macro e outra para microeventos. A fila com escopo "maior" contém tarefas que atualizam a IU (e DOM), enquanto a micro fila executa operações do tipo tarefa rápida.

Observe também que definir um tempo limite não garante que o código seja executado exatamente no valor especificado. O que isso faz é essencialmente colocar a função na fila superior (aquela que lida com a UI / DOM) e não a executa antes do tempo especificado.

Isso significa que definir um tempo limite de 0 o coloca na parte da IU / DOM da fila de eventos do javascript, para ser executado na próxima chance possível.

Isso significa que o DOM é atualizado com todos os itens anteriores da fila (como inserido via $.append(...);e, quando o código é executado, o DOM está totalmente disponível.

(ps - Aprendi isso em Secrects of the JavaScript Ninja - um excelente livro: https://www.manning.com/books/secrets-of-the-javascript-ninja )

jleach
fonte
Venho adicionando setTimeout()há anos sem saber por quê. Obrigada pelo esclarecimento!
movido
5

Tenho outra variante que pode ser útil para alguém:

$('<img src="http://example.com/someresource.jpg">').load(function() {
    $('#login').submit();
}).appendTo("body");
msangel
fonte
1
Para aqueles que pensam que está obsoleto e removido, é correto, mas você ainda pode usá-lo como ...on('load', function(){ ... veja aqui: stackoverflow.com/a/37751413/2008111
caramba
5

Encontrei o mesmo problema e encontrei uma solução simples. Depois de chamar a função append, adicione um documento pronto.

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

mergulhador de sombra
fonte
Agradável. Isso funcionou para mim (eu estava acrescentando HTML usando Handlebars e JSON e, claro, um documento normal. Já acontece muito cedo para tudo isso).
Katharine Osborne
1
@KatharineOsborne Isso não é diferente de um "document.ready regular". document.ready só é chamado em um cenário específico, usá-lo de maneira diferente não faz com que funcione de maneira diferente.
Kevin B
Bem, já se passou mais de um ano desde que deixei o comentário, mas IIRC, o contexto de onde você chama isso é importante (ou seja, em uma função). O código já foi passado para um cliente, então não tenho mais acesso a ele para me aprofundar.
Katharine Osborne
5

O uso MutationObserverpode funcionar como um retorno de chamada para o appendmétodo jQuery :

Eu expliquei isso em outra pergunta e, desta vez, darei apenas exemplos para navegadores modernos:

// 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>');
vsync
fonte
+1. De todas as respostas, a sua se encaixa melhor. Porém não funcionou no meu caso, adicionar várias linhas a uma tabela, porque ele entra em um loop infinito. O plugin que classifica a tabela mudará o DOM para que ele chame o observador infinitas vezes.
Azevedo
@Azevedo - por que seria infinito? tenho certeza de que é finito. de qualquer forma, você pode separar uma funcionalidade de classificação de um "evento" de linha adicionada sendo um pouco criativo. existem maneiras.
vsync
Quando o plugin sort classifica a tabela, ele aciona o observador (dom alterado), que irá acionar a classificação novamente. Agora estou pensando ... Eu poderia contar as linhas da tabela e fazer o observador chamar o classificador apenas se o contador de linhas fosse alterado.
Azevedo
3

Acho que está bem respondido, mas é um pouco mais específico para lidar com o "subChild_with_SIZE" (se vier do pai, mas você pode adaptá-lo de onde possa estar)

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

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

renatoluna
fonte
8
$.whensó funciona para os objetos que retornam um objeto de promessa
Rifat
está funcionando, mas dando um erro no console .. "então não é uma função"
Karan Datwani
1
Isso só funciona porque você está invocando anotherMethod()imediatamente. Não está sendo passado como um retorno de chamada.
yts
isso não faz sentido.
Kevin B
1

a função anexar Jquery retorna um objeto jQuery para que você possa apenas marcar um método no final

$("#root").append(child).anotherJqueryMethod();
Dve
fonte
Preciso me chamar de função javascript personalizada após .append, preciso recalcular o tamanho da raiz
David Horák
você pode usar o método jQuery each, mas append é um método síncrono, então você deve estar ok para fazer o que for necessário na próxima linha.
Dia
@JinDave, exatamente o que você precisa recalcular? Quantidade de filhos, altura dos pais ou o que significa tamanho ?
mekwall
0

Para imagens e outras fontes, você pode usar:

$(el).one('load', function(){
    // completed
}).each(function() {
    if (this.complete)
        $(this).load();
});
Micelina
fonte
0

A maneira mais limpa é fazê-lo passo a passo. Use uma função each para iterar em cada elemento. Assim que esse elemento for anexado, passe-o para uma função subsequente para processar esse elemento.

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

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

Encontrei esse problema ao codificar HTML5 para dispositivos móveis. Algumas combinações de navegador / dispositivo causaram erros porque o método .append () não refletiu as alterações no DOM imediatamente (causando a falha do JS).

A solução rápida e suja para esta situação foi:

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
fonte
Os intervalos nunca devem ser usados ​​para esperar a conclusão de um construtor de código. É totalmente não confiável.
Gabe Hiemstra
-1

Sim, você pode adicionar uma função de retorno de chamada a qualquer inserção de DOM:
$myDiv.append( function(index_myDiv, HTML_myDiv){ //.... return child })

Verifique a documentação do JQuery: http://api.jquery.com/append/
E aqui está um exemplo prático e semelhante: http://www.w3schools.com/jquery/ tryit.asp? filename = tryjquery_html_prepend_func

Pedro Ferreira
fonte
Mexendo no exemplo w3schools, você pode verificar o segundo parâmetro, substituindo o .prepend()método por: $ ("p"). Prepend (function (n, pHTML) {return "<b> Este elemento p </b> (\" <i > "+ pHTML +" </i> \ ") <b> tem índice" + n + "</b>.";
Pedro Ferreira
1
Você claramente não entende o propósito desse exemplo ... não é um retorno de chamada dizendo que o conteúdo foi anexado, mas sim uma função de retorno de chamada que retorna o que deve ser anexado.
vsync
@vsync: eactly. Você claramente não entendeu minha resposta. "filho" é o que foi acrescentado e não quando foi acrescentado.
Pedro Ferreira
-1

Eu tive um problema semelhante recentemente. A solução para mim foi recriar a coleção. Deixe-me tentar demonstrar:

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

Espero que isto ajude!

kralyk
fonte
Não me ajudou. :(
Pal
-1

No jquery você pode usar logo após o seu append

$(function(){
//code that needs to be executed when DOM is ready, after manipulation
});

$ () chama uma função que registra um retorno de chamada pronto para DOM (se uma função for passada para ele) ou retorna elementos do DOM (se uma string de seletor ou elemento for passado para ele)

Você pode encontrar mais aqui a
diferença entre $ e $ () em jQuery
http://api.jquery.com/ready/

AJchandu
fonte
-2

Eu sei que não é a melhor solução, mas a melhor prática:

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

setTimeout(function(){
  // Action after append
},100);
Manvel
fonte
8
Não acho que seja uma boa prática. 100 é um número arbitrário e um evento pode demorar mais.
JasTonAChair de
1
Caminho errado a seguir. Pode não levar sempre 100ms
axelvnk
Veja minha resposta aqui para uma explicação de por que isso funciona (o número pode / deve ser 0, não 100): stackoverflow.com/questions/6068955/…
jleach
1
é pelo menos uma prática melhor do que as respostas aqui usando .ready.
Kevin B
-4
$('#root').append(child);
// do your work here

Append não tem retornos de chamada, e este é um código executado de forma síncrona - não há risco de NÃO ser feito

David Fells
fonte
3
Eu vejo muitos comentários como este e eles não são úteis. @david detecta, sim, o JS IS síncrono, mas o DOM precisa de tempo para atualizar. Se você precisa manipular seus elementos anexados ao DOM, mas os elementos ainda não foram renderizados em seu DOM, mesmo que o anexo seja concluído, sua nova manipulação NÃO funcionará. (Por exemplo: $ ('# elemento'). On ().
NoobishPro
-4
$('#root').append(child).anotherMethod();
Vivek
fonte
1
Preciso me chamar de função javascript, não função jQuery
David Horák