40 votes

Comment puis-je déterminer si un élément DOM créé dynamiquement a été ajouté au DOM ?

Selon les spécifications seulement le BODY et FRAMESET fournissent un événement "onload" auquel se rattacher, mais j'aimerais savoir quand un élément DOM créé dynamiquement a été ajouté au DOM en JavaScript.

Les heuristiques super-naïves que j'utilise actuellement, et qui fonctionnent, sont les suivantes :

  • Traversez le parentNode de l'élément jusqu'à ce que je trouve l'ancêtre ultime (c'est-à-dire parentNode.parentNode.parentNode.etc. jusqu'à ce que parentNode soit nul).

  • Si l'ancêtre ultime a une valeur définie, non nulle. corps propriété

    • supposer que l'élément en question fait partie du dom
  • sinon

    • répéter ces étapes à nouveau dans 100 millisecondes

Ce que je recherche, c'est la confirmation que ce que je fais est suffisant (encore une fois, cela fonctionne à la fois dans IE7 et FF3) ou une meilleure solution que, pour une raison quelconque, j'ai complètement ignorée ; peut-être d'autres propriétés que je devrais vérifier, etc.


EDIT : Je veux une façon de faire qui soit compatible avec les navigateurs, je ne vis pas dans un monde à un seul navigateur, malheureusement ; cela dit, les informations spécifiques aux navigateurs sont appréciées, mais veuillez indiquer le navigateur que vous connaissez. fait travailler. Merci !

0 votes

Comment pouvez-vous ne pas savoir quand un contenu est ajouté à votre document ? N'êtes-vous pas l'auteur de la page ?

2 votes

@Sergey : Plus vous abstrayez les choses, plus il est probable que l'une des mains ne sache pas ce que l'autre fait.

0 votes

Exactement - je construis un "widget" qui devra savoir, en interne, quand il a été ajouté au DOM - mais comme je ne suis pas nécessairement le consommateur, je ne saurai pas quand, car le code client le déterminera.

30voto

Jason Bunting Points 27534

UPDATE : Pour ceux que cela intéresse, voici l'implémentation que j'ai finalement utilisée :

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's 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;
}

La raison pour laquelle j'ai voulu cela est de fournir un moyen de synthétiser la onload pour les éléments du DOM. Voici cette fonction (bien que j'utilise quelque chose de légèrement différent car je l'utilise en conjonction avec la fonction 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);
   }
}

À titre d'exemple, cette configuration pourrait être utilisée comme suit :

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

J'espère que cela sera utile à quelqu'un.

REMARQUE : la raison pour laquelle j'ai opté pour cette solution plutôt que pour la solution Réponse de Darryl C'est parce que la technique getElementById ne fonctionne que si vous êtes dans le même document ; j'ai quelques iframes sur une page et les pages communiquent entre elles de manière complexe - quand j'ai essayé cela, le problème était qu'il ne pouvait pas trouver l'élément parce qu'il faisait partie d'un document différent du code dans lequel il était exécuté.

0 votes

J'ai essayé les performances et parcourir l'arbre de dom est plus rapide tant que l'élément n'est pas présent, jsperf.com/is-in-dom

21voto

La réponse la plus simple est d'utiliser l'outil d'évaluation de la qualité de l'eau. Node.contains méthode prise en charge par Chrome, Firefox (Gecko), Internet Explorer, Opera et Safari. Voici un exemple :

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

Idéalement, nous utiliserions document.contains(el) mais cela ne fonctionne pas dans IE, nous utilisons donc document.body.contains(el) .

Malheureusement, il faut encore interroger, mais vérifier si un élément se trouve déjà dans le document est très simple :

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

Si l'ajout d'un peu de CSS à votre page ne vous pose pas de problème, voici une autre technique astucieuse qui utilise des animations pour détecter les insertions de nœuds : http://www.backalleycoder.com/2012/04/25/i-want-a-damnodeinserted/

0 votes

contains n'est pas une méthode de document - elle se trouve sur le site de l body propriété. Donc, document.contains ne fonctionnera pas, vous devez y accéder via le corps comme ceci : document.body.contains

0 votes

@JasonBunting, les deux fonctionneront. contains est une méthode de tous les Node objets. Les deux sites document et document.body sont Node comme tous les éléments HTML.

0 votes

Huh, eh bien dans IE 9, document.contains == undefined, mais document.body.contains != undefined. Donc, cela ne semble pas nécessairement être le cas, d'où mon commentaire :)

8voto

Darryl Hein Points 33819

Ne pouvez-vous pas faire un document.getElementById('newElementId'); et voir si cela retourne vrai. Si ce n'est pas le cas, comme vous le dites, attendez 100 ms et réessayez ?

1 votes

Et s'il n'a pas d'identifiant ? Cela ne fonctionnera pas dans ce cas, et c'est peut-être le cas. Bonne réponse sinon... je vais peut-être voir si cela va fonctionner pour certains de mes besoins, et utiliser l'autre méthode au cas où je n'aurais pas d'identifiant.

0 votes

Pourquoi ne pas simplement ajouter un identifiant ? Vous pouvez ajouter un identifiant unique en utilisant une sorte de chaîne aléatoire, puis rechercher cet identifiant. Je sais que vous n'aimez pas JQuery, mais avec JQuery vous pourriez facilement le trouver en vous basant sur une classe, un parent ou un enfant.

0 votes

C'est vrai, c'est peut-être une bonne façon de faire - je vais devoir y réfléchir un peu... merci. LOL @ moi n'aimant pas jQuery. Je l'aime, je n'aime pas quand les gens pensent que c'est la réponse à toutes les questions JavaScript autour :)

2voto

Leo Points 1587

Vous pourriez demander document.getElementsByTagName("*").length ou créer une fonction appendChild personnalisée comme la suivante :

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";
});

Mise à jour

Sur demande, ajouter les informations de mes commentaires dans mon billet

Il y avait un commentaire de John Resig sur la bibliothèque Peppy sur Ajaxian aujourd'hui qui semblait suggérer que sa bibliothèque Sizzle pourrait être capable de gérer les événements d'insertion DOM. Je suis curieux de voir s'il y aura du code pour gérer IE également.

En suivant l'idée de polling, j'ai lu que certaines propriétés d'éléments ne sont pas disponibles tant que l'élément n'a pas été ajouté au document (par exemple element.scrollTop), peut-être que vous pourriez polluer cela au lieu de faire tout le DOM traversal.

Une dernière chose : dans IE, une approche qui pourrait être intéressante à explorer est de jouer avec l'événement onpropertychange. Je pense que l'ajout d'un élément au document va déclencher cet événement pour au moins une propriété.

0 votes

Comme je l'ai mentionné dans les commentaires qui apparaissent directement sous ma question, je ne contrôle peut-être pas le moment où l'objet est ajouté au DOM, sinon je ne poserais même pas cette question. Mais merci d'avoir répondu !

0 votes

Quant à votre première idée, ce n'est pas une heuristique aussi étroite que celle dont j'ai besoin - d'autres codes pourraient ajouter des éléments DOM et je devrais supposer que lorsque la longueur change, c'est parce que mon un élément était ajouté... une telle hypothèse est contraire au déterminisme dont j'ai besoin.

0 votes

Je vois. Il y avait un commentaire de John Resig sur la bibliothèque Peppy sur Ajaxian aujourd'hui qui semblait suggérer que sa bibliothèque Sizzle pourrait être capable de gérer les événements d'insertion DOM. Je suis curieux de voir s'il y aura du code pour gérer IE également.

2voto

Borgar Points 12493

Dans un monde parfait, vous pourriez accrocher les événements de mutation. Je doute qu'ils fonctionnent de manière fiable, même sur les navigateurs standards. Il semble que vous ayez déjà implémenté un événement de mutation. Vous pourriez donc ajouter cette fonctionnalité afin d'utiliser un événement de mutation natif plutôt qu'un sondage de dépassement de délai sur les navigateurs qui les prennent en charge.

J'ai vu des implémentations de DOM-change qui surveillent les changements en comparant document.body.innerHTML à la dernière sauvegarde .innerHTML ce qui n'est pas très élégant (mais fonctionne). Puisque vous allez finalement vérifier si un nœud spécifique a déjà été ajouté, il est préférable de vérifier cela à chaque interruption.

Les améliorations auxquelles je pense sont en utilisant .offsetParent plutôt que .parentNode car cela va probablement couper quelques parents de votre boucle (voir les commentaires). Ou en utilisant compareDocumentIndex() sur tout sauf IE et les tests .sourceIndex propery pour IE (devrait être -1 si le noeud n'est pas dans le DOM).

Cela pourrait aussi aider : Implémentation du navigateur X compareDocumentIndex par John Resig .

0 votes

Il s'avère que la fonction offsetParent ne fonctionne pas toujours très bien... elle ne permet pas toujours de revenir au document lui-même, alors qu'en remontant l'arbre avec parentNode, elle le fera si le noeud fait partie de l'arbre DOM ; sinon, elle renvoie finalement null. Tous les chemins vers le haut de l'arbre avec offsetParent ne fonctionnent pas comme ça.

0 votes

Aussi, offsetParent ne fait pas partie de la spécification DOM, apparemment : developer.mozilla.org/Fr/DOM/Element.offsetParent De plus, il "renvoie un résultat nul lorsque l'élément a un style.display défini sur 'none'", ce qui me mettrait un peu dans l'embarras.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X