11548 sujets

JavaScript, DOM et API Web HTML5

Bonjour,

Je rencontre un phénomène bizarre avec Firefox en utilisant la fonction getElementsByName.

L'idée, c'est que j'ai un formulaire HTML avec un champ "input" de type "text" qui a pour attribut name la valeur "vrh". L'utilisateur doit pouvoir ajouter/supprimer d'autres champs texte du même type.

J'ai donc une fonction javascript qui insère un champ texte (j'utilise pour cela les fonctions DOM comme createElement et setAttribute). L'ajout de champs texte fonctionne bien. Voici un extrait de code, pour ceux que ça intéresse:

var e = document.createElement("input");
e.setAttribute("type", "text");
e.setAttribute("name", "vrh");
e.setAttribute("size", "6");
e.setAttribute("maxLength", "5");
maDiv.appendChild(e);


Pour la suppression des éléments, j'ai écrit une fonction qui supprime le dernier champ texte ajouté (= le dernier noeud de la div). Voici un extrait du code (qui fonctionne):

var listeH = document.getElementsByName("vrh");
if(listeH.length >= 2){
  // Je récupère le dernier résultat de getElementsByName
  var aSuppr = listeH[listeH.length-1];

  // Je le supprime
  aSuppr.parentNode.removeChild(aSuppr);
}


Jusqu'ici tout va bien.
Là où ça se complique, c'est que je voudrais parfois cacher le lien "Supprimer" qui permet à l'utilisateur de supprimer un champ (c'est en cliquant sur ce lien que la fonction ci-dessus est appelée). Par exemple, je veux cacher le lien "Supprimer" quand il n'y a qu'un seul champ (il doit y en avoir au moins un).

J'ai donc modifié la seconde fonction (celle qui supprime), comme ceci:

var listeH = document.getElementsByName("vrh");
if(listeH.length >= 2){
  // Je récupère le dernier résultat de getElementsByName
  var aSuppr = listeH[listeH.length-1];

  // Je le supprime
  [#red]alert("nbAvant = "+listeH.length);
  [#green]aSuppr.parentNode.removeChild(aSuppr);
  [#red]alert("nbAprès = "+listeH.length);[#green]

  // Je cache éventuellement le lien supprimer
  if(listeH.length == 2){
    // Je cache le lien supprimer car il y avait 2 champs texte avant que j'en supprime un (il n'y en a donc plus qu'un)
    document.getElementById("lienSupprimerVR").style.display = "none";
  }
}


Comme ça ne fonctionnait pas correctement (le lien "Supprimer" était caché quand il restait deux champs textes au lieu d'un seul), j'ai rajouté les deux alertes en rouge.

Verdict: le nombre d'éléments dans la liste "listeH" diminue lorsque je supprime le noeud de l'arbre. J'obtiens les affichages "nbAvant = 3" et "nbAprès = 2". Alors que je n'ai rien changé dans la liste.

Je ne constate cela que sous Firefox (j'ai la version 3.0.8). D'après moi, la liste n'a pas à être modifiée par la suppression d'un élément dans l'arbre.

Est-ce un bug connu ? Quelqu'un a-t-il déjà rencontré ce cas ?

Merci d'avance Smiley cligne
Modifié par jiber2fr (22 Apr 2009 - 14:49)
c'est en effet un problème.
Mais c'est tout à fait normal.
Tu supprime la référence au noeud.
Le garbage collector s'occupe de supprimer tous les variables faisant référence au noeud.

tu devrais faire le compte avant la suppression.
Si j'ai 3 éléments j'enlève le lien.
Salut masseuro, et merci pour ta réponse Smiley biggrin

Bien sûr, c'est ce que j'ai fait. Mais je pense qu'il s'agit d'un bug de Firefox. Je ne supprime pas le noeud, je ne le détruit pas, je le supprime de l'arbre. Le garbage collector est censé détruire les objets sur lesquels plus aucune variable ne pointe: hors, ce n'est pas le cas, il restait ma liste (listeH).

Par exemple, je pourrais très bien vouloir retirer temporairement ce noeud du DOM, faire des traitements dessus, puis le remettre dans le DOM.

D'ailleurs, la description de la méthode removeChild indique ceci:
a écrit :
Le nœud retiré existe toujours en mémoire, mais ne fait plus partie du DOM.


Cela dit, si on regarde la description (lien ci-dessus), on voit que removeChild retourne une référence sur le noeud qu'on vient de supprimer du DOM. Je ne vois l'intérêt de ce retour que pour résoudre ce "bug" qui semble donc "prévu".

Mais tu avoueras que cette altération de ma liste est un excès de zèle du garbage collector, et je pense que le comportement d'IE est, sur ce cas précis, le bon. Imagine que j'aie des structures de données compliquées, avec des objets, et des listes dedans... et que la suppression d'un noeud modifie tout ça Smiley eek C'est la porte ouverte à toutes les fenêtres Smiley lol

D'autres avis ? Smiley cligne
jiber2fr a écrit :
Mais je pense qu'il s'agit d'un bug de Firefox.

Et, pour tester cette supposition, tu as:
- fais des recherches dans les spécifications qui vont bien;
- testé avec d'autres navigateurs qu'Internet Explorer et Mozilla Firefox (le consensus global, s'il existe, étant souvent un bon indicateur pour savoir s'il s'agit d'un bug ou d'un comportement normal).

Pour les spécifications, je ne sais pas trop, mais pour le consensus global: Webkit et Opera pensent pareil que Firefox (testé à l'instant). Soit ils sont tous bugués, soit finalement c'est normal. Et vu les lacunes bien connues de l'implémentation de JavaScript dans IE, la piste «c'est normal» me semble la plus plausible.
Après vérification, les spécifications ne disent rien à ce sujet là.

Mais je trouve ce comportement pour le moins bizarre. Qu'il y ait consensus d'implémentation dans un sens ou dans l'autre, peu importe: en faisant abstraction du fait qu'on manipule des nœuds d'un arbre XML, as-tu déjà constaté un comportement similaire dans d'autres circonstances/langages ? Moi non.

Pour résumer, ce qu'il se passe, c'est en gros:
- J'ai un objet O référencé dans une liste L1 et dans une liste L2
- Je retire O de la liste L1
- Il a été supprimé de la liste L2
(O = mon noeud; L1 = le DOM; L2 = ma variable listeH)

Mais bon, si ça ne surprend personne d'autre que moi, tant pis.
Salut,
a écrit :
Mais je pense qu'il s'agit d'un bug de Firefox.
Tu n'y vas pas avec le dos de la cuillère, dis donc. Smiley smile

C'est pourtant un simple problème de vocabulaire. Ce que tu appelles une "liste" n'existe pas en Javascript. Je pense que tu fais référence à un tableau (array) puisque tu sembles attendre de ces "listes" un comportement approchant.

Or, et c'est là ton erreur, document.getElementsByName() ne retourne pas un tableau, mais une collection de noeuds (node collection). Certes, ça ressemble à un tableau, je te l'accorde, mais cela n'en est pas un. Essaye d'ajouter un élément avec maCollection.push() et tu comprendras tout de suite.

Une collection de noeuds représente en temps réel le nombre de noeuds correspondant à ta requête présents dans le DOM. Donc c'est normal que si tu supprimes un noeud du DOM il n'apparaisse plus dans la collection.

À des fins de test, tu peux convertir ta collection en tableau, supprimer quelques éléments du DOM, et là, tu obtiendras le comportement que tu attends : la length n'aura pas changé (les éléments supprimés étant toujours plus ou moins accessibles mais hors du DOM).
Modifié par marcv (23 Apr 2009 - 10:21)
De plus, je ne pense pas qu'Internet Explorer fasse vraiment exception à la règle. Par exemple, cette page de test donne le même résultat partout.

Il pourrait être intéressant de voir ton code en live, la vérité peut être ailleurs... Smiley cligne
Bonjour,

marcv, je crois que tu détiens la vérité Smiley biggrin

Je viens de faire une petite expérience qui confirme que la "liste" n'en est pas vraiment une. Voici le code:
<html>
<head>
<script language=javascript>

function test1(){
  var liste = document.getElementsByName("toto");
  var nbAvant = liste.length;
  liste[liste.length-1].parentNode.removeChild(liste[liste.length-1]);
  var nbApres = liste.length;
  alert("Test1 (getElementsByName): Avant suppression="+nbAvant+" - Après suppression="+nbApres);
}

function test2(){
  var e1 = document.getElementById("toto1");
  var e2 = document.getElementById("toto2");
  var e3 = document.getElementById("toto3");
  var liste = new Array(e1, e2, e3);
  var nbAvant = liste.length;
  liste[liste.length-1].parentNode.removeChild(liste[liste.length-1]);
  var nbApres = liste.length;
  alert("Test2 (getElementsByName): Avant suppression="+nbAvant+" - Après suppression="+nbApres);
}

</script>
</head>
<body>
<input type="text" name="toto" id="toto1"><input type="text" name="toto" id="toto2"><input type="text" name="toto" id="toto3">
<a href="javascript:test1();">Test 1</a><a href="javascript:test2();">Test 2</a>
</body>
</html>


L'idée: j'ai trois champs texte (input type=text) qui ont le même name: "toto" mais chacun un id différent: "toto1", "toto2" et "toto3".

J'ai une fonction test1() qui récupère une liste de ces trois champs avec getElementsByName("toto"), et qui supprime le troisième du DOM. Résultat: un élément a été "retiré" de ma liste (c'est ce comportement que je trouvais anormal).

J'ai une fonction test2() qui récupère une liste de ces trois champs, manuellement, avec trois getElementById() successifs. La liste contient la même chose que dans le premier test. Je supprime le troisième champ du DOM. Résultat: la liste contient toujours trois éléments.

Pour reproduire le test, il suffit de coller mon code dans un fichier html. (Recharger la page entre le test1 et le test2).