Función jQuery después de .append

90

¿Cómo llamar a una función después de que jQuery .appendesté completamente hecho?

He aquí un ejemplo:

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

El problema: cuando se agrega una estructura DOM compleja, el cálculo del nuevo tamaño del elemento raíz dentro de la devolución de llamada de la función de agregar es incorrecto. Se supone que el DOM aún no está completamente cargado, solo el primer hijo del DOM complejo agregado lo está.

David Horák
fuente
Deberías estar bien encadenando eventos; append()toma muy poco tiempo.
Bojangles
consulte stackoverflow.com/q/8840580/176140 (dom 'reflow' en navegadores webkit)
schellmax
Esto podría ser útil: stackoverflow.com/a/1489987/1375015
Konstantin Schubert

Respuestas:

70

Tiene muchas respuestas válidas aquí, pero ninguna de ellas realmente le dice por qué funciona como lo hace.

En JavaScript, los comandos se ejecutan uno a la vez, de forma sincrónica en el orden en que vienen, a menos que les indique explícitamente que sean asincrónicos mediante un tiempo de espera o un intervalo.

Esto significa que su .appendmétodo se ejecutará y nada más (sin tener en cuenta los posibles tiempos de espera o intervalos que puedan existir) se ejecutará hasta que ese método haya terminado su trabajo.

Para resumir, no hay necesidad de una devolución de llamada, ya .appendque se ejecutará de forma sincrónica.

mekwall
fuente
39
Todo esto es cierto, pero aquí está mi problema: agrego DOM complejo y, justo después de agregar, calculo el nuevo tamaño del elemento raíz, pero aquí obtuve un número incorrecto. Y piense que, el problema es este: DOM todavía no está completamente cargado, solo el primer hijo (.append ("<div id =" child "> <div id =" subChild_with_SIZE "> </div> </div>"))
David Horák
@JinDave, ¿A qué tamaño se refiere? ¿Es la cantidad de hijos, la altura de los padres o qué es lo que necesita calcular?
mekwall
1
@ marcus-ekwall jsfiddle.net/brszE/3 es solo un método de escritura, no un código de trabajo,
David Horák
22
@ DavidHorák, ¿por qué es esta la respuesta aceptada? Es cierto que a pesar de que ocurre el cambio, el código no se bloqueará hasta que el reflujo completo, bastante sorprendido, ¡esto tiene 32 votos a favor! (O ni siquiera puede causar reflujo) algo como las respuestas [aquí] ( stackoverflow.com/questions/ 8840580 /… ) podría ser más relevante.
Andreas
17
Estoy de acuerdo con Andreas, esta respuesta no es correcta. El problema es que a pesar de que append ha regresado, es posible que el elemento aún no esté disponible en el DOM (depende del navegador, funciona en algunos navegadores, se rompe en otros). El uso de un tiempo de espera aún no garantiza que el elemento estará en el DOM.
Severun
19

Bueno, tengo exactamente el mismo problema con el recálculo del tamaño y después de horas de dolor de cabeza tengo que no estar de acuerdo con .append()comportarme estrictamente sincrónico. Bueno, al menos en Google Chrome. Consulte el siguiente código.

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

La propiedad padding-left se recupera correctamente en Firefox pero está vacía en Chrome. Como todas las demás propiedades de CSS, supongo. Después de algunos experimentos, tuve que conformarme con envolver el 'getter' de CSS setTimeout()con un retraso de 10 ms, que sé que es feo pero el único que funciona en Chrome. Si alguno de ustedes tuviera una idea de cómo resolver este problema de una mejor manera, estaría muy agradecido.

Womi
fuente
15
esto me ayudó MUCHO: stackoverflow.com/q/8840580/176140 - es decir, append () ES sincrónico, pero la actualización DOM no lo es
schellmax
En ese caso, setTimeout no es feo (solo los 10ms lo son). Cada vez que solicite la manipulación "dom", Chrome esperará a que termine su código antes de ejecutarlo. Por lo tanto, si llama a item.hide seguido de item.show, no hará nada. Cuando su función de ejecución finaliza, entonces, aplica sus cambios al DOM. Si necesita esperar una actualización "dom" antes de continuar, simplemente haga su acción CSS / DOM, luego llame a settimeout (function () {qué hacer a continuación}) ;. ¡El settimeout actúa como un viejo "doevents" en vb6! Le dice al navegador que haga sus tareas de espera (actualizar DOM) y luego regresa a su código.
foxontherock
Vea la respuesta que hace uso de .ready()una solución mucho mejor y más elegante.
Mark Carpenter Jr
19

Aunque Marcus Ekwall tiene toda la razón sobre la sincronicidad de append, también he descubierto que en situaciones extrañas a veces el navegador no representa completamente el DOM cuando se ejecuta la siguiente línea de código.

En este escenario, las soluciones de shadowdiver siguen las líneas correctas, con el uso de .ready, sin embargo, es mucho más ordenado encadenar la llamada a su anexo original.

$('#root')
  .append(html)
  .ready(function () {
    // enter code here
  });
Daniel - Grupo SDS
fuente
Esto es absolutamente falso e inútil en todas las situaciones. api.jquery.com/ready
Kevin B
Hola Kevin, ¿de qué manera?
Daniel - Grupo SDS
No hay absolutamente ningún beneficio en usar .ready para esperar a que los elementos agregados "rendericen" más de lo que obtendría simplemente usando un settimeout, si y solo si el documento no está listo. si el documento ESTÁ listo, el código se ejecutará sincrónicamente, por lo que tendrá un impacto útil cero.
Kevin B
No creo que el ready esperará a que se cargue el documento adjunto, sino que esperará a que se cargue todo el DOM. Habían pasado aproximadamente 3 años desde que escribí esta respuesta, pero creo que estaba comentando más sobre el buzo en la sombra anterior, pero no tenía la capacidad de comentar en ese momento.
Daniel - Grupo SDS
1
En realidad, esto funciona mejor de lo esperado. Mejor que cualquier otra respuesta aquí.
Azul
8

Me sorprenden todas las respuestas aquí ...

Prueba esto:

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

Tenga en cuenta el tiempo de espera 0. No es un número arbitrario ... según tengo entendido (aunque mi comprensión puede ser un poco inestable), hay dos colas de eventos de JavaScript: una para macroeventos y otra para microeventos. La cola de alcance "más grande" contiene tareas que actualizan la IU (y DOM), mientras que la micro cola realiza operaciones de tipo de tarea rápida.

También tenga en cuenta que establecer un tiempo de espera no garantiza que el código funcione exactamente en ese valor especificado. Lo que hace esto es esencialmente poner la función en la cola superior (la que maneja la interfaz de usuario / DOM) y no la ejecuta antes del tiempo especificado.

Esto significa que establecer un tiempo de espera de 0 lo coloca en la parte de UI / DOM de la cola de eventos de JavaScript, para que se ejecute en la próxima oportunidad posible.

Esto significa que el DOM se actualiza con todos los elementos de la cola anteriores (como los insertados a través de $.append(...);, y cuando se ejecuta su código, el DOM está completamente disponible.

(PD: aprendí esto de Secrects of the JavaScript Ninja , un excelente libro: https://www.manning.com/books/secrets-of-the-javascript-ninja )

jleach
fuente
Llevo setTimeout()años agregando sin saber por qué. ¡Gracias por la explicación!
vapor el
5

Tengo otra variante que puede ser útil para alguien:

$('<img src="http://example.com/someresource.jpg">').load(function() {
    $('#login').submit();
}).appendTo("body");
msangel
fuente
1
Para aquellos que piensan que esto está obsoleto y eliminado, está bien, pero aún puede usarlo como ...on('load', function(){ ... ver aquí: stackoverflow.com/a/37751413/2008111
caramba
5

Me encontré con el mismo problema y encontré una solución simple. Agregue después de llamar a la función de agregar un documento listo.

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

buzo de sombras
fuente
Agradable. Esto funcionó para mí (estaba agregando HTML usando Handlebars y JSON y, por supuesto, un documento normal. Ya ocurre demasiado pronto para todo eso).
Katharine Osborne
1
@KatharineOsborne Esto no es diferente de un "documento normal. Listo". document.ready solo se llama en un escenario específico, usarlo de manera diferente no hace que funcione de manera diferente.
Kevin B
Bueno, ha pasado más de un año desde que dejé el comentario, pero IIRC, el contexto de donde llamas a esto es importante (es decir, en una función). Desde entonces, el código se pasó a un cliente, por lo que ya no tengo acceso a él para profundizar.
Katharine Osborne
5

El uso MutationObserverpuede actuar como una devolución de llamada para el appendmétodo jQuery :

Lo expliqué en otra pregunta , y esta vez solo daré un ejemplo 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
fuente
+1. De todas las respuestas, la tuya es la que mejor se adapta. Sin embargo, no funcionó en mi caso, agregando varias filas a una tabla, porque entra en un ciclo infinito. El complemento que ordena la tabla cambiará el DOM, por lo que llamará al observador infinitas veces.
Azevedo
@Azevedo - ¿por qué sería infinito? Estoy seguro de que es finito. de todos modos, puede separar una función de clasificación de un "evento" de fila agregada si es un poco creativo. hay maneras.
vsync
Cuando el complemento de ordenación ordena la tabla, activa el observador (dom cambiado), que volverá a desencadenar la ordenación. Ahora estoy pensando ... podría contar las filas de la tabla y hacer que el observador llame al clasificador solo si se cambia el contador de filas.
Azevedo
3

Creo que esto está bien respondido, pero esto es un poco más específico para manejar el "subChild_with_SIZE" (si viene del padre, pero puede adaptarlo desde donde puede estar)

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

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

renatoluna
fuente
8
$.whensolo funciona para aquellos objetos que devuelven un objeto de promesa
Rifat
funciona pero da un error en la consola .. "entonces no es una función"
Karan Datwani
1
Esto solo funciona porque está invocando de anotherMethod()inmediato ... No se pasa como una devolución de llamada.
2017
esto no tiene sentido.
Kevin B
1

la función de adición de Jquery devuelve un objeto jQuery para que pueda etiquetar un método al final

$("#root").append(child).anotherJqueryMethod();
Dve
fuente
Necesito llamarme a la función javascript personalizada después de .append, necesito recalcular el tamaño de la raíz
David Horák
podría usar jQuery en cada método, pero append es un método sincrónico, por lo que debería estar bien para hacer lo que necesite en la siguiente línea.
Dve
@JinDave, ¿qué es exactamente lo que necesitas recalcular? Cantidad de hijos, altura de los padres o ¿qué significa el tamaño ?
mekwall
0

Para imágenes y otras fuentes, puede usar eso:

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

La forma más limpia es hacerlo paso a paso. Utilice una función de each para recorrer cada elemento. Tan pronto como se agregue ese elemento, páselo a una función posterior para procesar ese elemento.

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

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

Encontré este problema al codificar HTML5 para dispositivos móviles. Algunas combinaciones de navegador / dispositivo causaron errores porque el método .append () no reflejó los cambios en el DOM de inmediato (lo que provocó que JS fallara).

La solución rápida y sucia para esta situación fue:

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
fuente
Los intervalos nunca deben usarse para esperar a que finalice un constructor de código. Es totalmente poco confiable.
Gabe Hiemstra
-1

Sí, puede agregar una función de devolución de llamada a cualquier inserción DOM:
$myDiv.append( function(index_myDiv, HTML_myDiv){ //.... return child })

Consulte la documentación de JQuery: http://api.jquery.com/append/
Y aquí hay un ejemplo práctico similar: http://www.w3schools.com/jquery/ tryit.asp? filename = tryjquery_html_prepend_func

Pedro Ferreira
fuente
Jugando con el ejemplo de w3schools, puede verificar el segundo parámetro, reemplazando el .prepend()método con: $ ("p"). Prepend (function (n, pHTML) {return "<b> Este elemento p </b> (\" <i > "+ pHTML +" </i> \ ") <b> tiene índice" + n + "</b>.";
Pedro Ferreira
1
Claramente no comprende el propósito de ese ejemplo ... no es una devolución de llamada que dice que se ha agregado el contenido, sino una función de devolución de llamada que devuelve lo que se debe agregar.
vsync
@vsync: en serio. Claramente no entendiste mi respuesta. "niño" es lo que se ha agregado y no cuando se agregó.
Pedro Ferreira
-1

Me encontré con un problema similar recientemente. La solución para mí fue volver a crear la colección. Déjame intentar demostrar:

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 esto ayude!

Kralyk
fuente
No me ayudó. :(
Pal
-1

En jquery puede usar justo después de su adición

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

$ () llama a una función que registra una devolución de llamada lista para DOM (si se le pasa una función) o devuelve elementos del DOM (si se le pasa una cadena de selección o un elemento)

Puede encontrar más aquí la
diferencia entre $ y $ () en jQuery
http://api.jquery.com/ready/

AJchandu
fuente
-2

Sé que no es la mejor solución, sino la mejor práctica:

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

setTimeout(function(){
  // Action after append
},100);
Manvel
fuente
8
No creo que esta sea una buena práctica. 100 es un número arbitrario y un evento puede tardar más.
JasTonAChair
1
Camino equivocado a seguir. Puede que no siempre tarde 100 ms
axelvnk
Vea mi respuesta aquí para obtener una explicación de por qué esto funciona (el número podría / debería ser 0, no 100): stackoverflow.com/questions/6068955/…
jleach
1
es al menos una mejor práctica que las respuestas aquí usando .ready.
Kevin B
-4
$('#root').append(child);
// do your work here

Append no tiene devoluciones de llamada, y este es un código que se ejecuta sincrónicamente; no hay riesgo de que NO se realice

David Fells
fuente
3
Veo demasiados comentarios como este y no son útiles. @david cae, sí, el JS ES sincrónico, pero el DOM necesita tiempo para actualizarse. Si necesita manipular los elementos agregados de DOM pero los elementos aún no se han procesado en su DOM, incluso si el agregado está hecho, su nueva manipulación NO funcionará. (Por ejemplo: $ ('# element').
On
-4
$('#root').append(child).anotherMethod();
Vivek
fuente
1
Necesito llamarme a la función javascript, no a la función jQuery
David Horák