11521 sujets

JavaScript, DOM et API Web HTML5

Bonjour

Quand on crée un élément DOM par
var myPar = document.createElement('p')

cet élément existe mais n'est pas inclus dans un parent, ce qui fait qu'il ne s'affiche pas.
Pour qu'il s'affiche, il faut l'inclure dans un élément parent par
 myDiv.appendChild(myPar)

On peut également le changer de place par
 myDiv2.appendChild(myPar)

ce qui le retire de son précédent parent.
Ce que j'aimerais faire, c'est "rendre un élément orphelin", c'est à dire le remettre dans l'état où il était au moment de sa création. La raison est que je veux détruire le parent, mais conserver cet enfant pour pouvoir l'inclure ultérieurement dans un nouveau parent.
Je n'ai pas trouvé de moyen simple, donc ce que je fais en général c'est ajouter une
<div id="buffer" style="display:none"></div>
où je le stocke de façon temporaire.
Avez vous quelque chose de plus simple à proposer?
Merci de vos avis.
Modifié par PapyJP (31 Dec 2018 - 10:32)
En poursuivant mes essais, j'ai découvert quelque shose qui pourrait vous intéresser.

Quand on écrit

var myPar = document.createElement('p');
myPar.id = 'myPar';
var myPar2 = document.getElementById('myPar');
console.log(myPar2);

on constate que "myPar2" est null.
Il faut avoir mis l'élément dans un nœud "parent" pour qu'il soit accessible.
En d'autres termes si on parvenait à rendre un élément orphelin, on ne pourrait plus l'adresser autrement que par la variable Javascript qui le contient.

Donc en pratique il semble bien qu'il n'y ait pas d'autre moyen de mettre un élément de côté que de le mettre dans une <div> de type buffer comme décrite plus haut, et de plus que cette <div> soit bien incluse dans le <body> de la page.
Modérateur
Bonjour,

NOP !

1) document.getElementById(id) ne cherche que les éléments effectivement dans le DOM (c'est à dire selon ton vocabulaire que les éléments insérés dans un parent). Elle ne cherche pas les éléments qui viennent d'être créés via un document.createElement(tag) mais qui ne sont pas encore dans le DOM (de ce point de vue, ton analyse est à peu près correcte, mais la raison fondamentale, c'est le fonctionnement de la méthode document.getElementById(id)).

2) Pour qu'un élément continue d'exister , il suffit qu'il soit référencé quelque part, soit dans le DOM, soit via une variable javascript. Si par exemple ta variable myPar est une variable globale, alors ton élément continue d'exister tant qu'on affecte pas autre chose à cette variable. Ceci est indépendant du fait que cet élément soit ou non déjà dans le DOM et qu'il ait ou non un parent. Le fait que l'élément existe ne suffit pas pour que document.createElement(id) le retrouve, d'où la valeur null obtenue dans ton script.

3) Si un élément n'est ni dans le DOM, ni de manière directe ou indirecte référencé par une variable javascript, alors le "nettoyeur" de javascript est susceptible de l'effacer. Mais de toute façon, on n'a alors (quasi) plus aucun moyen de retrouver cet élément.

EDIT: on notera à ce propos que getElementById() ne s'emploie qu'avec l'élément document, contrairement à d'autres méthodes de recherche comme querySelector() qui peuvent s'employer avec un élément quelconque.

Amicalement,
Modifié par parsimonhi (31 Dec 2018 - 13:53)
Merci pour ces précisions.
Je comprends très bien le pourquoi du comment.
Ce que je voulais dire, c'est simplement que je n'avais plus de moyen de retrouver cet élément, document.getElementById est une façon de faire qui aurait pu marcher, puisque l'élément créé se trouve effectivement dans le document, même s'il n'est pas inséré dans un élément du DOM.
A noter que dans mon test, l'élément existe encore mais que getDocumentByID ne le trouve pas, car le moteur Javascript ne constitue sa carte du contenu que lorsque l'on insère cet élément dans un autre élément.
Je n'ai pas essayé avec un querySelector, mais il est clair que ça doit être la même chose.

D'un autre point de vue le fait que le garbage collector supprime les multiples éléments que l'on est amené à créer de façon temporaire est plutôt une bonne chose...
Modifié par PapyJP (31 Dec 2018 - 19:58)
Modérateur
Bonjour,

Tant que tu as quelque chose qui pointe sur l'élément, tu peux le retrouver avec ce quelque chose.

S'il n'y a plus rien qui pointe sur l'élément, tu ne peux plus le retrouver, mais de toute façon, même si tu avais ce moyen, ça ne servirait à rien puisque l'élément n'existerait plus tout simplement.

Si tu veux conserver tes éléments temporaires, fabrique toi un élément racine avec la méthode document.createDocumentFragment() (mais que tu n'insère pas dans le dom), conserve une variable javascript qui pointe sur cet élément (on va supposer que tu l'appelles myRoot pour la suite), et insère tes autres éléments comme descendants de ton élément racine. Ce sera selon moi plus propre que d'insérer des éléments dans le DOM et les cacher d'une manière ou d'une autre. L'intérêt est que les méthodes genre myRoot.querySelector() vont fonctionner sur ce fragment.

Exemple (à insérer en fin d'un document html quelconque) :

EDIT: corrige un bug qui apparait quand on utilise Safari avec les sélecteurs :nth-of-type, nth-node, ... appliqués aux éléments insérés directement à la racine du fragment. Pas de bol ! Smiley cligne

var myRoot;
function doSomething()
{
	var b, e;
	myRoot = document.createDocumentFragment();
	b = document.createElement("div");
	b.id = "buffer";
	myRoot.appendChild(b);
	e = document.createElement("div");
	e.id = "child1";
	e.innerHTML = "Child 1";
	b.appendChild(e);
	e = document.createElement("div");
	e.id = "child2";
	e.innerHTML = "Child 2";
	b.appendChild(e);
	e = myRoot.querySelector("#buffer>div:nth-of-type(2)");
	if (e) alert(e.id);
	else alert("NOK");
}
window.addEventListener("load", doSomething, false);


Amicalement,
Modifié par parsimonhi (01 Jan 2019 - 15:41)
Merci de ta réponse, et bonne année!
Oui, j'ai bien pensé à remplacer le mécanisme du <div id="buffer"> par un objet global BUFFER qui contiendrait les éléments orphelins en attente d'adoption.
A l'expérience, je trouve cette méthode peu pratique dans le contexte où je veux l'utiliser.
Dans la page sur laquelle je concentre actuellement mes efforts, j'ai de multiples <div> dans lesquelles on peut temporairement afficher un élément contenant des informations complémentaires.
Ces éléments complémentaires sont eux mêmes générés en AJAX par une requête au serveur.
Quand un élément a été généré une fois, il est intéressant de le conserver quelque part et de le retrouver si nécessaire pour l'afficher ailleurs dans la page sans avoir besoin de le générer à nouveau.
Le plus simple est de donner un id aux éléments générés, de les stocker dans le buffer et de les retrouver par leur id.
Un mécanisme équivalent serait de définir un objet Buffer du genre :

function Buffer() {
    this.items = {};
    this.addItem = function(item) {
        this.items[item.id] = item;
   }
   this.getItem = function(id) {
        return this.items[id];
   }
}
BUFFER = new Buffer();

Mais c'est plus complexe à gérer que la <div id="buffer">, en particulier si on veut déplacer un élément d'un parent à un autre parent sans passer par BUFFER.

En gros mon code c'est quelque chose comme :

function getInfo(event) {
    event.stopPropagation();
    var element = event.currentTarget;
    var dataInfo = element.getAttribute('data-info');
    /* .... analyse des infos, dont l'id de l'élément à insérer ... */
   var child = document.getElementById(childID);
   if(child) element.appendChild(child);
   else { /* appel AJAX pour générer child */ }
}

Que child se trouve dans le buffer ou dans un autre parent, le code est le même.
Modifié par PapyJP (01 Jan 2019 - 17:40)
Modérateur
Bonjour,

Bonne année aussi.

Ton élément <div id="buffer"> devrait être stocké dans un documentFragment. Ensuite, tu fais comme tu fais déjà pour y ajouter et y retirer des éléments (à quelques détails de code près).

Laisser <div id="buffer"> dans le DOM, avec tous les reflows que ça engendre à chaque fois que tu vas y ajouter ou y retirer quelque chose, n'est vraiment pas une bonne pratique.

Sans compter les problèmes d'id qui vont se retrouver en doublon et autres bizarreries du même genre.

Amicalement,
parsimonhi a écrit :
Ton élément <div id="buffer"> devrait être stocké dans un documentFragment. Ensuite, tu fais comme tu fais déjà pour y ajouter et y retirer des éléments (à quelques détails de code près).

Ce sont ces "quelques détails" qui me posent problème.
Si j'écris

BUFFER = document.createDocumentFragment();

et qu'ensuite j'y mette d'autres éléments, comment vais-je retrouver ces éléments?
Certainement pas par document.getElementById, comme on a vu plus haut.
Je ne suis pas sûr que
var element = BUFFER.querySelector('#elementID')
fonctionne mieux.
Peut-être
var element = BUFFER.querySelector('[id="elementID"]')

C'est cela qui me bloque pour le moment.
parsimonhi a écrit :
Laisser <div id="buffer"> dans le DOM, avec tous les reflows que ça engendre à chaque fois que tu vas y ajouter ou y retirer quelque chose, n'est vraiment pas une bonne pratique.

Bien sûr ! c'est pour cela que j'ai ouvert cette discussion.
parsimonhi a écrit :
Sans compter les problèmes d'id qui vont se retrouver en doublon et autres bizarreries du même genre.

??? la nature même ce cette solution c'est qu'un élément est défini par son id unique, par lequel je le retrouve, qu'il soit dans le buffer ou non.
Modérateur
Bonjour,

Justement, BUFFER.querySelector('#elementID') va fonctionner.

BUFFER.getElementById('elementID') devrait fonctionner aussi dans le futur, mais pour l'instant, c'est pas encore bien supporté.

Amicalement,
Modifié par parsimonhi (01 Jan 2019 - 19:27)
Meilleure solution
C'est parfait! Merci beaucoup.
Pour ceux que ça intéresse, voici le code que j'utilise.
J'ai simplifié le code, qui fait appel à des fonctions de mon "jQuery perso", que j'avais commencé à faire bien avant qu'il existe LE jQuery. Je ne garantis pas que le code tel qu'écrit ci-dessous fonctionne tel quel.

function getNode(id) {
  var element = document.getElementById(id);
  if(element) return element;
  if(typeof BUFFER == 'undefined') return;
  return BUFFER.querySelector('#' + id);
}
function bufferize(element) {
  if(typeof BUFFER == 'undefined') BUFFER = document.createDocumentFragment();
  BUFFER.appendChild(element);
}

La recherche d'un élément se fait par getNode, la bufferisation d'un élément par bufferize.