Documenten Manipuleren

Als je een webpagina of app ontwerpt, zal je regelmatig de documentstructuur van je site willen manipuleren. Het zal een van je meest voorkomende taken zijn.  Meestal doen we dit met het Document Object Model (DOM). Het DOM is een reeks APIs waarmee we de HTML van onze pagina kunnen controleren en de informatie erin opmaken met allerlei stijlelementen. Het DOM maakt vooral gebruik van het Document object. In dit artikel leren we hoe we hoe we het DOM kunnen gebruiken en we bekijken een aantal interessante APIs waarmee we onze webomgeving op een interessante manier kunnen veranderen.

Vereiste kennis: Basiskennis van een computer, basisbegrippen van HTML, CSS en Javascript, inclusief Javascript objecten.
Doelstelling:

Vertrouwd worden met de kern APIs van het DOM en met de andere APIs die meestal met het DOM en documentmanipulatie worden geassociëerd.

De belangrijke onderdelen van een webbrowser 

Web browsers bestaan uit zeer complexe software met vele bewegende onderdelen erin. Veel van die delen kunnen niet worden gecontrolleerd of gemanipuleerd door een webontwikkelaar die Javascript gebruikt. Je zou kunnen veronderstellen dat dat slecht is, maar er zijn goede redenen voor het vergrendelen van browsers. De meeste ervan draaien rond veiligheid. Beeld je je een website in die zomaar toegang zou kunnen hebben tot je opgeslagen paswoorden of andere gevoelige informatie en zou kunnen inloggen op websites alsof hij jou was? 

Ondanks de beperkingen geven Web APIs ons toegang tot heel wat functionaliteit die ons in staat stelt vele dingen te doen met webpagina's. Er zijn een aantal evidente onderdelen waar je regelmatig naar zult verwijzen in je code. In het volgende diagram vind je de belangrijkste onderdelen van een webbrowser die betrokken zijn bij de weergave van een webpagina :

  • Het venster (of window) is de tabpagina van je webbrowser waarin je webpagina is geladen; deze wordt in Javascript voorgesteld door het Window object. Dit object geeft je toegang tot allerlei methodes die bijvoorbeeld de afmetingen van het venster kunnen weergeven (zie Window.innerWidth en Window.innerHeight). Je kan het document dat in het venster is geladen, manipuleren. Je kan ook data opslaan die de ingeladen website genereert. Daarvoor kan je een lokale database aan de cliënt-zijde gebruiken maar er bestaan ook andere bergingsmechanismen. Je kan een event handler aan het venster vastmaken en nog veel meer.
  • De navigator vertegenwoordigt de staat en de identiteit van de browser (i.e. de user-agent) zoals die in het web bestaat. In Javascript wordt de navigator voorgesteld door het Navigator object. Je kan dit object gebruiken om allerlei informatie op te halen. Dit kunnen geolocatiegegevens zijn, de voorkeurstaal van de gebruiker, een media stream vanuit de webcam van de gebruiker enz.
  • Het document (in browsers weergegeven door het DOM) is de eigenlijke pagina die in het venster wordt geladen. We kennen het in Javascript als het Document object. Je kan dit object gebruiken om informatie te verkrijgen over de HTML en CSS waaruit de pagina is opgebouwd en om die informatie te manipuleren. Het beschikt over methodes waarmee je naar een element in het DOM kan verwijzen en de inhoud van dat element veranderen. Je kan er ook een nieuwe stijl op toepassen, nieuwe elementen creëren en die aan het element toevoegen als zijn kinderen. Je kan het element zelfs helemaal verwijderen.

In dit artikel zullen we vooral aandacht besteden aan de manipulatie van het document, maar we zullen ook nog een paar andere nuttige zaken tonen.

Het document object model

Het document dat in een van de browsertabs van je webbrowser is geladen, wordt weergegeven door het document object model. Dit model heeft een boomstructuur en wordt door de browser zelf gecrëerd. Deze voorstellingswijze maakt de HTML zeer toegankelijk voor programmeertalen. De browser zelf gebruikt het DOM om allerlei regels rond vormgeving en andere informatie op de HTML toe te passen terwijl het de pagina weergeeft. Ontwikkelaars zoals jij kunnen Javascriptcode schrijven die het DOM manipuleert nadat de pagina al is opgeladen in de browser.

We hebben een eenvoudig voorbeeld gemaakt op dom-example.html (bekijk het ook live). Probeer dit voorbeeld in je browser te openen. Het is een zeer simpele pagina die een<section> element bevat waarin je een afbeelding en paragraaf kan vinden. In de paragraaf zit ook nog een link. De html-broncode ziet er zo uit :

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Simple DOM example</title>
  </head>
  <body>
      <section>
        <img src="dinosaur.png" alt="A red Tyrannosaurus Rex: A two legged dinosaur standing upright like a human, with small arms, and a large head with lots of sharp teeth.">
        <p>Here we will add a link to the <a href="https://www.mozilla.org/">Mozilla homepage</a></p>
      </section>
  </body>
</html>

Het DOM daarentegen ziet er zo uit:

Opmerking: Dit DOM boomdiagram werd gegenereerd met Ian Hickson's Live DOM viewer.

Zoals je kan zien krijgen elk element en elk stukje tekst in het document een plek in de boomstructuur. Zo’n plek noemen we een knooppunt (in het Engels een node). Je zal ook verschillende termen tegenkomen die worden gebruikt om te beschrijven met welk type knooppunt we te maken hebben en termen die hun posititie in de boom en hun relatie met elkaar beschrijven.

Je zal merken dat ik zowel Engelse als Nederlandse termen gebruik. Een vertaling kan zeer verhelderend zijn maar een woord als "afstammelingsknooppunt" vind ik veel te lang om nog werkbaar te zijn en als je het googelt, heb je nul resulaten. Ik eet dus een beetje van twee walletjes.

  • Element node (elementknooppunt): Een element zoals het in het DOM bestaat.
  • Root node (bronknooppunt): Het knooppunt dat bovenaan in de boom zit. Als het over HTML gaat, is dit altijd het HTML-knooppunt. (Andere opmaaktalen zoals SVG en custom XML zullen andere root nodes hebben.)
  • Child node (kindknooppunt): Een knooppunt dat in een andere knooppunt is genest en er direct onder zit. IMG is bijvoorbeeld een kind van SECTION in het voorbeeld hierboven.
  • Descendant node (afstammelingsknooppunt): Een knooppunt dat ergens in een andere node zit. IMG is bijvoorbeeld geen kind van BODY omdat het twee niveaus lager in de boom zit, maar het is wel een nakomeling van BODY.
  • Parent node (ouderknooppunt): een knooppunt dat een ander knooppunt bevat. BODY is bijvoorbeeld het ouderknoopunt van SECTION in het voorbeeld hierboven.
  • Sibling nodes (broerknooppunten): Knooppunten die zich op hetzelfde niveau in de DOM-boom bevinden. IMG en P zijn bijvoorbeeld elkaars broers in het voorbeeld hierboven.
  • Text node (tekstknooppunt): Een knooppunt dat een tekst bevat (i.e. een string).

Het is nuttig om je deze terminologie eigen te maken vooraleer je met het DOM begint te werken omdat een aantal andere termen die je zult tegenkomen op vergelijkbare wijze zijn opgebouwd. Je bent ze misschien zelf al tegengekomen als je CSS hebt geleerd (bijv. descendant selector, child selector).

Actief Leren: Eenvoudige DOM-Manipulatie

Onze eerste les over DOM-manipulatie beginnen we met een praktisch voorbeeld.

  1. Maak een lokale kopie van de dom-example.html pagina en het beeld dat erbij hoort.
  2. Voeg net onder </body> een <script></script> element toe. (Let op, dus onder de tweede tag die het body-element afsluit!).
  3. Om een element in het DOM te manipuleren, moet je het eerst selecteren en een verwijzing ernaar opslaan in een variabele. Om dat te doen, voeg je de volgende regel aan je script toe:
    var link = document.querySelector('a');
  4. Nu we een verwijzing naar het element hebben opgeslagen in een variabele, kunnen we beginnen met een aantal eigenschappen en methodes die beschikbaar zijn gemaakt voor de manipulatie van het DOM. (Deze worden gedefiniëerd in interfaces zoals het HTMLAnchorElement voor een <a> element, zijn algemenere ouder-interface, het HTMLElement, en Node die alle knooppunten in het DOM vertegenwoordigt. Laat ons eerst en vooral de tekst in de hyperlink veranderen door de waarde van de Node.textContentaan te passen. Voeg de volgende regel toe aan de vorige:
    link.textContent = 'Mozilla Developer Network';
  5. We moeten ook de URL veranderen waar de hyperlink naar verwijst zodat die ons niet naar de verkeerde pagina brengt als we erop klikken. Voeg daarvoor de volgende regel toe: :
    link.href = 'https://developer.mozilla.org';

Opmerking : Zoals met zoveel in Javascript, zijn er vele manieren om een element te selecteren en om een verwijzing ernaar op te slaan in een variable. Document.querySelector() is de aanbevolen moderne manier. Dat komt goed uit want deze methode laat je elementen selecteren met CSS-selectoren. De regel hierboven komt overeen met het eerste <a> element dat in het document opduikt. Als je meer dan één element wil manipuleren, kan je Document.querySelectorAll() gebruiken. Deze methode selecteert elk element in het document dat overeenkomt met de selector en slaat die op in een array.

Er zijn ook oudere methodes beschikbaar om verwijzingen naar een element te grijpen, zoals:

  • Document.getElementById(): selecteert een element via de waarde van het id-attribuut, bijvoorbeeld : <p id="myId">My paragraph</p>. Het ID wordt als parameter aan de functie doorgeven, i.e.var elementRef = document.getElementById('myId').
  • Document.getElementsByTagName(): brengt een array op die alle elementen van het gevraagde type bevat die zich in de pagina bevinden, bijvoorbeeld <p>s, <a>s, enz. Het type wordt ook als een parameter aan de functie doorgegeven, i.e. var elementRefArray = document.getElementsByTagName('p').

Deze twee methoden werken in oudere browsers die moderne methoden zoals querySelector() niet zullen begrijpen maar ze zijn niet zo praktisch. Kijk maar eens rond en zie welke anderen je nog kan vinden.

Nieuwe Knooppunten (Nodes) Creëren en Plaatsen

Hierboven heb je eens kunnen proeven van wat je kan doen, maar laat ons nu verder gaan en kijken hoe we nieuwe elementen kunnen creëren.

  1. We gebruiken opnieuw ons laatste voorbeeld en maken een referentie naar ons <section> element. Daarvoor voegen we de volgende code toe onderaan het script waar we mee bezig waren (doe ook hetzelfde met de volgende regels):
    var sect = document.querySelector('section');
  2. Nu gaan we een nieuwe paragraaf creëren met Document.createElement() en we gaan er wat tekst in steken op dezelfde manier die we ervoor hebben gebruikt:
    var para = document.createElement('p');
    para.textContent = 'We hope you enjoyed the ride.';
  3. Nu kan je de nieuwe paragraaf aan het einde van de sectie vastmaken met Node.appendChild():
    sect.appendChild(para);
  4. Als laatste stap van dit gedeelte, voegen we een tekstknooppunt aan de paragraaf toe waar de hyperlink in zit. Daarvoor creëren we eerst de textnode met Document.createTextNode():
    var text = document.createTextNode(' — the premier source for web development knowledge.');
  5. Nu grijpen we een referentie naar de paragraaf waar de hyperlink in zit en maken het tekstknooppunt eraan vast :
    var linkPara = document.querySelector('p');
    linkPara.appendChild(text);

Dat is het meeste van wat je nodig hebt om knooppunten aan het DOM toe te voegen. Je zal deze methoden vaak gebruiken als je dynamische interfaces maakt (we zullen later een paar voorbeelden bekijken.

Elementen Verplaatsen en Verwijderen

Het zal regelmatig nodig zijn om knooppunten te verplaatsen of zelfs helemaal uit het DOM te verwijderen. En dat is natuurlijk mogelijk.

Stel dat we de paragraaf met de hyperlink naar het einde van de sectie willen verplaatsen, dan doen we gewoon dit:

sect.appendChild(linkPara);

Dit verplaatst de paragraaf naar het einde van het <section> element. Je zou denken dat het een tweede kopie van de paragraaf zou maken, maar dat is niet het geval. linkPara is een referentie naar de enige kopie van die paragraaf. Als je een kopie zou willen maken en die ook nog toevoegen, zou je in de plaats daarvan Node.cloneNode() moeten gebruiken.

Een knooppunt is ook heel makkelijk te verwijderen, ten minste als je een verwijzing naar het knooppunt en zijn ouder hebt. In ons voorbeeld kunnen we gewoon Node.removeChild() gebruiken:

sect.removeChild(linkPara);

Het wordt iets complexer als je een knooppunt wil verwijderen dat is gebaseerd op een verwijzing naar zichzelf, wat vrij vaak voorkomt. Er bestaat geen methode die een knooppunt vertelt zichzelf te verwijderen en dus moet je het volgende doen:

linkPara.parentNode.removeChild(linkPara);

Probeer nu deze regels uit in je de code van het voorbeeld en bekijk het resultaat.

Stijl Manipuleren

Het is mogelijk om de CSS van je webpagina op verschillende manieren te manipuleren met Javascript.

Om te beginnen kan je met Document.stylesheets een lijst opvragen van alle stijlbladen die aan je document vasthangen. Deze methode brengt ons een array van CSSStyleSheet objecten op. Je kan dan naar wens stijlelementen toevoegen en verwijderen. We gaan echter niet dieper in op deze methodes omdat ze een beetje archaïsch zijn en nogal lastig in het gebruik. Er bestaan veel gemakkelijker methoden.

De eerste manier is om inline-stijlen onmiddellijk aan de elementen toe te voegen die je dynamisch wil vormgeven. Dit wordt met HTMLElement.style gedaan, die inline stijlinformatie over elk element in het document bevat. Je kan de eigenschappen van dit object bepalen en zo direct de stijl van je elementen bijwerken.

  1. Voeg de volgende regels toe aan het voorbeeld waar we mee bezig zijn:
    para.style.color = 'white';
    para.style.backgroundColor = 'black';
    para.style.padding = '10px';
    para.style.width = '250px';
    para.style.textAlign = 'center';
  2. Vernieuw de pagina en je zal zien dat de verschillende stijlen zijn toegepast op de paragraaf. Als je de paragraaf in je browser's Page Inspector/DOM inspector bekijkt, zal je zien dat deze stijlregels inderdaad inline in je document worden toegevoegd:
    <p style="color: white; background-color: black; padding: 10px; width: 250px; text-align: center;">We hope you enjoyed the ride.</p>

Opmerking: Let op hoe de Javascript versie van CSS-eigenschapen in lower camel case (*) wordt geschreven terwijl de CSS-versies een koppelteken krijgen (bijv. backgroundColor versus background-color). Let erop deze niet met elkaar te verwarren, anders zullen ze niet werken.
(*) De woorden worden aan elkaar geschreven en vanaf het tweede woord begint elk woord met een hoofdletter.

Er is nog een algemeen aanvaarde manier om je stijlen dynamisch in je document te manipuleren en die gaan we nu bekijken:

  1. Verwijder de vorige vijf regels die je aan je JavaScript hebt toegevoegd.
  2. Voeg de volgende regels toe aan je HTML <head>:
    <style>
    .highlight {
      color: white;
      background-color: black;
      padding: 10px;
      width: 250px;
      text-align: center;
    }
    </style>
  3. Nu gaan we een zeer nuttig methode gebruiken voor algemene HTML-manipulatie : Element.setAttribute(). Deze methode krijgt twee argumenten: het attribuut dat je aan het element wil vasthangen en de waarde die je het attribuut wenst te geven. In dit geval geven we onze paragraaf een klasse die de waarde ‘highlight’ krijgt.
    para.setAttribute('class', 'highlight');
  4. Vernieuw je pagina en je zult geen verandering zien; de CSS wordt nog steeds op de paragraaf toegepast. Deze keer gebeurt dat echter omdat we de paragraaf een klasse geven die wordt geselecteerd door onze CSS-regel, niet door het gebruik van een inline CSS-stijl.

Welke methode je kiest hangt van jou af; beide hebben hun voordelen en nadelen. De eerste methode vereist minder gedoe en is goed voor eenvoudig gebruik. De tweede is meer voor de puristen onder ons. (CSS en Javascript worden niet met elkaar vermengd want er zijn geen inline-regels. Inline-stijl wordt als een slechte gewoonte beschouwd). Als je grotere en complexere apps begint te bouwen, zal je waarschijnlijk de tweede methode gaan gebruiken, maar het is echt helemaal jouw beslissing.

Tot nu toe hebben we nog niet echt iets nuttigs gedaan! Het is zinloos om Javascript te gebruiken voor de creatie van statische inhoud – je kan die toch net zo goed in je HTML schrijven zonder Javascript te gebruiken. Het is ingewikkelder dan HTML en als je de inhoud met Javascript maakt, zijn er ook nog andere problemen mee verbonden (de Javascript-code is bijvoorbeeld onleesbaar voor zoekmachines).

In de volgende secties zullen we paar praktische manieren zien waarop we DOM APIs kunnen gebruiken.

Opmerking: Je kan onze afgewerkte versie van het dom-example.html op GitHub vinden. (bekijk het ook live).

Actief Leren: Nuttige informatie uit het window object halen.

Tot nu toe hebben enkel de kenmerken van Node en Document bekeken om documenten te manipuleren, maar er is geen reden waarom je geen data uit andere bronnen zou kunnen halen en die in je UI gebruiken. Denk eens even terug aan onze eenvoudige maps-example.html demo uit het vorige artikel. We hadden locatiedata gevonden en die gebruikt om een map van jouw streek te maken. Je moet er gewoon voor zorgen dat je data het juiste formaat heeft. Javascript maakt het gemakkelijker dan vele andere talen om dat te doen want het gebruikt zwakke types; nummers zullen bijvoorbeeld gemakkelijk in strings kunnen worden omgezet als je ze in je scherm wil afdrukken.

In dit voorbeeld zullen we een probleem oplossen dat vaak voorkomt : ervoor zorgen dat je applicatie even groot is als het window waarin het wordt bekeken, wat dan ook de grootte van dat venster mag zijn. Dit is vaak nuttig in situaties zoals spelletjes, waarin je zoveel mogelijk gebruik van de schermgrootte wil kunnen maken om het spel erin te spelen.

Om te beginnen maken we een lokale kopie van twee demo-bestanden: window-resize-example.html en bgtile.png. Open ze en bekijk ze eens. Je zal zien een <div> element zien dat een klein deel van het scherm beslaat. Op de achtergrond van het scherm wordt een tegel toegepast. Dit gaan we gebruiken om het UI-gebied van onze app weer te geven.

  1. Grijp eerst en vooral een referentie naar de div en dan naar de breedte en hoogte van het viewport (het venster waar we je document in zien zitten). Die sla je op in variabelen. Deze twee waarden worden handig opgeslagen in twee eigenschappen: Window.innerWidth en Window.innerHeight. Voeg de volgende regels toe aan je <script>:
    var div = document.querySelector('div');
    var WIDTH = window.innerWidth;
    var HEIGHT = window.innerHeight;
  2. Daarna gaan we de breedte en de hoogte van de div dynamisch aanpassen zodat ze gelijk worden aan die van de viewport. Voeg de volgende lijnen toe aan de vorige: :
    div.style.width = WIDTH + 'px';
    div.style.height = HEIGHT + 'px';
  3. Sla alles op en vernieuw je browser. Nu zou de div even groot als je viewport moeten zijn wat ook de afmetingen van je scherm zijn. Als je nu de afmetingen opnieuw probeert te veranderen om je venster groter te maken, zal je zien dat de div dezelfde grootte behoudt. We bepalen de afmetingen slechts eenmaal.
  4. We kunnen ook een gebeurtenis of event gebruiken zodat de afmetingen van de div samen met die van het venster veranderen. Het Window object beschikt over een event dat ‘resize’ heet en dat telkens wordt getriggered als de grootte van het venster wordt gewijzigd. De Window.onresize event handler staat ons toe dit event aan onze code vast te koppelen. Zo wordt die uitgevoerd telkens als de afmetingen van het window veranderen. Voeg de volgende lijnen toe onderaan je code:
    window.onresize = function() {
      WIDTH = window.innerWidth;
      HEIGHT = window.innerHeight;
      div.style.width = WIDTH + 'px';
      div.style.height = HEIGHT + 'px';
    }

Opmerking: Als je vastraakt, kan je altijd nog ons voltooide window resize voorbeeld bekijken. (bekijk het ook live).

Actief Leren : een dynamische boodschappenlijst

Om het artikel af te ronden, willen we je een kleine uitdaging aanbieden: we willen een eenvoudige boodschappenlijst maken waar je dynamisch artikelen aan kan toevoegen door een invoerveld en een knop te gebruiken. Als je een artikel in het veld invult en op de knop drukt:

  • Moet het artikel in de lijst verschijnen.
  • Moet elk artikel een knop krijgen waarop je kan drukken om het artikel van de lijst te verwijderen.
  • Moet het invoerveld leeg zijn en de focus moet erop liggen, klaar om er een ander artikel in in te vullen.

De voltooide demo zal er ongeveer zo uitzien:

Om de oefening te maken, volg je de stappen hieronder en zorg je ervoor dat de lijst zich gedraagt zoals hierboven is beschreven.

  1. Om te beginen download je dit bestand: shopping-list.html. Je zal zien dat er minimum aan CSS in zit, een lijst met een label, een invoerveld, een knop, een lege lijst en een <script>-element. Je zal al je code aan het script toevoegen.
  2. Creëer drie variabelen die een referentie naar de <ul><input>, en  <button> elementen bevatten.
  3. Creëer een functie die zal worden uitgevoerd als er op de knop wordt geklikt.
  4. In de functie sla je eerst de waarde die het invoerveld nu heeft, op in een variabele.
  5. Daarna maak je het invoerveld leeg door zijn waarde gelijk te maken aan een lege string: ''.
  6. Creëer drie nieuwe elementen <li><span>, en <button>. Sla ze op in variabelen.
  7. Bevestig het span-element en de knop aan het lijstonderdeel (li dus) en maak ze de kinderen van li.
  8. Zet de tekstinhoud van het span-element gelijk aan de waarde van het invoerveld dat je eerder al hebt opgeslagen en de tekst van de knop gelijk aan 'Verwijderen'.
  9. Bevestig het lijstonderdeel aan de lijst en maak het een kind van de lijst.
  10. Bevestig een event handler aan de verwijderknop, die het volledige lijstonderdeel zal verwijderen als erop wordt geklikt. (Herinner je je nog hoe we al een node/knooppunt hebben verwijderd? Deze keer wil je echter niet het kind van zijn ouder verwijderen, je wil het kind van de grootouder verwijderen!)
  11. Ten slotte gebruik je de focus()-methode om de focus op het invoerveld te leggen zodat je het volgende artikel kan invullen.

Opmerking: Als je echt vast raakt, kijk dan eens naar onze voltooide boodschappenlijst (bekijk hem ook live.)

Samenvating

We hebben het einde van ons artikel over DOM-manipulatie bereikt. Je kent nu de belangrijke onderdelen van een webbrowser waarmee documenten worden gecontrolleerd en andere aspecten die relevant zijn voor onze beleving van een webpagina. Maar vooral begrijp je nu wat het Document Object Model is en hoe je het kan manipuleren om nuttige functionaliteit te creëren

Zie ook

Er zijn nog veel meer eigenschappen die je kan gebruiken om je documenten te manipuleren. Bekijk de volgende onderwerpen en zie wat je nog kan ontdekken.

(Werp ook eens een blik op onze Web API index voor de volledige lijst van Web APIs die worden gedocumenteerd op MDN!)

Documentlabels en -medewerkers

 Aan deze pagina hebben bijgedragen: mientje
 Laatst bijgewerkt door: mientje,