Erschaffen und Vernichten von Elementen
Das Beste am DOM ist, dass man Elemente und Attribute nicht nur lesen und ändern, sondern auch neue erstellen oder alte löschen kann. Die folgenden Methoden helfen uns dabei.
Elemente und Inhalte erstellen
createElement(element)
- Erstellt ein neues Element
element
. createTextNode(string)
- Erstellt einen neuen Textknoten mit dem Wert
string
.
Neu erschaffene Elemente werden nicht sofort dem Dokument hinzugefügt, sondern verweilen im Speicher bis wir sie irgendwo in der Baumstrukur einfügen. Daher müssen diese Methoden mit dem Dokumentobjekt anstatt eines Knotenobjektes aufgerufen werden.
JavaScript: meinNeuerAbsatz=document.createElement('p'); meinNeuerText=document.createTextNode('this is a new paragraph');
Änderung bestehender Inhalte
setAttribute(attribute,value)
- Fügt dem Objekt ein neues Attribut
attribute
mit dem Wertvalue
hinzu. appendChild(child)
- Fügt dem Objekt den Kindknoten
child
hinzu.child
muss ein Objekt sein, keinstring
. cloneNode(bool)
- Kopiert den gesamten Knoten - wenn bool
true
ist, inklusive aller Kindknoten. hasChildNodes()
- Prüft ob ein Objekt Kindknoten enthält und gibt
true
zurück, falls das der Fall ist. insertBefore(newchild,oldchild)
- Fügt
newchild
voroldchild
in das Dokument ein. removeChild(oldchild)
- Löscht den Kindknoten
oldchild
. replaceChild(newchild,oldchild)
- Ersetzt
oldchild
durchnewchild
. removeAttribute(attribute)
- Löscht das Attribut
attribute
des Elements.
Das Bildbeispiel
Nehmen wir an wir haben ein paar Verweise, die auf Bilder zeigen. In Webbrowsern ohne JavaScript sollen diese Bilder in einem neuen Fenster angezeigt werden. Webbrowser mit JavaScript sollen die Bilder direkt hinter den Verweisen anzeigen.
HTML: <ul id="imglist"> <li><a href="home.gif" target="_blank">Home (new window)</a></li> <li><a href="home_on.gif" target="_blank">Home active (new window)</a></li> <li><a href="jscsshtml.gif" target="_blank">HTML-CSS-Javascript (new window)</a></li> </ul>
Wenn JavaScript und das DOM vorhanden sind wollen wir:
- Den Text "(new window)" in den Verweisen löschen.
- Einen Befehl hinzufügen, der die Funktion
popw()
aufruft.
Diese Funktion soll
- Das Bild unterhalb des Verweises anzeigen, falls es nicht schon vorhanden sein sollte.
- Das Bild löschen falls es vorhanden ist (um eine mehrmalige Anzeige zu vermeiden).
- Das Bild löschen sobald der Besucher es anklickt.
Das erste Problem zu lösen ist einfach:
JavaScript: function imgpop() { var il,imga,imgatxt; // gehe durch alle LI elemente in imglist il=document.getElementById('imglist').getElementsByTagName('li'); for(i=0;i<il.length;i++) { // nimm den ersten Verweis in dem derzeitigen LI imga=il[i].getElementsByTagName('a')[0]; // lösche (new window) im Verweistext // ( nodeValue des firstChild ) imgatxt=imga.firstChild; imgatxt.nodeValue=imgatxt.nodeValue.replace(/ \(new window\)/,''); // füge die Aufrufe hinzu um popw() zu starten imga.onclick=function(){return popw(this);} //imga.onkeypress=function(){return popw(this);} } }
In der Funktion popw()
verwenden wir einige der bereits
genannten Methoden:
Javascript: function popw(o) { var newimg; // falls der übergeordnete Knoten (li) bereits ein Bild enthält if(o.parentNode.getElementsByTagName('img').length>0) { // lösche es o.parentNode.removeChild(o.parentNode.getElementsByTagName('img')[0]); } else { // ansonsten, erschaffe ein neues Bild und rufe eine Funktion auf // die es löscht wenn der Benutzer es anklickt. newimg=document.createElement('img'); newimg.style.display='block'; newimg.onclick=function(){this.parentNode.removeChild(this);}; newimg.src=o.href; o.parentNode.appendChild(newimg) } return false; } Probier dieses Beispiel aus
Das Datumpicker-Beispiel
Nehmen wir an, wir haben ein Formular, das Datumsfelder enthält und wir wollen Besuchern mit JavaScript einen Kalender anbieten, der die Auswahl erleichtert. Besucher ohne JavaScript sollen ein einfaches Textfeld erhalten.
Die Funktion des Kalenders lassen wir hierbei aussen vor, da es den Rahmen dieses Kurses sprengen würde, und konzentrieren uns auf den Aufruf eines fiktiven Kalenders.
Wir beginnen mit dem nötigen HTML.
Die Elemente, die einen Kalender aufrufen sollen, markieren wir mit einer Klasse
namens date
.
HTML: <h1>Flight booking</h1> <form action="nosend.php" method="post" onsubmit="return check(this);"> <p>Step 1 of 4</p> <h2>Please select your dates</h2> <p> <label for="startdate">Start Date</label> <input type="text" class="date" id="startdate" name="startdate" /> </p> <p> <label for="enddate34;>End Date</label> <input type="text" class="date" id="enddate" name="enddate" /> </p> <p> <input type="submit" value="send" /> </p> </form>
Wir gehen durch alle input
Elemente im document
,
und prüfen, ob deren className
date
beinhaltet (Elemente können mehrere Klassen haben, es langt also nicht das
className
Attribut mit dem Namen zu vergleichen).
Falls dem so ist, erstellen wir ein neues Verweisobjekt und ein Textobjekt. Wir fügen den Text dem Verweis hinzu, und fügen den Verweis hinter dem Formularfeld ein.
JavaScript: function addPickerLink() { var inputs,pickLink,pickText; // gehe durch alle input Elemente inputs=document.getElementsByTagName('input'); for(i=0;i<inputs.length;i++) { // prüfe, ob "date" in dem Wert der Klasse vorkommt if(/date/.test(inputs[i].className)) { // erstelle einen neuen Verweis und einen Text pickLink=document.createElement('a'); pickText=document.createTextNode('pick a date'); // füge den Text dem Verweis hinzu pickLink.appendChild(pickText); // setze den Verweis auf # und füge die Funktion hinzu. pickLink.setAttribute('href','#'); pickLink.onclick=function(){picker(this);return false;}; //pickLink.onkeypress=function(){picker(this);return false;}; // füge den Verweis als neuen Kindknoten dem Elternknoten // hinzu. inputs[i].parentNode.appendChild(pickLink) } } } Probier das Pickerbeispiel aus.
Jetzt folgt jedem Datumsfeld ein Verweis, der die Funktion picker()
aufruft.
Dieser Funktion muß jetzt beigebracht werden, wohin der Rückgabewert geschrieben werden soll.
Da wir den Verweis als Objekt der Funktion übergeben, ist das das vorherige
Element auf der gleichen Ebene, das input
Element.
JavaScript: function picker(o) { alert('This is a simulation only.') // no real function today o.previousSibling.value='26/04/1975'; }
Fast richtig, allerdings kann das Einfügen des Verweises am Ende als neuer Kindknoten zu Problemen führen. Es kann beispielsweise sein, das im HTML auf dem Eingabefeld eine Leerzeile folgt, was von manchen Webbrowsern als eigener Knoten angesehen wird und damit zu einem Fehler führt.
Daher müssen wir prüfen, ob das vorherige Element auch wirklich ein Element ist, bevor wir den Wert ändern.
JavaScript: function picker(o) { alert('This is a simulation only.') // no real function today while(o.previousSibling.nodeType!=1) { o=o.previousSibling; } o.previousSibling.value='26/04/1975'; }
Schleifen sind meistens langsam und geben den Eindruck von nicht optimiertem Code. Um die Schleife zu umgehen, müssen wir die Funktion umschreiben.
Optimieren der addPickerLink()
Funktion
Die Verwendung von appendChild()
ist kinderleicht, allerdings
macht sie uns abhängig von dem verwendetem HTML. Was passiert wenn wir in der
Zukunft beispielsweise ein SPAN
mit einem Stern hinter dem Eingabefeld
einfügen möchten um es als Pflichtfeld zu markieren?
Der Trick ist insertBefore()
zusammen mit dem folgenden Knoten
auf der gleichen Ebene, dem nextSibling
, zu verwenden.
JavasSript: function addPickerLink() { var inputs,pickLink,pickText; inputs=document.getElementsByTagName('input'); for(i=0;i<inputs.length;i++) { if(/date/.test(inputs[i].className)) { pickLink=document.createElement('a'); pickText=document.createTextNode('pick a date'); pickLink.appendChild(pickText); pickLink.setAttribute('href','#'); pickLink.onclick=function(){picker(this)}; //pickLink.onkeypress=function(){picker(this)}; // füge den Verweis hinter dem Eingabefeld eininputs[i].parentNode.appendChild(pickLink)inputs[i].parentNode.insertBefore(pickLink,inputs[i].nextSibling); } } } Probier das optimierte Pickerbeispiel aus.
Dinge, die man sich merken sollte
Das ist Alles - mit diesen Hilfsmitteln können wir jedes einzelne Element im Dokument erreichen und ändern, und es dem Besucher einfacher machen ohne von JavaScript abhängig zu sein.
Am Anfang ist es etwas verwirrend, doch wenn man sich einmal in das DOM hineingedacht hat, wird es mit jedem mal einfacher.
Stolpersteine, die immer mal auftauchen können:
- Gehe auf Nummer Sicher: Überprüfe ob ein Element vorhanden ist,
bevor du es veränderst. Einige Webbrowser zucken mit den Achseln und
geben
false
zurück wenn wir aufobject.nextSibling.nodeName
prüfen, und es keinen weiteren Knoten auf der gleichen Ebene gibt oder dieser ein Textknoten ist. Andere Webbrowser brechen mit der Fehlermeldung ab, da wir versuchen ein Attribut eines nicht vorhandenen Elements zu ändern. - Versuche Dich soweit wie möglich unabhängig von HTML zu machen, da manche Browser Zeilenumbrüche als Knoten ansehen, und andere nicht. HTML Änderungen treten ausserdem dauernd auf, und sollten nicht Änderungen im JavaScript erfordern.
- Der Inhalt eines Elementes wird ausgelesen, indem man die Werte der Kindknoten
ausliest, nicht den Wert des Elementes!
document.getElementsByTagName('h2')[0].nodeValue
ist leer,document.getElementsByTagName('h2')[0].firstChild.nodeValue
ist, was wir wollen. - Wenn man auf
nodeName
oderattribute
prüft sollte man sicher gehen, unabhängig von Groß- und Kleinschreibung zu bleiben. Abhängig vom Dokumententyp geben Browser diese Werte als groß oder klein zurück. - Vom DOM erstelltes HTML ist in den meisten Fällen nicht XML-konform; wenn man erstelltes HTML weiterverwenden will, sollte es vorher gesäubert werden.
- Versuche Schleifen zu vermeiden, wenn möglich benutze IDs.
- Beachte die richtige Schreibweise. Oft passiert es,
das man Code stundenlang ändert, nur weil man sich auf ein
getElementsById
verlassen hat. - Mach Dich mit den Objekten und Attributen von JavaScript und HTML vertraut. Es bringt nichts auf Attribute zu hoffen, die von Anfang an nicht geplant waren.
- Nicht jeder schreibt Code wie man selbst. Es ist zum Beispiel besser zu prüfen ob das Klassenattribut einen bestimmten Text beinhaltet als sich darauf zu verlassen, dass nur eine CSS-Klasse verwendet wird.
Und was ist mit innerHTML?
Als Microsoft den Internet Explorer 4 herausbrachte, kam auch
innerHTML
auf, ein weiterer Weg, Inhalte zu erstellen oder zu
ändern. Es ist ein viel einfacherer Weg als der vom W3C
vorgeschlagene, besonders wenn man die Inhalte von allen
Kindknoten eines Elements auslesen will. Um das DOM-konform
zu erreichen, muß man
ziemlich komplexe Umwege [1]
gehen.
innerHTML
ist einfacher, hat aber auch seine Nachteile. Der
größte Nachteil ist dass der zurückgegeben Wert ein Text ist, und
keine Sammlung von Objekten. Ausserdem ist innerHTML
nur auf
HTML anwendbar, nicht auf
XML, und
DOM ist dazu gedacht, jede Art
von strukturiertem Text zu verarbeiten. Ein Vergleich der beiden Techniken und
Information darüber, welche Webbrowser innerHTML
verstehen,
kann in dem
DOM Kapitel von
Quirksmode.org[2]
oder der Diskussion bei
Developer-x[3] nachgelesen werden.
Links
[1] innerHTML für Mozilla http://www.webfx.nu/dhtml/mozInnerHTML/mozInnerHtml.html
[2] DOM Unterstützung und ein Vergleich mit innerHTML http://www.quirksmode.org
[3] DOM gegen innerHTML http://www.developer-x.com/content/innerhtml/