Sep 2008

Javascript – Manipulation des DOM-Baums

 
 

Nun geht's endlich an die Struktur: Die Javascript-Methoden des Node-Objekts ändern in erster Linie die Struktur des DOM-Baums: Sie operieren auf Knoten, fügen Knoten in den Dokumentenbaum ein, kopieren Knoten und entfernen ganze Äste aus dem Baum.

insertBefore(newChild, refChild) replaceChild(newChild, oldChild)
removeChild(oldChild) appendChild(newChild)
cloneNode(deep) normalize()
isSupported(feature, version)

node.insertBefore (newchild,oldchild)

fügt unterhalb von node einen Knoten newchild vor oldchild ein.

Ein Bild von Kong
<img id="jsdomIB" src="kong.jpg" width="152" height="145" alt="Ein Bild von Kong" />

Das Skript setzt die Variable node auf das Element mit der ID jsdomIB und prüft, ob node ein alt-Attribut hat und ob dieses alt-Attribut auch nicht leer ist. Erst dann weist das Javascript den Stringwert des alt-Attributs der Variablen alt zu und erzeugt einen Textknoten mit dem Inhalt der Variablen alt.

node.parentNode.insertBefore(imgHeader, node ); fügt den Textknoten imgHeader vor dem Knoten ein, an dem node hängt – also in node.parentNode vor node.

Das Script fügt noch ein frisch erzeugtes BR-Element in den Vaterknoten node.parentNode vor dem Knoten node ein und erzeugt dadurch einen Zeilenumbruch.

function insertBefore()
{
   var node = document.getElementById('jsdomIB');
   if (node.getAttribute('alt') && node.getAttribute('alt') != "") {
      var alt = node.getAttribute('alt');
      var imgHeader = document.createTextNode(alt);
      node.parentNode.insertBefore(imgHeader, node );
      var br = document.createElement('br');
      node.parentNode.insertBefore(br, node );
   }
}

Mindestens ebenso oft wie das Einfügen neuer Knoten vor einem vorhandenen Knoten ist das Einfügen eines Knotens nach einem Element gefragt – aber eine passende Javascript-Funktion gibt es dafür nicht. insertBehind simmuliert die fehlende Methode mit der Hilfe der Eigenschaft nextSibling.

Statt den neuen Knoten mit node.parentNode.insertBefore(br, node ); vor dem Knoten node zu hängen, fragt die Anweisung, ob ein nextSibling von node existiert (if (node.nextSibling)). Wenn ja, dann fügt die Anweisung node.parentNode.insertBefore(br, node.nextSibling); den neuen Knoten vor dem nächsten Knoten auf derselben Ebene ein – und das ist hinter dem Knoten node.

Wenn der Knoten der letzte Knoten seines Vaterknotens ist, darf der Knoten ohne jegliches Federlesen als letztes Kind an den Elternknoten gehangen werden: node.parentNode.appendChild(imgHeader);.

function insertBehind()
{
   var node = document.getElementById('jsdomIB');
   if (node.getAttribute('alt') && node.getAttribute('alt') != "") {
      var alt = node.getAttribute('alt');
      var imgHeader = document.createTextNode(alt);
      var br = document.createElement('br');
      
      if (node.nextSibling) {
         node.parentNode.insertBefore(imgHeader, node.nextSibling );
         node.parentNode.insertBefore(br, node.nextSibling);
      } else {
         node.parentNode.appendChild(br);
         node.parentNode.appendChild(imgHeader);
      }
   }
}

node.replaceChild(newChild, oldChild)

ersetzt unterhalb des Knotens node den Knoten oldChild durch den Knoten newChild.

Dabei können sowohl der Knoten, der ersetzt wird als auch der Knoten, der den alten Knoten ersetzt, vollständige Fragmente mitsamt beliebig vielen Kindknoten sein. Der Knoten, der den alten Knoten ersetzt, kann sowohl aus dem Dokument extrahiert als auch ein neu erzeugter Knoten sein.

  • Montag
  • Dienstag
  • Mittwoch
<ul id="jsdomUl1">
   <li>Montag</li>
   <li>Dienstag</li>
   <li>Mittwoch</li>
</ul>

Um den Knoten mit der id jsdomUl1 durch den neu erzeugten Knoten newNode zu ersetzen, muss newNode unterhalb des Elternknotens des alten Knotens eingehangen werden: oldNode.parentNode.replaceChild(newNode, oldNode);.

function replaceChild()
{
   var newNode = document.createElement('p');
   newNode.setAttribute('id', 'jsdomUl1');
   
   var newText = document.createTextNode('An drei Tagen in der Woche');
   newNode.appendChild(newText);
   
   var oldNode = document.getElementById('jsdomUl1');
   oldNode.parentNode.replaceChild(newNode, oldNode);
   return false; 
}

Wenn das Skript nach dem Ersetzen des alten Knotens erneut angestoßen wird, würde es einen Fehler erzeugen, weil der Knoten mit der id jsdomUl1 nun ja nicht mehr existiert. Darum bekommt der neu erzeugte Knoten das id-Attribut jsdomUl1.

node.appendChild (child)

Fügt dem Knoten node ein Kind als childNode hinzu. Wenn node schon Kinder hat, wird childNode hinter die älteren Kinder gehangen. node muss ein Elementknoten sein, Textknoten können keine Kinder haben.

<div id="fix">
</div>
function append()
{
   var fixed = document.getElementById('fix');
   var newP = document.createElement('p');
   var newText = document.createTextNode('Neuer Absatz');
   fixed.appendChild(newP);
   newP.appendChild(newText);
}
getElementById4.png
  • var fixed = document.getElementById('fix'); findet das Element mit dem id-Attribut 'fix' und setzt die Variable fixed als Referenz auf das Objekt.
  • var newP = document.createElement('p'); erzeugt einen neuen Elementknoten vom Typ p.
  • var newText = document.createTextNode('Neuer Absatz'); erzeugt einen neuen Textknoten und setzt die Variable newText als Referenz auf den Textknoten.
  • fixed.appendChild(newP); fügt den Knoten newP als Kindknoten an den Knoten fixed an.
  • newP.appendChild(newText); fügt den Textknoten an den Knoten newP an.

Das Einfügen neuer Knoten mit appendChild() in den leeren Platzhalter <div id="fix"> ist einfach, aber Platzhalter sind Ballast für das Markup und was passiert, wenn ein Skript etwas mit vielen, vielleicht sogar dynamisch erzeugten Elementen anstellen soll?

node.removeChild(oldChild)

Entfernt den Knoten oldChild unterhalb des Knotens node.

Dabei kann der Knoten oldChild ein vollständiges Fragment mit eigenen Kindknoten sein – die Methode removeChild entfernt ein komplettes Fragment mit sämtlichen Unterknoten.

zitrus.gif

<p id="Zitrus">
   <img src="zitrus.gif" width="150" height="115" alt="zitrus.gif" />
</p>

Der Knoten wird anhand seines id-Attributs Zitrus identifiziert und schnell und schmerzlos mitsamt seinem Kindknoten aus seinem Elternknoten entfernt: myNode.parentNode.removeChild(myNode);. Damit ein erneuter Aufruf nicht zu einem Skriptfehler führt, weil der Knoten mit der id Zitrus nicht mehr gefunden wird, prüft die erste Anweisung, ob der Knoten mit der id Zitrus existiert (if (document.getElementById('Zitrus'))).

if (document.getElementById('Zitrus')) {
   var myNode = document.getElementById('Zitrus');
   myNode.parentNode.removeChild(myNode); 
}

node.cloneNode(deep)

kopiert einen Knoten node. Der Parameter deep gibt an, ob der Knoten mitsamt sämtlichen Kindknoten geklont werden soll (deep = true).

Die erste Bedingung der if-Abfrage prüft, ob überhaupt ein TABLE-Element im Dokument existiert, die zweite, ob es bereits ein Element mit der id newTable gibt (damit das kopierte Element nicht mehrfach eingefügt wird). Wenn es also eine Tabelle im Dokument gibt, aber kein Element mit der id newTable, sucht die Anweisung var table = document.getElementsByTagName('table')[0] die erste Tabelle im Dokument und verweist mit der Variablen table auf die Tabelle.

if (document.getElementsByTagName('table') && 
                   !document.getElementById('newTable')) {
   var table = document.getElementsByTagName('table')[0];
   
   var newNode = table.cloneNode(true);
   
   newNode.setAttribute('id','newTable');
   var form = document.getElementById('jsDOMMan40');
   form.parentNode.insertBefore(newNode, form);
} else {
   alert('Element nicht gefunden oder Tabelle bereits vorhanden');
}

var newNode = table.cloneNode(true); erzeugt eine Kopie der der kompletten Tabelle und die nächste Anweisung (newNode.setAttribute('id','newTable');) gibt der Kopie die id newTable. Am Ende wird die Kopie der Tabelle vor das Formular-Element eingefügt.

node.normalize()

Die Methode normalize() normalisiert all Textknoten des Unterbaums von node und verbindet zwei oder mehr aufeinander folgende Textknoten in einen einzigen Knoten.

In der Normalform können Textknoten nur vom Markup wie Tags, Kommentare, Processing Instructions, CDATA-Sektionen und Entity Referenzen getrennt werden. normalize() ist gefragt, wenn sicher gestellt sein muss, dass der DOM-View des Dokuments nach dem Speichern und Neuladen gleich bleiben muss.

Die erste Anweisung liest das BODY-Element des Dokuments in den Knoten body, damit die Knoten-Methode body.normalize(); direkt die Normalisierung durchführen kann. Für die Ausgabe der Knoten nach der Normalisierung speichert var elems = body.childNodes alle Kindknoten des BODY-Elements – so kann der Block der for-Anweisung die Knotennamen in die Stringvariable nachher eintragen. Nach dem for-Block erzeugt das Skript noch einen Textknoten text und ersetzt durch die Anweisung pre.replaceChild(text, pre.firstChild) den alten Inhalt (die Liste der Knoten vor der Normalisierung) des PRE-Elements.

function normalize() 
{
	var body = document.getElementsByTagName('body')[0];
	
	body.normalize();
	var elems = body.childNodes;
	var nachher = "";
	for (var i=0; i<elems.length; i++) {
		nachher += elems[i].nodeName + " ";
	}
	var text = document.createTextNode(nachher);
	var pre = document.getElementById('manf20');
	pre.replaceChild(text, pre.firstChild);
	return false;
}

Am Rande: In FireFox ist vom Effekt der Normalisierung nichts zu sehen – allem Anschein nach normalisiert FireFox den DOM-Baum automatisch für die Ansicht im DOM-Inspektor.

Firefox DIV #text #comment #text #comment #text DIV #text #comment #text DIV #text
DIV #text #comment #text #comment #text DIV #text #comment #text DIV #text
Opera DIV #text #comment #text #comment #text DIV #text #comment #text DIV #text #text
DIV #text #comment #text #comment #text DIV #text #comment #text DIV #text
Safari DIV #text #text #text DIV #text #text DIV #text #text
DIV #text DIV #text DIV #text

Hinter den Kulissen

Neu eingefügte Knoten lassen sich im Quellcode des Dokuments nicht erspähen. Sie wurden lediglich als Knoten in den DOM-Baum des Browsers eingefügt, nicht aber als Markup und Text in den Quelltext geschrieben. Das HTML-Markup, das hier beim Auslösen des Buttons appendChild erscheint, ist nichts als ein „Fake“. Zudem hat sich das Skript darauf verlassen, dass der Platzhalter <div id="fix"> tatsächlich vorhanden ist – eine der vorrangigsten Fehlerquellen bei der Programmierung auf dem DOM: Wenn der Platzhalter nicht da ist, passiert beim Klick auf appendChild() nichts und nur eine neue Fehlermeldung füllt die Javascript-Konsole.

   


Copyright © 2000 - 2010 Media Engineering Alle Rechte vorbehalten
Design + Programmierung Media EngineeringImpressum und Nutzungsbestimmungen