JavaScript voor beginners: Troubleshooting en geavanceerdere onderwerpen

In de eerste tien afleveringen van deze cursus hebben we gezien hoe we met behulp van JavaScript onze webpagina's een stuk krachtiger en professioneler kunnen maken. In deze slotaflevering zullen we het hebben over enkele problemen die we tegen kunnen komen bij het programmeren, en daarnaast zullen enkele meer geavanceerde onderwerpen aan de orde komen.

Browsers herkennen

Verschillende browsers gaan soms op verschillende manieren om met JavaScript. Zo is Microsoft Internet Explorer vaak wat "toleranter" dan Netscape Navigator, ten opzichte van JavaScript dat zich niet helemaal aan de regels houdt. Neem bijvoorbeeld de validatieregel:

if (frm.email.value=='')
{
alert('Emailadres invullen a.u.b.!');
}


Internet Explorer behandelt deze regel alsof we de correcte formulering hadden gebruikt:

if (document.frm.email.value=='')
{
alert('Emailadres invullen a.u.b.!');
}


maar bij Netscape Navigator werkt de validatie niet. (Uit dit voorbeeld blijkt weer eens hoe belangrijk het is om onze code met meerdere browsers te controleren --in ieder geval met Internet Explorer en Netscape Navigator, en liefst niet alleen met de allerlaatste versie!)

Dergelijke verschillen in werking omzeilen we natuurlijk niet door te kijken met welke browser we te maken hebben en vervolgens de ene formulering te gebruiken of de andere, maar simpelweg door altijd de correcte variant te gebruiken: die werkt namelijk bij beide browsers!

Anders ligt dat in die gevallen waar een fundamenteel verschil bestaat tussen beide browsers; een goed voorbeeld daarvan is het gebruik van Dynamic HTML. Om een specifiek object "elem" te verbergen, gebruiken we voor Internet Explorer:

elem.style.visibility = 'hidden';

en voor Netscape Navigator:

document.elem.visibility = 'hidden';

We moeten hier echt andere code gebruiken voor beide browsers; de versie voor Internet Explorer werkt gewoonweg niet bij Navigator en andersom. Bovendien wordt Dynamic HTML alleen door de meer recente versies van beide browsers ondersteund. We hebben dus een methode nodig om vast te stellen met welke versie van welke browser we te maken hebben.

Hiertoe maken we gebruik van "navigator". Dit is een eigenschap van het window-object (we hoeven echter niet te schrijven: "window.navigator" maar kunnen volstaan met "navigator"). Deze eigenschap is zelf weer een object met eigenschappen (om precies te zijn: navigator verwijst naar het Navigator-object, waarvan slechts één enkele "instance" bestaat). De twee belangrijkste hiervan zijn "appName" en "appVersion".

De eigenschap "appName" geeft ons de naam van de gebruikte browser; bij de twee meest gebruikte browsers heeft deze eigenschap de waarde "Netscape" of "Microsoft Internet Explorer".

De precieze waarde van de eigenschap "appVersion" is browser- en platformafhankelijk. De string begint echter altijd met het versienummer, en we kunnen gebruik maken van parseFloat() om snel de gebruikte versie vast te stellen: alert(parseFloat(navigator.appVersion)) toont ons zowel hoofd- als subversie (bijvoorbeeld "4.07"). Hebben we alleen de hoofdversie nodig, dan kunnen we parseInt() gebruiken.

Rollover

Browser-herkenning komen we vaak tegen bij de zogenaamde "rollover": het ene plaatje wordt vervangen door het andere op het moment dat we de muiscursor er overheen bewegen, en verandert terug in het eerste zodra we de muiscursor weer van het plaatje afhalen. Dit effect wordt vooral gebruikt bij menu's, om de sitebezoeker visuele feedback te geven (een knop wordt bijvoorbeeld "ingedrukt" en weer "losgelaten").

In Netscape Navigator zijn rollovers mogelijk vanaf versie 3, maar in Internet Explorer pas vanaf versie 4. Door de manier waarop rollovers werken, moeten we speciale voorzorgsmaatregelen nemen om te voorkomen dat gebruikers met Internet Explorer 3 een foutmelding krijgen.

De meest basale manier om rollovers te implementeren is als volgt:

<SCRIPT>
<!--
function RollOver()
{
document.i.src = "i2.gif";
}


function RollOut()
{
document.i.src = "i1.gif";
}
// -->
</SCRIPT>


<A HREF="xyz.htm" onMouseOver="RollOver()" onMouseOut="RollOut()"><IMG NAME="i" SRC="i1.gif"></A>

De twee functies RollOver() en RollOut() maken beide gebruik van het Image-object. Image "i" (een eigenschap van document) heeft zelf een eigenschap "src"; door hier een nieuwe waarde aan toe te kennen, zorgen we ervoor dat dynamisch een nieuwe afbeelding wordt ingeladen en getoond.

(Een wat uitgebreidere implementatie zal waarschijnlijk de alternatieve afbeelding vooraf inladen, zodat deze reeds in de cache aanwezig is op het moment dat de MouseOver-gebeurtenis plaatsvindt. Als gevolg daarvan kan het ene plaatje zonder storende vertraging door het andere worden vervangen; voor het punt dat we hier willen illustreren, is dit detail echter niet van belang.)

De reden dat deze basale implementatie problemen geeft bij Internet Explorer 3, is dat het Image-object pas bestaat vanaf JavaScript versie 1.1, terwijl IE3 alleen versie 1.0 ondersteunt. Zonder speciale voorzorgsmaatregelen zou IE3 ons daarom de foutmelding geven dat image i geen object is! We komen daarom vaak rollovers tegen die er als volgt uitzien:

function RollOver()
{
var HasImg;
if ((parseInt(navigator.appVersion) >=4) || (navigator.appName=="Netscape" && parseInt(navigator.appVersion) >=3))
  HasImg = true
else
  HasImg = false;
if (HasImg)
  document.i.src = "i2.gif"
}


Bij Internet Explorer 3 krijgt HasImg de waarde false; de toekenning van een nieuwe waarde aan document.i.src wordt daarom niet uitgevoerd, en de foutmelding blijft achterwege.

Verschillende versies

Een probleem met deze benadering is, dat ze volledig browserafhankelijk is. Toevallig weten we dat versie 3 van Internet Explorer de rollover niet ondersteunt, en versie 4 wel. Hoe zit het echter met browsers als Opera of Mosaic? Zelfs als we de mogelijkheden van alle denkbare versies van alle denkbare browsers zouden kennen, dan nog is het praktisch ondoenlijk om die allemaal in onze code af te vangen. Er is echter een elegantere manier om het probleem van de rollover op te lossen.

We zagen dat het probleem wordt veroorzaakt doordat JavaScript versie 1.0 geen Image-object kent. Indien we nu kunnen vaststellen welke versie van JavaScript wordt ondersteund door de browser van onze sitebezoeker, weten we meteen of we de rollover-functie wel of niet kunnen gebruiken.

Je zou je kunnen voorstellen dat de JavaScript-versie een eigenschap is van navigator, net als de versie van de gebruikte browser; dit is echter niet het geval. In plaats daarvan maken we gebruik van het LANGUAGE-attribuut van de SCRIPT-tag.

Als we bijvoorbeeld opgeven:

<SCRIPT LANGUAGE="JavaScript1.1">

dan wordt de bijbehorende code alleen uitgevoerd als de gebruikte browser tenminste JavaScript versie 1.1 ondersteunt; in Internet Explorer versie 3 wordt deze code dus genegeerd.

We kunnen echter niet simpelweg de twee functies RollOver() en RollOut() opnemen binnen dit scriptblok, anders krijgen we alsnog een foutmelding op het moment dat we de muiscursor over de afbeelding bewegen.

Op dat moment vindt immers een MouseOver-gebeurtenis plaats, en de browser zal zoeken naar de functie RollOver() --tevergeefs, omdat die zich bevindt binnen een scriptblok dat als het ware "onzichtbaar" is voor de browser.

In plaats daarvan kunnen we als volgt te werk gaan. We introduceren eerst een variabele "jsver" waaraan we de juiste waarde toekennen:

<SCRIPT LANGUAGE="JavaScript">var jsver = 1.0;</SCRIPT>
<SCRIPT LANGUAGE="JavaScript1.1"> jsver = 1.1;</SCRIPT>


In een browser die alleen JavaScript versie 1.0 ondersteunt, wordt alleen de eerste toekenning uitgevoerd (jsver krijgt de waarde 1.0); in een browser die versie 1.1 (of hoger) ondersteunt, wordt zowel de eerste als de tweede toekenning uitgevoerd (en jsver heeft dus uiteindelijk de waarde 1.1).

Merk op dat de volgorde van de twee regels van belang is: als we die zouden omdraaien, zou een browser die versie 1.1 ondersteunt eerst de waarde 1.1 toekennen aan jsver. Zo'n browser ondersteunt echter óók versie 1.0, en daarom zou jsver onmiddellijk daarna de waarde 1.0 krijgen!

De beide functies nemen we vervolgens op in een "algemeen" JavaScript-blok, maar we voeren de rollover alleen uit indien jsver de waarde 1.1 heeft:

<SCRIPT LANGUAGE="JavaScript">
<!--
function RollOver()
{
if (jsver==1.1)
  document.i.src = "i2.gif";
}
//-->
</SCRIPT>


In dit verband zijn ook de NOSCRIPT-tags van belang: de HTML-code die wordt omsloten door deze tags wordt alleen getoond door browsers die helemaal geen JavaScript ondersteunen. (Daarbij gaat het niet zozeer om oeroude browsers, maar meer om gebruikers die JavaScript in hun browser hebben uitgeschakeld!)

Een voorbeeld van het gebruik van de NOSCRIPT-tag is:

<SCRIPT LANGUAGE="JavaScript">
<!--
alert('Welkom op onze site!')
// -->
</SCRIPT>
<NOSCRIPT>
Helaas, we konden u niet met behulp van JavaScript begroeten omdat uw browser deze taal niet ondersteunt...
</NOSCRIPT>


In de praktijk is het natuurlijk niet zo heel erg als we een bezoeker niet door middel van een alertbox kunnen begroeten, en we hoeven de gebruikers die JavaScript hebben uitgeschakeld daar niet echt van op de hoogte te stellen. Anders wordt het, als de functionaliteit van onze code essentieel is voor de goede werking van onze site. Je zou daarbij kunnen denken aan het volgende geval:

<NOSCRIPT>
LET OP: doordat uw browser geen JavaScript ondersteunt, kan uw invoer niet vooraf worden gevalideerd!
</NOSCRIPT>


Dit herinnert ons er meteen aan dat we nooit uitsluitend op client-side JavaScript mogen vertrouwen voor het valideren van invoer; op de server moeten we de gegevens altijd opnieuw controleren! Het voordeel van validatie door de browser is echter, dat de gebruiker niet steeds pas nà het versturen van de pagina te horen krijgt dat bijvoorbeeld een bepaald veld "verplicht" is (en daardoor wellicht tussentijds afhaakt).

Bestaat object?

Het controleren van de gebruikte versie van JavaScript verdient duidelijk de voorkeur boven het afvangen van specifieke browserversies. Een nadeel is dat we nog steeds moeten weten welke versie van JavaScript de door ons gewenste functionaliteit ondersteunt.

Gelukkig bestaat er een elegant alternatief: we kunnen namelijk controleren of het object bestaat. De verwijzing naar een niet-bestaand object geeft namelijk de waarde "null", hetgeen door JavaScript binnen logische expressies automatisch wordt omgezet in "false". Onze rollover maakt gebruik van het document.images object; we kunnen daarom schrijven:

function RollOver()
{
if (document.images)
  document.i.src = "i2.gif";
}


Op vergelijkbare wijze kunnen we kijken of bepaalde functies en methoden worden ondersteund, bijvoorbeeld:

if (window.RegExp)
{
  alert('Regular Expressions worden ondersteund!');
}


Merk op dat we RegExp schrijven en niet RegExp() --het gaat namelijk om een verwijzing, en niet om een aanroep. Ook moeten we de verwijzing vooraf laten gaan door "window.", omdat het window-object de "parent" van alle functies is. Testen we op het bestaan van een bepaalde methode, dan moeten we de volledige naam daarvan gebruiken, bijvoorbeeld: if (top.mainframe.document.captureEvents) {alert('OK')}

Fouten negeren

Met de tot nu toe besproken technieken kunnen we al heel veel foutsituaties afvangen. Er blijven echter altijd gevallen die we niet hadden kunnen voorzien (de bezoeker gebruikt bijvoorbeeld de allernieuwste beta-versie van een browser). Door gebruik te maken van een "error handler", kunnen we voorkomen dat de gebruiker wordt geconfronteerd met allerlei foutmeldingen. Dat gaat als volgt:

function noErrors(){return true};
window.onerror = noErrors;


Let wel: met deze techniek worden alleen de foutmeldingen onderdrukt; de fouten zelf worden natuurlijk niet netjes afgevangen! Ook moeten we er voor zorgen dat de error handler reeds actief is voordat eventuele fouten kunnen optreden. Daartoe zetten we de twee statements helemaal aan het begin van ons document (tussen de HEAD-tags).

Extern bestand

Tot nu toe hebben we onze scripts steeds opgenomen in de HTML-pagina waarin ze worden gebruikt. Dat zag er als volgt uit:

<SCRIPT LANGUAGE="JavaScript">
<!-- Afschermen voor browsers die geen JavaScript ondersteunen
alert('Hello, world!')
// Einde afscherming-->
</SCRIPT>


Het is echter ook mogelijk om een script op te nemen in een extern bestand, waar we in onze HTML-pagina vervolgens naar verwijzen. Dat gaat als volgt:

<SCRIPT LANGUAGE="JavaScript" SRC="hello.js"></SCRIPT>

Het bestand "hello.js" bevat in dat geval het volledige script. (De afscherming voor browsers die geen JavaScript ondersteunen, kan natuurlijk vervallen: bij dergelijke browsers wordt de SCRIPT-tag in zijn geheel genegeerd, en het externe bestand wordt dus ook niet geladen.)

Waarom zouden we nu gebruik maken van zo'n extern bestand? Een eerste overweging zou kunnen zijn, dat deze constructie onze code enigszins afschermt van nieuwsgierige blikken: wie de broncode van de HTML-pagina bekijkt, krijgt alleen bovenstaande SCRIPT-tag te zien, en niet de inhoud van het bestand "hello.js". De geboden bescherming is echter bepaald niet waterdicht, want het bestand kan wel degelijk worden teruggevonden in de cache van de browser!

Een tweede reden om een extern bestand te gebruiken heeft te maken met die caching. Stel dat onze site diverse pagina's bevat met steeds dezelfde invoervelden (bijvoorbeeld een naam, een emailadres en een woonplaats), waarvoor steeds dezelfde, uitgebreide validatie moet worden uitgevoerd.

De benodigde JavaScript-code kan in zo'n geval al gauw een of twee kilobyte in beslag nemen. Door die code op te nemen in een extern bestand waarnaar in de verschillende HTML-pagina's steeds wordt verwezen, voorkomen we dat dezelfde code steeds weer van de server naar de browser moet worden gestuurd: de browser kan, vanaf de tweede pagina die naar "hello.js" verwijst, immers gebruik maken van de versie die zich in de cache bevindt.

Het komt echter maar zelden voor dat dergelijke code een omvang heeft van vele kilobytes; we moeten ons van de te behalen tijdwinst dan ook niet al te veel voorstellen.

De belangrijkste reden om onze code centraal op te slaan in een extern bestand is wel de verhoogde onderhoudbaarheid: als we om welke reden dan ook iets willen veranderen in de code (om een fout te herstellen, bijvoorbeeld, of omdat we de functionaliteit willen wijzigen), dan hoeven we maar op een plaats iets te veranderen, (en we weten bovendien zeker dat we niet ergens een pagina vergeten die dezelfde code ook gebruikt).

Het gebruik van een extern bestand heeft overigens wel consequenties voor de HTML-code die we gebruiken. Een validatieregel kan bijvoorbeeld luiden:

if (document.frm.email.value=='') {alert('Emailadres invullen a.u.b.!')}

Dit zal alleen correct werken indien het invoerveld de naam "email" heeft, en indien dit veld deel uitmaakt van een formulier met de naam "frm". We moeten dus steeds deze namen gebruiken binnen de HTML-pagina's waarin we verwijzen naar het externe bestand met de JavaScript-validatie.

Conclusie

We hebben in de elf afleveringen van deze cursus een behoorlijke weg afgelegd. Met alle technieken die we besproken hebben, zouden we in staat moeten zijn om onze webpagina's een stuk krachtiger te maken.

Vaak zullen we met JavaScript de invoer van een gebruiker valideren, en soms zullen we functionaliteit kunnen aanbieden (bijvoorbeeld een spelletje, of een mini-applicatie) die met "gewone" HTML niet mogelijk is.

Het gevolg zal echter steeds zijn dat onze site plezieriger en professioneler overkomt op onze bezoekers. En was het ons daar allemaal niet om begonnen?