n00b pro

03. DOM deel 2

On Youtube

Video: tree navigatie, speciale nodes, classList, style, data-*, mouse en keyboard

https://www.youtube.com/embed/oPhKAHd3GA8

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:

Deze noden bestaan ook, maar zijn ofwel verouderd, ofwel voor ons niet relevant:

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:

We bekijken nu nog twee andere manieren:

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:

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:

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!