Document Object Model

Wat is de DOM?

We hebben ons in de vorige hoofdstukken vooral bezig gehouden met JavaScript op zich. Maar omdat we onze scripts vooral zullen laten samenwerken met een HTML-pagina, is het handig en belangrijk dat we daar één en ander over weten. Onze scripts zullen vasthangen aan een web- pagina en die komt altijd eerst, die brengt ons script mee van de web-server, niet andersom. Onze web-pagina, simpel of complex, is de omgeving die we willen aanpassen via JavaScript. We moeten via JavaScript kunnen ingrijpen op het HTML-document en vice versa. Dat ingrijpen doen we via de DOM, het Document Object Model. Je weg kennen binnen de DOM is belangrijk om goede scripts te schrijven.

Veel mensen doen heel vaag over de DOM en het is daarom soms moeilijk te verstaan. Op het web vind je dan ook van die grootse omschrijvingen als "The Document Object Model is an application programming interface that defines logical structure of well-formed XML and HTML documents". Dat klinkt heel ingewikkeld, en we gaan dat hier op deze manier dan ook niet verklaren. Want eigenlijk is het heel simpel.

Document

Dit is de web-pagina waar we op dat moment naar kijken, niet de web-site volledig, maar die ene enkele pagina. De pagina kan een verschillende representatie hebben. We hebben enerzijds de pure HTML-code, anderzijds hoe de pagina er uiteindelijk uitziet in de browser. Het is hetzelfde document, anders weergegeven. Voor JavaScript is de HTML-code belangrijk.

Object

Een object is een 'ding'. We hebben die in vorige hoofdstukken al gebruikt. Date, Object, Array, String, zijn allen objecten. In dit verband gaat het over de verschillende onderdelen van de HTML- code. De titel, een h1-tag, een unordered list, een list item, ... Al deze dingen zijn objecten en kunnen op zich zelf objecten bevatten.

Model

We kunnen van de ganse HTML-code een boomstructuur maken. Op die manier vinden we de weg naar een bepaald object. Bijvoorbeeld: in het document, staat een body, daarin staat een div-tag, daarin staat een p-tag, daarin staat een unordered list en daarin staat een list item. Dit is de weg, startende van het document naar dat ene list item.

Elk deel van deze boomstructuur noemen we een 'node'. Van elke node kunnen we ons afvragen, wie staat er boven, wie is de parent-node, wie staat er onder, wie is de child-node. Het is de relatie tussen al deze elementen, al deze nodes, in het document, dat het model wordt genoemd. Tegelijk is het model de afspraak hoe we objecten gaan noemen zodat iedereen dat op dezelfde manier doet.

Eens we weten hoe de DOM in elkaar zit, kunnen we JavaScript schrijven die elke pagina kan volgen of veranderen. We kunnen;

  • de tekst van de titel krijgen,
  • het tweede paragraaf element opvragen,
  • de derde link in het menu opvragen en deze verbergen door een CSS-style naar ('display:none') te veranderen,
  • alle achtergronden van elke paragraaf van de class 'important' veranderen naar een bepaalde kleur,
  • alle li-tags opvragen van alleen de laatste ul-tag,
  • zoek de afbeelding met id='logo' en deze 40 px naar links verplaatsen,
  • menu's aanmaken en ze op de juiste plaats zetten,
  • ... En om al deze dingen te kunnen moeten we onze weg vinden in de DOM.

Werken met nodes en elementen

Elke webpagina, elk document, is opgebouwd uit nodes. En terwijl we tot hiertoe enkel de tags nodes hebben genoemd, gaat het nog veel verder. Want de tekst in een node is zelf ook een node. De attributen in een tag, zijn zelf ook nodes en zo verder. Er zijn tot 12 verschilledende nodes voorgeschreven, maar er zijn er 3 die heel belangrijk zijn:

  • elements --> nodeType = 1
  • attributes --> nodeType = 2
  • tekst --> nodeType = 3

Voorbeeld:

<ul id="optionlist">
     <li>Dit is de eerste optie</li>
     <li>Dit is de tweede optie</li>
     <li>Dit is de derde optie</li>
</ul>

We hebben hier een unordered list met een attribuut id en drie list items.

De hele <ul> is een node, een element-node. Maar daarin, omschreven door id, zit een attribute- node. Dieper in de element-node, zitten de <li>. Ook dit zijn element-nodes. Hierin zit dan telkens weer een tekst-node, die louter de tekst voorstelt. En dat is een verschil met de element-nodes. Zij bevatten het element, niet de tekst. Andersom zal de tekst-node de tekst bevatten en niet het element. Klinkt logisch. Wat belangrijker is, is dat er een verschil is tussen de twee en meestal zullen we eerst de element-node aanspreken.

Aanspreken van DOM-elementen

Hoe schrijven we JavaScipt om DOM-elementen, om nodes aan te spreken? De eerste vraag die we ons moeten stellen is: is de node uniek in het document? En dat betekent; heeft de node een id? Zoals je weet kan je bij elk element, elke tag, een id-attribuut toevoegen en die zijn op zich uniek. Classes die je als attribuut meegeeft, zijn dat niet en hoeven dat ook niet te zijn.

Als het element uniek is, dan kan je het bereiken door misschien wel de belangrijkste opdracht in JavaScript:

document.getElementById();

of

document.querySelector();

Tussen de haken geef je het element dat je wil bereiken. Let ook op hoe de getElementById() wordt geschreven in functie van hoofdletters. Dat is belangrijk. het document-deel bepaalt dat je in het ganse document, het HTML-document gaat zoeken naar het specifieke element. Meestal steek je het resultaat in een variabele die je makkelijker kan aanspreken dan dit lange statement.

Stel:

<ul id="optionlist">
     <li>Dit is de eerste optie</li>
     <li>Dit is de tweede optie</li>
     <li>Dit is de derde optie</li>
</ul>
let unorderedList = document.getElementById("optionlist");

of

let unorderedList = document.querySelector("#optionlist");

Via deze variabele unorderedList kunnen we nu makkelijk die ene ganse unordered list uit het ganse HTML-document aanspreken. Vanaf nu kunnen we via deze variabele de inhoud van de unordered list aanpassen, er een list item aan toevoegen, ...

Let op, dit is geen inhoudelijke kopij van wat er in het HTML-document staat, enkel een link daar naartoe. Daarom kunnen we nu ook de child-nodes aanspreken, de list items, maar ook de parent node, die hier in dit voorbeeld niet gegeven werd.

Wat als we iets willen aanspreken dat niet uniek is? We zouden het een id kunnen geven, maar we kunnen ook getElementsByTagName() gebruiken. Let op het meervoud in dit statement. We halen dan ook alle nodes op die de bepaalde tag hebben. In het vorige voorbeeld;

let alleListItems = document.getElementsByTagName("li");

De variabele 'alleListItems' bevat nu een Array met de drie list items die we vonden in de DOM. We kunnen ze natuurlijk bekijken als volgt:

console.log(alleListItems[0]);
console.log(alleListItems[1]);
console.log(alleListItems[2]);

Wat je krijgt is een vermelding dat een li-tag werd gevonden. Wat je niet krijgt is de inhoud. Omdat het steeds een link blijft naar het object, de node, die je wil aanspreken.

Als er echter niets gevonden zou worden, krijg je een lege Array.

In de bijgevoegde webpagina kunnen we nu op basis van wat we net gezien hebben de DOM inspecteren met de volgende opdrachten.

// haal een enkel element op
let myTitleLink = document.getElementById("mainTitle");

// informatie over de node
// nodeType geeft een getal terug
console.log("Dit is een node van het type: ", myTitleLink.nodeType);

// laat de eigenlijke tekst en code zien die in de node zit.
console.log("Inner HTML: ", myTitleLink.innerHTML);

// toon het aantal childNodes van de titel
console.log("Child nodes: ", myTitleLink.childNodes.length);

// hoeveel links zitten er in de pagina?
let myLinks = document.getElementsByTagName("a");
console.log("Links: ", myLinks.length);

// haal nu de node met id "homeNav" op.
let navItems = document.getElementById("homeNav");
// informatie over die gevonden node
console.log("Dit is een node van type: ", navItems.nodeType);
console.log("Inner HTML: ", navItems.innerHTML);
console.log("Child nodes: ", navItems.childNodes.length);

// hoeveel ordered lists zijn er?
let orderedLists = document.getElementsByTagName("ol");
console.log("Ordered lists: ", orderedLists.length);

// Dieper graven in de DOM
// haal enkel de h1-tag op uit de div met id "specials",
// niet alle H1-tags
// we kunnen dit in 2 regels doen
let mySpecials = document.getElementById("specials");
let myH1 = mySpecials.getElementsByTagName("h1");
console.log(myH1[0].innerHTML);

// we kunnen dit ook in 1 regel doen
let mySpecial = document.getElementById("specials").getElementsByTagName("h1");
console.log(mySpecial[0].innerHTML);

DOM elementen aanpassen

Elementen uit de DOM opvragen is één ding, ze aanpassen is veel interessanter. Stap 1 is steeds het element dat je wil veranderen opvragen en opslaan in een variabele. Stap 2 is weten wat we willen veranderen aan dit element.

Je kan een attribuut aanpassen, een link veranderen, de tekst in een div aanpassen, ...

Eén van de simpelste dingen die we kunnen doen, is een attribuut aanpassen. Eens we het element waar we een attribuut van willen aanpassen hebben, dan bezit JavaScript twee functies die we kunnen gebruiken: getAttribite() en setAttribute().

getAttribute() is heel eenvoudig. Tussen de ronde haken en tussen aanhalingstekens moeten we zeggen welk attribuut we willen aanspreken. Het gaat hier over de naam van het attribuut: align, title, class, src, ...

setAttribute() is gelijkaardig, maar we hebben ook een waarde nodig die we aan het attribuut willen geven. De waarde die we meegeven is steeds een String.

setAttribute("align", "justify");
setAttribute("width", "200px");

Als het attribuut nog niet bestaat in de node die we aanpassen, zal die attribuut worden aangemaakt.

Zo kunnen we in het bijgevoegde HTML-document het volgende doen:

let myDiv = document.getElementById("mainContent");
myDiv.setAttribute("align", "right");

Merk op dat vooraleer we het script toepassen, er geen attribuut align aanwezig is in de div met id mainContent.

We kunnen ook de inhoud, de tekst, van een element veranderen. Dit doen we door innerHTML aan te spreken. In het zelfde voorbeeld kunnen we het volgende doen:

let myTitle = document.getElementById("mainTitle");
myTitle.innerHTML = "Welcome to Callifornia";

Opgelet! innerHTML kan veel meer bevatten. Doe maar eens het volgende:

let t = document.getElementById("mainNav");
console.log(t);
// merk op wat er allemaal in de innerHTML staat
t.innerHTML = "Hello world!";
// met deze regel vervangen we gans ons menu met een onopgemaakt zinnetje.

Denk dus goed na of je het juiste element correct aanspreekt.

DOM elementen creëren

Het is mogelijk om nieuwe DOM elementen te creëren. Let op het verschil; we maken hier geen attributen aan, maar nieuwe elementen zoals een nieuw list item, of een nieuwe div, een nieuwe paragraaf met inhoud ...

We moeten dit weer doen in een aantal stappen:

  • Stap 1: Creëer het element
  • Stap 2: Voeg het toe, op de juiste plaats in de de DOM.
  • Stap 3: Geef het nieuwe element inhoud

Stel dat we een unordered list met een id = "abc" hebben met drie list items waar we een vierde aan willen toevoegen. Allereerst creëren we een nieuw list item:

let newListItem = document.createElement("li");

Op deze moment bestaat het nieuwe list item enkel in het geheugen en zal nog niet te zien zijn in de pagina. We moeten het nog toevoegen aan de DOM. Tegelijk geven we de plaats waar het nieuwe list item moet komen.

document.getElementById("abc").appendChild(newListItem);

appendChild() zal het nieuwe list item toevoegen aan de DOM. Merk op dat het nieuwe element als laatste list item wordt geplaatst.

Nu moeten we enkel nog tekst toevoegen aan het nieuwe list item. Dit kunnen we doen door de 'innerHTML' aan te passen.

newListItem.innerHTML = "Hello world!";

Het moet wel gezegd dat het gebruik van 'innerHTML' hier in feite niet juist is. Wel is het aanmaken van een tekst-node en die node te plaatsen in het nieuw gemaakte element. Dus als we de laatste regel willen vervangen door iets wat correct is, dan moeten we het volgende doen:

let newText = document.createTextNode("Hello world!");
newListItem.appendChild(newText);

In de bijgeleverde webpagina, gaan we tekst toevoegen aan de div met id "trivia". We willen een titel <h1> en een paragraaf <p> met tekst in beide elementen. Merk op dat de div met id "trivia" nu leeg is. Met het volgende script voegen we daar tekst aan toe, die tegelijk vorm zal gegeven worden via de CSS die er aan gekoppeld is.

//creëer de elementen
let newHeading = document.createElement("h1");
let newParagraph = document.createElement("p");

// creëer de textnodes
let h1Text = document.createTextNode("Did You Know?");
let paraText = document.createTextNode("California produces over 17 million gallons of wine each year!");

// koppel de textnodes aan de elementen
newHeading.appendChild(h1Text);
newParagraph.appendChild(paraText);

// plaats de nieuwe elementen in het element met id "trivia"
document.getElementById("trivia").appendChild(newHeading);
document.getElementById("trivia").appendChild(newParagraph);

We stellen wel vast dat appendChild() steeds zijn toe te voegen element op de laaste plaats in de rij plaatst. Kijk nog maar eens naar het vorige voorbeeld met de list items. Maar wat als we het nieuwe list item in het begin van de unordered list willen plaatsen, of op de tweede plaats? We kunnen dan niet langer gebruik maken van appendChild() maar wel van insertBefore(). Uitleg bij wijze van een voorbeeld:

Stel dat we een <ul> (unordered list) met id = "abc" hebben, met 3 bestaande <li> (list item) en dat ik een <li> wil toevoegen op de derde plaats. Dit kunnen we doen door weer een <li> aan te maken en deze te plaatsen in de dom vòòr het vierde <li>. De HTML code zou er zo kunnen uitzien:

<ul id="abc">
     <li>Dit is li 1</li>
     <li>Dit is li 2</li>
     <li>Dit is li 3</li>
</ul>

Het JavaScript als volgt:

// maak het nieuwe element aan
let newListItem = document.createElement("li");

// maak de textnode aan voor het element
let newText = document.createTextNode("Dit wordt het derde item");

// hang de textnode aan het nieuwe element
newListItem.appendChild(newText);

// zoek het hoofdelement op (de ul)
let hoofdElement = document.getElementById("abc");

//zoek waar je het nieuwe element voor wil zetten
let derdeLi = hoofdElement.getElementsByTagName("li")[2];

// plaats het nieuwe element in de DOM
hoofdElement.insertBefore(newListItem, derdeLi);