Функция jQuery после .append

92

Как вызвать функцию после того, как jQuery .appendполностью завершен?

Вот пример:

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

Проблема: когда добавляется сложная структура DOM, вычисление нового размера корневого элемента в обратном вызове функции добавления неверно. Предполагается, что DOM еще не полностью загружен, только первый дочерний элемент добавленной сложной DOM.

Давид Хорак
источник
Вы должны уметь связывать события в цепочку; append()занимает очень мало времени.
Bojangles
см. stackoverflow.com/q/8840580/176140 (dom 'reflow' в браузерах webkit)
schellmax
Это может быть полезно: stackoverflow.com/a/1489987/1375015
Константин Шуберт

Ответы:

71

У вас есть много правильных ответов, но ни один из них не говорит вам, почему это работает именно так.

В JavaScript команды выполняются по одной за раз, синхронно в порядке их поступления, если вы явно не укажете, что они должны быть асинхронными, используя тайм-аут или интервал.

Это означает, что ваш .appendметод будет выполнен, и больше ничего (без учета возможных тайм-аутов или интервалов, которые могут существовать) не будет выполняться, пока этот метод не завершит свою работу.

Подводя итог, нет необходимости в обратном вызове, поскольку .appendон будет выполняться синхронно.

Mekwall
источник
40
Все это правда, но вот моя проблема: я добавляю сложную DOM, и сразу после добавления я вычисляю новый размер корневого элемента, но здесь я ошибся номером. И подумайте, проблема в следующем: DOM все еще не загружен полностью, только первый дочерний элемент (.append ("<div id =" child "> <div id =" subChild_with_SIZE "> </div> </div>"))
Давид Хорак
@JinDave, Вы о каком размере имеете ввиду? Это количество детей, рост родителей или что вам нужно рассчитать?
mekwall
1
@ marcus-ekwall jsfiddle.net/brszE/3 это всего лишь метод написания, а не рабочий код,
Давид Хорак
22
@ DavidHorák, почему это принятый ответ? Это правда, что даже несмотря на то, что изменение произойдет, код не будет блокироваться до тех пор, пока полная перекомпоновка не удивит, у него будет 32 голоса за! (Или это даже не вызывает перекомпоновку) что-то вроде ответов [здесь] ( stackoverflow.com/questions/ 8840580 /… ) может иметь отношение к руде.
Андреас
17
Я согласен с Андреасом, это неправильный ответ. Проблема в том, что даже несмотря на то, что добавление было возвращено, элемент все еще может быть недоступен в DOM (зависит от браузера, работает в некоторых браузерах, в других не работает). Использование тайм-аута еще не гарантирует, что элемент будет в DOM.
Severun
21

Хотя Маркус Эквалл абсолютно прав насчет синхронности добавления, я также обнаружил, что в странных ситуациях иногда DOM не полностью отображается браузером при выполнении следующей строки кода.

В этом сценарии решения shadowdiver находятся в правильном направлении - с использованием .ready - однако гораздо проще связать вызов с исходным дополнением.

$('#root')
  .append(html)
  .ready(function () {
    // enter code here
  });
Даниэль - SDS Group
источник
1
Это абсолютно неверно и бесполезно в любой ситуации. api.jquery.com/ready
Кевин Би
Привет, Кевин, каким образом?
Дэниел - SDS Group
нет абсолютно никакой пользы от использования .ready для ожидания «рендеринга» добавленных элементов, как и от простого использования settimeout, если и только если документ еще не готов. если документ готов, код будет выполняться синхронно, что не окажет полезного воздействия.
Kevin B
Я не думаю, что готовый будет ждать загрузки добавленного документа, но вместо этого будет ждать загрузки всей DOM. Прошло около 3 лет с тех пор, как я написал этот ответ, но я думаю, что больше комментировал Shadow Diver выше, но в то время у меня не было возможности комментировать.
Дэниел - SDS Group
1
На самом деле это работает лучше, чем ожидалось. Лучше, чем любой другой ответ здесь.
Blue
20

Что ж, у меня точно такая же проблема с пересчетом размера, и после нескольких часов головной боли я не согласен с тем, чтобы .append()вести себя строго синхронно. Хорошо хоть в Google Chrome. См. Следующий код.

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

Свойство padding-left правильно извлекается в Firefox, но в Chrome оно пусто. Полагаю, как и все другие свойства CSS. После некоторых экспериментов мне пришлось довольствоваться заключением «геттера» CSS setTimeout()с задержкой в 10 мс, что, как я знаю, УЖАСНО, но единственный, работающий в Chrome. Если бы у кого-то из вас была идея, как лучше решить эту проблему, я был бы очень благодарен.

Womi
источник
15
это мне ОЧЕНЬ помогло: stackoverflow.com/q/8840580/176140 - это означает, что append () ЯВЛЯЕТСЯ синхронным, а обновление DOM - нет
schellmax
В этом случае setTimeout не уродлив (только 10 мс). Каждый раз, когда вы запрашиваете манипуляцию с "dom", хром будет ждать завершения вашего кода перед его выполнением. Итак, если вы вызовете item.hide, а затем item.show, он ничего не сделает. Когда ваша выполняемая функция завершена, она применяет ваши изменения к DOM. Если вам нужно дождаться обновления «dom», прежде чем продолжить, просто выполните действие CSS / DOM, а затем вызовите settimeout (function () {что делать дальше}) ;. Settimeout действует как старые добрые «события» в vb6! Он сообщает браузеру выполнить свои ожидающие задачи (обновить DOM) и после этого вернуться к вашему коду.
foxontherock
Посмотрите ответ, в котором используется .ready()гораздо лучшее и более элегантное решение.
Марк Карпентер-младший,
8

Меня удивляют все ответы здесь ...

Попробуй это:

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

Обратите внимание на тайм-аут 0. Это не произвольное число ... как я понимаю (хотя мое понимание может быть немного шатким), есть две очереди событий javascript - одна для макро-событий и одна для микро-событий. Очередь с «большей» областью действия содержит задачи, которые обновляют пользовательский интерфейс (и DOM), а микро-очередь выполняет операции типа быстрой задачи.

Также имейте в виду, что установка тайм-аута не гарантирует, что код будет работать точно с указанным значением. По сути, это помещает функцию в более высокую очередь (ту, которая обрабатывает UI / DOM) и не запускает ее раньше указанного времени.

Это означает, что установка тайм-аута, равного 0, помещает его в UI / DOM-часть очереди событий javascript для запуска при следующей возможности.

Это означает, что DOM обновляется со всеми предыдущими элементами очереди (например, вставленными через $.append(...);, и когда ваш код запускается, DOM полностью доступен.

(ps - я узнал об этом из Secrects of the JavaScript Ninja - отличной книги: https://www.manning.com/books/secrets-of-the-javascript-ninja )

Jleach
источник
Я добавлял много setTimeout()лет, не зная почему. Спасибо за объяснение!
вышел
5

У меня есть еще один вариант, который может кому-то пригодиться:

$('<img src="http://example.com/someresource.jpg">').load(function() {
    $('#login').submit();
}).appendTo("body");
мсангел
источник
1
Для тех, кто думает, что это устарело и удалено, это правильно, но вы все равно можете использовать его, как ...on('load', function(){ ... здесь: stackoverflow.com/a/37751413/2008111
caramba
5

Я столкнулся с той же проблемой и нашел простое решение. После вызова функции добавления добавьте готовый документ.

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

тень
источник
Ницца. Это сработало для меня (я добавлял HTML с помощью Handlebars и JSON и, конечно же, обычного документа. Это происходит слишком рано для всего этого).
Кэтрин Осборн
1
@KatharineOsborne Это не отличается от «обычного document.ready». document.ready вызывается только в одном конкретном сценарии, использование его по-другому не заставляет его работать по-другому.
Кевин Би
Ну, прошло больше года с тех пор, как я оставил комментарий, но IIRC, контекст, в котором вы это вызываете, важен (то есть в функции). С тех пор код был передан клиенту, поэтому у меня больше нет к нему доступа, чтобы
Кэтрин Осборн,
5

Использование MutationObserverможет действовать как обратный вызов для appendметода jQuery :

Я объяснил это в другом вопросе , и на этот раз приведу пример только для современных браузеров:

// 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
источник
+1. Из всех ответов лучше всего подходит твой. Однако в моем случае это не сработало, добавление различных строк в таблицу, потому что это заходит в бесконечный цикл. Плагин, который сортирует таблицу, изменит DOM, поэтому он будет вызывать наблюдателя бесконечное количество раз.
Азеведо
@Azevedo - почему это должно быть бесконечно? я уверен, что это конечно. в любом случае, вы можете отделить функцию сортировки от добавленной строки «событие», проявив немного творчества. есть способы.
vsync
Когда плагин сортировки сортирует таблицу, он запускает наблюдателя (доменное имя изменено), который снова запускает сортировку. Теперь я думаю ... Я мог бы подсчитать строки таблицы и заставить наблюдателя вызывать сортировщик, только если был изменен счетчик строк.
Azevedo
3

Я думаю, что на это хорошо дан ответ, но это немного более конкретно для обработки "subChild_with_SIZE" (если это исходит от родителя, но вы можете адаптировать его там, где это может быть)

$("#root").append(
    $('<div />',{
        'id': 'child'
    })
)
.children()
.last()
.each(function() {
    $(this).append(
        $('<div />',{
            'id': $(this).parent().width()
        })
    );
});
Джеймс Джонс
источник
2

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

Renatoluna
источник
8
$.whenработает только для тех объектов, которые возвращают объект обещания
Rifat
он работает, но выдает ошибку на консоли .. «
Значит,
1
Это работает только потому, что вы вызываете anotherMethod()сразу .. Это не передается как обратный вызов.
yts
это не имеет никакого смысла.
Кевин Би
1

функция добавления Jquery возвращает объект jQuery, поэтому вы можете просто пометить метод в конце

$("#root").append(child).anotherJqueryMethod();
Два
источник
Мне нужно вызвать пользовательскую функцию javascript после .append, мне нужно пересчитать размер корневого каталога,
Дэвид Хорак,
вы могли бы использовать метод jQuery each, но append - это синхронный метод, поэтому вы должны быть в порядке, просто сделайте все, что вам нужно, в следующей строке.
Дв
@JinDave, что именно вам нужно пересчитать? Количество детей, рост родителей или что означает размер ?
mekwall
0

Для изображений и других источников вы можете использовать это:

$(el).one('load', function(){
    // completed
}).each(function() {
    if (this.complete)
        $(this).load();
});
Mycelin
источник
0

Самый чистый способ - делать это шаг за шагом. Для обхода каждого элемента используйте функцию each. Как только этот элемент добавлен, передайте его следующей функции для обработки этого элемента.

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

    var remove = '<a href="#">remove</a>' ;
    $('li').each(function(){
        $(this).append(remove);   
        processAppended(this);    
    });​
хитвилл
источник
-1

Я столкнулся с этой проблемой при кодировании HTML5 для мобильных устройств. Некоторые комбинации браузера / устройства вызывали ошибки, потому что метод .append () не сразу отражал изменения в DOM (что приводило к сбою JS).

Путем быстрого и грязного решения этой ситуации было:

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>');
Пыры Люкас
источник
Никогда не следует использовать интервалы для ожидания завершения конструктора кода. Это совершенно ненадежно.
Гейб Хиемстра 05
-1

Да, вы можете добавить функцию обратного вызова к любой вставке DOM:
$myDiv.append( function(index_myDiv, HTML_myDiv){ //.... return child })

проверьте документацию JQuery: http://api.jquery.com/append/
И вот практический аналогичный пример: http://www.w3schools.com/jquery/ tryit.asp? filename = tryjquery_html_prepend_func

Педро Феррейра
источник
Повозившись с примером w3schools, вы можете проверить второй параметр, заменив .prepend()метод на: $ ("p"). Prepend (function (n, pHTML) {return "<b> This p element </b> (\" <i > "+ pHTML +" </i> \ ") <b> имеет индекс" + n + "</b>.";
Педро Феррейра
1
Вы явно не понимаете цель этого примера ... это не обратный вызов, говорящий о добавлении контента, а скорее функция обратного вызова, которая возвращает то, что должно быть добавлено.
vsync
@vsync: точно. Вы явно не поняли моего ответа. «ребенок» - это то, что было добавлено, а не то, когда оно было добавлено.
Педро Феррейра,
-1

Недавно я столкнулся с подобной проблемой. Решением для меня было воссоздать коллекцию. Попробую продемонстрировать:

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

Надеюсь это поможет!

кралык
источник
Мне это не помогло. :(
Pal
-1

В jquery вы можете использовать сразу после добавления

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

$ () вызывает функцию, которая либо регистрирует обратный вызов, готовый к DOM (если ему передана функция), либо возвращает элементы из DOM (если ему передана строка селектора или элемент)

Здесь вы можете найти больше
различий между $ и $ () в jQuery
http://api.jquery.com/ready/

AJchandu
источник
-2

Я знаю, что это не лучшее решение, но лучшая практика:

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

setTimeout(function(){
  // Action after append
},100);
Manvel
источник
8
Я не думаю, что это хорошая практика. 100 - произвольное число, и событие может занять больше времени.
JasTonAChair
1
Неправильный путь. Это может не всегда занимать 100 мс
axelvnk 01
См. Мой ответ здесь, чтобы объяснить, почему это работает (число может / должно быть 0, а не 100): stackoverflow.com/questions/6068955/…
jleach
1
это, по крайней мере, лучшая практика, чем ответы здесь с использованием .ready.
Kevin B
-4
$('#root').append(child);
// do your work here

В приложении нет обратных вызовов, и это код, который выполняется синхронно - нет риска, что это НЕ будет выполнено.

Дэвид Феллс
источник
3
Я вижу слишком много подобных комментариев, и они бесполезны. @david падает, да, JS синхронный, но DOM нужно время для обновления. Если вам нужно манипулировать добавленными элементами DOM, но эти элементы еще не были отрисованы в DOM, даже если добавление выполнено, ваши новые манипуляции НЕ будут работать. (Например: $ ('# element').
On
-4
$('#root').append(child).anotherMethod();
Вивек
источник
1
Мне нужно называть меня функцией javascript, а не функцией jQuery
Дэвид Хорак,