4. DOM Manipulatie & events
4. DOM Manipulatie & events
In dit hoofdstuk maken we de overstap van JavaScript in de terminal naar JavaScript in de browser. Dit doen we op twee manieren, enerzijds wordt in deze tekst een eenvoudige To-Do applicatie gebouwd, anderzijs wordt de theorie in de presentatie uitgelegd aan de hand van losstaande voorbeelden.
Als toevoeging op de theorie in deze les, raden we de appendix 11 aan, deze geeft meer informatie over de debugger.
De code voor beide aanpakken is onderaan deze pagina te vinden.
Startbestanden
Voor het To-Do voorbeeld, vertrek deze tekst van onderstaande startbestanden die de UI van de pagina definiëren.
Startbestanden
Scripts koppelen aan een webpagina
JavaScript-code kan op twee manieren gekoppeld worden aan een webpagina, inline in de pagina of via een extern script. In beide gevallen wordt gebruik gemakt van het <script> tag.
Begrip: script tag
Het <script> element wordt gebruikt om JavaScript code te linken in een HTML-pagina. Via het src attribuut kan een link naar een externe JavaScript file toegevoegd worden.
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Voorbeeld</title>
</head>
<body>
<script src="/index.js"></script>
</body>
</html>prompt('Dit werkt')Indien gewenst kan de JavaScript-code ook in de body van het <script> tag geplaatst worden, in dat geval wordt het src attribuut natuurlijk niet gebruikt.
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Voorbeeld</title>
</head>
<body>
<script>
prompt('Dit werkt')
</script>
</body>
</html>In beide gevallen plaats je het <script> element onderaan de pagina, op deze manier garandeer je dat de HTML-pagina geladen is voordat je JavaScript code uitvoert. Het voordeel hiervan is tweeledig, enerzijds zal het script zo geen fouten vertonen omdat de dingen die gemanipuleerd worden nog niet bestaan, maar anderzijds wordt de pagina zo ook sneller geladen. Voor de gebruiker is de HTML/de UI het belangrijkst. Die moet zo snel mogelijk op het scherm komen. Als de JavaScript code eerst verwerkt wordt door de browser, kan dit het tekenen van de UI vertragen.
In deze cursus kiezen we veelal voor de tweede optie, een apart script is properder en makkelijker onderhoudbaar. Welke optie er ook gebruikt wordt, alle JavaScript die we tot nu toe besproken hebben, is beschikbaar binnen het <script> tag.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>To-Do</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body class="bg-light">
<div class="container py-5">
... Weggelaten uit dit fragment
</div>
<script src="13_todo.js"></script>
</body>
</html>const tasks = [
{id: '86d02cfe-4c78-482d-9c2c-8ef7c07bf14d', name: 'Sample Task 1', done: true},
{id: 'df1e0fa1-76b0-4a8b-aa66-0d3072bfca5e', name: 'Sample Task 2', done: true},
{id: 'ec36eda3-3db6-482f-87b9-00c2e714132e', name: 'Sample Task 3', done: true},
{id: '455bf4c8-3f30-49eb-bd32-def01aec05d2', name: 'Sample Task 4', done: true},
{id: '4edb896b-8834-4e0b-968d-419832576cd6', name: 'Sample Task 5', done: true},
{id: 'bd074497-6dad-41a1-83a7-304d34d8524d', name: 'Sample Task 6', done: true},
]HTML Document Object Model (DOM)
Wanneer een webpagina wordt geladen, creëert de browser een Document Object Model, beter bekend als de DOM. Dit model kan gevisualiseerd worden een logische boomstructuur van objecten. Elk onderdeel van de HTML-pagina (titels, paragrafen, afbeeldingen, links, ...) vormen een 'knoop' (node) in deze boom.
Beschouw onderstaand HTML-document:
<html>
<head>
<title>DOM Voorbeeld</title>
</head>
<body>
<h1>DOM Voorbeeld</h1>
<a href="https://developer.mozilla.org/en-US/">Mozilla Developer Network</a>
</body>
</html>Dit DOM van dit document kan als volgt gevisualiseerd worden in een boomstructuur:
Zoals op bovenstaand diagram te zien is, worden niet enkel de HTML-elementen een node (knoop) in deze boom, maar worden ook text-nodes en attribute-nodes aangemaakt. Alhoewel het mogelijk is om deze laatste twee rechtstreeks te manipuleren via JavaScript, wordt dit doorgaans niet gedaan, de focus van de meeste JavaScript applicaties ligt op het manipuleren van element nodes. Als een <p> node aangemaakt wordt, worden de bijhorende text en attribute nodes ook impliciet aangemaakt, dat expliciet doen is dus zelden nodig.
Via de DOM-interface kan JavaScript nagenoeg alles op een pagina aanpassen terwijl de pagina gebruikt wordt door een bezoeker:
- Inhoud en Structuur: JavaScript kan alle HTML-elementen en attributen op de pagina wijzigen, verwijderen of juist nieuwe elementen toevoegen.
- Vormgeving: Alle CSS-stijlen kunnen dynamisch worden aangepast, bijvoorbeeld een knop die van kleur verandert wanneer de client eroverheen beweegt met de cursor.
- Interactie: JavaScript kan reageren op bestaande HTML-events zoals een muisklik of een toetsaanslag en zelfs volledig nieuwe events creëren.
HTML-elementen aanspreken vanuit JavaScript
Om een HTML-element aan te spreken vanuit JavaScript hebben we verschillende opties, eenerzijds kunnen we vertrouwen op de automatische variabelen, anderzijds kunnen we expliciete variabelen aanmaken met behulp van de documentmethodes.
Beschouw volgend HTML-document:
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Selector voorbeeld</title>
</head>
<body>
<a id="link1" href="http://foo.com">Foo</a>
<a id="link2" href="http://baz.com">Bar</a>
<a id="link3" href="http://bar.com">Baz</a>
<a href="http://foobar.com">FooBar</a>
</body>
</html>Elke browser maakt voor dit document drie variabelen aan op basis van het id attribuut, link1, link2 en link3. Via deze variabelen kan de link dan aangepast of verwijderd worden.
Alhoewel dit op het eerste zicht handig lijkt, raden we het gebruik van deze variabelen ten zeerste af. Omdat de variabelen automatisch beschikbaar zijn, moeten deze nergens gedeclareerd worden, wat betekent dat we geen foutmeldingen krijgen als we deze variabelen per ongeluk opnieuw declareren. Als een variabele user eerst verwijst naar een <div< waarin de gebruikers gegevens moeten komen en later naar een object met diezelfde gebruikersgegeven, kan dat tot heel lastig te debuggen code leiden. Het is altijd beter om de variabelen expliciet te definiëren en gebruik te maken van de element-accesor methodes.
Begrip: HTML-elementen vinden
JavaScript voorziet verschillende methodes om elementen op te halen uit de DOM. Bijna al van deze methodes geven een live referentie naar één of meerdere HTML-elementen terug, dit betekent dat de returnwaarde altijd gesynchroniseerd is met de DOM. De enigste uitzondering hierop is de querySelectorAll methode. Als iets gewijzigd wordt in de browser (door een actie van de gebruiker) wordt de returnwaarde ook bijgewerkt, en omgekeerd wordt elke wijziging die via JavaScript gedaan wordt, ook onmiddelijk zichtbaar op de browser.
We onderscheiden binnen deze methodes drie verschillende returntypes.
Methodes die één Element teruggeven
Een Element stelt exact één tag in de DOM voor, één paragraaf, één afbeelding, één div, ...
getElementById: Deze methode krijgt één parameter, het id van het op te halen element.
const someElement = document.getElementById('someId') someElement.style.color = '#FFF'querySelector: Deze methode krijg een CSS-selector als argument en geeft het eerste overeenkomstige element terug.
const someElement = document.querySelector('#someId') someElement.style.color = '#FFF'
Methodes die een HTMLCollection teruggeven
Een HTMLCollection is een verzameling van één of meerdere Element items. Op deze verzameling zijn de array-methodes zoals forEach, map, filter, ... niet beschikbaar. Om over de collectie te itereren moet de collectie omgevormd worden naar een array, of moet een klassieke lus gebruikt worden.
getElementByClassName: Deze methode krijgt één parameter, de (CSS) klasse van de elementen die opgehaald moeten worden. Aangezien een klasse per definitie aan meerdere elementen toegekend kan worden, geeft deze methode altijd een collectie terug.
const elementsWithWhiteText = document.getElementsByClassName('white-text') // Optie één: converteer naar een array. Array.from(elementsWithWhiteText).forEach(elem => elem.style.color = '#FFF') // Optie twee: klassiek lus for (const elem of elementsWithWhiteText) { elem.style.color = '#FFF' }getElementsByTagName: Deze methode heeft één parameter, de naam van het HTML-tag dat opgehaald moet worden. Aangezien eenzelfde tag per definitie meerdere keren kan voorkomen in een HTML-document, geeft deze methode altijd een verzameling terug.
const paragraphs = document.getElementsByTagName('p') // Optie één: converteer naar een array. Array.from(paragraphs).forEach(elem => elem.style.borderLeft = '1px solid black') // Optie twee: klassiek lus for (const elem of paragraphs) { elem.style.borderLeft = '1px solid black' }
Methodes die een NodeList teruggeven
Een NodeList is een verzameling van één of meer Element items, maar in tegenstelling tot een HTMLCollection, is de forEach methode hier wel beschikbaar (andere methodes blijven onbruikbaar).
getElementsByName: Deze methode krijgt één parameter, de waarde van het name attribuut van een formulier element. Aangezien meerdere formulier elementen hetzelfde name attribuut kunnen hebben (checkboxes, radio button, ...) geeft deze methode altijd een verzameling van elementen terug.
const selectedNewslettersElements = document.getElementsByName('newsletter') const selectedNewsletters = [] selectedNewslettersElements.forEach(elem => elem.checked && selectedNewsletters.push(elem.value))querySelectorAll: Deze methode heeft één parameter, een CSS-selector en geeft alle elementen terug die overeen komen met de opgegeven selector.
const quotes = document.getElementsByTagName('p.quote') for (const elem of paragraphs) { elem.style.borderLeft = '3px solid black' elem.style.paddingLeft = '1em' elem.style.marginLeft = '1em' }
We gebruiken deze methodes nu om variabelen te bouwen voor elke knop in de ToDo applicatie, daarnaast halen we ook een referentie op naar de unordered list waarin de taken gerenderd moeten worden.
const tasks = [/* Inhoud weggelaten in dit fragment. */]
const taskInput = document.getElementById('taskInput');
const addTaskBtn = document.getElementById('addTaskBtn');
const taskList = document.getElementById('taskList');
const clearAll = document.getElementById('clearAll');
const filters = document.getElementById('filters');<div class="container py-5">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card shadow">
<div class="card-body">
<h2 class="card-title text-center mb-4">Task Master</h2>
<div class="input-group mb-3">
<input type="text" id="taskInput" class="form-control" placeholder="Add a new task..." aria-label="Task input">
<button class="btn btn-primary" type="button" id="addTaskBtn">Add Task</button>
</div>
<div id="filters" class="d-flex justify-content-between mb-3">
<small class="text-muted">Filter:</small>
<div>
<button class="btn btn-sm btn-outline-secondary active" id="filterAll">All</button>
<button class="btn btn-sm btn-outline-secondary" id="filterActive">Active</button>
</div>
</div>
<ul class="list-group" id="taskList">
</ul>
</div>
<div class="card-footer text-center">
<button class="btn btn-link btn-sm text-danger" id="clearAll">Clear All Tasks</button>
</div>
</div>
</div>
</div>
</div>HTMLElementen maken en renderen vanuit JavaScript
In de startbestanden is een array van taken beschikbaar, deze moeten echter nog gerenderd worden op de HTML-pagina. Hiervoor maken we gebruik van de createElement en appendChild methodes.
Begrip: HTML-elementen toevoegen of verwijderen
createElement: Maak een nieuw element aan van het opgegeven type. Via de eerste parameter wordt type gespecifieerd, dit kan eender welk HTML-tag zijn.
const title = document.createElement('h1')appendChild: Voeg een HTML-element toe als een kind aan een ander HTML-element.
const title = document.createElement('h1') document.body.appendChild(title)removeChild: Verwijder een HTMLElement uit een ander element. Het element moet steeds verwijderd worden van de directe parent.
// 1. Een element moet steeds uit de directe parent verwijderd worden. const parent = document.getElementById("myList"); // 2. Haal het kind op dat verwijderd moet worden. const child = document.querySelector("#myList > #item1"); // 3. Verwijder het kind. parent.removeChild(child);replaceChild: Vervang een element met een ander.
// Een element kan enkel vervangen worden in de directe parent. const parent = document.getElementById("myList"); const old = document.getElementById("someItem"); const newItem = document.createElement('div') parent.replace(newItem, old)remove: Verwijder een element uit de DOM.
document.getElementById('someId').remove()
Via de createElement methode maken we een nieuw list-item aan voor elke taak dat we vervolgens toevoegen aan de lijst via de appendChild methode.
Aangezien de appendChild methode beschikbaar is op elk HTML-element (ook diegene die aangemaakt zijn in JavaScript), kunnen we de HTML-structuur uit de startcode eenvoudig nabouwen in JavaScript.
function createTask() {
const taskItem = document.createElement('li')
const title = document.createElement('span')
taskItem.appendChild(title)
const buttonContainer = document.createElement('div')
taskItem.appendChild(buttonContainer)
const toggleButton = document.createElement('button')
buttonContainer.appendChild(toggleButton)
const deleteButton = document.createElement('button')
buttonContainer.appendChild(deleteButton)
return taskItem
}
function renderTasks() {
for (const task of tasks) {
const taskItem = createTask()
taskList.appendChild(taskItem)
}
}
renderTasks()Na deze code toe te voegen ziet de website er als volgt uit.

Attributen toevoegen aan HTMLElementen
Het is duidelijk dat bovenstaande code nog niet in orde is. De styling komt nog niet overeen met de template code in de startbestanden, en de naam van de taak wordt nog niet weergegeven.
Begrip: Attributen aanpassen
Elke HTMLElement heeft verschillende attributen waarmee extra informatie over het element meegegeven kan worden.
Bijna elk attribuut is beschikbaar als property of het HTMLElement. Dit betekent dat het mogelijk is om de waarde van een bestaand attribuut uit te lezen of een nieuwe waarde toe te kennen zoals we dat zouden doen voor elk ander object.
CSS-eigenschappen kunnen op dezelfde manier aangepast worden, let hier wel op, de CSS-eigenschappen staan nu in camelCase in plaats van kebab-case.
Sommige attributen heeft een bepaalde betekenis binnen JavaScript en kunnen daarom niet als propertynaam gebruikt worden. Zo wordt class gebruikt om een klasse aan te maken, maar ook om een CSS-klasse toe te voegen aan een HTMLElement, de property wordt daarom vervangen met className.
// Maak een nieuw hidden element aan.
const errorMessage = document.createElement('p')
errorMessage.hidden = true
// Pas de style van een element aan via een geneste property.
errorMessage.style.color = 'red'
errorMessage.style.border = '1px solid red'
errorMessage.style.borderRadius = '5px'
// Pas de style aan door een CSS-klasse toe te voegen.
errorMessage.className = 'error-message'
// Lees de waarde van een formulierelement uit.
const nameInput = document.getElementById('name-input')
const name = nameInput.value- element.hasAttribute(attributeName): Controleert of een element een opgegeven attribuut heeft.
- element.setAttribute(attributeName, attributeValue): Wijzigt de waarde van een attribuut.
- element.getAttribute(attributeName): Geeft de waarde van een attribuut terug.
- element.removeAttribute(attributeName): Verwijdert een attribuut van een element.
- element.toggleAttrivute(attributeName): Verandert de waarde van een boolean attribuut van true naar false of omgekeerd.
Via de classList property kunnen we de CSS-klasse uit de template code eenvoudig toevoegen. Merk op dat niet elk item de klasse completed moet krijgen, deze klasse mag enkel toegekend worden aan de items waarvoor de done property op true staat. Via de classList property kunnen we een klasse toevoegen, verwijderen of toggellen in een bestaande lijst van klassen.
function createTask(task) {
const taskItem = document.createElement('li')
taskItem.className = 'list-group-item d-flex justify-content-between align-items-center'
task.done && taskItem.classList.add('completed')
const title = document.createElement('span')
taskItem.appendChild(title)
const buttonContainer = document.createElement('div')
buttonContainer.className = 'd-flex gap-2'
taskItem.appendChild(buttonContainer)
const toggleButton = document.createElement('button')
toggleButton.className = 'btn btn-primary btn-sm delete-btn'
buttonContainer.appendChild(toggleButton)
const deleteButton = document.createElement('button')
deleteButton.className = 'btn btn-danger btn-sm delete-btn'
buttonContainer.appendChild(deleteButton)
return taskItem
}
function renderTasks() {
for (const task of tasks) {
const taskItem = createTask(task)
taskList.appendChild(taskItem)
}
}
renderTasks()Na deze code toe te voegen ziet de website er als volgt uit.

Inhoud van een element aanpassen
De taken zijn nu bijna in orde, enkel de naam van de taak en de labels van de knoppen moeten nog toegevoegd worden.
Begrip: Inhoud van een HTMLElement aanpassen
De inhoud van een element kan via drie properties uitgelezen of aangepast worden:
innerHTML: Geeft de HTML-code terug die als kind meegegeven is aan het element.
Waarschuwing
Gebruik deze property nooit om willekeurige HTML-code toe te voegen aan een website, hier zouden script tags of andere dingen in kunnen zitten die gebruikt kunnen worden voor een Cross-site scripting (XSS) aanval.
<html lang="en"> <body> <div id="inhtml"> <span>Hello <span style="display: none;">World</span></span> </div> <script> const inHtml = document.getElementById('inhtml') // Print <span>Hello <span style="display: none;">World</span></span> console.log(inHtml.innerHTML) // Past de inhoud aan naar een titel met de H1 styling. inHtml.innerHTML = '<h1>Inner HTML</h1>' </script> </body> </html>innerText: Geeft de gerenderde text terug. Inhoud die via CSS of accessibility attributes verborgen is, wordt hier niet weergegeven.
<html lang="en"> <body> <div id="inTxt"> <span>Hello <span style="display: none;">World</span></span> </div> <script> const inTxt = document.getElementById('inTxt') // Print Hello console.log(inTxt.innerHTML) // Past de inhoud aan naar de string <h1>Inner HTML</h1>, tags worden genegeerd. inTxt.innerText = '<h1>Inner HTML</h1>' </script> </body> </html>textContent: Geeft alle textinhoud van een element, inclusief spaties en newlines. Ook dingen die door CSS of accessibility attributes verborgen zijn, worden weergegeven.
<html lang="en"> <body> <div id="txtContent"> <span>Hello <span style="display: none;">World</span></span> </div> <script> const txtContent = document.getElementById('txtContent') // Print "\n Hello World\n" console.log(txtContent.textContent) // Past de inhoud aan naar de string <h1>Inner HTML</h1>, tags worden genegeerd. txtContent.innerText = '<h1>Inner HTML</h1>' </script> </body> </html>
Aangezien we enkel tekst moeten renderen, gebruiken we innerText property om de naam van de taak en de labels van de knoppen in te stellen.
function createTask(task) {
const taskItem = document.createElement('li')
taskItem.className = 'list-group-item d-flex justify-content-between align-items-center'
task.done && taskItem.classList.add('completed')
const title = document.createElement('span')
title.innerText = task.name
taskItem.appendChild(title)
const buttonContainer = document.createElement('div')
buttonContainer.className = 'd-flex gap-2'
taskItem.appendChild(buttonContainer)
const toggleButton = document.createElement('button')
toggleButton.className = 'btn btn-primary btn-sm delete-btn'
toggleButton.innerText = `Mark as ${task.done ? 'To-Do' : 'Completed'}`
buttonContainer.appendChild(toggleButton)
const deleteButton = document.createElement('button')
deleteButton.className = 'btn btn-danger btn-sm delete-btn'
deleteButton.innerText = 'Delete'
buttonContainer.appendChild(deleteButton)
return taskItem
}We gebruiken dezelfde property om de bestaande inhoud te verwijderen in de renderTasks functie. Op deze manier kunnen we de functie meerdere keerde oproepen als een nieuwe taak aangemaakt, verwijderd of bewerkt wordt.
function renderTasks() {
taskList.innerText = ''
for (const task of tasks) {
const taskItem = createTask(task)
taskList.appendChild(taskItem)
}
}Events
Via de "Clear All" knop moeten alle taken verwijderd worden uit de pagina, hiervoor moeten we twee dingen doen. Enerzijds moeten we reageren op het click event, anderzijds moeten we de inhoud van de unordered list leeg maken.
Begrip: Events
Events worden afgevuurd om de code te verwittigen dat er iets interessant gebeurd is. De bron van deze events is meestal een actie van de gebruiker, e.g. het klikken op een knop, het herschalen van het venster, het typen in een formulier, ... In sommige gevallen kan een event ook veroorzaakt worden door gebeurtenis op het systeem van de gebruiker, zo kan een bijna lege batterij of een wijziging in de netwerkverbinding ook JavaScript code triggeren. De volledige lijst van events is beschikbaar op mdn.
Events kunnen op twee manieren gekoppeld worden aan JavaScript code. Aan HTML-attributen zoals onclick, onchange, ... kan JavaScript code meegegeven worden, anderszijds kan een event gekoppeld worden aan HTMLElement via de addEventListener methode.
De tweede aanpak geniet in alle gevallen de voorkeur, via de addEventListener methode wordt altijd een parameter meegegeven aan de handler functie die informatie over het event bevat. Wat deze informatie juist is, verschilt van event tot event. Een MouseEvent bevat informatie over hoe de mouse bewogen is (boven-onder, links-rechts, snelheid), een ChangeEvent bevat informatie over de waarde die in het formulierelement ingegeven is, een KeydownEvent bevat de knop die ingedrukt is, ...
<html lang="en">
<head>
<title>Voorbeeld</title>
</head>
<body>
<button onclick="showAlert1()">Click Me!</button>
<button id="second-button">Click Me V2!</button>
<script>
function showAlert1() {
alert('Hello World!')
}
document.getElementById('second-button')
.addEventListener('click', showAlert2)
function showAlert2(eventInfo) {
console.log(eventInfo)
alert('Hello World!')
}
</script>
</body>
</html>Via de addEventListener functie koppelen we een event aan de "Clear All Tasks" knop dat alle taken verwijderd.
const clearAll = document.getElementById('clearAll');
clearAll.addEventListener('click', () => {
// Verwijder alle items uit de array zonder de variabele om te vormen naar een let.
tasks.length = 0
renderTasks()
})Taken aanmaken
Om een taak aan te maken, gebruiken we opnieuw een click-event. Dit keer lezen we invoer van de gebruiker uit van het formulier, dit kan via de value property van een HTMLFormElement. Merk op dat we een UUID genereren via de crypto.randomUUID methode.
const taskInput = document.getElementById('taskInput');
const addTaskBtn = document.getElementById('addTaskBtn');
addTaskBtn.addEventListener('click', () => {
const name = taskInput.value
if (name === "") return alert("Please enter a task!");
tasks.push({
id: window.crypto.randomUUID(),
name,
done: false,
})
taskInput.value = ''
renderTasks()
})Event delegation
Via de "All" en "Active" knoppen moeten de taken gefilterd worden, hiervoor kunnen we natuurlijk weer een event koppelen aan beide knoppen, maar we kunnen ook gebruik maken van event delegation.
Begrip: Events
Events worden afgevuurd om de code te verwittigen dat er iets interessant gebeurd is. De bron van deze events is meestal een actie van de gebruiker, e.g. het klikken op een knop, het herschalen van het venster, het typen in een formulier, ... In sommige gevallen kan een event ook veroorzaakt worden door gebeurtenis op het systeem van de gebruiker, zo kan een bijna lege batterij of een wijziging in de netwerkverbinding ook JavaScript code triggeren. De volledige lijst van events is beschikbaar op mdn.
Events kunnen op twee manieren gekoppeld worden aan JavaScript code. Aan HTML-attributen zoals onclick, onchange, ... kan JavaScript code meegegeven worden, anderszijds kan een event gekoppeld worden aan HTMLElement via de addEventListener methode.
De tweede aanpak geniet in alle gevallen de voorkeur, via de addEventListener methode wordt altijd een parameter meegegeven aan de handler functie die informatie over het event bevat. Wat deze informatie juist is, verschilt van event tot event. Een MouseEvent bevat informatie over hoe de mouse bewogen is (boven-onder, links-rechts, snelheid), een ChangeEvent bevat informatie over de waarde die in het formulierelement ingegeven is, een KeydownEvent bevat de knop die ingedrukt is, ...
<html lang="en">
<head>
<title>Voorbeeld</title>
</head>
<body>
<button onclick="showAlert1()">Click Me!</button>
<button id="second-button">Click Me V2!</button>
<script>
function showAlert1() {
alert('Hello World!')
}
document.getElementById('second-button')
.addEventListener('click', showAlert2)
function showAlert2(eventInfo) {
console.log(eventInfo)
alert('Hello World!')
}
</script>
</body>
</html>Als we een event koppelen aan de #filters container, moet we nog steeds weten op welke knop er gedrukt is. Hiervoor gebruiken we de data attributes, dit zijn valide HTML-attributen die beginnen met de data- prefix en verder alle mogelijke waarden kunnen bevatten.
De actieve filter-knop moet de active klasse krijgen. Via de document.querySelectorAll methode kunnen we alle items met data-filter attributen ophalen.
Tenslotte passen we de renderTasks methode aan zodat enkel de gefilterde taken gerenderd worden.
const filters = document.getElementById('filters');
const filterButtons = document.querySelectorAll('[data-filter]')
let filter = 'all'
function renderTasks() {
taskList.innerText = ''
const filteredTasks = tasks.filter(t => filter === 'all' || t.done === false && filter === 'active')
for (const task of filteredTasks) {
const taskItem = createTask(task)
taskList.appendChild(taskItem)
}
}
filters.addEventListener('click', (evt) => {
filterButtons.forEach(fb => fb.classList.remove('active'))
filter = evt.target.dataset.filter
evt.target.classList.add('active')
renderTasks()
})<div class="container py-5">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card shadow">
<div class="card-body">
<h2 class="card-title text-center mb-4">Task Master</h2>
<div class="input-group mb-3">
<input type="text" id="taskInput" class="form-control" placeholder="Add a new task..." aria-label="Task input">
<button class="btn btn-primary" type="button" id="addTaskBtn">Add Task</button>
</div>
<div id="filters" class="d-flex justify-content-between mb-3">
<small class="text-muted">Filter:</small>
<div>
<button class="btn btn-sm btn-outline-secondary active"
id="filterAll" data-filter="all">All</button>
<button class="btn btn-sm btn-outline-secondary"
id="filterActive" data-filter="active">Active</button>
</div>
</div>
<ul class="list-group" id="taskList">
</ul>
</div>
<div class="card-footer text-center">
<button class="btn btn-link btn-sm text-danger" id="clearAll">Clear All Tasks</button>
</div>
</div>
</div>
</div>
</div>Taken verwijderen & aanpassen
De laatste operaties die nog niet geïmplementeerd zijn, zijn het verwijderen en aanpassen van taken. Omdat deze knoppen in JavaScript opgebouwd worden, in plaats van in HTML, kunnen we eenvoudig een event listener koppelen aan de knoppen zonder dat hier een document.getElementById voor nodig is.
function createTask(task) {
const taskItem = document.createElement('li')
taskItem.className = 'list-group-item d-flex justify-content-between align-items-center'
task.done && taskItem.classList.add('completed')
const title = document.createElement('span')
title.innerText = task.name
taskItem.appendChild(title)
const buttonContainer = document.createElement('div')
buttonContainer.className = 'd-flex gap-2'
taskItem.appendChild(buttonContainer)
const toggleButton = document.createElement('button')
toggleButton.className = 'btn btn-primary btn-sm'
toggleButton.innerText = `Mark as ${task.done ? 'To-Do' : 'Completed'}`
toggleButton.addEventListener('click', () => {
const taskToToggle = tasks.find(t => t.id === task.id)
taskToToggle.done = !taskToToggle.done
renderTasks()
})
buttonContainer.appendChild(toggleButton)
const deleteButton = document.createElement('button')
deleteButton.className = 'btn btn-danger btn-sm'
deleteButton.innerText = 'Delete'
deleteButton.addEventListener('click', () => {
const i = tasks.findIndex(t => t.id === task.id)
tasks.splice(i, 1)
renderTasks()
})
buttonContainer.appendChild(deleteButton)
return taskItem
}Het eindproduct ondersteund nu volledige CRUD-functionaliteit: