Dethe Elza ( [email protected] ), architecte technique senior, Blast Radius
Le modèle objet de document (DOM) est l'un des outils les plus couramment utilisés pour manipuler les données XML et HTML, mais son potentiel est rarement pleinement exploité. En tirant parti du DOM et en le rendant plus facile à utiliser, vous bénéficiez d'un outil puissant pour les applications XML, y compris les applications Web dynamiques.
Ce numéro présente une chroniqueuse invitée, amie et collègue Dethe Elza. Dethe possède une vaste expérience dans le développement d'applications Web utilisant XML et je tiens à le remercier de m'avoir aidé à introduire la programmation XML à l'aide du DOM et d'ECMAScript. Restez à l'écoute de cette chronique pour plus d'articles de Dethe.
- David Mertz
DOM est l'une des API standards pour le traitement du XML et du HTML. Il est souvent critiqué pour être gourmand en mémoire, lent et verbeux. Pourtant, c'est le meilleur choix pour de nombreuses applications, et il est certainement plus simple que SAX, l'autre API majeure de XML. Le DOM apparaît progressivement dans des outils tels que les navigateurs web, les navigateurs SVG, OpenOffice, etc.
DOM est formidable car il s’agit d’un standard largement implémenté et intégré à d’autres standards. En tant que norme, sa gestion des données est indépendante du langage de programmation (ce qui peut ou non être un point fort, mais au moins cela rend la façon dont nous traitons les données cohérente). Le DOM est désormais non seulement intégré aux navigateurs Web, mais fait également partie de nombreuses spécifications basées sur XML. Maintenant qu'il fait partie de votre arsenal, et peut-être que vous l'utilisez encore occasionnellement, je suppose qu'il est temps de profiter pleinement de ce qu'il apporte.
Après avoir travaillé avec le DOM pendant un certain temps, vous verrez des modèles se développer, des choses que vous voudrez faire encore et encore. Les raccourcis vous aident à travailler avec des DOM longs et à créer un code élégant et explicite. Voici une collection de trucs et astuces que j'utilise fréquemment, ainsi que quelques exemples JavaScript.
La première astuce
pour insertAfter et prependChild
est qu’il n’y a « pas d’astuce ».Le DOM dispose de deux méthodes pour ajouter un nœud enfant à un nœud conteneur (généralement un élément, ou un document ou un fragment de document) : appendChild(node) et insertBefore(node, referenceNode). Il semble qu'il manque quelque chose. Que se passe-t-il si je souhaite insérer ou ajouter un nœud enfant après un nœud de référence (en faisant du nouveau nœud le premier de la liste) ? Pendant de nombreuses années, ma solution a été d'écrire la fonction suivante :
Listing 1. Mauvaises méthodes d'insertion et d'ajout en
function insertAfter (parent, nœud, referenceNode) {
if(referenceNode.nextSibling) {
parent.insertBefore(node, referenceNode.nextSibling);
} autre {
parent.appendChild(nœud);
}
}
function prependChild (parent, nœud) {
si (parent.firstChild) {
parent.insertBefore(noeud, parent.firstChild);
} autre {
parent.appendChild(nœud);
}
}
En fait, comme le Listing 1, la fonction insertBefore() a été définie pour revenir à appendChild() lorsque le nœud de référence est vide. Ainsi, au lieu d'utiliser les méthodes ci-dessus, vous pouvez utiliser les méthodes du Listing 2, ou les ignorer et simplement utiliser les fonctions intégrées :
Listing 2. Manière correcte d'insérer et d'ajouter d'avant
function insertAfter (parent, nœud, referenceNode) {
parent.insertBefore(node, referenceNode.nextSibling);
}
function prependChild (parent, nœud) {
parent.insertBefore(noeud, parent.firstChild);
}
Si vous êtes nouveau dans la programmation DOM, il est important de souligner que, même si plusieurs pointeurs peuvent pointer vers un nœud, ce nœud ne peut exister qu'à un seul emplacement dans l'arborescence DOM. Donc, si vous souhaitez l'insérer dans l'arborescence, il n'est pas nécessaire de le supprimer d'abord de l'arborescence car il sera supprimé automatiquement. Ce mécanisme est pratique lors de la réorganisation des nœuds en les insérant simplement dans leurs nouvelles positions.
Selon ce mécanisme, si vous souhaitez échanger les positions de deux nœuds adjacents (appelés node1 et node2), vous pouvez utiliser l'une des solutions suivantes :
node1.parentNode.insertBefore(node2, node1);
ou
node1.parentNode.insertBefore(node1.nextSibling, node1);
Que pouvez-vous faire d'autre avec DOM ?
DOM est largement utilisé dans les pages Web. Si vous visitez le site des bookmarklets (voir Ressources), vous trouverez de nombreux scripts courts et créatifs qui peuvent réorganiser les pages, extraire des liens, masquer des images ou des publicités Flash, et bien plus encore.
Toutefois, étant donné qu'Internet Explorer ne définit pas de constantes d'interface de nœud pouvant être utilisées pour identifier les types de nœuds, vous devez vous assurer que si vous omettez une constante d'interface, vous définissez d'abord la constante d'interface dans le script DOM pour le Web.
Listing 3. S'assurer que le nœud est défini
if (!window['Noeud']) {
window.Node = new Object();
Noeud.ELEMENT_NODE = 1 ;
Noeud.ATTRIBUTE_NODE = 2 ;
Noeud.TEXT_NODE = 3 ;
Noeud.CDATA_SECTION_NODE = 4 ;
Noeud.ENTITY_REFERENCE_NODE = 5 ;
Noeud.ENTITY_NODE = 6 ;
Noeud.PROCESSING_INSTRUCTION_NODE = 7 ;
Noeud.COMMENT_NODE = 8 ;
Noeud.DOCUMENT_NODE = 9 ;
Noeud.DOCUMENT_TYPE_NODE = 10 ;
Noeud.DOCUMENT_FRAGMENT_NODE = 11 ;
Noeud.NOTATION_NODE = 12 ;
}
Le listing 4 montre comment extraire tous les nœuds de texte contenus dans un nœud :
Listing 4. Texte interne
fonction innerText (nœud) {
// est-ce un nœud texte ou CDATA ?
if (node.nodeType == 3 || node.nodeType == 4) {
renvoyer node.data ;
}
var je;
var returnValue = [];
pour (i = 0; i < node.childNodes.length; i++) {
returnValue.push(innerText(node.childNodes[i]));
}
return returnValue.join('');
}
Raccourcis
Les gens se plaignent souvent que le DOM est trop verbeux et que les fonctions simples nécessitent beaucoup de code. Par exemple, si vous souhaitez créer un élément <div> contenant du texte et répondant à un clic sur un bouton, le code pourrait ressembler à :
Listing 5. Le "long chemin" pour créer un élément <div>
fonction handle_button() {
var parent = document.getElementById('myContainer');
var div = document.createElement('div');
div.className = 'myDivCSSClass';
div.id = 'monDivId';
div.style.position = 'absolu' ;
div.style.left = '300px';
div.style.top = '200px';
var text = "Ceci est le premier texte du reste de ce code";
var textNode = document.createTextNode(text);
div.appendChild(textNode);
parent.appendChild(div);
}
Si vous créez fréquemment des nœuds de cette manière, taper tout ce code vous fatiguera rapidement. Il devait y avoir une meilleure solution – et elle existait ! Voici un utilitaire qui vous aide à créer des éléments, à définir les propriétés et les styles des éléments et à ajouter des nœuds enfants de texte. À l'exception du paramètre name, tous les autres paramètres sont facultatifs.
Listing 6. Raccourci de fonction elem()
function elem (nom, attributs, style, texte) {
var e = document.createElement(nom);
si (attrs) {
pour (entrez les attributs) {
if (clé == 'classe') {
e.className = attrs[clé];
} sinon if (clé == 'id') {
e.id = attrs[clé];
} autre {
e.setAttribute(clé, attrs[clé]);
}
}
}
si (style) {
pour (clé avec style) {
e.style[clé] = style[clé];
}
}
si (texte) {
e.appendChild(document.createTextNode(text));
}
retourner e ;
}
En utilisant ce raccourci, vous pouvez créer l'élément <div> dans le listing 5 de manière plus concise. Notez que les paramètres attrs et style sont donnés à l’aide d’objets texte JavaScript.
Listing 7. Un moyen simple de créer un <div>
fonction handle_button() {
var parent = document.getElementById('myContainer');
parent.appendChild(elem('div',
{classe : 'myDivCSSClass', identifiant : 'myDivId'}
{position : 'absolue', gauche : '300px', haut : '200px'},
'Ceci est le premier texte du reste de ce code'));
}
Cet utilitaire peut vous faire gagner beaucoup de temps lorsque vous souhaitez créer rapidement un grand nombre d'objets DHTML complexes. Modèle ici signifie que si vous avez une structure DOM spécifique qui doit être créée fréquemment, utilisez un utilitaire pour les créer. Non seulement cela réduit la quantité de code que vous écrivez, mais cela réduit également les copier-coller répétitifs de code (un coupable d'erreurs) et permet de penser plus clairement lors de la lecture du code.
Quelle est la prochaine étape ?
Le DOM a souvent du mal à vous dire quel est le prochain nœud dans l'ordre du document. Voici quelques utilitaires pour vous aider à avancer et à reculer entre les nœuds :
Listing 8. nextNode et prevNode
// renvoie le nœud suivant dans l'ordre du document
fonction nextNode(nœud) {
if (!node) renvoie null ;
si (node.firstChild){
retourner node.firstChild;
} autre {
return nextWide (nœud);
}
}
// fonction d'assistance pour nextNode()
fonction nextWide (nœud) {
if (!node) renvoie null ;
si (node.nextSibling) {
retourner node.nextSibling ;
} autre {
return nextWide(node.parentNode);
}
}
// renvoie le nœud précédent dans l'ordre du document
fonction prevNode(nœud) {
if (!node) renvoie null ;
si (node.previousSibling) {
return previousDeep(node.previousSibling);
}
return node.parentNode;
}
// fonction d'assistance pour prevNode()
fonction précédenteDeep (nœud) {
if (!node) renvoie null ;
while (node.childNodes.length) {
noeud = noeud.lastChild;
}
nœud de retour ;
}
Utilisez facilement DOM
Parfois, vous souhaiterez peut-être parcourir le DOM, en appelant une fonction sur chaque nœud ou en renvoyant une valeur de chaque nœud. En fait, parce que ces idées sont si générales, DOM niveau 2 inclut déjà une extension appelée DOM Traversal and Range (qui définit des objets et des API pour itérer tous les nœuds du DOM), qui est utilisée pour appliquer une fonction et sélectionner une plage dans le DOM. . Étant donné que ces fonctions ne sont pas définies dans Internet Explorer (du moins pas encore), vous pouvez utiliser nextNode() pour faire quelque chose de similaire.
Ici, l’idée est de créer des outils simples et communs puis de les assembler de différentes manières pour obtenir l’effet souhaité. Si vous êtes familier avec la programmation fonctionnelle, cela vous semblera familier. La bibliothèque Beyond JS (voir Ressources) fait avancer cette idée.
Listing 9. Utilitaires DOM fonctionnels
// renvoie un tableau de tous les nœuds, en commençant à startNode et
// continue à travers le reste de l'arborescence DOM
fonction listNodes (startNode) {
var liste = nouveau tableau();
var noeud = startNode;
while(nœud) {
list.push(nœud);
nœud = nextNode (nœud);
}
liste de retour ;
}
// Identique à listNodes(), mais fonctionne à rebours depuis startNode.
// Notez que ce n'est pas la même chose que d'exécuter listNodes() et
// inversant la liste.
fonction listNodesReversed(startNode) {
var liste = nouveau tableau();
var noeud = startNode;
while(nœud) {
list.push(nœud);
nœud = prevNode (nœud);
}
liste de retour ;
}
// applique la fonction à chaque nœud de nodeList, renvoie une nouvelle liste de résultats
carte de fonctions (liste, func) {
var liste_résultats = new Array();
pour (var i = 0; i < list.length; i++) {
result_list.push(func(list[i]));
}
renvoie la liste_résultats ;
}
// applique le test à chaque nœud, renvoie une nouvelle liste de nœuds pour lesquels
// test (nœud) renvoie vrai
fonction filtre (liste, test) {
var liste_résultats = new Array();
pour (var i = 0; i < list.length; i++) {
if (test(list[i])) result_list.push(list[i]);
}
renvoie la liste_résultats ;
}
Le listing 9 contient quatre outils de base. Les fonctions listNodes() et listNodesReversed() peuvent être étendues à une longueur facultative, similaire à la méthode slice() du Array. Je laisse cela comme un exercice pour vous. Une autre chose à noter est que les fonctions map() et filter() sont complètement génériques et peuvent être utilisées pour travailler avec n'importe quelle liste (pas seulement des listes de nœuds). Maintenant, je vous montre quelques façons de les combiner.
Listing 10. Utilisation des utilitaires fonctionnels
// Une liste de tous les noms d'éléments dans l'ordre du document
fonction isElement (nœud) {
return node.nodeType == Node.ELEMENT_NODE;
}
fonction nodeName (nœud) {
retourner node.nodeName ;
}
var elementNames = map(filter(listNodes(document),isElement), nodeName);
// Tout le texte du document (ignore CDATA)
fonction isText (nœud) {
return node.nodeType == Node.TEXT_NODE;
}
fonction nodeValue (nœud) {
renvoyer node.nodeValue ;
}
var allText = map(filter(listNodes(document), isText), nodeValue);
Vous pouvez utiliser ces utilitaires pour extraire des identifiants, modifier des styles, rechercher certains nœuds et les supprimer, et bien plus encore. Une fois que les API DOM Traversal et Range sont largement implémentées, vous pouvez les utiliser pour modifier l'arborescence DOM sans créer au préalable la liste. Non seulement ils sont puissants, mais ils fonctionnent également de la même manière que ce que j’ai souligné ci-dessus.
La zone à risque des DOM
Notez que l'API DOM principale ne vous permet pas d'analyser les données XML dans le DOM, ni de sérialiser le DOM en XML. Ces fonctions sont définies dans l'extension DOM niveau 3 "Load and Save", mais elles ne sont pas encore entièrement implémentées, alors n'y pensez pas maintenant. Chaque plateforme (navigateur ou autre application DOM professionnelle) possède sa propre méthode de conversion entre DOM et XML, mais la conversion multiplateforme dépasse le cadre de cet article.
DOM n'est pas un outil très sûr, en particulier lorsque vous utilisez l'API DOM pour créer des arborescences qui ne peuvent pas être sérialisées au format XML. Ne mélangez jamais les API DOM1 sans espace de noms et les API compatibles avec l'espace de noms DOM2 (par exemple, createElement et createElementNS) dans le même programme. Si vous utilisez des espaces de noms, essayez de déclarer tous les espaces de noms à la position de l'élément racine et ne remplacez pas le préfixe de l'espace de noms, sinon les choses deviendront très confuses. De manière générale, tant que vous suivez votre routine, vous ne déclencherez pas de situations critiques qui pourraient vous causer des ennuis.
Si vous utilisez innerText et innerHTML d'Internet Explorer pour l'analyse, vous pouvez essayer d'utiliser la fonction elem(). En créant des utilitaires similaires, vous obtenez plus de commodité et héritez des avantages du code multiplateforme. Mélanger ces deux méthodes est très mauvais.
Certains caractères Unicode ne sont pas inclus dans XML. L'implémentation du DOM permet de les ajouter, mais la conséquence est qu'ils ne peuvent pas être sérialisés. Ces caractères incluent la plupart des caractères de contrôle et des caractères individuels dans des paires de substitution Unicode. Cela ne se produit que si vous essayez d'inclure des données binaires dans le document, mais c'est une autre situation délicate.
Conclusion
J'ai couvert une grande partie de ce que le DOM peut faire, mais le DOM (et JavaScript) peut faire bien plus encore. Étudiez et explorez ces exemples pour voir comment ils peuvent être utilisés pour résoudre des problèmes pouvant nécessiter des scripts clients, des modèles ou des API spécialisées.
Le DOM a ses limites et ses défauts, mais il présente également de nombreux avantages : il est intégré à de nombreuses applications ; il fonctionne de la même manière que vous utilisiez la technologie Java, Python ou JavaScript ; il est très simple d'utiliser SAX et d'utiliser les modèles mentionnés ci-dessus ; qui est à la fois simple et puissant à utiliser. Un nombre croissant d'applications commencent à prendre en charge DOM, notamment les applications basées sur Mozilla, OpenOffice et XMetaL de Blast Radius. De plus en plus de spécifications nécessitent le DOM et l'étendent (par exemple, SVG), afin que le DOM soit toujours autour de vous. Il serait judicieux d’utiliser cet outil largement déployé.