On Youtube: tree navigatie, speciale nodes, classList, style, data-*, mouse en keyboard
- 0:18 herhaling: basisoefening met events (menu click events voorbeeld)
- 4:40 speciale nodes
- 8:02 CSS manipulatie
- 20:26 DOM tree navigatie (menu volgende knop voorbeeld)
- 30:55 input events
- 45:14 wat hebben we geleerd?
- 47:11 herwerking fotogallerij voorbeeld (data-*, classList)
- 55:38 herwerking formchecking (DOM tree navigatie, classList)
DOM tree navigatie
Eenmaal je een node gevonden hebt (bv. met querySelector()
) kan je verder navigeren in de
boomstructuur met properties als parentNode
, children
, nextElementSibling
enz...:
<header>
<h1>DOM navigation</h1>
</header>
<nav>
<ul id="mainmenu">
<li><a href="about.html">Over mij</a></li>
<li><a href="practical.html">Praktisch</a></li>
<li><a href="contact.html">Contact</a></li>
</ul>
</nav>
const menu = document.querySelector('#mainmenu');
console.log(`menu's parent node:`);
console.log(menu.parentNode);
console.log(`menu's child nodes:`);
console.log(menu.children);
const nav = document.querySelector('nav');
console.log(`nav's previous sibling:`);
console.log(nav.previousElementSibling);
Belangrijkste properties:
property | omschrijving |
---|---|
node.children |
collectie van children van de huidige node |
node.firstElementChild |
eerste child van de huidige node |
node.lastElementChild |
laatste child van de huidige node |
node.nextElementSibling |
volgende sibling (null indien niet gevonden) |
node.parentNode |
parent van de huidige node |
node.previousElementSibling |
vorige sibling (null indien niet gevonden) |
Een handige methode is ook closest()
, die de eerste ancestor zoekt die voldoet aan de meegegeven selector
const nav = someLink.closest('nav'); // search from some link up the tree till you find 'nav'
Speciale nodes
Een node in Javascript stelt bijna altijd een HTML element in het document voor. Er zijn echter nog een paar nodes die daarbuiten vallen, waarvan de belangrijkste zijn:
window
: het browservensterdocument
: het HTML documentlocation
: de adresbalk
Deze noden bestaan ook, maar zijn ofwel verouderd, ofwel voor ons niet relevant:
navigator
: gebruikte browser (niet meer relevant)screen
: het scherm van de gebruiker (vervangen door media queries)history
: browse historiek
Window node
Deze node stelt het browservenster voor. De lijst properties en methodes bevat voor ons niet
zoveel interssants. Een paar gekende zijn window.print()
, window.scrollTo()
en
window.alert()
. Een voorbeeld van die laatste (dialoogvenster):
window.alert('hallo wereld!');
De methodes alert()
, confirm()
en prompt()
zijn UI blokkerende
dialoogvensters (i.e. je kan niks meer doen tot je ze weggeklikt hebt) en mogen dus niet
gebruikt worden in deze cursus.
Document node
Deze node stelt het HTML document voor. De lijst properties en methodes is lang; je
herkent o.a. de methodes document.querySelector()
en document.querySelectorAll()
.
De rest is weinig gebruikt of verouderd; de enige nog (een beetje) interssante property is
document.title
, die verwijst naar <title>, de titel getoond op het browser tabblad:
document.title = 'kot kot kedei';
Location node
Deze node stelt de adresbalk voor. In de lijst
properties en methodes vind je o.a. de properties location.href
(het adres) en
location.search
(alles na "?"), en de location.reload()
methode.
Een voorbeeld van een redirect met location.href
:
if (location.search == '?gotogoogle') {
location.href = 'https://www.google.com'; // redirect
}
CSS manipulatie
We hebben in de vorige presentatie al twee manieren gezien om nodes te manipuleren:
- HTML attributen: deze zijn in Javascript beschikbaar onder properties met dezelfde naam
- DOM properties: bijkomende properties van nodes die niet overeenkomen met HTML attributen
We bekijken nu nog twee andere manieren:
- style: via deze property kan je rechtstreeks CSS properties aanpassen (enkel indien
classList
geen optie is) - classList: via deze property kan je CSS classes toevoegen of verwijderen (aanbevolen waar mogelijk)
style property
Je kan vanuit Javascript alle CSS eigenschappen van een node aanpassen met de style
property. De namen zijn gelijk, al wordt b.v. background-color
wel geCamelCased tot backgroundColor
.
Voorbeeld waarbij in een lijst het huidige jaar gemarkeerd wordt:
<ul id="jaar">
<li>2021</li>
<li>2022</li>
<li>2023</li>
<li>2024</li>
<li>2025</li>
<li>2026</li>
</ul>
const currentYear = new Date().getFullYear();
const years = document.querySelectorAll('#jaar li');
years.forEach(li => {
if (li.innerHTML == currentYear) {
li.style.border = '3px dotted black';
li.style.backgroundColor = '#aaf';
li.style.color = 'white';
li.style.fontStyle = 'italic';
}
});
classList property
Meestal is het makkelijker gewoon opmaak te groeperen onder een CSS class, en die dan toe te
wijzen (of te verwijderen) via de classList property met de methodes add()
,
remove()
, toggle()
en contains()
. Ze verschijnen in de HTML bij de class
attribuut. Het vorige voorbeeld herschreven:
<header>
<h1>DOM navigation</h1>
</header>
<nav>
<ul id="mainmenu">
<li><a href="about.html">Over mij</a></li>
<li><a href="practical.html">Praktisch</a></li>
<li><a href="contact.html">Contact</a></li>
</ul>
</nav>
.todayYear {
border: 3px dotted black;
background-color: #aaf;
color: white;
font-style: italic;
}
const currentYear = new Date().getFullYear();
const years = document.querySelectorAll('#jaar li');
years.forEach(li => {
if (li.innerHTML == currentYear) {
li.classList.add('todayYear')
}
});
data-* attributen
Wat in WPF kan met de Tag attribuut — bijkomende informatie aan controls koppelen — kan in
Javascript met data-* attributen.
De regel is: je mag vrij attributen verzinnen toevoegen aan je HTML, zolang ze maar geprefixed zijn met
data-
. Gebruik de dataset
property om ze te lezen en schrijven. Een voorbeeld:
<ul id="zaden">
<li data-afstand="10cm" data-zaaitijd="maart tot juni">wortelen</li>
<li data-afstand="30cm" data-zaaitijd="februari tot maart">selder</li>
<li data-afstand="3cm" data-zaaitijd="maart tot september">radijs</li>
</ul>
const zaden = document.querySelectorAll('#zaden li');
console.log(zaden.length);
zaden.forEach(zaad => {
console.log(`
plant: ${zaad.innerHTML}
planttijd: ${zaad.dataset.zaaitijd}
afstand: ${zaad.dataset.afstand}`);
});
Input events
Toetsenbord
Bij keyboard events bevat het event object e
alle eigenschappen van de ingedrukte toets.
<p>tik iets in...</p>
<p><input type="text" id="inp1" /></p>
<p>
karakter: <span id="kar"></span>
<br>code: <span id="code"></span>
<br>shift: <span id="shift"></span>
</p>
const inp = document.querySelector('#inp1');
inp.addEventListener('keydown', function(e) {
console.log(e); // inspecteer alle properties met console.log()
document.querySelector('#kar').innerHTML = e.key;
document.querySelector('#code').innerHTML = e.keyCode;
document.querySelector('#shift').innerHTML = e.shiftKey ? 'ja' : 'nee ';
});
Muis
Bij mouse events bevat het event object e
alle eigenschappen van de muisklik.
<p>klik ergens in de pagina...</p>
<p>
positie: <span id="pos"></span>
</p>
const body = document.querySelector('body');
body.addEventListener('click', function(e) {
console.log(e); // inspecteer alle properties met console.log()
document.querySelector('#pos').innerHTML = `(${e.clientX}, ${e.clientY})`;
});
Herwerkte voorbeelden
Foto gallerij herwerkt
Ter illustratie herwerken we de fotogalerij, met volgende aanpassingen:
- we gebruiken
<div>
's in plaats van<ul><li><a>
's - we gebruiken
data-photo
attributen in plaats van de href van de<a>
's om de url naar de foto te onthouden - we voegen een CSS class
current
toe aan de actieve<div>
(getoonde foto)
De oude HTML:
<ul class="thumbs">
<li><a href="img/photo1.jpg"><img src="img/photo1-s.jpg" alt="Amman, Jordaniƫ"></a></li>
<li><a href="img/photo17.jpg"><img src="img/photo17-s.jpg" alt="Noorderlicht, Lapland"></a></li>
<li><a href="img/photo6.jpg"><img src="img/photo6-s.jpg" alt="mozaĆÆek, Isfahan, Iran"></a></li>
<li><a href="img/photo3.jpg"><img src="img/photo3-s.jpg" alt="Atlas, Marokko"></a></li>
<li><a href="img/photo20.jpg"><img src="img/photo20-s.jpg" alt="bevroren bomen, Lapland"></a></li>
</ul>
<figure id="figBig">
<img src="img/photo1.jpg" alt="">
<figcaption>Amman, Jordaniƫ</figcaption>
</figure>
De vernieuwe HTML:
<div class="thumbs">
<div data-photo="img/photo1.jpg" data-desc="Amman, Jordaniƫ" class="current"><img src="img/photo1-s.jpg" alt=""></div>
<div data-photo="img/photo17.jpg" data-desc="Noorderlicht, Lapland"><img src="img/photo17-s.jpg" alt=""></div>
<div data-photo="img/photo6.jpg" data-desc="mozaĆÆek, Isfahan"><img src="img/photo6-s.jpg" alt=", Iran"></div>
<div data-photo="img/photo3.jpg" data-desc="Atlas, Marokko"><img src="img/photo3-s.jpg" alt=""></div>
<div data-photo="img/photo20.jpg" data-desc="bevroren bomen, Lapland"><img src="img/photo20-s.jpg" alt=""></div>
</div>
<figure id="figBig">
<img src="img/photo1.jpg" alt="random image">
<figcaption></figcaption>
</figure>
We voegen ook nog een visueel effect toe, waarbij standaard alle thumbnails in grijswaarden staan, en de reeds
bezochte in kleur gezet worden met een CSS class visited
. De aangepaste CSS:
...
.thumbs img {
filter: grayscale(1);
opacity: 0.8;
transition: opacity 0.5s, filter 0.5s;
}
.thumbs img:hover,
.thumbs .current img {
opacity: 1;
}
.thumbs .visited img {
filter: grayscale(0);
}
De vernieuwde Javascript:
const figBig = document.querySelector('#figBig');
const thumbs = document.querySelectorAll('.thumbs div');
thumbs.forEach(thn => {
thn.addEventListener('click', function () {
// verwijder current class van vorige thumb
document.querySelector('.current').classList.remove('current');
// voeg current en visited class toe aan huidige thumb
thn.classList.add('current');
thn.classList.add('visited');
// pas afbeelding en beschrijving aan
figBig.querySelector('img').src = thn.dataset.photo;
figBig.querySelector('figcaption').innerHTML = thn.dataset.desc;
});
});
Je herkent volgende nieuwe technieken:
- gebruik
classList
om de huidige en visited thumbnails te markeren - gebruik
data
-attributen in combinatie metdataset
voor de url en beschrijving
Form checking herwerkt
In de eerste versie stonden de teksten van foutmeldingen in de
Javascript, terwijl het content is en dus eigenlijk thuishoort in de HTML.
We herwerken het met gebruik van o.a. classList
en
parentNode
. Bekijk zeker de uitleg in de video.
De aangepaste HTML:
<form id="frmLogin">
<h1>Registreren</h1>
<div>
<label for="email">Email: </label>
<input type="email" id="email" required>
<span class="message is-empty">email mag niet leeg zijn</span>
</div>
<div>
<label for="pw">Paswoord: </label>
<input type="password" id="pw" required>
<span class="message is-empty">paswoord mag niet leeg zijn</span>
<span class="message has-no-capital">paswoord moet een hoofdletter bevatten</span>
</div>
<input type="submit" value="Inloggen">
</form>
In de aangepaste CSS verbergen we standaard de foutmeldingen:
label {
display: block;
}
label, input {
margin: 5px 0;
}
.message {
color: #900;
display: none;
font-size: smaller;
font-style: italic;
}
.message.show {
display: block;
}
In de Javascript tonen we de nodige foutmeldingen:
const frmLogin = document.querySelector('#frmLogin');
const inpEmail = frmLogin.querySelector('#email');
const inpPw = frmLogin.querySelector('#pw');
const msgEmailIsEmpty = inpEmail.parentNode.querySelector('.message.is-empty');
const msgPwIsEmpty = inpPw.parentNode.querySelector('.message.is-empty');
const msgPwHasNoCapital = inpPw.parentNode.querySelector('.message.has-no-capital');
// disable HTML5 validation
frmLogin.setAttribute('novalidate', 'novalidate');
// halt form submissions and check form
frmLogin.addEventListener('submit', function (e) {
e.preventDefault();
// clear all messages
msgEmailIsEmpty.classList.remove('show');
msgPwIsEmpty.classList.remove('show');
msgPwHasNoCapital.classList.remove('show');
// email can't be empty
if (inpEmail.value == '') {
msgEmailIsEmpty.classList.add('show');
}
// password must have at least one capital letter
if (inpPw.value.toLowerCase() == inpPw.value) {
msgPwHasNoCapital.classList.add('show');
}
// password can't be empty
if (inpPw.value == '') {
msgPwIsEmpty.classList.add('show');
}
// if all ok; submit form
if (frmLogin.querySelectorAll('.message.show').length == 0) {
frmLogin.submit();
}
});
Er zijn tal van versies mogelijk, niet alleen voor de Foto gallerij of de Form checking, maar voor elke toepassing. Het is aan jou om daar je eigen stijl in te zoeken!