Dethe Elza ( [email protected] ), leitende technische Architektin, Blast Radius
Das Document Object Model (DOM) ist eines der am häufigsten verwendeten Werkzeuge zur Bearbeitung von XML- und HTML-Daten, doch sein Potenzial wird selten vollständig ausgeschöpft. Indem Sie die Vorteile des DOM nutzen und es einfacher verwenden, erhalten Sie ein leistungsstarkes Tool für XML-Anwendungen, einschließlich dynamischer Webanwendungen.
In dieser Ausgabe ist die Gastkolumnistin, Freundin und Kollegin Dethe Elza zu Gast. Dethe verfügt über umfangreiche Erfahrung in der Entwicklung von Webanwendungen mit XML und ich möchte ihm dafür danken, dass er mir bei der Einführung der XML-Programmierung mit DOM und ECMAScript geholfen hat. Weitere Kolumnen von Dethe finden Sie in dieser Kolumne.
- David Mertz
DOM ist eine der Standard-APIs zur Verarbeitung von XML und HTML. Es wird oft dafür kritisiert, dass es speicherintensiv, langsam und ausführlich ist. Dennoch ist es für viele Anwendungen die beste Wahl und sicherlich einfacher als SAX, die andere wichtige API von XML. DOM taucht nach und nach in Tools wie Webbrowsern, SVG-Browsern, OpenOffice usw. auf.
DOM ist großartig, weil es ein Standard ist und weithin implementiert und in andere Standards integriert ist. Standardmäßig ist der Umgang mit Daten unabhängig von der Programmiersprache (was vielleicht eine Stärke ist, aber zumindest die Art und Weise, wie wir mit Daten umgehen, konsistent macht). Das DOM ist mittlerweile nicht nur in Webbrowsern integriert, sondern ist auch Teil vieler XML-basierter Spezifikationen. Jetzt, da es Teil Ihres Arsenals ist und Sie es vielleicht immer noch gelegentlich verwenden, ist es wohl an der Zeit, die Vorteile, die es bietet, voll auszuschöpfen.
Nachdem Sie eine Weile mit dem DOM gearbeitet haben, werden Sie feststellen, dass sich Muster entwickeln – Dinge, die Sie immer wieder tun möchten. Mithilfe von Verknüpfungen können Sie mit langen DOMs arbeiten und selbsterklärenden, eleganten Code erstellen. Hier finden Sie eine Sammlung von Tipps und Tricks, die ich häufig verwende, sowie einige JavaScript-Beispiele.
Der erste Trick
beim insertAfter und prependChild
besteht darin, dass es „keinen Trick“ gibt.Das DOM verfügt über zwei Methoden zum Hinzufügen eines untergeordneten Knotens zu einem Containerknoten (normalerweise ein Element oder ein Dokument oder Dokumentfragment): appendChild(node) und insertBefore(node, referenceNode). Es scheint, als würde etwas fehlen. Was passiert, wenn ich nach einem Referenzknoten einen untergeordneten Knoten einfügen oder voranstellen möchte (wodurch der neue Knoten der erste in der Liste wird)? Viele Jahre lang bestand meine Lösung darin, die folgende Funktion zu schreiben:
Listing 1. Falsche Methoden zum Einfügen und Hinzufügen von
Funktion insertAfter(parent, node, referenceNode) {
if(referenceNode.nextSibling) {
parent.insertBefore(node, referenceNode.nextSibling);
} anders {
parent.appendChild(node);
}
}
Funktion prependChild(parent, node) {
if (parent.firstChild) {
parent.insertBefore(node, parent.firstChild);
} anders {
parent.appendChild(node);
}
}
Tatsächlich wurde die Funktion insertBefore() wie in Listing 1 so definiert, dass sie zu appendChild() zurückkehrt, wenn der Referenzknoten leer ist. Anstatt also die oben genannten Methoden zu verwenden, können Sie die Methoden in Listing 2 verwenden oder sie überspringen und einfach die integrierten Funktionen verwenden:
Listing 2. Korrekte Vorgehensweise zum Einfügen und Hinzufügen von zuvor
Funktion insertAfter(parent, node, referenceNode) {
parent.insertBefore(node, referenceNode.nextSibling);
}
Funktion prependChild(parent, node) {
parent.insertBefore(node, parent.firstChild);
}
Wenn Sie neu in der DOM-Programmierung sind, ist es wichtig, darauf hinzuweisen, dass Sie zwar mehrere Zeiger haben können, die auf einen Knoten verweisen, dieser Knoten jedoch nur an einer Stelle im DOM-Baum vorhanden sein kann. Wenn Sie es also in den Baum einfügen möchten, müssen Sie es nicht zuerst aus dem Baum entfernen, da es automatisch entfernt wird. Dieser Mechanismus ist praktisch, wenn Sie Knoten neu anordnen, indem Sie sie einfach an ihren neuen Positionen einfügen.
Wenn Sie gemäß diesem Mechanismus die Positionen zweier benachbarter Knoten (Knoten1 und Knoten2 genannt) vertauschen möchten, können Sie eine der folgenden Lösungen verwenden:
node1.parentNode.insertBefore(node2, node1);
oder
node1.parentNode.insertBefore(node1.nextSibling, node1);
Was kann man sonst noch mit DOM machen?
DOM wird häufig in Webseiten verwendet. Wenn Sie die Bookmarklets-Website besuchen (siehe Ressourcen), finden Sie viele kreative Kurzskripte, mit denen Sie Seiten neu anordnen, Links extrahieren, Bilder oder Flash-Anzeigen ausblenden und vieles mehr können.
Da Internet Explorer jedoch keine Knotenschnittstellenkonstanten definiert, die zur Identifizierung von Knotentypen verwendet werden können, müssen Sie sicherstellen, dass Sie, wenn Sie eine Schnittstellenkonstante weglassen, die Schnittstellenkonstante zunächst im DOM-Skript für das Web definieren.
Listing 3. Sicherstellen, dass der Knoten definiert ist
if (!window['Node']) {
window.Node = neues Objekt();
Node.ELEMENT_NODE = 1;
Node.ATTRIBUTE_NODE = 2;
Node.TEXT_NODE = 3;
Node.CDATA_SECTION_NODE = 4;
Node.ENTITY_REFERENCE_NODE = 5;
Node.ENTITY_NODE = 6;
Node.PROCESSING_INSTRUCTION_NODE = 7;
Node.COMMENT_NODE = 8;
Node.DOCUMENT_NODE = 9;
Node.DOCUMENT_TYPE_NODE = 10;
Node.DOCUMENT_FRAGMENT_NODE = 11;
Node.NOTATION_NODE = 12;
}
Listing 4 zeigt, wie alle in einem Knoten enthaltenen Textknoten extrahiert werden:
Listing 4. Interner Text
Funktion innerText(node) {
// Ist das ein Text- oder CDATA-Knoten?
if (node.nodeType == 3 || node.nodeType == 4) {
return node.data;
}
var i;
var returnValue = [];
for (i = 0; i < node.childNodes.length; i++) {
returnValue.push(innerText(node.childNodes[i]));
}
return returnValue.join('');
}
Verknüpfungen
Leute beschweren sich oft darüber, dass das DOM zu ausführlich ist und dass einfache Funktionen viel Code erfordern. Wenn Sie beispielsweise ein
Listing 5. Der „lange Weg“ zum Erstellen eines
Wenn Sie auf diese Weise häufig Knoten erstellen, wird Sie die Eingabe dieses ganzen Codes schnell ermüden. Es musste eine bessere Lösung geben – und die gab es! Hier ist ein Dienstprogramm, mit dem Sie Elemente erstellen, Elementeigenschaften und -stile festlegen und untergeordnete Textknoten hinzufügen können. Mit Ausnahme des Namensparameters sind alle anderen Parameter optional.
Listing 6. Funktionsverknüpfung elem()
Funktion elem(name, attrs, style, text) {
var e = document.createElement(name);
if (attrs) {
for (Attribute eingeben) {
if (key == 'class') {
e.className = attrs[key];
} else if (key == 'id') {
e.id = attrs[key];
} anders {
e.setAttribute(key, attrs[key]);
}
}
}
if (Stil) {
for (Schlüssel im Stil) {
e.style[key] = style[key];
}
}
if (Text) {
e.appendChild(document.createTextNode(text));
}
Rückkehr e;
}
Mit dieser Verknüpfung können Sie das
Listing 7. Eine einfache Möglichkeit, ein
Dieses Dienstprogramm kann Ihnen viel Zeit sparen, wenn Sie schnell eine große Anzahl komplexer DHTML-Objekte erstellen möchten. Muster bedeutet hier: Wenn Sie eine bestimmte DOM-Struktur haben, die häufig erstellt werden muss, verwenden Sie ein Dienstprogramm, um diese zu erstellen. Dies reduziert nicht nur die Menge an Code, die Sie schreiben, sondern auch das wiederholte Ausschneiden und Einfügen von Code (eine Fehlerursache) und erleichtert das klare Denken beim Lesen des Codes.
Was kommt als nächstes?
Dem DOM fällt es oft schwer, Ihnen zu sagen, welcher Knoten in der Reihenfolge des Dokuments der nächste ist. Hier sind einige Dienstprogramme, die Ihnen helfen, zwischen Knoten vorwärts und rückwärts zu wechseln:
Listing 8. nextNode und prevNode
// Nächsten Knoten in Dokumentreihenfolge zurückgeben
Funktion nextNode(node) {
if (!node) return null;
if (node.firstChild){
return node.firstChild;
} anders {
return nextWide(node);
}
}
// Hilfsfunktion für nextNode()
Funktion nextWide(node) {
if (!node) return null;
if (node.nextSibling) {
return node.nextSibling;
} anders {
return nextWide(node.parentNode);
}
}
// Vorherigen Knoten in Dokumentreihenfolge zurückgeben
Funktion prevNode(node) {
if (!node) return null;
if (node. previousSibling) {
return previousDeep(node. previousSibling);
}
return node.parentNode;
}
// Hilfsfunktion für prevNode()
Funktion previousDeep(node) {
if (!node) return null;
while (node.childNodes.length) {
node = node.lastChild;
}
Rückkehrknoten;
}
Nutzen Sie ganz einfach DOM
Manchmal möchten Sie möglicherweise das DOM durchlaufen, eine Funktion auf jedem Knoten aufrufen oder einen Wert von jedem Knoten zurückgeben. Da diese Ideen so allgemein sind, enthält DOM Level 2 tatsächlich bereits eine Erweiterung namens DOM Traversal and Range (die Objekte und APIs für die Iteration aller Knoten im DOM definiert), die zum Anwenden von Funktionen und zum Auswählen eines Bereichs im DOM verwendet wird . Da diese Funktionen im Internet Explorer nicht definiert sind (zumindest noch nicht), können Sie mit nextNode() etwas Ähnliches tun.
Hier geht es darum, einige einfache, gängige Werkzeuge zu erstellen und diese dann auf unterschiedliche Weise zusammenzusetzen, um den gewünschten Effekt zu erzielen. Wenn Sie mit funktionaler Programmierung vertraut sind, wird Ihnen dies bekannt vorkommen. Die Beyond JS-Bibliothek (siehe Ressourcen) führt diese Idee weiter.
Listing 9. Funktionale DOM-Dienstprogramme
// ein Array aller Knoten zurückgeben, beginnend bei startNode und
// Fortsetzung durch den Rest des DOM-Baums
Funktion listNodes(startNode) {
var list = new Array();
var node = startNode;
while(Knoten) {
list.push(node);
node = nextNode(node);
}
Rückgabeliste;
}
// Das Gleiche wie listNodes(), funktioniert aber ab startNode rückwärts.
// Beachten Sie, dass dies nicht dasselbe ist wie das Ausführen von listNodes() und
// die Liste umkehren.
Funktion listNodesReversed(startNode) {
var list = new Array();
var node = startNode;
while(Knoten) {
list.push(node);
node = prevNode(node);
}
Rückgabeliste;
}
// func auf jeden Knoten in nodeList anwenden, neue Ergebnisliste zurückgeben
Funktionskarte(Liste, Funktion) {
var result_list = new Array();
for (var i = 0; i < list.length; i++) {
result_list.push(func(list[i]));
}
return result_list;
}
// Test auf jeden Knoten anwenden, eine neue Liste der Knoten zurückgeben, für die
// test(node) gibt true zurück
Funktion filter(list, test) {
var result_list = new Array();
for (var i = 0; i < list.length; i++) {
if (test(list[i])) result_list.push(list[i]);
}
return result_list;
}
Listing 9 enthält vier grundlegende Tools. Die Funktionen listNodes() und listNodesReversed() können auf eine optionale Länge erweitert werden, ähnlich der Slice()-Methode des Arrays. Ich überlasse dies als Übung für Sie. Beachten Sie außerdem, dass die Funktionen „map()“ und „filter()“ völlig generisch sind und für die Arbeit mit jeder Liste (nicht nur mit Knotenlisten) verwendet werden können. Jetzt zeige ich Ihnen einige Möglichkeiten, wie sie kombiniert werden können.
Listing 10. Verwendung funktionaler Dienstprogramme
// Eine Liste aller Elementnamen in Dokumentreihenfolge
Funktion isElement(node) {
return node.nodeType == Node.ELEMENT_NODE;
}
Funktion nodeName(node) {
return node.nodeName;
}
var elementNames = map(filter(listNodes(document),isElement), nodeName);
// Der gesamte Text aus dem Dokument (ignoriert CDATA)
Funktion isText(node) {
return node.nodeType == Node.TEXT_NODE;
}
Funktion nodeValue(node) {
return node.nodeValue;
}
var allText = map(filter(listNodes(document), isText), nodeValue);
Mit diesen Dienstprogrammen können Sie IDs extrahieren, Stile ändern, bestimmte Knoten finden und entfernen und vieles mehr. Sobald die DOM-Traversal- und Range-APIs umfassend implementiert sind, können Sie sie verwenden, um den DOM-Baum zu ändern, ohne zuerst die Liste zu erstellen. Sie sind nicht nur leistungsstark, sondern funktionieren auch auf ähnliche Weise wie das, was ich oben hervorgehoben habe.
Die Gefahrenzone des DOM
Beachten Sie, dass Sie mit der DOM-Kern-API keine XML-Daten in DOM analysieren oder DOM in XML serialisieren können. Diese Funktionen sind in der DOM Level 3-Erweiterung „Load and Save“ definiert, aber noch nicht vollständig implementiert, denken Sie also jetzt nicht darüber nach. Jede Plattform (Browser oder andere professionelle DOM-Anwendung) verfügt über ihre eigene Methode zur Konvertierung zwischen DOM und XML, die plattformübergreifende Konvertierung geht jedoch über den Rahmen dieses Artikels hinaus.
DOM ist kein sehr sicheres Werkzeug – insbesondere wenn die DOM-API zum Erstellen von Bäumen verwendet wird, die nicht als XML serialisiert werden können. Mischen Sie niemals DOM1-Nicht-Namespace-APIs und DOM2-Namespace-fähige APIs (z. B. createElement und createElementNS) im selben Programm. Wenn Sie Namespaces verwenden, versuchen Sie, alle Namespaces an der Position des Stammelements zu deklarieren und das Namespace-Präfix nicht zu überschreiben, da es sonst sehr verwirrend wird. Generell gilt: Solange Sie Ihrer Routine folgen, werden Sie keine kritischen Situationen auslösen, die Sie in Schwierigkeiten bringen könnten.
Wenn Sie innerText und innerHTML des Internet Explorers zum Parsen verwendet haben, können Sie es mit der Funktion elem() versuchen. Durch die Erstellung ähnlicher Dienstprogramme erhalten Sie mehr Komfort und profitieren von den Vorteilen plattformübergreifenden Codes. Das Mischen dieser beiden Methoden ist sehr schlecht.
Einige Unicode-Zeichen sind nicht in XML enthalten. Durch die Implementierung des DOM können Sie diese hinzufügen, die Konsequenz ist jedoch, dass sie nicht serialisiert werden können. Zu diesen Zeichen gehören die meisten Steuerzeichen und einzelne Zeichen in Unicode-Ersatzpaaren. Dies passiert nur, wenn Sie versuchen, Binärdaten in das Dokument aufzunehmen, aber das ist eine weitere Fallstricksituation.
Abschluss
Ich habe viel darüber besprochen, was das DOM kann, aber es gibt noch so viel mehr, was das DOM (und JavaScript) kann. Studieren und erforschen Sie diese Beispiele, um zu sehen, wie sie zur Lösung von Problemen verwendet werden können, die möglicherweise Client-Skripte, Vorlagen oder spezielle APIs erfordern.
Das DOM hat seine Grenzen und Mängel, aber es hat auch viele Vorteile: Es ist in viele Anwendungen integriert; es funktioniert auf die gleiche Weise, egal ob Sie Java-Technologie, Python oder JavaScript verwenden; es ist sehr einfach, die oben genannten Vorlagen zu verwenden; die sowohl einfach als auch leistungsstark zu bedienen ist. Immer mehr Anwendungen beginnen, DOM zu unterstützen, darunter Mozilla-basierte Anwendungen, OpenOffice und XMetaL von Blast Radius. Immer mehr Spezifikationen erfordern das DOM und erweitern es (z. B. SVG), sodass das DOM immer in Ihrer Nähe ist. Es wäre ratsam, dieses weit verbreitete Tool zu verwenden.