» direct naar zoek en menu

Tijdschrift voor webwerkers » Artikel #91

Structuur in de chaos - Scheid XHTML, CSS en JavaScript

Vroeger was het zo simpel. Je kreeg een ontwerp aangeleverd, inclusief de waarschuwing dat alles pixel-precies moest kloppen in de browser van de klant, Explorer 3.

Je zette een basistabel op, zorgde dat spacer.gif in je pixmap stond, en kon beginnen. Menulijsten, subtiele dropshadows, teksten met een marge, het waren allemaal dankbare aanleidingen om een nieuw blik tabellen open te trekken. Klopte het daarna nog steeds niet helemaal, dan moest je er nog veel en veel meer tabellen inproppen, anders kreeg de ontwerper een creatieve hartverzakking.

Tegenwoordig weten we beter. We hebben tabellen-in-tabellen-in-tabellen naar de vuilnisbelt van de geschiedenis verwezen, scheiding van structuur en presentatie omarmd, en zijn enthousiast geworden over CSS. Niettemin is het praktiseren van nieuwe technieken alléén niet voldoende. pagina’s die volledig in CSS zijn gemaakt, kunnen ook een ongelofelijke warboel vormen, en haast niet te wijzigen zijn zonder dagenlange studie (ik kan het weten, ik heb ook zulke pagina’s gemaakt).

Maar hoe maak je nu een goede XHTML-structuur, die een solide basis vormt voor CSS en JavaScript, en die voor andere webontwikkelaars makkelijk te begrijpen is?

Strikte scheiding

De voorkant van een website rust op drie poten: structuur, presentatie en gedrag, oftewel XHTML, CSS en JavaScript. Deze drie poten dienen goed met elkaar samen te werken en strikt van elkaar gescheiden te zijn. Klinkt tegenstrijdig, maar dat is het niet.

Strikt gescheiden betekent dat elke poot zijn eigen plek heeft: .html bestanden voor de structuur, een .css bestand voor de presentatie en een .js bestand voor het gedrag. Een beetje site bestaat uit tientallen XHTML-pagina’s, maar als al deze pagina’s naar dezelfde .css en .js bestanden linken, kun je door een paar regels code te veranderen de presentatie of het gedrag van de gehele site wijzigen. Strikte scheiding vereenvoudigt dus het onderhoud van de site.

Strikte scheiding kan ook tot schonere code leiden. Je hoeft geen <FONT> tags, geneste tabellen en andere opmaakcode meer te gebruiken, je kunt het lettertype en de marges immers via CSS instellen. Je hoeft ook geen event handlers, zoals onMouseOver="switchImages('tranendal',7,false)", in je XHTML meer te zetten. Je kunt de event handlers immers geheel vanuit het .js bestand initialiseren.

Hierdoor blijft je XHTML overzichtelijk, en ook je JavaScript kan aan duidelijkheid winnen. Een strikt gescheiden JavaScript kan nog steeds een rommeltje zijn, maar je bent er tenminste zeker van dat alle noodzakelijke code zich in één bestand bevindt, en dat je niet ook nog tientallen XHTML-pagina’s hoeft te doorzoeken op obscure functie-aanroepen.

Semantiek

XHTML, CSS en JavaScript moeten wel goed samenwerken. De belangrijkste voorwaarde voor deze samenwerking is een goede XHTML-structuur. De zogenaamde semantische benadering biedt hier aanknopingspunten.

Sommige XHTML elementen hebben een speciale betekenis. <h1>, bijvoorbeeld, is “van nature” een kop, terwijl een <p> “van nature” een paragraaf is. Je kunt er redelijkerwijs van uitgaan dat alle grafische- en tekstbrowsers een kop meer gewicht zullen geven dan gewone tekst, en een paragraaf netjes zullen scheiden van andere paragrafen. Voorleesbrowsers voor blinden en slechtzienden zouden de kop iets anders kunnen uitspreken en een kleine pauze tussen de paragrafen in acht kunnen nemen.

Natuurlijk mag elke browser zelf bepalen hoe hij met deze structurele elementen omgaat, en sommigen zullen het slechter doen dan anderen. Niettemin heb je als webontwikkelaar je plicht gedaan: je hebt duidelijk aangegeven welke tekst waarvoor dient.

Vanuit deze benadering is het algemeen gebruik geworden om de hoofdnavigatie van je site in een <ul>/<li> te structureren. Een <ul> is immers semantisch gezien een lijst, en de links in de hoofdnavigatie vormen ook een lijst. Nadat je op deze manier de navigatie correct hebt gestructureerd, kun je je in CSS druk gaan maken over de presentatie van de links.

Verder horen tabellen uitsluitend gebruikt te worden voor het presenteren van tabulaire data (data is tabulair als je zelfs in drukwerk een tabel zou gebruiken om ze te presenteren). Dat is op zich goed, maar het probleem is dat sommige soorten presentatie – vooral rasters en verticale uitlijning – niet of heel moeilijk in CSS te creëeren zijn. Tabellen zijn dan veel makkelijker.

Een enkele keer is het gebruik van één (1!) tabel om de hoofdelementen in een raster te plaatsen dus ook toegestaan. Maar probeer het altijd eerst in CSS! Een tabel is een paardenmiddel dat uitsluitend gebruikt mag worden als je na dagen nijver CSS schrijven nog steeds helemaal nergens bent gekomen. Zelfs een mislukte CSS-poging geeft je veel meer nuttige ervaring dan de zoveelste tabel.

Helaas hebben niet alle semantische regels zin. Haarkloverijen over de relatieve waarde van <b> en <strong>, of het correcte gebruik van de zes headers bieden soms leuke lectuur, maar hebben weinig praktische waarde en kunnen een moralistische ondertoon krijgen die sommige webontwikkelaars doet besluiten semantiek en standaarden maar te laten voor wat ze zijn en verder te ploeteren met tabellen-in-tabellen.

Gebruik dus altijd je gezonde verstand als het over semantiek gaat, en wantrouw de predikers.

Omgekeerd is niet alle XHTML semantisch verantwoord. De veelgebruikte <div> heeft geen enkele “natuurlijke” betekenis, en wordt alleen gebruikt om er presentatie en gedrag aan te hangen. Daar is op zich niets mis mee, maar het probleem is dat <div> zich als de tabellen van het CSS-tijdperk beginnen te gedragen. Werkt ’t niet? Prop er meer <div>’s in! Dat is jammer, en hopelijk zullen er in de toekomst betere regels komen om excessieve “divitis” te vermijden.

Als je eenmaal een XHTML-structuur gemaakt hebt, voeg je een <link> tag toe die de presentatie importeert en een <script> tag die het gedrag importeert.

Aanhaakpunten - CSS

Presentatie en gedrag hebben aanhaakpunten nodig, code waarmee je als webontwikkelaar kunt zeggen “presenteer dit blok zo” en “geef alle links in dit blok dat gedrag”

De meest gebruikelijke aanhaakpunten zijn class en id. Voor CSS zijn ze onontbeerlijk. Let op: “onontbeerlijk” betekent niet “prop maar door totdat alles een class heeft“. Structuren als deze zijn niet nodig:

<div class="text">
 <div class="bodytext">
  <p class="maintext">Ontslagbegeleidingsprocedures zijn geen zaak meer voor
  de betrokkenen alleen.</p>
 </div>
 <div class="bodytext">

  <p class="maintext">Ook overheid, vakbonden en maatschappelijk middenveld
  spreken tegenwoordig een woordje mee.</p>
 </div>
</div>
<div class="tekst">

 <p>Ontslagbegeleidingsprocedures zijn geen zaak meer voor de betrokkenen
 alleen.</p>
 <p>Ook overheid, vakbonden en maatschappelijk middenveld spreken
 tegenwoordig een woordje mee.</p>
</div>

Je hoeft de paragrafen geen aparte class te geven, je kunt ze stijlen door de div.tekst p selector. Deze zogenaamde “descendant selector” is uitermate krachtig en ik gebruik hem zelf graag en vaak. Deel je pagina in in verschillende blokken met een class of id, gebruik binnen deze blokken uitsluitend simpele structurele XHTML, en je kunt er zonder problemen stijlen aan toekennen.

Neem dit voorbeeld:

<div class="navigatie">
 <ul>
  <li><a href="sollproc.html">Sollicitatieprocedures</a></li>

  <li><a href="ontproc.html">Ontslagprocedures</a></li>
  <li><a href="ontbegproc.html">Ontslagbeleidingsprocedures</a></li>
 </ul>

</div>

<div class="tekst">
 <p>Ontslagbegeleidingsprocedures zijn geen zaak meer voor de betrokkenen
 alleen.</p>
 <p>Ook overheid, vakbonden en <a href="mm.html">maatschappelijk
 middenveld</a> spreken tegenwoordig een woordje mee.</p>

</div>

De links in de navigatie stijl je met div.navigatie a, die in de tekst met div.tekst a. Zoals je ziet heb je geen extra classes nodig, de twee divs zijn voldoende.

Navigatiestijlen zijn meestal wat complexer dan tekststijlen, en daarom is het bijzonder handig dat de navigatie naast a-elementen ook li-elementen bevat. Kijk bijvoorbeeld eens naar Sliding Doors of CSS, een schitterende techniek van CSS-goeroe Douglas Bowman die zonder een li/a structuur niet werkt.

Alleen als je een zeer expliciete uitzondering wilt definieren, kun je een extra class of id toekennen, zoals bijvoorbeeld:

<div class="navigatie">
 <ul>
  <li><a href="sollproc.html">Sollicitatieprocedures</a></li>
  <li><a href="ontproc.html">Ontslagprocedures</a></li>

  <li><a href="ontbegproc.html" id="hierbenik">Ontslagbeleidingsprocedures</a></li>
 </ul>
</div>

Nu kun je een specifieke stijl definieren voor de link naar de pagina waar de gebruiker op dit moment is. Deze link is een uitzondering, daarom is het gebruik van een extra id toegestaan.

Als je jezelf strenge regels oplegt voor het gebruik van classes en id's, en pas na grote aarzeling een extra class of id toevoegt, dan zal dat schonere en duidelijkere code opleveren.

Aanhaakpunten - JavaScript

Ook JavaScript heeft aanhaakpunten nodig. In simpele situaties kun je dezelfde classes en id's gebruiken die je al voor CSS gedefinieerd hebt. Over het algemeen werkt JavaScript beter met id's, omdat de getElementById() methode een simpele manier verschaft om elementen met een bepaald id aan te spreken. Er is geen overeenkomstige getElementsByClassName() methode, dus als je een element met een bepaalde class wilt zoeken, moet je het gehele document doorlopen.

Laten we een gedrag toekennen aan alle links in de navigatie, bijvoorbeeld het tonen van een klein blokje extra informatie over elke link. Dat zou je ook met CSS kunnen doen, maar vandaag gebruiken we JavaScript, mede omdat het een open vraag is welke methode "beter" is.

We willen dus een onmouseover event handler toekennen aan alle links in de navigatie, maar niet aan de links in het tekstgedeelte. Derhalve is het lastigste probleem het zoeken van de correcte links, oftewel het zoeken naar een aanhaakpunt.

De structuur van onze simpele pagina geeft al direct een "natuurlijk" aanhaakpunt: alle navigatielinks staan in een <ul>. We zouden dus dit kunnen doen:

var x = document.getElementsByTagName('ul')[0].getElementsByTagName('a');
for (var i=0;i<x.length;i++)
 x[i].onmouseover = toonExtraInfo;

Pak alle a elementen die kinderen zijn van het eerste ul-element in de pagina en geef ze allemaal een onmouseover event handler mee.

Dit werkt in eerste instantie prima. Deze aanpak legt echter de structuur van de XHTML aan zeer rigide banden. Je moet er volkomen zeker van zijn dat er nooit een <ul> vóór de navigatie zal voorkomen, niet alleen in de huidige site, maar ook in alle eventuele toekomstige uitbreidingen. Je kunt dus nooit de navigatie eens naar onderaan het document verplaatsen, omdat je dan in problemen zou komen als het tekstgedeelte ook een <ul> bevat.

Verder moet moet je er volkomen zeker van zijn dat je altijd en eeuwig de <ul> in de navigatie zal blijven gebruiken, en hem nooit zal inruilen voor, bijvoorbeeld, een <ol>.

Zelf vind ik deze structuur te rigide. Ik ga liever één stapje hoger en neem de <div class="navigatie"> als aanhaakpunt, omdat dit veel logischer is. De <div> zal altijd blijven bestaan als container van de navigatie, en als ik op class zoek, maakt het ook niet meer uit waar in het document de <div> precies staat. Ik kan de navigatie nu naar de onderkant van het document verplaatsen zonder mijn script te herzien.

var x = document.getElementsByTagName('div');
for (var i=0;i<x.length;i++)
{
 if (x[i].className != 'navigatie') continue;
 var y = x[i].getElementsByTagName('a');
 for (var j=0;j<y.length;j++)
  y[j].onmouseover = toonExtraInfo;
}

Maar deze functie is ook redelijk ingewikkeld, omdat we alle <div>'s in het hele document moeten aflopen om te kijken of ze soms class="navigatie" hebben. Vandaar dat ik er nog liever voor kies om de class in een id te veranderen:


<div id="navigatie">

Dan krijgen we dit simpele script:

var x = document.getElementById('navigatie').getElementsByTagName('a');
for (var i=0;i<x.length;i++)
 x[i].onmouseover = toonExtraInfo;

Mijns inziens is een id het beste aanhaakpunt voor een simpel script.

Complexere aanhaakpunten

Het kiezen van aanhaakpunten kan echter ingewikkeld worden. Als je iets schrijft dat complexer is dan één klein effectje op één groep elementen, moet je eerst een half uur fundamenteel nadenken over je XHTML-structuur.

Neem bijvoorbeeld een formuliervalidatie; het zou erg handig zijn om in de XHTML van een formulierveld aan te kondigen dat dit veld verplicht is. Je kunt dan dit soort code gaan gebruiken

function valideerFormulier()
{
 var x = document.forms[0].elements;
 for (var i=0;i<x.length;i++)
 {
  if ([dit veld is verplicht] && !x[i].value)
    // wijs gebruiker op fout
 }
}

We moeten dus eerst kijken of een formulierveld verplicht is, als het dat is en geen waarde heeft, dan waarschuwen we de gebruiker en zetten de formulierverzending stop. Maar hoe definieren we dit aanhaakpunt? Een mogelijkheid is de informatie in de class op te nemen:

<input name="naam" class="verplicht" />

if (x[i].className == 'verplicht' && !x[i].value)

Als je ook CSS-classes wilt gebruiken, gebruik je

<input name="naam" class="breedveld verplicht" />

if (x[i].className.indexOf('verplicht') != -1 && !x[i].value)

Persoonlijk ben ik echter niet zo gecharmeerd van deze aanpak. classes zijn voor CSS bedoeld, niet voor JavaScript. Bovendien wordt het benodigde script ingewikkelder dan noodzakelijk is.

Neem nu een script dat bijhoudt hoeveel tekens de gebruiker in een textarea heeft getypt (het oude maxlength attribuut werkt alleen voor <input>, niet voor <textarea>).

In de site zitten meerdere van deze textareas, en ze accepteren allemaal een ander maximum aantal tekens. We hebben nu twee brokjes informatie nodig:

  1. Heeft deze textarea een maximumlengte?
  2. Hoeveel bedraagt deze maximumlengte?

We krijgen dan een script als:

var x = document.getElementsByTagName('textarea');
for (var i=0;i<x.length;i++)
{
 if ([deze textarea heeft een maximumlengte])
  x[i].onkeypress = controleerLengte;
}

function controleerLengte()
{
 var max = [lees de maximumlengte];
 if (this.value.length > max)
  // wijs gebruiker op fout
}

Laten we proberen dit aanhaakpunt te definieren met een class. Het kan, maar het wordt erg ingewikkeld:

<textarea class="groot maxlen=300">

Uitvinden of de textarea een maximumlengte heeft is nog simpel:

if (x[i].className.indexOf('maxlen') != -1)
 x[i].onkeypress = controleerLengte;

Maar nu moeten we de daadwerkelijke waarde van de maximumlengte eruit peuteren, en dat is erg lastig. We nemen aan dat de maxlen de laatste waarde in de class is (als het dat niet is, wordt de code nog ingewikkelder).

var tmp = this.className.substring(this.className.indexOf('maxlen'));
var max = tmp.substring(tmp.indexOf('=')+1);

Nogmaals, technisch gesproken kan dit allemaal. Maar als we eens goed naar dit voorbeeld kijken, zien we dat de maximumlengte het meest logisch kan worden uitgedrukt als een naam/waarde paar, oftewel een attribuut. Als het attribuut aanwezig is, dient de textarea gecontroleerd te worden, en de waarde van het attribuut geeft de maximumlengte:

<textarea class="groot" maxlen="300">

if (x[i].getAttribute('maxlen'))
 x[i].onkeypress = controleerLengte;

var max = this.getAttribute('maxlen');

Veel simpeler, toch? Het enige nadeel is dat het maxlen attribuut niet bestaat, en dat de W3C validator begint te klagen als hij hem tegenkomt. Mijns inziens is dat geen groot probleem, je kunt deze meldingen gewoon negeren zonder dat het nadelige gevolgen heeft.

Niettemin zullen sommige webontwikkelaars valide XHTML op prijs stellen. Daarom moet iedereen voor zich beslissen of hij dergelijke maatwerkattributen wil gebruiken, of de validator tevreden wil stellen door de informatie in de class op te nemen.

En waarom gebruik ik maxlen in plaats van maxlength? Omdat Opera geen bestaande attributen op de verkeerde tags accepteert. Een script dat naar maxlength in een <textarea> zoekt werkt niet in deze browser, omdat het attribuut alleen maar op een <input> bestaat. Attributen die in het geheel niet bestaan, mogen echter weer wel van deze browser, en ook van alle andere.

Toegankelijkheid

We hebben nu een logische, simpele XHTML structuur die aanhaakpunten voor CSS en JavaScript bevat. Als de pagina ook nog aan de toegankelijkheidseisen voldoet, zijn we een heel eind gekomen. Maar hoe controleer je dat?

Bekijk de pagina eens zonder presentatie en gedrag, en zelfs zonder plaatjes. Verwijder de <link> tag naar de presentatie en de <script> tag naar het gedrag, schakel de plaatjes uit (bijvoorbeeld door alle 'pix/' in de pagina even tijdelijk door 'ix/' te vervangen). Bekijk de pagina. Kun je makkelijk bij zowel navigatie als inhoud komen? Werkt de navigatie? Is de titel/kop van de pagina duidelijk? Mis je informatie doordat plaatjes ontbreken? Is er functionaliteit die JavaScript vereist?

Als de tekst nog steeds leesbaar en begrijpelijk is, en als de navigatie nog steeds werkbaar is, weet je dat je pagina in principe toegankelijk is. Is de pagina ontoegankelijk, los het probleem dan op de simpelste manier op. Een extra linkje of paragraafje dat je met CSS of JavaScript verbergt voor moderne grafische browsers kan al wonderen doen.

Let op zinvolle alt-teksten voor plaatjes. Een goede alt-tekst herhaalt de letterlijke tekst in het plaatje, inclusief hoofdletters en leestekens. Als er geen tekst in het plaatje staat, moet je je afvragen of een alt-tekst zin heeft. alt="Sfeerbeeld met komkommer" helpt niemand, maar alt="Foto van demonstranten met ludieke zwaaihandjes" zou, afhankelijk van de context, nuttige informatie kunnen leveren. Besluit je dat een alternatieve tekst zinloos is, gebruik dan alt="", zodat browsers geen teksten als “Image” tonen (of uitspreken).

Na deze acties heb je nog niet aan alle toegankelijkheidsrichtlijnen voldaan, maar je weet wel zeker dat je 90% van de problemen hebt opgelost.

Conclusie

  1. Een goede XHTML-structuur is schoon en werkbaar en voldoet aan alle redelijke semantische eisen.
  2. De structuur moet simpele aanhaakpunten bieden aan CSS en JavaScript, maar niet teveel classes en id's gebruiken.
  3. Gedragsdefinities voor JavaScript kunnen ingewikkeld worden, maar een half uurtje fundamenteel nadenken voor je begint helpt een heleboel.
  4. Als CSS en JavaScript niet werken, moet de pagina nog steeds leesbaar en navigeerbaar zijn.

Het is eigenlijk niet eens zo moeilijk.

Auteur

Peter-Paul Koch

is freelance webontwikkelaar te Amsterdam. Hij is gespecialiseerd in client side programmeren (xhtml, css, javascript).

Publicatiedatum: 13 oktober 2004

Let op

Naar Voren is op 18 juli 2010 gestopt met publiceren. De artikelen staan als een soort archief online. Het kan dus zijn dat de informatie verouderd is en dat er inmiddels veel betere of makkelijkere manieren zijn om je doel te bereiken.

Copyright © 2002-heden » NAAR VOREN en de auteurs