Как определить, был ли добавлен динамически созданный элемент DOM в DOM?

В соответствии с spec только элементы BODY и FRAMESET предоставляют событие "onload" для attach, но я хотел бы знать, когда динамически созданный элемент DOM был добавлен в DOM в JavaScript.

Супер-наивные эвристики, которые я сейчас использую, которые работают, следующие:

  • Переместите свойство parentNode элемента обратно до тех пор, пока я не найду конечного предка (то есть parentNode.parentNode.parentNode.etc, пока parentNode не будет null)

  • Если у конечного предка есть определенное, ненулевое свойство тело

    • Предположим, что этот элемент является частью dom
  • else

    • повторите эти шаги за 100 миллисекунд

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


ИЗМЕНИТЬ: Я хочу, чтобы браузер был несовместим с этим, я не живу в мире с одним браузером, к сожалению; это говорит о том, что информация, относящаяся к браузеру, оценивается, но учтите, какой браузер вы знаете, что он работает. Спасибо!

Ответ 1

UPDATE: для всех, кто интересуется этим, вот реализация, которую я наконец использовал:

function isInDOMTree(node) {
   // If the farthest-back ancestor of our node has a "body"
   // property (that node would be the document itself), 
   // we assume it is in the page DOM tree.
   return !!(findUltimateAncestor(node).body);
}
function findUltimateAncestor(node) {
   // Walk up the DOM tree until we are at the top (parentNode 
   // will return null at that point).
   // NOTE: this will return the same node that was passed in 
   // if it has no ancestors.
   var ancestor = node;
   while(ancestor.parentNode) {
      ancestor = ancestor.parentNode;
   }
   return ancestor;
}

Причина, по которой я хотел, это предоставить способ синтеза события onload для элементов DOM. Вот эта функция (хотя я использую что-то немного другое, потому что я использую ее в сочетании с MochiKit):

function executeOnLoad(node, func) {
   // This function will check, every tenth of a second, to see if 
   // our element is a part of the DOM tree - as soon as we know 
   // that it is, we execute the provided function.
   if(isInDOMTree(node)) {
      func();
   } else {
      setTimeout(function() { executeOnLoad(node, func); }, 100);
   }
}

Например, эту настройку можно использовать следующим образом:

var mySpan = document.createElement("span");
mySpan.innerHTML = "Hello world!";
executeOnLoad(mySpan, function(node) { 
   alert('Added to DOM tree. ' + node.innerHTML);
});

// now, at some point later in code, this
// node would be appended to the document
document.body.appendChild(mySpan);

// sometime after this is executed, but no more than 100 ms after,
// the anonymous function I passed to executeOnLoad() would execute

Надеюсь, что это полезно кому-то.

ПРИМЕЧАНИЕ. Причина, по которой я закончил это решение, а не Darryl answer, состояла в том, что метод getElementById работает только в том случае, если вы находитесь в пределах тот же документ; У меня есть некоторые iframes на странице, и страницы обмениваются друг с другом сложными способами - когда я это пробовал, проблема заключалась в том, что он не мог найти элемент, потому что он был частью другого документа, кроме кода, который он выполнял в.

Ответ 2

Самый простой ответ - использовать метод Node.contains, поддерживаемый Chrome, Firefox (Gecko), Internet Explorer, Opera, и Safari. Вот пример:

var el = document.createElement("div");
console.log(document.body.contains(el)); // false
document.body.appendChild(el);
console.log(document.body.contains(el)); // true
document.body.removeChild(el);
console.log(document.body.contains(el)); // false

В идеале мы использовали бы document.contains(el), но это не работает в IE, поэтому мы используем document.body.contains(el).

К сожалению, вам все равно нужно опросить, но проверка наличия элемента в документе еще очень проста:

setTimeout(function test() {
  if (document.body.contains(node)) {
    func();
  } else {
    setTimeout(test, 50);
  }
}, 50);

Если вы согласны с добавлением CSS на свою страницу, вот еще один умный метод, который использует анимации для обнаружения вложений node: http://www.backalleycoder.com/2012/04/25/i-want-a-damnodeinserted/

Ответ 3

Не можете ли вы сделать document.getElementById('newElementId'); и посмотреть, возвращает ли это значение true. Если нет, как вы говорите, подождите 100 мс и повторите попытку?

Ответ 4

Вместо того, чтобы ходить по дереву DOM до элемента документа, просто используйте element.ownerDocument. см. здесь: https://developer.mozilla.org/en-US/docs/DOM/Node.ownerDocument и сделайте следующее:

element.ownerDocument.body.contains(element)

и вы хорошо.

Ответ 5

Вы можете запросить document.getElementsByTagName( "*" ). length или создать пользовательскую функцию appendChild, например, следующую:

var append = function(parent, child, onAppend) {
  parent.appendChild(child);
  if (onAppend) onAppend(child);
}

//inserts a div into body and adds the class "created" upon insertion
append(document.body, document.createElement("div"), function(el) {
  el.className = "created";
});

Обновление

По запросу добавление информации из моих комментариев в мой пост

Был комментарий John Resig в библиотеке Peppy на Ajaxian сегодня, который, казалось, предполагал, что его библиотека Sizzle может обрабатывать События вставки DOM. Мне любопытно узнать, будет ли код для обработки IE также

Следуя идее опроса, я прочитал, что некоторые свойства элемента недоступны до тех пор, пока элемент не будет добавлен к документу (например, element.scrollTop), возможно, вы могли бы опросить это, а не делать все обход DOM.

Последнее: в IE один подход, который стоит изучить, - это играть с событием onpropertychange. Я полагаю, что добавление его к документу должно инициировать это событие для хотя бы одного свойства.

Ответ 6

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

Я видел реализации DOM-изменений, которые отслеживают изменения, сравнивая document.body.innerHTML с последним сохраненным .innerHTML, который не совсем изящный (но работает). Поскольку вы в конечном счете собираетесь проверить, добавлен ли какой-либо конкретный node, тогда вам лучше просто проверить, что каждое прерывание.

Усовершенствования, о которых я могу думать, - это , используя .offsetParent, а не .parentNode, поскольку он, скорее всего, вырезает несколько родителей из вашего цикла (см. комментарии). Или используя compareDocumentIndex() для всех, кроме IE, и для тестирования .sourceIndex для IE (должно быть -1, если node не находится в DOM).

Это также может помочь: X-браузер compareDocumentIndex, реализуемый Джоном Ресигом.

Ответ 7

Вы хотите событие DOMNodeInserted (или DOMNodeInsertedIntoDocument).

Изменить: вполне возможно, что эти события не поддерживаются IE, но я не могу проверить это прямо сейчас.

Ответ 8

Вот еще одно решение проблемы, извлеченное из кода jQuery. Следующая функция может использоваться, чтобы проверить, подключен ли node к DOM или если он "отключен".

function isDisconnected( node ) {
    return !node || !node.parentNode || node.parentNode.nodeType === 11;
}

A nodeType === 11 определяет DOCUMENT_FRAGMENT_NODE, который является node, не связанным с объектом документа.

Для получения дополнительной информации см. следующую статью Джона Ресига: http://ejohn.org/blog/dom-documentfragments/

Ответ 9

var finalElement=null; 

Я строю dom-объекты в цикле, когда цикл находится в последнем цикле, и создается lastDomObject, затем:

 finalElement=lastDomObject; 

оставить цикл:

while (!finalElement) { } //this is the delay... 

Следующее действие может использовать любой из вновь созданных объектов dom. Он работал с первой попытки.

Ответ 10

В IE не поддерживается ни один из событий DOMNodeInserted или DOMNodeInsertedIntoDocument. Еще одна недостающая функция IE [только] DOM - это функция compareDocumentPosition, которая предназначена для того, чтобы делать то, что предлагает ее название.

Что касается предлагаемого решения, я думаю, что нет лучшего (если вы не совершаете какие-то грязные трюки, например, перезаписываете реализации собственных DOM-элементов)

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

Я также считаю, что проблема, о которой вы описали, не является той проблемой, которая у вас есть, это выглядит скорее одним из потенциальных решений вашей проблемы. Можете ли вы, вероятно, подробно объяснить прецедент?